summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:32:49 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-19 03:32:49 +0000
commit8053187731ae8e3eb368d8360989cf5fd6eed9f7 (patch)
tree32bada84ff5d7460cdf3934fcbdbe770d6afe4cd
parentInitial commit. (diff)
downloadrnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.tar.xz
rnp-8053187731ae8e3eb368d8360989cf5fd6eed9f7.zip
Adding upstream version 0.17.0.upstream/0.17.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
-rw-r--r--.cirrus.yml152
-rw-r--r--.clang-format67
-rw-r--r--.codespellrc3
-rw-r--r--.editorconfig23
-rw-r--r--.gitattributes1
-rw-r--r--.github/issue_template.md18
-rw-r--r--.github/workflows/centos-and-fedora.yml375
-rw-r--r--.github/workflows/codeql.yml75
-rw-r--r--.github/workflows/coverity.yml63
-rw-r--r--.github/workflows/debian.yml138
-rw-r--r--.github/workflows/dispatch.yml48
-rw-r--r--.github/workflows/fuzzing.yml41
-rw-r--r--.github/workflows/lint.yml61
-rw-r--r--.github/workflows/macos.yml168
-rw-r--r--.github/workflows/nix.yml45
-rw-r--r--.github/workflows/ubuntu.yml322
-rw-r--r--.github/workflows/windows-msys2.yml144
-rw-r--r--.github/workflows/windows-native.yml174
-rw-r--r--.gitignore464
-rw-r--r--.gitmodules3
-rwxr-xr-xBrewfile7
-rw-r--r--CHANGELOG.md436
-rw-r--r--CMakeLists.txt206
-rw-r--r--LICENSE-OCB.md93
-rw-r--r--LICENSE.md178
-rw-r--r--README.adoc69
-rw-r--r--_config.yml1
-rw-r--r--ci/botan-modules45
-rwxr-xr-xci/build_package_rpm.sh100
-rw-r--r--ci/docker/Makefile11
-rw-r--r--ci/docker/fedora35.Dockerfile52
-rwxr-xr-xci/docker/fedora35.test-locally22
-rw-r--r--ci/env-common.inc.sh29
-rw-r--r--ci/env-freebsd.inc.sh8
-rw-r--r--ci/env-linux.inc.sh46
-rw-r--r--ci/env.inc.sh29
-rw-r--r--ci/gha/setup-env.inc.sh60
-rwxr-xr-xci/install.sh7
-rwxr-xr-xci/install_cacheable_dependencies.sh7
-rwxr-xr-xci/install_noncacheable_dependencies.sh8
-rw-r--r--ci/lib/cacheable_install_functions.inc.sh242
-rw-r--r--ci/lib/install_functions.inc.sh869
-rwxr-xr-xci/local.sh16
-rwxr-xr-xci/main.sh109
-rwxr-xr-xci/run.sh5
-rwxr-xr-xci/style-check.sh14
-rwxr-xr-xci/success.sh16
-rwxr-xr-xci/tests/ci-tests.sh144
-rwxr-xr-xci/tests/deb-tests.sh102
-rwxr-xr-xci/tests/pk-tests.sh144
-rwxr-xr-xci/tests/pkg-tests.sh102
-rwxr-xr-xci/tests/rpm-tests.sh126
-rw-r--r--ci/utils.inc.sh43
-rw-r--r--cmake/CTestCostData.txt159
-rw-r--r--cmake/Modules/AdocMan.cmake135
-rw-r--r--cmake/Modules/FindBotan2.cmake131
-rw-r--r--cmake/Modules/FindGnuPG.cmake137
-rw-r--r--cmake/Modules/FindJSON-C.cmake123
-rw-r--r--cmake/Modules/FindOpenSSLFeatures.cmake171
-rw-r--r--cmake/Modules/FindWindowsSDK.cmake662
-rw-r--r--cmake/Modules/findopensslfeatures.c101
-rw-r--r--cmake/info.cmake41
-rw-r--r--cmake/librnp.pc.in13
-rw-r--r--cmake/packaging.cmake79
-rw-r--r--cmake/rnp-config.cmake.in31
-rw-r--r--cmake/rnp_tests_discover.cmake40
-rw-r--r--cmake/version.cmake163
-rw-r--r--default.nix44
-rw-r--r--doc/tests/README.md72
-rw-r--r--doc/tests/rnpkeys-generate-key.md192
-rw-r--r--docs/c-usage.adoc129
-rw-r--r--docs/cli-usage.adoc174
-rw-r--r--docs/code-of-conduct.adoc128
-rw-r--r--docs/develop.adoc435
-rw-r--r--docs/develop/cpp-usage.adoc49
-rw-r--r--docs/develop/packaging.adoc78
-rw-r--r--docs/develop/release-workflow.adoc122
-rw-r--r--docs/installation.adoc262
-rw-r--r--docs/navigation.adoc18
-rw-r--r--docs/signing-keys.adoc5
-rw-r--r--flake.lock42
-rw-r--r--flake.nix26
-rwxr-xr-xgit-hooks/enable.sh3
-rwxr-xr-xgit-hooks/pre-commit.sh125
-rw-r--r--include/rekey/rnp_key_store.h149
-rw-r--r--include/repgp/repgp_def.h505
-rw-r--r--include/rnp.h42
-rw-r--r--include/rnp/rnp.h3150
-rw-r--r--include/rnp/rnp_def.h35
-rw-r--r--include/rnp/rnp_err.h77
-rw-r--r--src/common/CMakeLists.txt64
-rw-r--r--src/common/file-utils.cpp359
-rw-r--r--src/common/file-utils.h89
-rw-r--r--src/common/getoptwin.h34
-rw-r--r--src/common/str-utils.cpp217
-rw-r--r--src/common/str-utils.h63
-rw-r--r--src/common/time-utils.cpp96
-rw-r--r--src/common/time-utils.h38
-rw-r--r--src/common/uniwin.h60
-rw-r--r--src/examples/CMakeLists.txt140
-rw-r--r--src/examples/README.md5
-rw-r--r--src/examples/decrypt.c138
-rw-r--r--src/examples/dump.c163
-rw-r--r--src/examples/encrypt.c133
-rw-r--r--src/examples/generate.c330
-rw-r--r--src/examples/sign.c168
-rw-r--r--src/examples/verify.c165
-rw-r--r--src/fuzzing/CMakeLists.txt145
-rw-r--r--src/fuzzing/dump.c54
-rw-r--r--src/fuzzing/keyimport.c97
-rw-r--r--src/fuzzing/keyring.c52
-rw-r--r--src/fuzzing/keyring_g10.cpp51
-rw-r--r--src/fuzzing/keyring_kbx.c50
-rw-r--r--src/fuzzing/sigimport.c51
-rw-r--r--src/fuzzing/verify.c58
-rw-r--r--src/fuzzing/verify_detached.c59
-rwxr-xr-xsrc/lib/CMakeLists.txt594
-rw-r--r--src/lib/config.h.in72
-rw-r--r--src/lib/crypto.cpp244
-rw-r--r--src/lib/crypto.h118
-rw-r--r--src/lib/crypto/backend_version.cpp184
-rw-r--r--src/lib/crypto/backend_version.h44
-rw-r--r--src/lib/crypto/bn.cpp107
-rw-r--r--src/lib/crypto/bn.h64
-rw-r--r--src/lib/crypto/bn_ossl.cpp84
-rw-r--r--src/lib/crypto/cipher.cpp78
-rw-r--r--src/lib/crypto/cipher.hpp77
-rw-r--r--src/lib/crypto/cipher_botan.cpp245
-rw-r--r--src/lib/crypto/cipher_botan.hpp73
-rw-r--r--src/lib/crypto/cipher_ossl.cpp284
-rw-r--r--src/lib/crypto/cipher_ossl.hpp80
-rw-r--r--src/lib/crypto/common.h51
-rw-r--r--src/lib/crypto/dl_ossl.cpp200
-rw-r--r--src/lib/crypto/dl_ossl.h44
-rw-r--r--src/lib/crypto/dsa.cpp382
-rw-r--r--src/lib/crypto/dsa.h141
-rw-r--r--src/lib/crypto/dsa_ossl.cpp355
-rw-r--r--src/lib/crypto/ec.cpp187
-rw-r--r--src/lib/crypto/ec.h173
-rw-r--r--src/lib/crypto/ec_curves.cpp323
-rw-r--r--src/lib/crypto/ec_ossl.cpp349
-rw-r--r--src/lib/crypto/ec_ossl.h42
-rw-r--r--src/lib/crypto/ecdh.cpp377
-rw-r--r--src/lib/crypto/ecdh.h117
-rw-r--r--src/lib/crypto/ecdh_ossl.cpp389
-rw-r--r--src/lib/crypto/ecdh_utils.cpp166
-rw-r--r--src/lib/crypto/ecdh_utils.h47
-rw-r--r--src/lib/crypto/ecdsa.cpp267
-rw-r--r--src/lib/crypto/ecdsa.h57
-rw-r--r--src/lib/crypto/ecdsa_ossl.cpp190
-rw-r--r--src/lib/crypto/eddsa.cpp212
-rw-r--r--src/lib/crypto/eddsa.h54
-rw-r--r--src/lib/crypto/eddsa_ossl.cpp156
-rw-r--r--src/lib/crypto/elgamal.cpp302
-rw-r--r--src/lib/crypto/elgamal.h116
-rw-r--r--src/lib/crypto/elgamal_ossl.cpp418
-rw-r--r--src/lib/crypto/hash.cpp160
-rw-r--r--src/lib/crypto/hash.hpp98
-rw-r--r--src/lib/crypto/hash_botan.hpp68
-rw-r--r--src/lib/crypto/hash_common.cpp194
-rw-r--r--src/lib/crypto/hash_crc24.cpp288
-rw-r--r--src/lib/crypto/hash_crc24.hpp47
-rw-r--r--src/lib/crypto/hash_ossl.cpp152
-rw-r--r--src/lib/crypto/hash_ossl.hpp55
-rw-r--r--src/lib/crypto/hash_sha1cd.cpp94
-rw-r--r--src/lib/crypto/hash_sha1cd.hpp52
-rw-r--r--src/lib/crypto/mem.cpp68
-rw-r--r--src/lib/crypto/mem.h158
-rw-r--r--src/lib/crypto/mem_ossl.cpp113
-rw-r--r--src/lib/crypto/mpi.cpp119
-rw-r--r--src/lib/crypto/mpi.h58
-rw-r--r--src/lib/crypto/ossl_common.h40
-rw-r--r--src/lib/crypto/rng.cpp59
-rw-r--r--src/lib/crypto/rng.h78
-rw-r--r--src/lib/crypto/rng_ossl.cpp48
-rw-r--r--src/lib/crypto/rsa.cpp419
-rw-r--r--src/lib/crypto/rsa.h90
-rw-r--r--src/lib/crypto/rsa_ossl.cpp629
-rw-r--r--src/lib/crypto/s2k.cpp203
-rw-r--r--src/lib/crypto/s2k.h65
-rw-r--r--src/lib/crypto/s2k_ossl.cpp97
-rw-r--r--src/lib/crypto/sha1cd/sha1.c2162
-rw-r--r--src/lib/crypto/sha1cd/sha1.h122
-rw-r--r--src/lib/crypto/sha1cd/ubc_check.c908
-rw-r--r--src/lib/crypto/sha1cd/ubc_check.h62
-rw-r--r--src/lib/crypto/signatures.cpp281
-rw-r--r--src/lib/crypto/signatures.h69
-rw-r--r--src/lib/crypto/sm2.cpp383
-rw-r--r--src/lib/crypto/sm2.h79
-rw-r--r--src/lib/crypto/sm2_ossl.cpp76
-rw-r--r--src/lib/crypto/symmetric.cpp648
-rw-r--r--src/lib/crypto/symmetric.h252
-rw-r--r--src/lib/crypto/symmetric_ossl.cpp644
-rw-r--r--src/lib/defaults.h86
-rw-r--r--src/lib/ffi-priv-types.h240
-rw-r--r--src/lib/fingerprint.cpp117
-rw-r--r--src/lib/fingerprint.h39
-rw-r--r--src/lib/generate-key.cpp467
-rw-r--r--src/lib/json-utils.cpp103
-rw-r--r--src/lib/json-utils.h91
-rw-r--r--src/lib/key-provider.cpp117
-rw-r--r--src/lib/key-provider.h120
-rw-r--r--src/lib/librnp.3.adoc89
-rw-r--r--src/lib/librnp.symbols1
-rw-r--r--src/lib/librnp.vsc4
-rw-r--r--src/lib/logging.cpp75
-rw-r--r--src/lib/logging.h97
-rw-r--r--src/lib/pass-provider.cpp56
-rw-r--r--src/lib/pass-provider.h61
-rw-r--r--src/lib/pgp-key.cpp2776
-rw-r--r--src/lib/pgp-key.h671
-rw-r--r--src/lib/rnp.cpp8403
-rw-r--r--src/lib/sec_profile.cpp228
-rw-r--r--src/lib/sec_profile.hpp112
-rw-r--r--src/lib/types.h482
-rw-r--r--src/lib/utils.cpp80
-rw-r--r--src/lib/utils.h101
-rw-r--r--src/lib/version.h.in52
-rw-r--r--src/librekey/g23_sexp.hpp71
-rw-r--r--src/librekey/kbx_blob.hpp162
-rw-r--r--src/librekey/key_store_g10.cpp1243
-rw-r--r--src/librekey/key_store_g10.h42
-rw-r--r--src/librekey/key_store_kbx.cpp706
-rw-r--r--src/librekey/key_store_kbx.h36
-rw-r--r--src/librekey/key_store_pgp.cpp241
-rw-r--r--src/librekey/key_store_pgp.h83
-rw-r--r--src/librekey/rnp_key_store.cpp803
-rw-r--r--src/librepgp/stream-armor.cpp1287
-rw-r--r--src/librepgp/stream-armor.h174
-rw-r--r--src/librepgp/stream-common.cpp1212
-rw-r--r--src/librepgp/stream-common.h556
-rw-r--r--src/librepgp/stream-ctx.cpp69
-rw-r--r--src/librepgp/stream-ctx.h123
-rw-r--r--src/librepgp/stream-def.h67
-rw-r--r--src/librepgp/stream-dump.cpp2533
-rw-r--r--src/librepgp/stream-dump.h53
-rw-r--r--src/librepgp/stream-key.cpp1469
-rw-r--r--src/librepgp/stream-key.h143
-rw-r--r--src/librepgp/stream-packet.cpp1228
-rw-r--r--src/librepgp/stream-packet.h323
-rw-r--r--src/librepgp/stream-parse.cpp2636
-rw-r--r--src/librepgp/stream-parse.h123
-rw-r--r--src/librepgp/stream-sig.cpp1557
-rw-r--r--src/librepgp/stream-sig.h437
-rw-r--r--src/librepgp/stream-write.cpp1973
-rw-r--r--src/librepgp/stream-write.h83
-rw-r--r--src/rnp/CMakeLists.txt100
-rw-r--r--src/rnp/fficli.cpp3229
-rw-r--r--src/rnp/fficli.h295
-rw-r--r--src/rnp/rnp.1.adoc431
-rw-r--r--src/rnp/rnp.cpp695
-rw-r--r--src/rnp/rnpcfg.cpp572
-rw-r--r--src/rnp/rnpcfg.h219
-rw-r--r--src/rnpkeys/CMakeLists.txt101
-rw-r--r--src/rnpkeys/main.cpp136
-rw-r--r--src/rnpkeys/rnpkeys.1.adoc453
-rw-r--r--src/rnpkeys/rnpkeys.cpp648
-rw-r--r--src/rnpkeys/rnpkeys.h80
-rw-r--r--src/rnpkeys/tui.cpp413
-rw-r--r--src/tests/CMakeLists.txt245
-rw-r--r--src/tests/cipher.cpp1013
-rw-r--r--src/tests/cipher_cxx.cpp381
-rw-r--r--src/tests/cli.cpp857
-rw-r--r--src/tests/cli_common.py237
-rwxr-xr-xsrc/tests/cli_perf.py316
-rwxr-xr-xsrc/tests/cli_tests.py5048
-rw-r--r--src/tests/data/.gitattributes19
-rw-r--r--src/tests/data/.keepme0
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024-sec.gpg28
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024.gpgbin0 -> 965 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048-sec.gpg33
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048.gpgbin0 -> 1221 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234-sec.gpg32
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234.gpgbin0 -> 1157 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048-sec.gpg43
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048.gpgbin0 -> 1665 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112-sec.gpg44
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112.gpgbin0 -> 1705 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072-sec.gpg56
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072.gpgbin0 -> 2305 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024-sec.gpg35
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024.gpgbin0 -> 722 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048-sec.gpg59
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048.gpgbin0 -> 1238 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072-sec.gpg83
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072.gpgbin0 -> 1750 bytes
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096-sec.gpg107
-rw-r--r--src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096.gpgbin0 -> 2262 bytes
-rwxr-xr-xsrc/tests/data/cli_EncryptSign/regenerate_keys126
-rw-r--r--src/tests/data/issue1188/armored_revocation_signature.pgp12
-rw-r--r--src/tests/data/keyrings/1/info.txt1
-rw-r--r--src/tests/data/keyrings/1/pubring.gpgbin0 -> 3535 bytes
-rw-r--r--src/tests/data/keyrings/1/pubring.gpg.asc67
-rw-r--r--src/tests/data/keyrings/1/secring-cast5.gpgbin0 -> 4909 bytes
-rw-r--r--src/tests/data/keyrings/1/secring.gpgbin0 -> 4922 bytes
-rw-r--r--src/tests/data/keyrings/2/info.txt1
-rw-r--r--src/tests/data/keyrings/2/pubring.gpgbin0 -> 281 bytes
-rw-r--r--src/tests/data/keyrings/2/secring.gpgbin0 -> 435 bytes
-rw-r--r--src/tests/data/keyrings/3/info.txt1
-rw-r--r--src/tests/data/keyrings/3/private-keys-v1.d/63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59.keybin0 -> 1158 bytes
-rw-r--r--src/tests/data/keyrings/3/private-keys-v1.d/7EAB41A2F46257C36F2892696F5A2F0432499AD3.keybin0 -> 1158 bytes
-rw-r--r--src/tests/data/keyrings/3/pubring.kbxbin0 -> 1435 bytes
-rw-r--r--src/tests/data/keyrings/4/info.txt2
-rwxr-xr-xsrc/tests/data/keyrings/4/pubring.pgpbin0 -> 314 bytes
-rwxr-xr-xsrc/tests/data/keyrings/4/rsav3-p.asc12
-rwxr-xr-xsrc/tests/data/keyrings/4/rsav3-s.asc16
-rwxr-xr-xsrc/tests/data/keyrings/4/secring.pgpbin0 -> 501 bytes
-rw-r--r--src/tests/data/keyrings/5/pubring.gpgbin0 -> 447 bytes
-rw-r--r--src/tests/data/keyrings/5/secring.gpgbin0 -> 521 bytes
-rw-r--r--src/tests/data/keyrings/6/pubring.gpgbin0 -> 1201 bytes
-rw-r--r--src/tests/data/keyrings/6/secring.gpgbin0 -> 2503 bytes
-rw-r--r--src/tests/data/test_cli/hello.txt2
-rw-r--r--src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.keybin0 -> 352 bytes
-rw-r--r--src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.keybin0 -> 352 bytes
-rw-r--r--src/tests/data/test_cli_g10_defkey/g10/pubring.kbxbin0 -> 13254 bytes
-rw-r--r--src/tests/data/test_cli_rnpkeys/g10_list_keys72
-rw-r--r--src/tests/data/test_cli_rnpkeys/g10_list_keys_no_bp72
-rw-r--r--src/tests/data/test_cli_rnpkeys/g10_list_keys_sec72
-rw-r--r--src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_bp72
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_000000001
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb12
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec12
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec_y2k3812
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig17
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig_y2k3817
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_y2k3812
-rw-r--r--src/tests/data/test_cli_rnpkeys/getkey_zzzzzzzz1
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_keys24
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec24
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k3824
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k3824
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs35
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec35
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k3835
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k3835
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_2_list_keys6
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs7
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_3_list_keys8
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k388
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs10
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k3810
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_5_list_keys8
-rw-r--r--src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs10
-rw-r--r--src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt34
-rw-r--r--src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt_y2k3834
-rw-r--r--src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys72
-rw-r--r--src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_bp72
-rw-r--r--src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec66
-rw-r--r--src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs95
-rw-r--r--src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_bp95
-rw-r--r--src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec77
-rw-r--r--src/tests/data/test_ffi_json/generate-bad-pk-alg.json10
-rw-r--r--src/tests/data/test_ffi_json/generate-eddsa-wrong-prefs.json14
-rw-r--r--src/tests/data/test_ffi_json/generate-pair-dsa-elg.json11
-rw-r--r--src/tests/data/test_ffi_json/generate-pair.json25
-rw-r--r--src/tests/data/test_ffi_json/generate-primary.json15
-rw-r--r--src/tests/data/test_ffi_json/generate-sub.json11
-rw-r--r--src/tests/data/test_forged_keys/dsa-eg-pub-forged-key.pgpbin0 -> 1881 bytes
-rw-r--r--src/tests/data/test_forged_keys/dsa-eg-pub-forged-material.pgpbin0 -> 1881 bytes
-rw-r--r--src/tests/data/test_forged_keys/dsa-eg-pub-forged-subkey.pgpbin0 -> 1881 bytes
-rw-r--r--src/tests/data/test_forged_keys/dsa-eg-pub.pgpbin0 -> 1881 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-25519-pub-forged-key.pgpbin0 -> 214 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-25519-pub-forged-material.pgpbin0 -> 214 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-25519-pub-future-cert-malf-bind.pgpbin0 -> 604 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-25519-pub-future-cert.pgpbin0 -> 604 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-25519-pub.pgpbin0 -> 214 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-expired-key.pgpbin0 -> 460 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-expired-subkey.pgpbin0 -> 460 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-forged-key.pgpbin0 -> 454 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-forged-material.pgpbin0 -> 454 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-forged-subkey.pgpbin0 -> 454 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-no-binding.pgpbin0 -> 332 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-no-cert-malf-binding.pgpbin0 -> 304 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub-no-certification.pgpbin0 -> 304 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-pub.pgpbin0 -> 454 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-sec-expired-key.pgpbin0 -> 626 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-sec-expired-subkey.pgpbin0 -> 626 bytes
-rw-r--r--src/tests/data/test_forged_keys/ecc-p256-sec.pgpbin0 -> 620 bytes
-rw-r--r--src/tests/data/test_forged_keys/eddsa-2012-md5-pub.pgpbin0 -> 393 bytes
-rw-r--r--src/tests/data/test_forged_keys/eddsa-2012-md5-sec.pgpbin0 -> 467 bytes
-rw-r--r--src/tests/data/test_forged_keys/eddsa-2024-pub.pgpbin0 -> 406 bytes
-rw-r--r--src/tests/data/test_forged_keys/eddsa-2024-sec.pgpbin0 -> 480 bytes
-rw-r--r--src/tests/data/test_forged_keys/rsa-rsa-pub-forged-key.pgpbin0 -> 1719 bytes
-rw-r--r--src/tests/data/test_forged_keys/rsa-rsa-pub-forged-material.pgpbin0 -> 1719 bytes
-rw-r--r--src/tests/data/test_forged_keys/rsa-rsa-pub-forged-subkey.pgpbin0 -> 1719 bytes
-rw-r--r--src/tests/data/test_forged_keys/rsa-rsa-pub-future-key.pgpbin0 -> 1201 bytes
-rw-r--r--src/tests/data/test_forged_keys/rsa-rsa-pub.pgpbin0 -> 1719 bytes
-rw-r--r--src/tests/data/test_forged_keys/rsa-rsa-sec-future-key.pgpbin0 -> 2594 bytes
-rw-r--r--src/tests/data/test_forged_keys/rsa-rsa-sec.pgpbin0 -> 3753 bytes
-rw-r--r--src/tests/data/test_fuzz_dump/clusterfuzz-testcase-minimized-fuzz_dump-5757362284265472bin0 -> 93740 bytes
-rw-r--r--src/tests/data/test_fuzz_dump/outofmemory-5570076898623488bin0 -> 3187 bytes
-rw-r--r--src/tests/data/test_fuzz_dump/timeout-6462239459115008bin0 -> 46 bytes
-rw-r--r--src/tests/data/test_fuzz_dump/timeout-7e498daecad7ee646371a466d4a317c59fe7db89bin0 -> 19194 bytes
-rw-r--r--src/tests/data/test_fuzz_keyimport/crash_25f06f13b48d58a5faf6c36fae7fcbd958359199bin0 -> 1105 bytes
-rw-r--r--src/tests/data/test_fuzz_keyimport/crash_37e8ed57ee47c1991b387fa0506f361f9cd9c663bin0 -> 1312 bytes
-rw-r--r--src/tests/data/test_fuzz_keyimport/crash_e932261875271ccf497715de56adf7caf30ca8a73
-rw-r--r--src/tests/data/test_fuzz_keyimport/leak_11307b70cc609c93fc3a49d37f3a31166df50f44bin0 -> 60 bytes
-rw-r--r--src/tests/data/test_fuzz_keyimport/leak_371b211d7e9cf9857befcf06c7da74835e249ee7bin0 -> 83 bytes
-rw-r--r--src/tests/data/test_fuzz_keyimport/timeout-9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051bin0 -> 182220 bytes
-rw-r--r--src/tests/data/test_fuzz_keyimport/timeout_9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051bin0 -> 726 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring/crash-7ff10f10a95b78461d6f3578f5f99e870c792b9fbin0 -> 41 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring/crash-8619144979e56d07ab4890bf564b90271ae9b1c9bin0 -> 56 bytes
-rwxr-xr-xsrc/tests/data/test_fuzz_keyring/leak-542d4e51506e3e9d34c9b243e608a964dabfdb21bin0 -> 540 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring/leak-5ee77f7ae99d7815d069afe037c42f4887193215bin0 -> 81 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring/timeout-6140201111519232bin0 -> 791280 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring_g10/crash_4ec166859e821aee27350dcde3e9c06b07a677f71
-rw-r--r--src/tests/data/test_fuzz_keyring_g10/crash_55286253259325441
-rw-r--r--src/tests/data/test_fuzz_keyring_g10/crash_c9cabce6f8d7b36fde0306c86ce81c4f554cbd2a1
-rw-r--r--src/tests/data/test_fuzz_keyring_kbx/crash-5526a2e13255018c857ce493c28ce7108b8b2987bin0 -> 32 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring_kbx/crash-b894a2f79f7d38a16ae0ee8d74972336aa3f5798bin0 -> 150 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring_kbx/leak-52c65c00b53997178f4cd9defa0343573ea8dda6bin0 -> 361 bytes
-rw-r--r--src/tests/data/test_fuzz_keyring_kbx/leak-b02cd1c6b70c10a8a673a34ba3770b39468b7ddfbin0 -> 481 bytes
-rw-r--r--src/tests/data/test_fuzz_sigimport/timeout-821848a7b6b667fc41e5ff130415b3efd22ed118bin0 -> 47498 bytes
-rw-r--r--src/tests/data/test_fuzz_verify/timeout-25b8c9d824c8eb492c827689795748298a2b0a46bin0 -> 12874 bytes
-rw-r--r--src/tests/data/test_fuzz_verify/timeout-5229070269153280bin0 -> 12 bytes
-rw-r--r--src/tests/data/test_fuzz_verify/timeout-6613852539453440bin0 -> 670251 bytes
-rw-r--r--src/tests/data/test_fuzz_verify/timeout-c2aff538c73b447bca689005e9762840b5a022d0bin0 -> 39966 bytes
-rw-r--r--src/tests/data/test_fuzz_verify_detached/clusterfuzz-testcase-minimized-fuzz_verify_detached-5092660526972928bin0 -> 263676 bytes
-rw-r--r--src/tests/data/test_fuzz_verify_detached/outofmemory-23094cb781b2cf6d1749ebac8bd0576e51440498-zbin0 -> 466 bytes
-rw-r--r--src/tests/data/test_fuzz_verify_detached/outofmemory-dea88a4aa4ab5fec1291446db702ee893d5559cfbin0 -> 742567 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-2-keys-same-grip.pgpbin0 -> 858 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-2-subs-same-grip.pgpbin0 -> 640 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-3-uids-primary-boris.pgpbin0 -> 930 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-3-uids-primary-expiring.pgpbin0 -> 948 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-3-uids.pgpbin0 -> 927 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-rev-no-reason.pgpbin0 -> 337 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-s2k-101-1-subs.pgpbin0 -> 925 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-s2k-101-2-card-len.pgpbin0 -> 942 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-s2k-101-2-card.pgpbin0 -> 942 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-s2k-101-3.pgpbin0 -> 925 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-s2k-101-no-sign-sub.pgpbin0 -> 520 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-s2k-101-unknown.pgpbin0 -> 925 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-sig-misc-values.pgpbin0 -> 1051 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-sub-rev-no-reason.pgpbin0 -> 1096 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-sub-sig-fp.pgpbin0 -> 512 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-sub-sig-keyid.pgpbin0 -> 489 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-uid-binding.pgpbin0 -> 459 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/alice-wrong-mpi-bit-count.pgpbin0 -> 218 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec-prot.asc14
-rw-r--r--src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec.asc13
-rw-r--r--src/tests/data/test_key_edge_cases/key-25519-non-tweaked.asc11
-rw-r--r--src/tests/data/test_key_edge_cases/key-25519-tweaked-sec.asc13
-rw-r--r--src/tests/data/test_key_edge_cases/key-25519-tweaked-wrong-crc.asc13
-rw-r--r--src/tests/data/test_key_edge_cases/key-binding-hash-alg.asc14
-rw-r--r--src/tests/data/test_key_edge_cases/key-create-expiry-32bit.asc12
-rw-r--r--src/tests/data/test_key_edge_cases/key-critical-notations-sec.pgpbin0 -> 633 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-critical-notations.pgpbin0 -> 467 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-eddsa-small-x-pub.asc11
-rw-r--r--src/tests/data/test_key_edge_cases/key-eddsa-small-x-sec.asc14
-rw-r--r--src/tests/data/test_key_edge_cases/key-eg-4096-pub.pgpbin0 -> 2138 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-eg-4096-sec.pgpbin0 -> 2330 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-eg-small-subgroup-pub.pgpbin0 -> 1870 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec-enc.pgpbin0 -> 2260 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec.pgpbin0 -> 2168 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-empty-packets.pgpbin0 -> 6 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-empty-packets.txt7
-rw-r--r--src/tests/data/test_key_edge_cases/key-empty-uid-raw.txt91
-rw-r--r--src/tests/data/test_key_edge_cases/key-empty-uid.json170
-rw-r--r--src/tests/data/test_key_edge_cases/key-empty-uid.pgpbin0 -> 201 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-empty-uid.txt43
-rw-r--r--src/tests/data/test_key_edge_cases/key-expired-cert-direct.pgpbin0 -> 781 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-malf-sig.json45
-rw-r--r--src/tests/data/test_key_edge_cases/key-malf-sig.pgpbin0 -> 201 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-malf-sig.txt15
-rw-r--r--src/tests/data/test_key_edge_cases/key-primary-uid-conflict-pub.pgpbin0 -> 548 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-primary-uid-conflict-sec.pgpbin0 -> 714 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-rsa-2001-pub.asc26
-rw-r--r--src/tests/data/test_key_edge_cases/key-rsa-2001-sec.asc50
-rw-r--r--src/tests/data/test_key_edge_cases/key-sec.asc11
-rw-r--r--src/tests/data/test_key_edge_cases/key-sub-0-expiry.pgpbin0 -> 408 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-sub-crit-note-pub.pgpbin0 -> 479 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-sub-crit-note-sec.pgpbin0 -> 645 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-subpacket-101-110.json250
-rw-r--r--src/tests/data/test_key_edge_cases/key-subpacket-101-110.pgpbin0 -> 246 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/key-subpacket-101-110.txt63
-rw-r--r--src/tests/data/test_key_edge_cases/key-unhashed-subpkts.pgpbin0 -> 2017 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/pubring-malf-cert.pgpbin0 -> 3535 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0-bind.pgpbin0 -> 3535 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0.pgpbin0 -> 3535 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/pubring-malf-key0.pgpbin0 -> 3535 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/secring-malf-key0.pgpbin0 -> 4909 bytes
-rw-r--r--src/tests/data/test_key_edge_cases/secring-malf-key1.pgpbin0 -> 4909 bytes
-rw-r--r--src/tests/data/test_key_validity/CMakeLists.txt25
-rw-r--r--src/tests/data/test_key_validity/alice-cert.pgpbin0 -> 146 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-expired-claus-cert.asc12
-rw-r--r--src/tests/data/test_key_validity/alice-pub.asc9
-rw-r--r--src/tests/data/test_key_validity/alice-rev.pgp8
-rw-r--r--src/tests/data/test_key_validity/alice-revoker-sig.asc8
-rw-r--r--src/tests/data/test_key_validity/alice-revoker-sig.pgpbin0 -> 146 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-revoker.pgpbin0 -> 868 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-sec.asc11
-rw-r--r--src/tests/data/test_key_validity/alice-sign-sub-exp-pub.asc14
-rw-r--r--src/tests/data/test_key_validity/alice-sign-sub-exp-sec.asc17
-rw-r--r--src/tests/data/test_key_validity/alice-sign-sub-pub.pgpbin0 -> 540 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-sign-sub-sec.pgpbin0 -> 706 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-sigs-malf.pgpbin0 -> 268 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-sigs.asc16
-rw-r--r--src/tests/data/test_key_validity/alice-sigs.pgpbin0 -> 268 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-sub-pub.pgpbin0 -> 429 bytes
-rw-r--r--src/tests/data/test_key_validity/alice-sub-sec.pgpbin0 -> 595 bytes
-rw-r--r--src/tests/data/test_key_validity/basil-pub.asc10
-rw-r--r--src/tests/data/test_key_validity/basil-sec.asc11
-rw-r--r--src/tests/data/test_key_validity/case1/pubring.gpgbin0 -> 337 bytes
-rw-r--r--src/tests/data/test_key_validity/case10/pubring.gpgbin0 -> 554 bytes
-rw-r--r--src/tests/data/test_key_validity/case11/pubring.gpgbin0 -> 701 bytes
-rw-r--r--src/tests/data/test_key_validity/case12/pubring.gpgbin0 -> 701 bytes
-rw-r--r--src/tests/data/test_key_validity/case13/pubring.gpgbin0 -> 701 bytes
-rw-r--r--src/tests/data/test_key_validity/case14/pubring.gpgbin0 -> 695 bytes
-rw-r--r--src/tests/data/test_key_validity/case15/pubring.gpgbin0 -> 525 bytes
-rw-r--r--src/tests/data/test_key_validity/case2/pubring.gpgbin0 -> 705 bytes
-rw-r--r--src/tests/data/test_key_validity/case3/pubring.gpgbin0 -> 559 bytes
-rw-r--r--src/tests/data/test_key_validity/case4/pubring.gpgbin0 -> 429 bytes
-rw-r--r--src/tests/data/test_key_validity/case5/CMakeLists.txt38
-rw-r--r--src/tests/data/test_key_validity/case5/generate.cpp160
-rw-r--r--src/tests/data/test_key_validity/case5/pubring.gpgbin0 -> 678 bytes
-rw-r--r--src/tests/data/test_key_validity/case6/pubring.gpgbin0 -> 551 bytes
-rw-r--r--src/tests/data/test_key_validity/case7/pubring.gpgbin0 -> 569 bytes
-rw-r--r--src/tests/data/test_key_validity/case8/message.txt3
-rw-r--r--src/tests/data/test_key_validity/case8/message.txt.asc17
-rw-r--r--src/tests/data/test_key_validity/case8/primary.pgpbin0 -> 53 bytes
-rw-r--r--src/tests/data/test_key_validity/case8/pubring.gpgbin0 -> 375 bytes
-rw-r--r--src/tests/data/test_key_validity/case8/subkey-no-sig.pgpbin0 -> 81 bytes
-rw-r--r--src/tests/data/test_key_validity/case8/subkey.pgpbin0 -> 322 bytes
-rw-r--r--src/tests/data/test_key_validity/case9/pubring.gpgbin0 -> 581 bytes
-rw-r--r--src/tests/data/test_key_validity/cases.txt77
-rw-r--r--src/tests/data/test_key_validity/claus-pub.asc10
-rw-r--r--src/tests/data/test_key_validity/claus-sec.asc11
-rw-r--r--src/tests/data/test_key_validity/encrypting-primary.pgp41
-rw-r--r--src/tests/data/test_key_validity/rsa_key_small_sig-pub.asc26
-rw-r--r--src/tests/data/test_key_validity/rsa_key_small_sig-sec.asc48
-rw-r--r--src/tests/data/test_large_MPIs/message.enc.rsa16384.pgpbin0 -> 2240 bytes
-rw-r--r--src/tests/data/test_large_MPIs/message.enc.rsa16385.pgpbin0 -> 2240 bytes
-rw-r--r--src/tests/data/test_large_MPIs/rsa-priv-16384bits.pgpbin0 -> 18673 bytes
-rw-r--r--src/tests/data/test_large_MPIs/rsa-priv-16385bits.pgpbin0 -> 18673 bytes
-rw-r--r--src/tests/data/test_large_MPIs/rsa-pub-16384bits.pgpbin0 -> 8411 bytes
-rw-r--r--src/tests/data/test_large_MPIs/rsa-pub-16385bits.pgpbin0 -> 8411 bytes
-rw-r--r--src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgpbin0 -> 8208 bytes
-rw-r--r--src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.16385sig.sigbin0 -> 2108 bytes
-rw-r--r--src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.sigbin0 -> 2108 bytes
-rw-r--r--src/tests/data/test_large_packet/4g.bzip2.gpgbin0 -> 3349 bytes
-rw-r--r--src/tests/data/test_list_packets/ecc-p256-pub.asc14
-rw-r--r--src/tests/data/test_list_packets/list_all.txt152
-rw-r--r--src/tests/data/test_list_packets/list_grips.txt81
-rw-r--r--src/tests/data/test_list_packets/list_json.txt270
-rw-r--r--src/tests/data/test_list_packets/list_json_all.txt299
-rw-r--r--src/tests/data/test_list_packets/list_json_grips.txt274
-rw-r--r--src/tests/data/test_list_packets/list_json_mpi.txt276
-rw-r--r--src/tests/data/test_list_packets/list_json_raw.txt289
-rw-r--r--src/tests/data/test_list_packets/list_mpi.txt77
-rw-r--r--src/tests/data/test_list_packets/list_raw.txt148
-rw-r--r--src/tests/data/test_list_packets/list_standard.txt77
-rw-r--r--src/tests/data/test_messages/data.enc.small-rsa10
-rw-r--r--src/tests/data/test_messages/eddsa-zero-r.txt.sigbin0 -> 1947 bytes
-rw-r--r--src/tests/data/test_messages/eddsa-zero-s.txt.sigbin0 -> 1336 bytes
-rw-r--r--src/tests/data/test_messages/expired_signing_key-pub.asc12
-rw-r--r--src/tests/data/test_messages/expired_signing_key-sec.asc13
-rw-r--r--src/tests/data/test_messages/expired_signing_sub-pub.asc16
-rw-r--r--src/tests/data/test_messages/expired_signing_sub-sec.asc18
-rw-r--r--src/tests/data/test_messages/future.pgpbin0 -> 469 bytes
-rw-r--r--src/tests/data/test_messages/message-32k-crlf.txt738
-rw-r--r--src/tests/data/test_messages/message-32k-crlf.txt.gpgbin0 -> 2847 bytes
-rw-r--r--src/tests/data/test_messages/message-32k-crlf.txt.sigbin0 -> 181 bytes
-rw-r--r--src/tests/data/test_messages/message-trailing-cr.txt3
-rw-r--r--src/tests/data/test_messages/message-trailing-cr.txt.sig-textbin0 -> 181 bytes
-rw-r--r--src/tests/data/test_messages/message.4k-long-lines16
-rw-r--r--src/tests/data/test_messages/message.4k-long-lines.asc27
-rw-r--r--src/tests/data/test_messages/message.aead-last-zero-chunk.encbin0 -> 32426 bytes
-rw-r--r--src/tests/data/test_messages/message.aead-last-zero-chunk.enc-ocbbin0 -> 32425 bytes
-rw-r--r--src/tests/data/test_messages/message.aead-last-zero-chunk.txt1243
-rw-r--r--src/tests/data/test_messages/message.compr-encr.31-rounds45
-rw-r--r--src/tests/data/test_messages/message.compr-encr.32-rounds51
-rw-r--r--src/tests/data/test_messages/message.compr.128-rounds320
-rw-r--r--src/tests/data/test_messages/message.enc-passwordbin0 -> 12752 bytes
-rw-r--r--src/tests/data/test_messages/message.sig.asc.malf9
-rw-r--r--src/tests/data/test_messages/message.text-sig-crcr703
-rw-r--r--src/tests/data/test_messages/message.text-sig-crcr.sigbin0 -> 130 bytes
-rw-r--r--src/tests/data/test_messages/message.txt3
-rw-r--r--src/tests/data/test_messages/message.txt.2sigsbin0 -> 250 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.2sigs-2bin0 -> 250 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.asc8
-rw-r--r--src/tests/data/test_messages/message.txt.clear-2-sigs15
-rw-r--r--src/tests/data/test_messages/message.txt.clear-2-sigs-215
-rw-r--r--src/tests/data/test_messages/message.txt.cleartext-malf15
-rw-r--r--src/tests/data/test_messages/message.txt.cleartext-nosig8
-rw-r--r--src/tests/data/test_messages/message.txt.cleartext-signed15
-rw-r--r--src/tests/data/test_messages/message.txt.cleartext-signed-nonewline14
-rw-r--r--src/tests/data/test_messages/message.txt.crlf3
-rw-r--r--src/tests/data/test_messages/message.txt.empty.sigbin0 -> 227 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-3key-2pbin0 -> 826 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-aead-eaxbin0 -> 468 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-aead-eax-malfbin0 -> 468 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-aead-ocb1
-rw-r--r--src/tests/data/test_messages/message.txt.enc-aead-ocb-aes2
-rw-r--r--src/tests/data/test_messages/message.txt.enc-aead-ocb-malf1
-rw-r--r--src/tests/data/test_messages/message.txt.enc-eg-badbin0 -> 699 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-eg-bad2bin0 -> 699 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-hidden-1bin0 -> 583 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-hidden-2bin0 -> 584 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-malf-1bin0 -> 325 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-malf-2bin0 -> 326 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-malf-3bin0 -> 326 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-malf-4bin0 -> 327 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-malf-5bin0 -> 325 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-mdcbin0 -> 320 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-no-mdcbin0 -> 296 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-sign-25519bin0 -> 406 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.enc-wrong-algbin0 -> 325 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.encryptedbin0 -> 325 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.literal3
-rw-r--r--src/tests/data/test_messages/message.txt.markerbin0 -> 523 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.marker.asc14
-rw-r--r--src/tests/data/test_messages/message.txt.marker.malfbin0 -> 525 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.pkesk-skesk-v10bin0 -> 555 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.pkesk-skesk-v10-onlybin0 -> 379 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.sigbin0 -> 187 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.sig-textbin0 -> 181 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.sig.malfbin0 -> 187 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.sig.wrong-mpi-bitlenbin0 -> 187 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.sign-small-eddsa-xbin0 -> 160 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signedbin0 -> 331 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-2-2-onepassbin0 -> 439 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-2-2-onepass-v10bin0 -> 439 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-2-2-sig-v10bin0 -> 439 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-2-2-sig-v10-2bin0 -> 439 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-2-onepassbin0 -> 314 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-class19bin0 -> 266 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-encryptedbin0 -> 518 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-expired-keybin0 -> 265 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-expired-subbin0 -> 265 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-md5-afterbin0 -> 467 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-md5-beforebin0 -> 469 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-no-zbin0 -> 361 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-no-z-malfbin0 -> 361 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-sha1-afterbin0 -> 330 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-sha1-beforebin0 -> 332 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-sym-none-z2
-rw-r--r--src/tests/data/test_messages/message.txt.signed-unknown-onepass-hashbin0 -> 299 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-wrong-onepassbin0 -> 299 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed-wrong-onepass-hashbin0 -> 299 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed.crit-notationbin0 -> 359 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed.invsigbin0 -> 361 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed.malfsigbin0 -> 361 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed.md5bin0 -> 330 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed.nosigbin0 -> 174 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed.sha1bin0 -> 332 bytes
-rw-r--r--src/tests/data/test_messages/message.txt.signed.unknownbin0 -> 490 bytes
-rw-r--r--src/tests/data/test_messages/message.wrong-armor.asc3
-rw-r--r--src/tests/data/test_messages/message.zlib-quinebin0 -> 180 bytes
-rw-r--r--src/tests/data/test_messages/message_mdc_8k_1.pgpbin0 -> 131265 bytes
-rw-r--r--src/tests/data/test_messages/message_mdc_8k_2.pgpbin0 -> 131259 bytes
-rw-r--r--src/tests/data/test_messages/message_mdc_8k_cut1.pgpbin0 -> 131264 bytes
-rw-r--r--src/tests/data/test_messages/message_mdc_8k_cut22.pgpbin0 -> 131243 bytes
-rw-r--r--src/tests/data/test_messages/shattered-1.pdfbin0 -> 422435 bytes
-rw-r--r--src/tests/data/test_messages/shattered-1.pdf.sigbin0 -> 181 bytes
-rw-r--r--src/tests/data/test_messages/shattered-2.pdfbin0 -> 422435 bytes
-rw-r--r--src/tests/data/test_messages/shattered-2.pdf.gpgbin0 -> 381801 bytes
-rw-r--r--src/tests/data/test_partial_length/message.txt.partial-1gbin0 -> 1139 bytes
-rw-r--r--src/tests/data/test_partial_length/message.txt.partial-256bin0 -> 724 bytes
-rw-r--r--src/tests/data/test_partial_length/message.txt.partial-signedbin0 -> 237 bytes
-rw-r--r--src/tests/data/test_partial_length/message.txt.partial-zero-lastbin0 -> 717 bytes
-rw-r--r--src/tests/data/test_partial_length/pubring.gpg.partialbin0 -> 3536 bytes
-rw-r--r--src/tests/data/test_repgp/encrypted_key.gpgbin0 -> 2199 bytes
-rw-r--r--src/tests/data/test_repgp/encrypted_text.gpgbin0 -> 241 bytes
-rw-r--r--src/tests/data/test_repgp/signed.gpgbin0 -> 238 bytes
-rw-r--r--src/tests/data/test_single_export_subkeys/list_key_export_single.txt75
-rw-r--r--src/tests/data/test_stream_armor/1024_peek_buf.asc52
-rw-r--r--src/tests/data/test_stream_armor/64k_whitespace_before_armored_message.asc52
-rw-r--r--src/tests/data/test_stream_armor/b64_trailer_extra_data.b645
-rw-r--r--src/tests/data/test_stream_armor/blank_line_with_whitespace.asc52
-rw-r--r--src/tests/data/test_stream_armor/duplicate_header_line.asc54
-rw-r--r--src/tests/data/test_stream_armor/ecc-25519-pub-bad-crc.asc9
-rw-r--r--src/tests/data/test_stream_armor/empty_header_line.asc53
-rw-r--r--src/tests/data/test_stream_armor/extra_line_before_trailer.asc52
-rw-r--r--src/tests/data/test_stream_armor/long_b64_trailer.b645
-rw-r--r--src/tests/data/test_stream_armor/long_header_line.asc53
-rw-r--r--src/tests/data/test_stream_armor/long_header_line_1024.asc53
-rw-r--r--src/tests/data/test_stream_armor/long_header_line_64k.asc53
-rw-r--r--src/tests/data/test_stream_armor/long_header_nameline_64k.asc53
-rw-r--r--src/tests/data/test_stream_armor/message_64k_oneline.asc5
-rw-r--r--src/tests/data/test_stream_armor/too_short_header.asc4
-rw-r--r--src/tests/data/test_stream_armor/wrong_b64_trailer.asc9
-rw-r--r--src/tests/data/test_stream_armor/wrong_chars_base64_1.asc52
-rw-r--r--src/tests/data/test_stream_armor/wrong_chars_base64_2.asc52
-rw-r--r--src/tests/data/test_stream_armor/wrong_chars_base64_3.asc52
-rw-r--r--src/tests/data/test_stream_armor/wrong_chars_base64_4.asc52
-rw-r--r--src/tests/data/test_stream_armor/wrong_chars_crc.asc52
-rw-r--r--src/tests/data/test_stream_armor/wrong_chars_header.asc52
-rw-r--r--src/tests/data/test_stream_armor/wrong_header_line.asc54
-rw-r--r--src/tests/data/test_stream_key_load/dsa-eg-pub.asc44
-rw-r--r--src/tests/data/test_stream_key_load/dsa-eg-sec.asc48
-rw-r--r--src/tests/data/test_stream_key_load/ecc-25519-photo-pub.asc44
-rw-r--r--src/tests/data/test_stream_key_load/ecc-25519-pub-2.b647
-rw-r--r--src/tests/data/test_stream_key_load/ecc-25519-pub-3.b649
-rw-r--r--src/tests/data/test_stream_key_load/ecc-25519-pub-4.b647
-rw-r--r--src/tests/data/test_stream_key_load/ecc-25519-pub.asc9
-rw-r--r--src/tests/data/test_stream_key_load/ecc-25519-pub.b647
-rw-r--r--src/tests/data/test_stream_key_load/ecc-25519-sec.asc11
-rw-r--r--src/tests/data/test_stream_key_load/ecc-bp256-pub.asc14
-rw-r--r--src/tests/data/test_stream_key_load/ecc-bp256-sec.asc17
-rw-r--r--src/tests/data/test_stream_key_load/ecc-bp384-pub.asc17
-rw-r--r--src/tests/data/test_stream_key_load/ecc-bp384-sec.asc21
-rw-r--r--src/tests/data/test_stream_key_load/ecc-bp512-pub.asc19
-rw-r--r--src/tests/data/test_stream_key_load/ecc-bp512-sec.asc24
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256-pub.asc14
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256-revoked-key.asc17
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256-revoked-sub.asc17
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256-revoked-uid.asc20
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256-sec.asc17
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256k1-pub-2.b6413
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256k1-pub.asc14
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256k1-pub.b641
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256k1-pub.pgpbin0 -> 450 bytes
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p256k1-sec.asc17
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p384-pub.asc16
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p384-sec.asc21
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p521-pub.asc19
-rw-r--r--src/tests/data/test_stream_key_load/ecc-p521-sec.asc24
-rw-r--r--src/tests/data/test_stream_key_load/ecc-x25519-pub.asc13
-rw-r--r--src/tests/data/test_stream_key_load/ecc-x25519-sec.asc16
-rw-r--r--src/tests/data/test_stream_key_load/eddsa-00-pub.pgpbin0 -> 389 bytes
-rw-r--r--src/tests/data/test_stream_key_load/eddsa-00-sec.pgpbin0 -> 461 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/2F25DB025DEBF3EA2715350209B985829B04F50A.key2
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/3E36CDC06F95B604429321B3E3D6B2A2A5CDD562.key32
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/48FFED40D018747363BDEFFDD404D1F4870F8064.key1
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/498B89C485489BA16B40755C0EBA580166393074.keybin0 -> 350 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/552286BEB2999F0A9E26A50385B90D9724001187.keybin0 -> 1094 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/5A484F56AB4B8B6583B6365034999F6543FAE1AE.key2
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/636C983EDB558527BA82780B52CB5DAE011BE46B.key2
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF.key3
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/9133E4A7E8FC8515518DF444C3F2F247EEBBADEC.key3
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/940D97D75C306D737A59A98EAFF1272832CEDC0B.keybin0 -> 332 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7.keybin0 -> 357 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A1338230AED1C9C125663518470B49056C9D1733.keybin0 -> 401 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.keybin0 -> 352 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C.keybin0 -> 1607 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE.keybin0 -> 332 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8.key2
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/C1678B7DE5F144C93B89468D5F9764ACE182ED36.key3
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/CED7034A8EB5F4CE90DF99147EC33D86FCD3296C.keybin0 -> 1069 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D148210FAF36468055B83D0F5A6DEB83FBC8E864.keybin0 -> 1607 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D91B789603EC9138AA20342A2B6DC86C81B70F5D.keybin0 -> 454 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.keybin0 -> 352 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FCEB1E2A5E3402B8E76E7B89A4EB12CF52B50C25.key31
-rw-r--r--src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA.keybin0 -> 454 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g10/pubring.kbxbin0 -> 11640 bytes
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/2D5DAB4841F4DBB74DEC7050A4B07458234ACB82.key7
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5012C691581B550965573790E1156BBE903ABAA0.key45
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B0DF3754AA0877E228FBFFDBDE337744EA244D4.key8
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B5E0586D3F942C5DBBF1FD21CCD46C364EFC4C4.key24
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/7C1187B9FD883651040A6EA6D50C226317A16C5A.key7
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/8C28B6E8F9ABCD9F9F24B0AFA139828BF700E8CE.key10
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/B06D02DFA4405556F467ED9DAB952260C130FE5C.key7
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/C998889DD8F40CF9960C1FE939DAD37DC1F3CB03.key8
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/D8654AE2EF28B8093824651380B8C1F4B5DF0E46.key10
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/DCCBAD8A71D6281D1462FD8BDCB1A8567C38357C.key29
-rw-r--r--src/tests/data/test_stream_key_load/g23/private-keys-v1.d/FD454CBC445A1D8AC346BED0D4A03C3511B8428F.key45
-rw-r--r--src/tests/data/test_stream_key_load/g23/pubring.kbxbin0 -> 7321 bytes
-rw-r--r--src/tests/data/test_stream_key_load/key0-sub01.pgpbin0 -> 1702 bytes
-rw-r--r--src/tests/data/test_stream_key_load/key0-sub02.pgpbin0 -> 2006 bytes
-rw-r--r--src/tests/data/test_stream_key_load/key0-sub1.pgpbin0 -> 1398 bytes
-rw-r--r--src/tests/data/test_stream_key_load/rsa-rsa-2-pub.asc30
-rw-r--r--src/tests/data/test_stream_key_load/rsa-rsa-2-sec.asc59
-rw-r--r--src/tests/data/test_stream_key_load/rsa-rsa-pub.asc40
-rw-r--r--src/tests/data/test_stream_key_load/rsa-rsa-sec.asc83
-rw-r--r--src/tests/data/test_stream_key_merge/key-both.asc202
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-just-key.pgpbin0 -> 400 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-just-subkey-1.pgpbin0 -> 841 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-just-subkey-2-no-sigs.pgpbin0 -> 817 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-just-subkey-2.pgpbin0 -> 1377 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-no-key-subkey-1.pgpbin0 -> 841 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-subkey-1-no-sigs.pgpbin0 -> 800 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-subkey-1.pgpbin0 -> 1241 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-subkey-2.pgpbin0 -> 2259 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-uid-1-no-sigs.pgpbin0 -> 417 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-uid-1.pgpbin0 -> 882 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub-uid-2.pgpbin0 -> 882 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub.asc79
-rw-r--r--src/tests/data/test_stream_key_merge/key-pub.pgpbin0 -> 3582 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-sec-just-subkey-1.pgpbin0 -> 1858 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-sec-just-subkey-2-no-sigs.pgpbin0 -> 900 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-sec-no-uid-no-sigs.pgpbin0 -> 3734 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-sec-uid-1-subkey-1.pgpbin0 -> 3757 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/key-sec.asc123
-rw-r--r--src/tests/data/test_stream_key_merge/key-sec.pgpbin0 -> 5699 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-key-pub.pgpbin0 -> 400 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-key-sec.pgpbin0 -> 1417 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-sub0-pub.pgpbin0 -> 400 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-sub0-sec.pgpbin0 -> 1417 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-sub0-sig.pgpbin0 -> 441 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-sub1-pub.pgpbin0 -> 817 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-sub1-sec.pgpbin0 -> 900 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-sub1-sig.pgpbin0 -> 560 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-uid0-sig.pgpbin0 -> 465 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-uid0.pgp1
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-uid1-sig.pgpbin0 -> 465 bytes
-rw-r--r--src/tests/data/test_stream_key_merge/pkt-uid1.pgp1
-rw-r--r--src/tests/data/test_stream_signatures/pub.asc41
-rw-r--r--src/tests/data/test_stream_signatures/revoked-key-sig.gpgbin0 -> 196 bytes
-rw-r--r--src/tests/data/test_stream_signatures/sec.asc83
-rw-r--r--src/tests/data/test_stream_signatures/signature-timestamp.asc12
-rw-r--r--src/tests/data/test_stream_signatures/source.txt1
-rw-r--r--src/tests/data/test_stream_signatures/source.txt.asc18
-rw-r--r--src/tests/data/test_stream_signatures/source.txt.asc.asc35
-rw-r--r--src/tests/data/test_stream_signatures/source.txt.sigbin0 -> 438 bytes
-rw-r--r--src/tests/data/test_stream_signatures/source.txt.sig.asc13
-rw-r--r--src/tests/data/test_stream_signatures/source.txt.text.sigbin0 -> 438 bytes
-rw-r--r--src/tests/data/test_stream_signatures/source_forged.txt1
-rw-r--r--src/tests/data/test_stream_verification/verify_encrypted_no_key.pgpbin0 -> 634 bytes
-rw-r--r--src/tests/data/test_stream_z/128mb.zipbin0 -> 157609 bytes
-rw-r--r--src/tests/data/test_stream_z/128mb.zip.cutbin0 -> 20000 bytes
-rw-r--r--src/tests/data/test_stream_z/128mb.zlibbin0 -> 157618 bytes
-rw-r--r--src/tests/data/test_stream_z/128mb.zlib.cutbin0 -> 157600 bytes
-rw-r--r--src/tests/data/test_stream_z/4gb.bzip2bin0 -> 43149 bytes
-rw-r--r--src/tests/data/test_stream_z/4gb.bzip2.asc762
-rw-r--r--src/tests/data/test_stream_z/4gb.bzip2.cutbin0 -> 43130 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-expired.pgpbin0 -> 1213 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-sig-expired.pgpbin0 -> 156 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-sig-revocation.pgpbin0 -> 145 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-uid-expired-sig.pgpbin0 -> 397 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-uid-prim-expired-sig.pgpbin0 -> 538 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-uids-pub-no-expire.pgpbin0 -> 3923 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-uids-pub.pgpbin0 -> 3929 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-uids-revoked-valid.pgpbin0 -> 8166 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-uids-sec.pgpbin0 -> 4089 bytes
-rw-r--r--src/tests/data/test_uid_validity/key-uids-with-invalid.pgpbin0 -> 3929 bytes
-rw-r--r--src/tests/data/test_validate_key_material/dsa-eg-pub.pgpbin0 -> 1881 bytes
-rw-r--r--src/tests/data/test_validate_key_material/dsa-eg-sec.pgpbin0 -> 1974 bytes
-rw-r--r--src/tests/data/test_validate_key_material/dsa-pub.pgpbin0 -> 817 bytes
-rw-r--r--src/tests/data/test_validate_key_material/dsa-sec.pgpbin0 -> 854 bytes
-rw-r--r--src/tests/data/test_validate_key_material/ecdh-p256-sec.pgpbin0 -> 125 bytes
-rw-r--r--src/tests/data/test_validate_key_material/ecdsa-p256-sec.pgpbin0 -> 121 bytes
-rw-r--r--src/tests/data/test_validate_key_material/ed25519-sec.pgpbin0 -> 90 bytes
-rw-r--r--src/tests/data/test_validate_key_material/eg-pub.pgpbin0 -> 784 bytes
-rw-r--r--src/tests/data/test_validate_key_material/eg-sec-small-group-enc.pgpbin0 -> 1090 bytes
-rw-r--r--src/tests/data/test_validate_key_material/eg-sec-small-group.pgpbin0 -> 1044 bytes
-rw-r--r--src/tests/data/test_validate_key_material/eg-sec.pgpbin0 -> 840 bytes
-rw-r--r--src/tests/data/test_validate_key_material/readme.txt48
-rw-r--r--src/tests/data/test_validate_key_material/rsa-pub.pgpbin0 -> 400 bytes
-rw-r--r--src/tests/data/test_validate_key_material/rsa-sec.pgpbin0 -> 1371 bytes
-rw-r--r--src/tests/data/test_validate_key_material/rsa-ssb.pgpbin0 -> 1371 bytes
-rw-r--r--src/tests/data/test_validate_key_material/rsa-sub.pgpbin0 -> 400 bytes
-rw-r--r--src/tests/data/test_validate_key_material/x25519-sec.pgpbin0 -> 95 bytes
-rw-r--r--src/tests/exportkey.cpp69
-rw-r--r--src/tests/ffi-enc.cpp1362
-rw-r--r--src/tests/ffi-key-prop.cpp1406
-rw-r--r--src/tests/ffi-key-sig.cpp1626
-rw-r--r--src/tests/ffi-key.cpp4442
-rw-r--r--src/tests/ffi-uid.cpp379
-rw-r--r--src/tests/ffi.cpp6025
-rw-r--r--src/tests/file-utils.cpp73
-rw-r--r--src/tests/fuzz_dump.cpp55
-rw-r--r--src/tests/fuzz_keyimport.cpp57
-rw-r--r--src/tests/fuzz_keyring.cpp54
-rw-r--r--src/tests/fuzz_keyring_g10.cpp45
-rw-r--r--src/tests/fuzz_keyring_kbx.cpp52
-rw-r--r--src/tests/fuzz_sigimport.cpp40
-rw-r--r--src/tests/fuzz_verify.cpp49
-rw-r--r--src/tests/fuzz_verify_detached.cpp47
-rw-r--r--src/tests/generatekey.cpp1243
-rw-r--r--src/tests/gnupg.py121
-rw-r--r--src/tests/issues/1030.cpp78
-rw-r--r--src/tests/issues/1115.cpp47
-rw-r--r--src/tests/issues/1171.cpp65
-rw-r--r--src/tests/issues/oss-fuzz-25489.cpp68
-rw-r--r--src/tests/kbx-nsigs-test.cpp137
-rw-r--r--src/tests/key-add-userid.cpp223
-rw-r--r--src/tests/key-grip.cpp156
-rw-r--r--src/tests/key-prefs.cpp102
-rw-r--r--src/tests/key-protect.cpp358
-rw-r--r--src/tests/key-store-search.cpp237
-rw-r--r--src/tests/key-unlock.cpp221
-rw-r--r--src/tests/key-validate.cpp765
-rw-r--r--src/tests/large-mpi.cpp127
-rw-r--r--src/tests/large-packet.cpp54
-rw-r--r--src/tests/load-g10.cpp111
-rw-r--r--src/tests/load-g23.cpp89
-rw-r--r--src/tests/load-pgp.cpp985
-rw-r--r--src/tests/log-switch.cpp146
-rw-r--r--src/tests/partial-length.cpp228
-rw-r--r--src/tests/pipe.cpp105
-rw-r--r--src/tests/rng-randomness.cpp53
-rw-r--r--src/tests/rnp.py141
-rw-r--r--src/tests/rnp_tests.cpp93
-rw-r--r--src/tests/rnp_tests.h81
-rw-r--r--src/tests/s2k-iterations.cpp104
-rw-r--r--src/tests/streams.cpp1782
-rw-r--r--src/tests/support.cpp1216
-rw-r--r--src/tests/support.h298
-rw-r--r--src/tests/user-prefs.cpp127
-rw-r--r--src/tests/utils-hex2bin.cpp84
-rw-r--r--src/tests/utils-rnpcfg.cpp167
-rw-r--r--version.txt1
892 files changed, 120530 insertions, 0 deletions
diff --git a/.cirrus.yml b/.cirrus.yml
new file mode 100644
index 0000000..0d0115f
--- /dev/null
+++ b/.cirrus.yml
@@ -0,0 +1,152 @@
+# Copyright (c) 2023 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.
+
+freebsd_instance:
+ image: freebsd-12-3-release-amd64
+
+task:
+ name: build
+ only_if: $CIRRUS_BRANCH == 'main' || $CIRRUS_BRANCH =~ 'release/.*' || $CIRRUS_PR != ''
+ skip: "!changesInclude('.cirrus.yml') && changesIncludeOnly('/*.sh', '/.*', '/_*', 'Brewfile', 'docs/**', '**.adoc', '**.md', '**.nix', 'flake.lock', '.github/**') || $CIRRUS_CHANGE_MESSAGE =~ '.*skip ci.*'"
+ env:
+ matrix:
+ - { CIRRUS_CLONE_SUBMODULES: true, CRYPTO_BACKEND: openssl, CRYPTO_LIB_INSTALL: openssl, SHARED_LIBS: on }
+ - { CIRRUS_CLONE_SUBMODULES: true, CRYPTO_BACKEND: botan, CRYPTO_LIB_INSTALL: botan2, SHARED_LIBS: on }
+ - { CIRRUS_CLONE_SUBMODULES: true, CRYPTO_BACKEND: botan, CRYPTO_LIB_INSTALL: botan2, SHARED_LIBS: off }
+
+ dependencies_script: |
+ pkg install -y gcc cmake pkgconf googletest gnupg $CRYPTO_LIB_INSTALL json-c rubygem-asciidoctor
+
+ user_script: |
+ pw useradd -n rnpuser -m
+ printf "\nrnpuser ALL=(ALL) NOPASSWD: ALL\n" > /usr/local/etc/sudoers.d/rnpuser
+ chown -R rnpuser:rnpuser "$PWD"
+
+ configure_script: |
+ su rnpuser -c 'cmake . -B build \
+ -DBUILD_SHARED_LIBS=$SHARED_LIBS \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DDOWNLOAD_GTEST=OFF \
+ -DCRYPTO_BACKEND=$CRYPTO_BACKEND'
+
+ build_script: |
+ su rnpuser -c 'cmake --build build --config Release --parallel $(sysctl -n hw.ncpu)'
+
+ test_script: |
+ su rnpuser -c 'mkdir -p build/Testing/Temporary'
+ su rnpuser -c 'cp cmake/CTestCostData.txt build/Testing/Temporary'
+ su rnpuser -c 'PATH=$PWD/build/src/lib:$PATH \
+ ctest --parallel $(sysctl -n hw.ncpu) --test-dir build -C Debug --output-on-failure'
+
+task:
+ name: package_source
+ only_if: $CIRRUS_BRANCH == 'main' || $CIRRUS_BRANCH =~ 'release/.*' || $CIRRUS_PR != ''
+ skip: "!changesInclude('.cirrus.yml') && changesIncludeOnly('/*.sh', '/.*', '/_*', 'Brewfile', 'docs/**', '**.adoc', '**.md', '**.nix', 'flake.lock', '.github/**') || $CIRRUS_CHANGE_MESSAGE =~ '.*skip ci.*'"
+ env:
+ CIRRUS_CLONE_SUBMODULES: true
+
+ dependencies_script: |
+ pkg install -y gcc cmake pkgconf botan2 json-c rubygem-asciidoctor
+
+ configure_script: |
+ cmake . -B build \
+ -DBUILD_SHARED_LIBS=ON \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DBUILD_TESTING=OFF \
+ -DCRYPTO_BACKEND=botan \
+ -DCMAKE_INSTALL_PREFIX=/usr
+
+ package_script: |
+ cpack -B build/source-pkg -G FREEBSD --config build/CPackSourceConfig.cmake
+
+ package_source_artifacts:
+ path: "build/source-pkg/*.pkg"
+
+ tests_artifacts:
+ path: "ci/tests/**"
+
+task:
+ name: package_binary
+ depends_on: [ package_source ]
+ only_if: $CIRRUS_BRANCH == 'main' || $CIRRUS_BRANCH =~ 'release/.*' || $CIRRUS_PR != ''
+ skip: "!changesInclude('.cirrus.yml') && changesIncludeOnly('/*.sh', '/.*', '/_*', 'Brewfile', 'docs/**', '**.adoc', '**.md', '**.nix', 'flake.lock', '.github/**') || $CIRRUS_CHANGE_MESSAGE =~ '.*skip ci.*'"
+
+ clone_script: |
+ echo "Not cloning rnp repo"
+
+ dependencies_script: |
+ pkg install -y wget unzip gcc cmake pkgconf botan2 json-c rubygem-asciidoctor bzip2
+
+ download_script: |
+ wget -q https://api.cirrus-ci.com/v1/artifact/build/$CIRRUS_BUILD_ID/package_source.zip
+ unzip package_source.zip
+ pkg add --relocate "$PWD" "$(ls ./build/source-pkg/*.pkg)"
+
+ configure_script: |
+ cmake usr/local -B build \
+ -DBUILD_SHARED_LIBS=ON \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DBUILD_TESTING=OFF \
+ -DCRYPTO_BACKEND=botan \
+ -DCMAKE_INSTALL_PREFIX=/usr
+
+ build_script: |
+ cmake --build build --config Release --parallel $(sysctl -n hw.ncpu)
+
+ install_script: |
+ cmake --install build
+
+ package_script: |
+ cpack -B build/binary-pkg -G FREEBSD --config build/CPackConfig.cmake
+
+ package_binary_artifacts:
+ path: "build/binary-pkg/*.pkg"
+
+task:
+ name: test_binary
+ depends_on: [ package_binary ]
+ only_if: $CIRRUS_BRANCH == 'main' || $CIRRUS_BRANCH =~ 'release/.*' || $CIRRUS_PR != ''
+ skip: "!changesInclude('.cirrus.yml') && changesIncludeOnly('/*.sh', '/.*', '/_*', 'Brewfile', 'docs/**', '**.adoc', '**.md', '**.nix', 'flake.lock', '.github/**') || $CIRRUS_CHANGE_MESSAGE =~ '.*skip ci.*'"
+
+ clone_script: |
+ echo "Not cloning rnp repo"
+
+ dependencies_script: |
+ pkg install -y bash wget git botan2 json-c bzip2
+
+ download_script: |
+ wget -q https://api.cirrus-ci.com/v1/artifact/build/$CIRRUS_BUILD_ID/package_binary.zip
+ unzip -j package_binary.zip
+
+ download_tests_script: |
+ wget -q https://api.cirrus-ci.com/v1/artifact/build/$CIRRUS_BUILD_ID/tests.zip
+ unzip tests.zip
+
+ setup_shunit_script: |
+ git clone --depth 1 https://github.com/kward/shunit2 ci/tests/shunit2
+
+ test_script: |
+ ln -s /usr/local/bin/bash /bin/bash
+ chmod +x ci/tests/pkg-tests.sh
+ ci/tests/pkg-tests.sh
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 0000000..f462018
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,67 @@
+---
+Language: Cpp
+# BasedOnStyle: Mozilla
+AlignAfterOpenBracket: Align
+AlignConsecutiveAssignments: false
+AlignConsecutiveDeclarations: true
+AlignEscapedNewlinesLeft: true
+AlignOperands: true
+AlignTrailingComments: true
+AllowAllParametersOfDeclarationOnNextLine: true
+AllowShortBlocksOnASingleLine: false
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: false
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterDefinitionReturnType: All
+AlwaysBreakAfterReturnType: AllDefinitions
+AlwaysBreakBeforeMultilineStrings: false
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+ AfterControlStatement: false
+ AfterEnum: false
+ AfterFunction: true
+ AfterStruct: false
+ AfterUnion: false
+ BeforeElse: false
+ IndentBraces: false
+BreakBeforeBinaryOperators: None
+BreakBeforeBraces: Custom
+BreakBeforeTernaryOperators: false
+BreakStringLiterals: true
+ColumnLimit: 95
+ContinuationIndentWidth: 2
+DerivePointerAlignment: false
+DisableFormat: false
+ExperimentalAutoDetectBinPacking: false
+IncludeIsMainRegex: '$'
+IndentCaseLabels: false
+IndentWidth: 4
+IndentWrappedFunctionNames: false
+KeepEmptyLinesAtTheStartOfBlocks: false
+MacroBlockBegin: ''
+MacroBlockEnd: ''
+MaxEmptyLinesToKeep: 1
+PenaltyBreakBeforeFirstCallParameter: 19
+PenaltyBreakComment: 300
+PenaltyBreakString: 1000
+PenaltyExcessCharacter: 1000000
+PenaltyReturnTypeOnItsOwnLine: 200
+PointerAlignment: Right
+ReflowComments: true
+SortIncludes: false
+SpaceAfterCStyleCast: true
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeParens: ControlStatements
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInContainerLiterals: true
+SpacesInCStyleCastParentheses: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+Standard: Auto
+TabWidth: 8
+UseTab: Never
+...
+
diff --git a/.codespellrc b/.codespellrc
new file mode 100644
index 0000000..d2148e1
--- /dev/null
+++ b/.codespellrc
@@ -0,0 +1,3 @@
+[codespell]
+ignore-words-list = fpr,keypair,keypairs
+skip = src/tests/data/**
diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..172675f
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,23 @@
+# top-most EditorConfig file
+root = true
+
+# Unix-style newlines with a newline ending every file
+[*]
+end_of_line = lf
+insert_final_newline = true
+
+[*.{c,cpp,h{,.in}}]
+indent_style = space
+indent_size = 4
+
+[*.{cmake{,.in},json,sh,yml}]
+indent_style = space
+indent_size = 2
+
+[CMakeLists.txt]
+indent_style = space
+indent_size = 2
+
+[*.{adoc,md}]
+indent_style = space
+indent_size = 2
diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..01fd2b4
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1 @@
+.gitignore -text
diff --git a/.github/issue_template.md b/.github/issue_template.md
new file mode 100644
index 0000000..0317a81
--- /dev/null
+++ b/.github/issue_template.md
@@ -0,0 +1,18 @@
+## Description
+
+Describe the problem here.
+
+## Steps to Reproduce
+
+1.
+2.
+3.
+
+## Expected Behavior
+
+What is the expected behavior?
+
+## Actual Behavior
+
+What behavior did you observe instead?
+
diff --git a/.github/workflows/centos-and-fedora.yml b/.github/workflows/centos-and-fedora.yml
new file mode 100644
index 0000000..ad4e6c1
--- /dev/null
+++ b/.github/workflows/centos-and-fedora.yml
@@ -0,0 +1,375 @@
+name: centos-and-fedora
+
+on:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/centos-and-fedora.yml'
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ CODECOV_TOKEN: dbecf176-ea3f-4832-b743-295fd71d0fad
+
+#
+# Dependencies that are created during packaging
+#
+# OS botan botan repository json-c json-c repository
+# ----------------------------------------------------------------------------
+# CentOS 7 2.16.0 ribose json-c12 (0.12.1) ribose
+# CentOS 8 2.16.0 ribose 0.13.1 el8
+# CentOS 9 2.19.3 el9 0.14 el9
+# Fedora 35 2.18.2 fc35 0.15 fc35
+# Fedora 36 2.19.1 fc36 0.15 fc36
+#
+jobs:
+ tests:
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ container: ${{ matrix.image.container }}
+ timeout-minutes: 70
+ strategy:
+ fail-fast: false
+ matrix:
+ env:
+ - { CC: gcc, CXX: g++, BUILD_MODE: normal, USE_STATIC_DEPENDENCIES: yes }
+# normal --> Release build; sanitize --> Debug build so theoretically test conditions are different
+# - { CC: clang, CXX: clang++, BUILD_MODE: normal, USE_STATIC_DEPENDENCIES: yes }
+ - { CC: clang, CXX: clang++, BUILD_MODE: sanitize, USE_STATIC_DEPENDENCIES: yes }
+
+# Should you add a new OS/version please consider adding its default version of botan2 and json-c to this test matrix
+ image:
+ - { name: 'CentOS 7', container: 'centos:7', gpg_ver: stable, backend: Botan, botan_ver: 2.16.0, locale: en_US.UTF-8 }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: stable, backend: Botan, botan_ver: 2.16.0, locale: C.UTF-8 }
+ - { name: 'CentOS 9', container: 'quay.io/centos/centos:stream9', gpg_ver: stable, backend: Botan, botan_ver: 2.19.3, locale: C.UTF-8 }
+ - { name: 'Fedora 35', container: 'fedora:35', gpg_ver: stable, backend: Botan, botan_ver: 2.18.2, locale: C.UTF-8 }
+ - { name: 'Fedora 36', container: 'fedora:36', gpg_ver: stable, backend: Botan, botan_ver: 2.19.1, locale: C.UTF-8 }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: lts, backend: Botan, sm2: On, locale: C.UTF-8 }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: stable, backend: Botan, sm2: Off, locale: C.UTF-8 }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: lts, backend: OpenSSL, locale: C.UTF-8 }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: beta, backend: Botan, sm2: On, locale: C.UTF-8 }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: 2.3.1, backend: Botan, sm2: On, locale: C.UTF-8 }
+ - { name: 'CentOS 9', container: 'quay.io/centos/centos:stream9', gpg_ver: stable, backend: OpenSSL, idea: On, locale: C.UTF-8 }
+ - { name: 'CentOS 9', container: 'quay.io/centos/centos:stream9', gpg_ver: stable, backend: OpenSSL, idea: Off, locale: C.UTF-8 }
+ - { name: 'Fedora 35', container: 'fedora:35', gpg_ver: stable, backend: OpenSSL, locale: C.UTF-8 }
+ - { name: 'Fedora 36', container: 'fedora:36', gpg_ver: stable, backend: OpenSSL, locale: C.UTF-8 }
+
+
+ include:
+ # Coverage report for Botan backend
+ - image: { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: stable, backend: Botan, sm2: On, locale: C.UTF-8 }
+ env: { CC: gcc, CXX: g++, BUILD_MODE: coverage , RNP_TESTS: ".*", USE_STATIC_DEPENDENCIES: yes }
+ # Coverage report for OpenSSL 1.1.1 backend
+ - image: { name: 'CentOS 8', container: 'tgagor/centos:stream8', gpg_ver: stable, backend: OpenSSL, locale: C.UTF-8 }
+ env: { CC: gcc, CXX: g++, BUILD_MODE: coverage , RNP_TESTS: ".*", USE_STATIC_DEPENDENCIES: yes }
+ # Coverage report for OpenSSL 3.0 backend
+ - image: { name: 'Fedora 36', container: 'fedora:36', gpg_ver: stable, backend: OpenSSL, locale: C.UTF-8 }
+ env: { CC: gcc, CXX: g++, BUILD_MODE: coverage , RNP_TESTS: ".*", USE_STATIC_DEPENDENCIES: yes }
+
+ env: ${{ matrix.env }}
+ name: ${{ matrix.image.name }} ${{ matrix.image.backend }} [test mode ${{ matrix.env.BUILD_MODE }}; CC ${{ matrix.env.CC }}; GnuPG ${{ matrix.image.gpg_ver }}; SM2 ${{ matrix.image.sm2 }}; IDEA ${{ matrix.image.idea }}]
+ steps:
+ - name: Install prerequisites for prerequisites
+ if: matrix.image.container == 'centos:7'
+ run: yum -y install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm
+
+ - name: Install prerequisites
+ run: yum -y install git sudo
+
+ - name: Setup environment
+ run: |
+ set -o errexit -o pipefail -o noclobber -o nounset
+ echo LANG=${{ matrix.image.locale }} >> $GITHUB_ENV
+ echo LC_ALL=${{ matrix.image.locale }} >> $GITHUB_ENV
+ echo LC_LANG=${{ matrix.image.locale }} >> $GITHUB_ENV
+ echo GPG_VERSION=${{ matrix.image.gpg_ver }} >> $GITHUB_ENV
+ echo ENABLE_SM2=${{ matrix.image.sm2 }} >> $GITHUB_ENV
+ echo ENABLE_IDEA=${{ matrix.image.idea }} >> $GITHUB_ENV
+ backend=${{ matrix.image.backend }}
+ backend="$(echo "${backend:-}" | tr '[:upper:]' '[:lower:]')"
+ echo CRYPTO_BACKEND="$backend" >> $GITHUB_ENV
+ echo BOTAN_VERSION=${{ matrix.image.botan_ver }} >> $GITHUB_ENV
+ useradd rnpuser
+ echo -e "rnpuser\tALL=(ALL)\tNOPASSWD:\tALL" > /etc/sudoers.d/rnpuser
+ echo -e "rnpuser\tsoft\tnproc\tunlimited\n" > /etc/security/limits.d/30-rnpuser.conf
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: true
+
+ - name: Setup noncacheable dependencies
+ run: |
+ . ci/gha/setup-env.inc.sh
+ exec su rnpuser -c ci/install_noncacheable_dependencies.sh
+
+ - name: Cache
+ id: cache
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.CACHE_DIR }}
+ key: ${{ matrix.image.container }}-${{ matrix.image.backend }}-${{ matrix.env.BUILD_MODE }}-${{ matrix.env.CC }}-${{ matrix.image.gpg_ver }}-${{ matrix.image.sm2 }}-${{ matrix.image.idea }}-${{ hashFiles('ci/**') }}-${{ hashFiles('.github/workflows/centos-and-fedora.yml') }}
+
+ - name: Adjust folder ownership
+ run: |
+ set -o errexit -o pipefail -o noclobber -o nounset
+ chown -R rnpuser:rnpuser $PWD
+
+ - name: Setup cacheable dependencies
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: exec su rnpuser -c ci/install_cacheable_dependencies.sh
+
+ - name: Build and Test
+ run: exec su rnpuser -c ci/run.sh
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ci/tests/shunit2
+
+ - name: Run additional ci tests
+ run: ci/tests/ci-tests.sh
+
+ package-source:
+ runs-on: ubuntu-latest
+ container: ${{ matrix.env.container }}
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ env:
+ - { name: 'CentOS 7', container: 'centos:7', LC_ALL: en_US.UTF-8 }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', LC_ALL: C.UTF-8 }
+ - { name: 'CentOS 9', container: 'quay.io/centos/centos:stream9', LC_ALL: C.UTF-8 }
+ - { name: 'Fedora 35', container: 'fedora:35', LC_ALL: C.UTF-8 }
+ - { name: 'Fedora 36', container: 'fedora:36', LC_ALL: C.UTF-8 }
+ name: Package ${{ matrix.env.name }} SRPM
+ env: ${{ matrix.env }}
+
+ steps:
+ - name: Install prerequisites for prerequisites
+ if: matrix.env.container == 'centos:7'
+ run: yum -y install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm
+
+ - name: Install prerequisites
+ run: yum -y install git sudo rpm-build
+
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: true
+
+ - name: Setup noncacheable dependencies
+ run: |
+ . ci/gha/setup-env.inc.sh
+ ci/install_noncacheable_dependencies.sh
+
+ - name: Configure
+ run: cmake -B build -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF
+
+ - name: Package SRPM
+ run: cpack -B build/SRPM -G RPM --config build/CPackSourceConfig.cmake
+
+ - name: Upload SRPM
+ uses: actions/upload-artifact@v3
+ with:
+ name: 'SRPM ${{ matrix.env.name }}'
+ path: 'build/SRPM/*.src.rpm'
+ retention-days: 5
+
+ - name: Stash packaging tests
+ uses: actions/upload-artifact@v3
+ with:
+ name: tests
+ path: 'ci/tests/**'
+ retention-days: 1
+
+ package:
+ runs-on: ubuntu-latest
+ needs: package-source
+ container: ${{ matrix.env.container }}
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ env:
+ - { name: 'CentOS 7', container: 'centos:7', LC_ALL: en_US.UTF-8 }
+# CXXFLAGS environment setting resolves dual ABI issues caused by BOTAN libraries with the version of GCC installed at 'tgagor/centos:stream8'
+# https://gcc.gnu.org/onlinedocs/gcc-5.2.0/libstdc++/manual/manual/using_dual_abi.html
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8', CXXFLAGS: -D_GLIBCXX_USE_CXX11_ABI=0, LC_ALL: C.UTF-8 }
+ - { name: 'CentOS 9', container: 'quay.io/centos/centos:stream9', LC_ALL: C.UTF-8 }
+ - { name: 'Fedora 35', container: 'fedora:35', LC_ALL: C.UTF-8 }
+ - { name: 'Fedora 36', container: 'fedora:36', LC_ALL: C.UTF-8 }
+ name: Package ${{ matrix.env.name }} RPM
+ env: ${{ matrix.env }}
+ steps:
+ - name: Install prerequisites for prerequisites
+ if: matrix.env.container == 'centos:7'
+ run: yum -y install http://opensource.wandisco.com/centos/7/git/x86_64/wandisco-git-release-7-2.noarch.rpm
+
+ - name: Install prerequisites
+ run: yum -y install git sudo tar cpio rpm-build
+
+ - name: Download SRPM
+ uses: actions/download-artifact@v3
+ with:
+ name: 'SRPM ${{ matrix.env.name }}'
+ path: ~/rpmbuild/SRPMS
+
+ - name: Extract SRPM
+ run: |
+ rpm -i -v ~/rpmbuild/SRPMS/*.src.rpm
+ tar xzf ~/rpmbuild/SOURCES/*.tar.gz --strip 1 -C ~/rpmbuild/SOURCES
+
+ - name: Setup noncacheable dependencies
+ run: |
+ cd ~/rpmbuild/SOURCES/
+ . ci/gha/setup-env.inc.sh
+ ci/install_noncacheable_dependencies.sh
+
+ - name: Permanently enable rh-ruby30
+ if: matrix.env.container == 'centos:7'
+ run: bash -c "echo \"$(cut -f 2- -d ' ' /opt/rh/rh-ruby30/enable)\"">> $GITHUB_ENV
+
+ - name: Build rnp
+ run: |
+ cmake ~/rpmbuild/SOURCES -B ~/rpmbuild/SOURCES/BUILD -DBUILD_SHARED_LIBS=ON -DBUILD_TESTING=OFF \
+ -DCMAKE_INSTALL_PREFIX=/usr
+ cmake --build ~/rpmbuild/SOURCES/BUILD --config Release
+
+ - name: Package rpm
+ run: cpack -G RPM -B ~/rpmbuild/SOURCES/RPMS --config ~/rpmbuild/SOURCES/BUILD/CPackConfig.cmake
+
+ - name: Upload Artifact
+ uses: actions/upload-artifact@v3
+ with:
+ name: 'RPM ${{ matrix.env.name}}'
+ path: '~/rpmbuild/SOURCES/RPMS/*.rpm'
+ retention-days: 5
+
+# The main purpose of this step is to test the RPMS in a pristine environment (as for the end user).
+# ci-scripts are deliberately not used, as they recreate the development environment,
+# and this is something we proudly reject here
+
+ rpm-tests:
+ runs-on: ubuntu-latest
+ needs: package
+ container: ${{ matrix.env.container }}
+ timeout-minutes: 30
+ strategy:
+ fail-fast: false
+ matrix:
+ env:
+ - { name: 'CentOS 7', container: 'centos:7' }
+ - { name: 'CentOS 8', container: 'tgagor/centos:stream8' }
+ - { name: 'CentOS 9', container: 'quay.io/centos/centos:stream9' }
+ - { name: 'Fedora 35', container: 'fedora:35' }
+ - { name: 'Fedora 36', container: 'fedora:36' }
+ name: RPM test on ${{ matrix.env.name }}
+
+ steps:
+ - name: Install prerequisites
+ run: yum -y install sudo wget binutils
+
+# CentOS 7/8 packages depend on botan.so.16 that gets installed from ribose repo
+# Fedora 35/36 packages depend on botan.so.19 that comes Fedora package, that is available by default
+# CentOS 9 depend on botan.so.19 and needs EPEL9 repo that needs to be installed
+# ribose repo is also a source of json-c (v12 aka json-c12) for CentOS 7
+
+ - name: Install ribose-packages
+ if: matrix.env.container == 'centos:7' || matrix.env.container == 'tgagor/centos:stream8'
+ run: |
+ sudo rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages-next.pub
+ sudo wget https://github.com/riboseinc/yum/raw/master/ribose.repo -O /etc/yum.repos.d/ribose.repo
+
+ - name: Install epel-release
+ if: matrix.env.container == 'quay.io/centos/centos:stream9'
+ run: |
+ sudo dnf -y install 'dnf-command(config-manager)'
+ sudo dnf config-manager --set-enabled crb
+ sudo dnf -y install epel-release
+
+ - name: Install xargs
+ if: matrix.env.container == 'fedora:35'
+ run: sudo yum -y install findutils
+
+ - name: Download rnp rpms
+ uses: actions/download-artifact@v3
+ with:
+ name: 'RPM ${{ matrix.env.name}}'
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ci/tests/shunit2
+
+ - name: Unstash tests
+ uses: actions/download-artifact@v3
+ with:
+ name: tests
+ path: ci/tests
+
+ - name: Run rpm tests
+# RPM tests
+# - no source checkout or upload [we get only test scripts from the previous step using GHA artifacts]
+# - no environment set up with rnp scripts
+# - no dependencies setup, we test that yum can install whatever is required
+ run: |
+ chmod +x ci/tests/rpm-tests.sh
+ ci/tests/rpm-tests.sh
+
+ - name: Run symbol visibility tests
+ run: |
+ chmod +x ci/tests/ci-tests.sh
+ sudo yum -y localinstall librnp0-0*.*.rpm librnp0-devel-0*.*.rpm rnp0-0*.*.rpm
+ ci/tests/ci-tests.sh
+ sudo yum -y erase $(rpm -qa | grep rnp)
+
+ - name: Setup minimalistic build environment
+ run: |
+ sudo yum -y install make gcc gcc-c++ zlib-devel bzip2-devel botan2-devel
+ mkdir cmake
+ wget https://github.com/Kitware/CMake/releases/download/v3.12.0/cmake-3.12.0-Linux-x86_64.sh -O cmake/cmake.sh
+ sudo sh cmake/cmake.sh --skip-license --prefix=/usr/local
+
+# Ribose repo provides json-c12-devel for CentOS7;
+# el8, el9, fr35, fr36 provide json-c-devel (version 12+)
+ - name: Setup json-c12
+ if: matrix.env.container == 'centos:7'
+ run: sudo yum -y install json-c12-devel
+
+ - name: Setup json-c
+ if: matrix.env.container != 'centos:7'
+ run: sudo yum -y install json-c-devel
+
+ - name: Run packaging tests
+ run: |
+ chmod +x ci/tests/pk-tests.sh
+ ci/tests/pk-tests.sh
diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml
new file mode 100644
index 0000000..f4a5a24
--- /dev/null
+++ b/.github/workflows/codeql.yml
@@ -0,0 +1,75 @@
+name: "CodeQL"
+
+on:
+ push:
+ branches: [ "main" ]
+ pull_request:
+ branches: [ "main" ]
+ schedule:
+ - cron: "21 15 * * 6"
+
+jobs:
+ analyze:
+ name: Analyze
+ runs-on: ubuntu-latest
+ permissions:
+ actions: read
+ contents: read
+ security-events: write
+
+ strategy:
+ fail-fast: false
+ matrix:
+ language: [ python, cpp ]
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: true
+
+ - name: Install Packages (cpp)
+ if: ${{ matrix.language == 'cpp' }}
+ run: |
+ sudo apt-get update
+ sudo apt-get install --yes libjson-c-dev libgtest-dev
+
+ - name: After Prepare (cpp)
+ if: ${{ matrix.language == 'cpp' }}
+ run: |
+ set -eux
+ mkdir botan_build
+ mkdir botan_install
+ export BOTAN_INSTALL=$(pwd)/botan_install && echo "BOTAN_INSTALL=$BOTAN_INSTALL" >> $GITHUB_ENV
+ export BOTAN_MODULES=$(<$(pwd)/ci/botan-modules tr '\n' ',') && echo "BOTAN_MODULES=$BOTAN_MODULES" >> $GITHUB_ENV
+ git clone --depth 1 --branch 2.17.3 https://github.com/randombit/botan botan_build
+ pushd botan_build
+ ./configure.py --prefix=${BOTAN_INSTALL} --with-debug-info --cxxflags="-fno-omit-frame-pointer" --without-documentation --without-openssl --build-targets=shared --minimized-build --enable-modules="$BOTAN_MODULES"
+ make -j2 install
+ popd
+ mkdir cmake314
+ wget --quiet -O - https://cmake.org/files/v3.18/cmake-3.18.6-Linux-x86_64.tar.gz | tar --strip-components=1 -xz -C cmake314
+ export PATH=$(pwd)/cmake314/bin:${PATH} && echo "PATH=$PATH" >> $GITHUB_ENV
+
+ - name: Initialize CodeQL
+ uses: github/codeql-action/init@v2
+ with:
+ languages: ${{ matrix.language }}
+ queries: +security-and-quality
+
+ - name: Autobuild
+ uses: github/codeql-action/autobuild@v2
+ if: ${{ matrix.language == 'python' }}
+
+ - name: Build cpp
+ if: ${{ matrix.language == 'cpp' }}
+ run: |
+ pwd
+ mkdir rnp-build; pushd rnp-build
+ cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_PREFIX_PATH="${BOTAN_INSTALL}" -DDOWNLOAD_GTEST=Off
+ make -j2
+
+ - name: Perform CodeQL Analysis
+ uses: github/codeql-action/analyze@v2
+ with:
+ category: "/language:${{ matrix.language }}"
diff --git a/.github/workflows/coverity.yml b/.github/workflows/coverity.yml
new file mode 100644
index 0000000..35f1ea5
--- /dev/null
+++ b/.github/workflows/coverity.yml
@@ -0,0 +1,63 @@
+name: coverity
+
+on:
+ schedule:
+ # every day at 9:00 UTC
+ - cron: '0 9 * * *'
+
+env:
+ CORES: 2
+ BUILD_MODE: normal
+ GPG_VERSION: stable
+ RNP_TESTS: ''
+ USE_STATIC_DEPENDENCIES: yes
+
+jobs:
+ scan:
+ runs-on: ubuntu-latest
+ timeout-minutes: 20
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+ - name: Setup environment
+ run: |
+ . ci/gha/setup-env.inc.sh
+ ci/install_noncacheable_dependencies.sh
+ - name: Cache
+ id: cache
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.CACHE_DIR }}
+ key: ${{ github.workflow }}-${{ runner.os }}-${{ env.BUILD_MODE }}-gpg-${{ env.GPG_VERSION }}-${{ hashFiles('ci/**') }}-${{ hashFiles('.github/workflows/**') }}
+ - name: Build cache
+ if: steps.cache.outputs.cache-hit != 'true'
+ run: |
+ set -x
+ ci/install_cacheable_dependencies.sh botan jsonc
+ - name: Download Coverity
+ env:
+ TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
+ run: |
+ wget -q https://scan.coverity.com/download/cxx/linux64 --post-data "token=$TOKEN&project=$GITHUB_REPOSITORY" -O cov-analysis-linux64.tar.gz
+ mkdir cov-analysis-linux64
+ tar xzf cov-analysis-linux64.tar.gz --strip 1 -C cov-analysis-linux64
+ - name: Build
+ run: |
+ set -x
+ export PATH="$PWD/cov-analysis-linux64/bin:$PATH"
+ cov-build --dir cov-int ci/main.sh
+ - name: Submit
+ env:
+ TOKEN: ${{ secrets.COVERITY_SCAN_TOKEN }}
+ run: |
+ tar czvf results.tgz cov-int
+ curl \
+ --form project=$GITHUB_REPOSITORY \
+ --form token=$TOKEN \
+ --form email=packaging@ribose.com \
+ --form file=@results.tgz \
+ --form version=$GITHUB_REF \
+ --form description=$GITHUB_SHA \
+ https://scan.coverity.com/builds?project=$GITHUB_REPOSITORY
diff --git a/.github/workflows/debian.yml b/.github/workflows/debian.yml
new file mode 100644
index 0000000..30991fc
--- /dev/null
+++ b/.github/workflows/debian.yml
@@ -0,0 +1,138 @@
+name: debian
+
+on:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/debian.yml'
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ CORES: 2
+ LANG: C.UTF-8
+ LC_ALL: C.UTF-8
+ LC_LANG: C.UTF-8
+ CMAKE_VER: '3.20.6-2'
+ BUILD_MODE: normal
+ GPG_VERSION: stable
+ SUDO: ""
+ USE_STATIC_DEPENDENCIES: yes
+ RNP_LOG_CONSOLE: 1
+
+jobs:
+ tests:
+ name: ${{ matrix.image.container }} [CC ${{ matrix.env.CC }}; backend ${{ matrix.image.backend }}; GnuPG stable]
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 120
+ strategy:
+ fail-fast: false
+ matrix:
+ image:
+ - { container: 'i386/debian:11', cpu: 'i386', arch: 'ia32', backend: 'botan' }
+ - { container: 'i386/debian:11', cpu: 'i386', arch: 'ia32', backend: 'openssl' }
+ - { container: 'amd64/debian:11', cpu: 'x86_64', arch: 'x64', backend: 'botan' }
+ - { container: 'amd64/debian:11', cpu: 'x86_64', arch: 'x64', backend: 'openssl' }
+ - { container: 'i386/debian:10', cpu: 'i386', arch: 'ia32', backend: 'botan' }
+ env:
+ - { CC: 'gcc', CXX: 'g++' }
+ - { CC: 'clang', CXX: 'clang++' }
+
+ container: ${{ matrix.image.container }}
+
+ env: ${{ matrix.env }}
+ steps:
+ - name: Install prerequisites
+ run: |
+ apt update
+ apt -y install git sudo wget
+
+ - name: Setup environment
+ shell: bash
+ # rnpuser is only needed for rnpkeys_generatekey_verifykeyHomeDirNoPermission test
+ run: |
+ set -x
+ echo IMAGE=${{ matrix.image.container }} >> $GITHUB_ENV
+ echo CPU=${{ matrix.image.cpu }} >> $GITHUB_ENV
+ echo CRYPTO_BACKEND=${{ matrix.image.backend }} >> $GITHUB_ENV
+ echo "SUDO=sudo" >> $GITHUB_ENV
+ useradd rnpuser
+ printf "\nrnpuser\tALL=(ALL)\tNOPASSWD:\tALL" > /etc/sudoers.d/rnpuser
+ printf "\nrnpuser\tsoft\tnproc\tunlimited\n" > /etc/security/limits.d/30-rnpuser.conf
+
+ - name: Checkout on x86_x64
+ if: env.CPU == 'x86_64'
+ uses: actions/checkout@v3
+ with:
+ submodules: true
+
+ - name: Checkout on i386
+ if: env.CPU == 'i386'
+ uses: actions/checkout@v1
+ with:
+ submodules: true
+
+ - name: Install cmake
+ run: |
+ wget -nv https://github.com/xpack-dev-tools/cmake-xpack/releases/download/v${{ env.CMAKE_VER }}/xpack-cmake-${{ env.CMAKE_VER }}-linux-${{ matrix.image.arch }}.tar.gz
+ tar -zxf xpack-cmake-${{ env.CMAKE_VER }}-linux-${{ matrix.image.arch }}.tar.gz --directory /usr/local --strip-components=1 --skip-old-files
+
+ - name: Setup noncacheable dependencies
+ shell: bash
+ run: |
+ . ci/gha/setup-env.inc.sh
+ ci/install_noncacheable_dependencies.sh
+
+ - name: Cache
+ id: cache
+ uses: actions/cache@v3
+ if: env.CPU == 'x86_64'
+ with:
+ path: ${{github.workspace}}/${{ env.CACHE_DIR }}
+ key: ${{ matrix.image.container }}-${{ matrix.env.CC }}-${{ matrix.image.backend }}-${{ hashFiles('ci/**') }}-${{ hashFiles('.github/workflows/debian.yml') }}
+
+ - name: Setup cacheable dependencies
+ if: steps.cache.outputs.cache-hit != 'true'
+ shell: bash
+ run: |
+ set -euxo pipefail
+ ci/install_cacheable_dependencies.sh
+
+ - name: Build and Test
+ shell: bash
+ run: |
+ set -x
+ chown -R rnpuser:rnpuser $PWD
+ exec su rnpuser -c ci/run.sh
+
+ - name: Package
+ run: |
+ set -x
+ cd ${LOCAL_BUILDS}/rnp-build
+ cpack -G DEB -D CPACK_DEBIAN_PACKAGE_SHLIBDEPS_PRIVATE_DIRS="${BOTAN_INSTALL}/lib;${JSONC_INSTALL}/lib;${GPG_INSTALL}/lib"
diff --git a/.github/workflows/dispatch.yml b/.github/workflows/dispatch.yml
new file mode 100644
index 0000000..9206412
--- /dev/null
+++ b/.github/workflows/dispatch.yml
@@ -0,0 +1,48 @@
+# Copyright (c) 2023 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.
+
+name: dispatch
+on:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ workflow_dispatch:
+
+jobs:
+ dispatch:
+ name: Dispatch dependent repositories
+ strategy:
+ fail-fast: false
+ matrix:
+ repo: [ 'ruby-rnp', 'php-rnp', 'py-rnp' ]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Dispatch dependent repositories
+ uses: peter-evans/repository-dispatch@v2
+ with:
+ token: ${{ secrets.RNP_CI_PAT_TOKEN }}
+ repository: rnpgp/${{ matrix.repo }}
+ event-type: 'rnp update'
+ client-payload: '{ "tag": "${{ github.ref_name }}" }'
diff --git a/.github/workflows/fuzzing.yml b/.github/workflows/fuzzing.yml
new file mode 100644
index 0000000..81336ec
--- /dev/null
+++ b/.github/workflows/fuzzing.yml
@@ -0,0 +1,41 @@
+name: fuzzing
+
+on:
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+jobs:
+ fuzzing:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Build Fuzzers
+ id: build
+ uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'rnp'
+ dry-run: false
+ - name: Run Fuzzers
+ uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master
+ with:
+ oss-fuzz-project-name: 'rnp'
+ fuzz-seconds: 1800
+ dry-run: false
+ - name: Upload Crash
+ uses: actions/upload-artifact@v2
+ if: failure() && steps.build.outcome == 'success'
+ with:
+ name: artifacts
+ path: ./out/artifacts
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
new file mode 100644
index 0000000..d6220ee
--- /dev/null
+++ b/.github/workflows/lint.yml
@@ -0,0 +1,61 @@
+name: lint
+
+on:
+ push:
+ branches:
+ - main
+ pull_request:
+ paths-ignore:
+ - '/.*'
+ - '/_*'
+ - '!.clang-format'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+jobs:
+ clang-format:
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+ - uses: DoozyX/clang-format-lint-action@v0.15
+ with:
+ clangFormatVersion: 11.0.0
+ shellcheck:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+ - uses: ludeeus/action-shellcheck@master
+ with:
+ scandir: './ci'
+ env:
+ SHELLCHECK_OPTS: -x
+ version-cmake-up-to-date:
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 10
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ path: rnp
+ - name: Download latest version.cmake
+ uses: actions/checkout@v3
+ with:
+ repository: rnpgp/cmake-versioning
+ fetch-depth: 1
+ path: cmake-versioning
+ - name: Compare
+ run: |
+ set -x
+ diff "rnp/cmake/version.cmake" "cmake-versioning/version.cmake"
diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml
new file mode 100644
index 0000000..0cfea7e
--- /dev/null
+++ b/.github/workflows/macos.yml
@@ -0,0 +1,168 @@
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+name: macos
+
+on:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/macos.yml'
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ BOTAN_VERSION: 2.19.3
+
+jobs:
+ tests:
+ name: ${{ matrix.os }} [ backend ${{ matrix.backend }}, shared libs ${{ matrix.shared_libs }} ]
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+# On MacOS gcc is alias of clang these days
+ os: [ macos-11, macos-12 ]
+ backend: [ 'botan' ]
+ shared_libs: [ 'on' ]
+ include:
+ - { os: 'macos-11', backend: 'openssl@1.1', shared_libs: 'on' }
+ - { os: 'macos-12', backend: 'openssl@3', shared_libs: 'on' }
+ - { os: 'macos-12', backend: 'botan', shared_libs: 'off' }
+
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 250
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - name: Configure openssl 1.1 backend
+ if: matrix.backend == 'openssl@1.1'
+ run: |
+ echo "brew \"openssl@1.1\"" >> Brewfile
+ echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl@1.1)" >> $GITHUB_ENV
+ echo "CRYPTO_BACKEND=openssl" >> $GITHUB_ENV
+
+ - name: Configure openssl 3 backend
+ if: matrix.backend == 'openssl@3'
+ run: |
+ echo "brew \"openssl@3\"" >> Brewfile
+ echo "OPENSSL_ROOT_DIR=$(brew --prefix openssl@3)" >> $GITHUB_ENV
+ echo "CRYPTO_BACKEND=openssl" >> $GITHUB_ENV
+
+# Brew installs Botan3 now and it is not supported yet
+#
+# - name: Configure botan backend
+# if: ${{ matrix.backend == 'botan' }}
+# run: |
+# echo "brew \"botan\"" >> Brewfile
+# echo "CRYPTO_BACKEND=botan" >> $GITHUB_ENV
+
+ - name: Install dependencies
+ run: brew bundle
+
+ - name: Botan2 cache
+ id: cache
+ uses: actions/cache@v3
+ if: matrix.backend == 'botan'
+ with:
+ path: Botan-${{ env.BOTAN_VERSION }}
+ key: ${{ matrix.os }}-Botan-${{ env.BOTAN_VERSION }}
+
+ - name: Build Botan2
+ if: matrix.backend == 'botan' && steps.cache.outputs.cache-hit != 'true'
+ run: |
+ wget -qO- https://botan.randombit.net/releases/Botan-${{ env.BOTAN_VERSION }}.tar.xz | tar xvJ
+ cd Botan-${{ env.BOTAN_VERSION }}
+ ./configure.py --prefix=/usr/local
+ make
+ cd ..
+
+ - name: Install Botan2
+ if: matrix.backend == 'botan'
+ run: |
+ cd Botan-${{ env.BOTAN_VERSION }}
+ sudo make install
+ cd ..
+
+ - name: Configure
+ run: |
+ echo "CORES=$(sysctl -n hw.ncpu)" >> $GITHUB_ENV
+ echo "RNP_INSTALL=$PWD/rnp-install" >> $GITHUB_ENV
+ cmake -B build -DBUILD_SHARED_LIBS=${{ matrix.shared_libs }} \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCMAKE_INSTALL_PREFIX="$PWD/rnp-install" \
+ -DDOWNLOAD_GTEST=OFF \
+ -DCRYPTO_BACKEND=${{ env.CRYPTO_BACKEND }} .
+
+ - name: Build
+ run: cmake --build build --config Release --parallel ${{ env.CORES }}
+
+ - name: Install
+ run: cmake --install build
+
+ - name: Test
+ run: |
+ mkdir -p "build/Testing/Temporary"
+ cp "cmake/CTestCostData.txt" "build/Testing/Temporary"
+ export PATH="$PWD/build/src/lib:$PATH"
+ ctest --parallel ${{ env.CORES }} --test-dir build -C Debug --output-on-failure
+
+ - name: Checkout shell test framework
+ if: matrix.shared_libs == 'on'
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ci/tests/shunit2
+
+ - name: Run additional ci tests
+ if: matrix.shared_libs == 'on'
+ run: zsh -o shwordsplit -- ci/tests/ci-tests.sh
diff --git a/.github/workflows/nix.yml b/.github/workflows/nix.yml
new file mode 100644
index 0000000..e71ee51
--- /dev/null
+++ b/.github/workflows/nix.yml
@@ -0,0 +1,45 @@
+name: nix
+
+on:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - 'ci/**'
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/nix.yml'
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - 'ci/**'
+
+jobs:
+ build:
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ubuntu-latest, macos-latest]
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+ - uses: cachix/install-nix-action@v15
+ with:
+ nix_path: nixpkgs=channel:nixos-unstable
+ - run: nix build .?submodules=1
diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml
new file mode 100644
index 0000000..51fd0c3
--- /dev/null
+++ b/.github/workflows/ubuntu.yml
@@ -0,0 +1,322 @@
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+name: ubuntu
+
+on:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/ubuntu.yml'
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+jobs:
+ tests:
+ name: ${{ matrix.os }} [CC ${{ matrix.env.CC }}; ${{ matrix.backend.name }}; shared libs ${{ matrix.shared_libs }}; GnuPG stable]
+ runs-on: ${{ matrix.os }}
+ strategy:
+ fail-fast: false
+ matrix:
+ os: [ ubuntu-latest ]
+ shared_libs: [ 'on' ]
+ backend:
+ - { name: 'botan', package: 'libbotan-2-dev' }
+ - { name: 'openssl', package: 'libssl-dev' }
+ env:
+ - { CC: gcc, CXX: g++ }
+ - { CC: clang, CXX: clang++ }
+ include:
+ # This implies openssl 1.1.1 as opposed to ubuntu-latest which is openssl 3
+ - os: ubuntu-20.04
+ shared_libs: 'on'
+ backend: { name: 'openssl', package: 'libssl-dev' }
+ env: { CC: gcc, CXX: g++ }
+
+ - os: ubuntu-latest
+ shared_libs: 'off'
+ backend: { name: 'botan', package: 'libbotan-2-dev' }
+ env: { CC: gcc, CXX: g++ }
+
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ env: ${{ matrix.env }}
+ timeout-minutes: 50
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - name: Install dependencies
+# Already installed on GHA: build-essential libbz2-dev zlib1g-dev
+ run: |
+ sudo apt-get -y update
+ sudo apt-get -y install cmake libjson-c-dev ${{ matrix.backend.package }} asciidoctor
+
+ - name: Configure
+ run: |
+ echo CORES="$(nproc --all)" >> $GITHUB_ENV
+ cmake -B build -DBUILD_SHARED_LIBS=${{ matrix.shared_libs}} \
+ -DCRYPTO_BACKEND=${{ matrix.backend.name }} \
+ -DDOWNLOAD_GTEST=ON \
+ -DCMAKE_BUILD_TYPE=Release .
+
+ - name: Build
+ run: cmake --build build --config "Release" --parallel ${{ env.CORES }}
+
+ - name: Test
+ run: |
+ mkdir -p "build/Testing/Temporary"
+ cp "cmake/CTestCostData.txt" "build/Testing/Temporary"
+ export PATH="$PWD/build/src/lib:$PATH"
+ ctest --parallel ${{ env.CORES }} --test-dir build -C Debug --output-on-failure
+
+ cmake-offline-googletest-src:
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 30
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get -y update
+ sudo apt-get -y install cmake libjson-c-dev libbotan-2-dev asciidoctor googletest
+
+ - name: Configure
+ run: |
+ echo CORES="$(nproc --all)" >> $GITHUB_ENV
+ cmake -B build -DBUILD_SHARED_LIBS=ON \
+ -DCRYPTO_BACKEND=botan \
+ -DDOWNLOAD_GTEST=OFF \
+ -DGTEST_SOURCES=/usr/src/googletest \
+ -DCMAKE_BUILD_TYPE=Release .
+
+ - name: Build
+ run: cmake --build build --config "Release" --parallel ${{ env.CORES }}
+
+ - name: Test
+ run: |
+ mkdir -p "build/Testing/Temporary"
+ cp "cmake/CTestCostData.txt" "build/Testing/Temporary"
+ export PATH="$PWD/build/src/lib:$PATH"
+ ctest --parallel ${{ env.CORES }} --test-dir build -C Debug --output-on-failure
+
+ - name: Check googletest
+ run: |
+ [ -d "build/src/tests" ]
+ [ -d "build/src/tests/googletest-build" ]
+ [ ! -d "build/src/tests/googletest-src" ]
+
+ cmake-offline-googletest:
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 30
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get -y update
+ sudo apt-get -y install cmake libjson-c-dev libbotan-2-dev asciidoctor googletest
+
+ - name: Build googletest
+ run: |
+ cmake -B googletest-build /usr/src/googletest
+ cmake --build googletest-build
+ sudo cmake --install googletest-build
+
+ - name: Configure
+ run: |
+ echo CORES="$(nproc --all)" >> $GITHUB_ENV
+ cmake -B build -DBUILD_SHARED_LIBS=ON \
+ -DCRYPTO_BACKEND=botan \
+ -DDOWNLOAD_GTEST=OFF \
+ -DCMAKE_BUILD_TYPE=Release .
+
+ - name: Build
+ run: cmake --build build --config "Release" --parallel ${{ env.CORES }}
+
+ - name: Test
+ run: |
+ mkdir -p "build/Testing/Temporary"
+ cp "cmake/CTestCostData.txt" "build/Testing/Temporary"
+ export PATH="$PWD/build/src/lib:$PATH"
+ ctest --parallel ${{ env.CORES }} --test-dir build -C Debug --output-on-failure
+
+ - name: Check googletest
+ run: |
+ [ -d "build/src/tests" ]
+ [ ! -d "build/src/tests/googletest-build" ]
+ [ ! -d "build/src/tests/googletest-src" ]
+
+ package-source:
+ runs-on: ubuntu-latest
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 30
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - name: Install dependencies
+ run: |
+ sudo apt-get -y update
+ sudo apt-get -y install cmake libjson-c-dev libbotan-2-dev asciidoctor
+
+ - name: Configure
+ run: |
+ cmake . -B build \
+ -DBUILD_SHARED_LIBS=ON \
+ -DBUILD_TESTING=OFF \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCRYPTO_BACKEND=botan \
+ -DCMAKE_INSTALL_PREFIX=/usr
+
+ - name: Package source
+ run: cpack -B build/source-deb -G DEB --config build/CPackSourceConfig.cmake
+
+ - name: Upload source package
+ uses: actions/upload-artifact@v3
+ with:
+ name: 'source-debian'
+ path: 'build/source-deb/*.deb'
+ retention-days: 5
+
+ - name: Stash packaging tests
+ uses: actions/upload-artifact@v3
+ with:
+ name: tests
+ path: 'ci/tests/**'
+ retention-days: 1
+
+ package:
+ runs-on: ubuntu-latest
+ needs: package-source
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 30
+ steps:
+ - name: Install dependencies
+ run: |
+ sudo apt-get -y update
+ sudo apt-get -y install cmake libjson-c-dev libbotan-2-dev asciidoctor
+
+ - name: Download source package
+ uses: actions/download-artifact@v3
+ with:
+ name: 'source-debian'
+ path: source-debian
+
+ - name: Extract sources
+ run: dpkg-deb --extract source-debian/*.deb rnp
+
+ - name: Configure
+ run: |
+ echo CORES="$(nproc --all)" >> $GITHUB_ENV
+ cmake rnp -B rnp/build \
+ -DBUILD_SHARED_LIBS=ON \
+ -DBUILD_TESTING=OFF \
+ -DCMAKE_BUILD_TYPE=Release \
+ -DCRYPTO_BACKEND=botan \
+ -DCMAKE_INSTALL_PREFIX=/usr
+
+ - name: Build
+ run: cmake --build rnp/build --config Release --parallel ${{ env.CORES }}
+
+ - name: Create binary package
+ run: cpack -G DEB -B debian --config rnp/build/CPackConfig.cmake
+
+ - name: Upload binary package
+ uses: actions/upload-artifact@v3
+ with:
+ name: 'debian'
+ path: 'debian/*.deb'
+ retention-days: 5
+
+ debian-tests:
+ runs-on: ubuntu-latest
+ needs: package
+ timeout-minutes: 30
+ steps:
+ - name: Download enp deb file
+ uses: actions/download-artifact@v3
+ with:
+ name: 'debian'
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ci/tests/shunit2
+
+ - name: Unstash tests
+ uses: actions/download-artifact@v3
+ with:
+ name: tests
+ path: ci/tests
+
+ - name: Run debian tests
+# - no source checkout or upload [we get only test scripts from the previous step using GHA artifacts]
+# - no environment set up with rnp scripts
+# - no dependencies setup, we test that apt can install whatever is required
+ run: |
+ chmod +x ci/tests/deb-tests.sh
+ ci/tests/deb-tests.sh
diff --git a/.github/workflows/windows-msys2.yml b/.github/workflows/windows-msys2.yml
new file mode 100644
index 0000000..017f0bb
--- /dev/null
+++ b/.github/workflows/windows-msys2.yml
@@ -0,0 +1,144 @@
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+name: windows-msys2
+
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/windows-msys2.yml'
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ RNP_INSTALL: /home/runneradmin/rnp-install
+
+jobs:
+ tests:
+ name: windows-latest msys2 [msystem ${{ matrix.msystem }}; backend ${{ matrix.backend.name }}; build shared libs ${{ matrix.shared_libs }}; GnuPG stable]
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ timeout-minutes: 70
+ runs-on: windows-latest
+ defaults:
+ run:
+ shell: msys2 {0}
+ strategy:
+ fail-fast: false
+ matrix:
+ msystem: [ 'ucrt64', 'mingw64', 'clang64' ]
+ shared_libs: [ 'on' ]
+ backend:
+ - { name: 'botan', lib: 'libbotan' }
+ - { name: 'openssl', lib: 'openssl' }
+
+ include:
+ - msystem: 'ucrt64'
+ shared_libs: 'off'
+ backend: { name: 'botan', lib: 'libbotan' }
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ fetch-depth: 1
+ submodules: true
+
+ - name: Install MSys
+ uses: msys2/setup-msys2@v2
+ with:
+ msystem: ${{ matrix.msystem }}
+ update: true
+ install: >-
+ git
+ base-devel
+ p7zip
+ pacboy: >-
+ toolchain:p
+ cmake:p
+ ${{ matrix.backend.lib }}:p
+ json-c:p
+ asciidoctor:p
+ gtest:p
+ path-type: minimal
+
+ - name: Configure
+ run: |
+ echo CORES="$(nproc --all)" >> $GITHUB_ENV
+ echo CRYPTO_BACKEND=${{ matrix.backend.name }} >> $GITHUB_ENV
+ cmake -B build -DBUILD_SHARED_LIBS=${{ matrix.shared_libs }} \
+ -DCRYPTO_BACKEND=${{ matrix.backend.name }} \
+ -DCMAKE_INSTALL_PREFIX=${{ env.RNP_INSTALL }} \
+ -DDOWNLOAD_GTEST=OFF \
+ -DCMAKE_BUILD_TYPE=Release .
+
+ - name: Build
+ run: cmake --build build --config "Release" --parallel ${{ env.CORES }}
+
+ - name: Test
+ run: |
+ mkdir -p "build/Testing/Temporary"
+ cp "cmake/CTestCostData.txt" "build/Testing/Temporary"
+ export PATH="$PWD/build/src/lib:$PATH"
+ ctest --parallel ${{ env.CORES }} --test-dir build -C Debug --output-on-failure
+
+ - name: Install
+ run: cmake --install build
+
+ - name: Checkout shell test framework
+ uses: actions/checkout@v3
+ with:
+ repository: kward/shunit2
+ path: ci/tests/shunit2
+
+ - name: Run additional ci tests
+ if: ${{ matrix.shared_libs == 'on' }}
+ run: ci/tests/ci-tests.sh
diff --git a/.github/workflows/windows-native.yml b/.github/workflows/windows-native.yml
new file mode 100644
index 0000000..87c37ac
--- /dev/null
+++ b/.github/workflows/windows-native.yml
@@ -0,0 +1,174 @@
+# Copyright (c) 2023 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.
+
+name: windows-native
+on:
+ push:
+ branches:
+ - main
+ - 'release/**'
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+ - '.github/workflows/*.yml'
+ - '!.github/workflows/windows-native.yml'
+ pull_request:
+ paths-ignore:
+ - '/*.sh'
+ - '/.*'
+ - '/_*'
+ - 'Brewfile'
+ - 'docs/**'
+ - '**.adoc'
+ - '**.md'
+ - '**.nix'
+ - 'flake.lock'
+
+concurrency:
+ group: '${{ github.workflow }}-${{ github.job }}-${{ github.head_ref || github.ref_name }}'
+ cancel-in-progress: true
+
+env:
+ VCPKG_DIR: C:/vcpkg
+ VCPKG_DEFAULT_BINARY_CACHE: ${{github.workspace}}\cache
+ RNP_TEST_DATA: ${{github.workspace}}\src\tests\data
+
+jobs:
+ build_and_test:
+ name: Windows-2019 [arch ${{ matrix.arch.name }}, toolset ${{ matrix.toolset }}, backend ${{ matrix.backend }}, build shared libs ${{ matrix.shared_libs }}, use CMake prefix path ${{ matrix.use_cmake_prefix_path }}]
+ runs-on: windows-2019
+ if: "!contains(github.event.head_commit.message, 'skip ci')"
+ strategy:
+ fail-fast: false
+ matrix:
+ arch: [ { name: 'x64', triplet: 'x64-windows' } ]
+ toolset: [ 'v142', 'ClangCL' ]
+ backend: [ 'botan', 'openssl' ]
+ shared_libs: [ 'on', 'off']
+ use_cmake_prefix_path: [ 'off' ]
+ include:
+ - arch: { name: 'x64', triplet: 'x64-windows' }
+ toolset: 'ClangCL'
+ backend: 'openssl'
+ use_cmake_prefix_path: 'on'
+ shared_libs: 'on'
+ - arch: { name: 'x64', triplet: 'x64-windows' }
+ toolset: 'ClangCL'
+ backend: 'openssl'
+ use_cmake_prefix_path: 'on'
+ shared_libs: 'off'
+ - arch: { name: 'Win32', triplet: 'x86-windows' }
+ toolset: 'ClangCL'
+ backend: 'botan'
+ use_cmake_prefix_path: 'off'
+ shared_libs: 'on'
+ - arch: { name: 'Win32', triplet: 'x86-windows' }
+ toolset: 'v142'
+ backend: 'openssl'
+ use_cmake_prefix_path: 'off'
+ shared_libs: 'off'
+
+ steps:
+ - name: Checkout
+ uses: actions/checkout@v3
+ with:
+ submodules: true
+ lfs: true
+ fetch-depth: 1
+
+ - name: vcpkg parameters
+ run: |
+ vcpkg version >> vcpkg.version
+ mkdir -p ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
+
+ - name: vcpkg cache
+ uses: actions/cache@v3
+ with:
+ path: ${{ env.VCPKG_DEFAULT_BINARY_CACHE }}
+ key: vcpkg-${{ hashFiles('vcpkg.version') }}-${{ matrix.arch.triplet }}-${{ matrix.toolset }}-${{ matrix.backend }}
+
+ - name: vcpkg packages
+ shell: bash
+ run: vcpkg install --triplet ${{ matrix.arch.triplet }} bzip2 zlib json-c getopt dirent ${{ matrix.backend }}
+
+ - name: Set OPENSSL_ROOT_DIR
+ # Ensure consistent access to openssl installation for test_backend_version test
+ # There is another one instance of ssl at /mingw and /mingw/bin is always at the first position at PATH
+ if: matrix.backend == 'openssl'
+ shell: bash
+ run: echo OPENSSL_ROOT_DIR=${{ env.VCPKG_DIR }}/installed >> $GITHUB_ENV
+
+ - name: Adjust settings for s2k_iteration_tuning test
+ # This step adjusts s2k_iteration_tuning threshold for
+ # s2k_iteration_tuning test (src/tests/cipher.cpp)
+ # It looks like cl on Win32 does not provide robust response
+ if: matrix.arch.name == 'Win32' && matrix.toolset == 'ClangCL'
+ shell: bash
+ run: echo CXXFLAGS="-DS2K_MINIMUM_TUNING_RATIO=4" >> $GITHUB_ENV
+
+ - name: Configure using vpkg toolchain file
+ if: matrix.use_cmake_prefix_path != 'on'
+ shell: bash
+ run: |
+ echo CORES="$(nproc --all)" >> $GITHUB_ENV
+ cmake -B build -G "Visual Studio 16 2019" \
+ -A ${{ matrix.arch.name }} \
+ -T ${{ matrix.toolset }} \
+ -DBUILD_SHARED_LIBS=${{ matrix.shared_lib}} \
+ -DCRYPTO_BACKEND=${{ matrix.backend }} \
+ -DCMAKE_TOOLCHAIN_FILE=${{ env.VCPKG_DIR }}/scripts/buildsystems/vcpkg.cmake .
+
+ - name: Configure using CMake prefix path
+ if: matrix.use_cmake_prefix_path == 'on'
+ shell: bash
+ run: |
+ echo CORES="$(nproc --all)" >> $GITHUB_ENV
+ cmake -B build -G "Visual Studio 16 2019" \
+ -A ${{ matrix.arch.name }} \
+ -T ${{ matrix.toolset }} \
+ -DBUILD_SHARED_LIBS=${{ matrix.shared_lib}} \
+ -DCRYPTO_BACKEND=${{ matrix.backend }} \
+ -DCMAKE_PREFIX_PATH=${{ env.VCPKG_DIR }}/installed/${{ matrix.arch.triplet }} .
+ echo ${{ env.VCPKG_DIR }}/installed/${{ matrix.arch.triplet }}/bin >> $GITHUB_PATH
+
+ - name: Compile
+ shell: bash
+ run: cmake --build build --config "Release" --parallel ${{ env.CORES }}
+
+ - name: Test
+ shell: bash
+ # Sometimes running cli_tests in parallel causes instability [???]
+ # ctest --test-dir build -R cli_tests -C Debug --output-on-failure
+ # ctest --parallel ${{ env.CORES }} --test-dir build -R rnp_tests -C Debug --output-on-failure
+ run: |
+ mkdir -p "build/Testing/Temporary"
+ cp "cmake/CTestCostData.txt" "build/Testing/Temporary"
+ ctest --parallel ${{ env.CORES }} --test-dir build -C Debug --output-on-failure
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..773a9bd
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,464 @@
+*.loT
+
+core.*
+vgcore.*
+
+src/lib/librnp-*.a
+src/lib/config.h
+src/lib/version.h
+librnp-*.pc
+rpm-ldconfig
+src/lib/rnp-config-version.cmake
+src/lib/rnp-config.cmake
+src/rnp/rnp
+src/rnpkeys/rnpkeys
+src/tests/rnp_tests*
+src/tests/googletest-*
+
+# CMake
+CPackConfig.cmake
+CPackSourceConfig.cmake
+DartConfiguration.tcl
+_CPack_Packages/
+
+# VSCode
+.vscode/
+.vs/
+
+# VisualStudio
+CMakeSettings.json
+out/
+
+# Nix
+result
+
+# Test framework
+ci/tests/shunit2
+
+# Created by https://www.toptal.com/developers/gitignore/api/c,vim,c++,macos,linux,patch,cmake,emacs,vscode,python,windows,textmate,sublimetext
+# Edit at https://www.toptal.com/developers/gitignore?templates=c,vim,c++,macos,linux,patch,cmake,emacs,vscode,python,windows,textmate,sublimetext
+
+### C ###
+# Prerequisites
+*.d
+
+# Object files
+*.o
+*.ko
+*.obj
+*.elf
+
+# Linker output
+*.ilk
+*.map
+*.exp
+
+# Precompiled Headers
+*.gch
+*.pch
+
+# Libraries
+*.lib
+*.a
+*.la
+*.lo
+
+# Shared objects (inc. Windows DLLs)
+*.dll
+*.so
+*.so.*
+*.dylib
+
+# Executables
+*.exe
+*.out
+*.app
+*.i*86
+*.x86_64
+*.hex
+
+# Debug files
+*.dSYM/
+*.su
+*.idb
+*.pdb
+
+# Kernel Module Compile Results
+*.mod*
+*.cmd
+.tmp_versions/
+modules.order
+Module.symvers
+Mkfile.old
+dkms.conf
+
+### C++ ###
+# Prerequisites
+
+# Compiled Object files
+*.slo
+
+# Precompiled Headers
+
+# Linker files
+
+# Debugger Files
+
+# Compiled Dynamic libraries
+
+# Fortran module files
+*.mod
+*.smod
+
+# Compiled Static libraries
+*.lai
+
+# Executables
+
+### CMake ###
+CMakeLists.txt.user
+CMakeCache.txt
+CMakeFiles
+CMakeScripts
+Testing
+Makefile
+cmake_install.cmake
+install_manifest.txt
+compile_commands.json
+CTestTestfile.cmake
+_deps
+CMakeUserPresets.json
+
+### CMake Patch ###
+# External projects
+*-prefix/
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+ltximg/**
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
+### Linux ###
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### macOS ###
+# General
+.DS_Store
+.AppleDouble
+.LSOverride
+
+# Icon must end with two \r
+Icon
+
+
+# Thumbnails
+._*
+
+# Files that might appear in the root of a volume
+.DocumentRevisions-V100
+.fseventsd
+.Spotlight-V100
+.TemporaryItems
+.Trashes
+.VolumeIcon.icns
+.com.apple.timemachine.donotpresent
+
+# Directories potentially created on remote AFP share
+.AppleDB
+.AppleDesktop
+Network Trash Folder
+Temporary Items
+.apdisk
+
+### Patch ###
+*.orig
+*.rej
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+
+# Distribution / packaging
+.Python
+/build/
+/develop-eggs/
+/downloads/
+/eggs/
+.eggs/
+/lib/
+/lib64/
+/parts/
+/sdist/
+/var/
+/wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+pytestdebug.log
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+doc/_build/
+
+# PyBuilder
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+pythonenv*
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# profiling data
+.prof
+
+### SublimeText ###
+# Cache files for Sublime Text
+*.tmlanguage.cache
+*.tmPreferences.cache
+*.stTheme.cache
+
+# Workspace files are user-specific
+*.sublime-workspace
+
+# Project files should be checked into the repository, unless a significant
+# proportion of contributors will probably not be using Sublime Text
+# *.sublime-project
+
+# SFTP configuration file
+sftp-config.json
+
+# Package control specific files
+Package Control.last-run
+Package Control.ca-list
+Package Control.ca-bundle
+Package Control.system-ca-bundle
+Package Control.cache/
+Package Control.ca-certs/
+Package Control.merged-ca-bundle
+Package Control.user-ca-bundle
+oscrypto-ca-bundle.crt
+bh_unicode_properties.cache
+
+# Sublime-github package stores a github token in this file
+# https://packagecontrol.io/packages/sublime-github
+GitHub.sublime-settings
+
+### TextMate ###
+*.tmproj
+*.tmproject
+tmtags
+
+### Vim ###
+# Swap
+[._]*.s[a-v][a-z]
+!*.svg # comment out if you don't need vector files
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+Sessionx.vim
+
+# Temporary
+.netrwhist
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
+
+### vscode ###
+.vscode/*
+!.vscode/settings.json
+!.vscode/tasks.json
+!.vscode/launch.json
+!.vscode/extensions.json
+*.code-workspace
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.toptal.com/developers/gitignore/api/c,vim,c++,macos,linux,patch,cmake,emacs,vscode,python,windows,textmate,sublimetext
diff --git a/.gitmodules b/.gitmodules
new file mode 100644
index 0000000..6cdbd78
--- /dev/null
+++ b/.gitmodules
@@ -0,0 +1,3 @@
+[submodule "src/libsexp"]
+ path = src/libsexp
+ url = https://github.com/rnpgp/sexp.git
diff --git a/Brewfile b/Brewfile
new file mode 100755
index 0000000..1860110
--- /dev/null
+++ b/Brewfile
@@ -0,0 +1,7 @@
+brew "cmake"
+brew "pkg-config"
+brew "googletest"
+brew "gnupg"
+brew "python"
+brew "json-c"
+brew "asciidoc"
diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
index 0000000..611e461
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,436 @@
+## Changelog
+
+### 0.17.0 [2023-05-01]
+
+#### General
+
+* Added support for hidden recipient during decryption.
+* Added support for AEAD-OCB for OpenSSL backend.
+* Improve support for offline secret keys during default key selection.
+* Support for GnuPG 2.3+ secret key store format.
+* SExp parsing code is moved to separate library, https://github.com/rnpgp/sexp.
+* Mark subkeys as expired instead of invalid if primary key is expired.
+* AEAD: use OCB by default instead of EAX.
+* Do not attempt to validate signatures of unexpected types.
+* Use thread-safe time and date handling functions.
+* Added ENABLE_BLOWFISH, ENABLE_CAST5 and ENABLE_RIPEMD160 build time options.
+* Do not use `EVP_PKEY_CTX_set_dsa_paramgen_q_bits()` if OpenSSL backend version is < 1.1.1e.
+* Corrected usage of CEK/KEK algorithms if those differs.
+
+#### FFI
+
+* Added function `rnp_signature_export()`.
+* Added flag `RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT` to `rnp_op_verify_set_flags()`.
+
+#### CLI
+
+* Added default armor message type for `--enarmor` command.
+* Added command `--set-filename` to specify which file name should be stored in message.
+* Added `--add-subkey` subcommand to the `--edit-key`.
+* Added `set-expire` subcommand to the `--edit-key`.
+* Added `--s2k-iterations` and `--s2k-msec` options to the `rnp`.
+* Added `--allow-weak-hash` command to allow usage of weak hash algorithms.
+* Report number of new/updated keys during the key import.
+
+### 0.16.3 [2023-04-11]
+
+#### Security
+
+* Fixed issue with possible hang on malformed inputs (CVE-2023-29479).
+* Fixed issue where in some cases, secret keys remain unlocked after use (CVE-2023-29480).
+
+### 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.
+* Added compile-time switches to disable certain features (AEAD, Brainpool curves, SM2/SM3/SM4 algorithms, Twofish)
+* Fixed possible incompatibility with GnuPG on x25519 secret key export from RNP to GnuPG.
+* Fixed building if Git is not available.
+* Fixed export of non-FFI symbols from the rnp.so/rnp.dylib.
+* Fixed support for Gnu/Hurd (absence of PATH_MAX).
+* Added support for `None` compression algorithm.
+* Added support for the dumping of notation data signature subpackets.
+* Fixed key expiration time calculation in the case with newer non-primary self-certification.
+* Improved performance of key import (no key material checks)
+
+#### Security
+
+* Added initial support for customizable security profiles.
+* Mark SHA1 signatures produced later than 2019-01-19, as invalid.
+* Mark MD5 signatures produced later than 2012-01-01, as invalid.
+* Remove SHA1 and 3DES from the default key preferences.
+* Use SHA1 collision detection code when using SHA1.
+* Mark signatures with unknown critical notation as invalid.
+* Do not prematurely mark secret keys as valid.
+* Validate secret key material before the first operation.
+* Limit the number of possible message recipients/signatures to a reasonable value (16k).
+* Limit the number of signature subpackets during parsing.
+
+#### FFI
+
+* Added functions `rnp_backend_string()` and `rnp_backend_version()`.
+* Added functions `rnp_key_25519_bits_tweaked()` and `rnp_key_25519_bits_tweak()` to check and fix x25519 secret key bits.
+* Added security profile manipulation functions: `rnp_add_security_rule()`, `rnp_get_security_rule()`, `rnp_remove_security_rule()`.
+* Added function `rnp_signature_get_expiration()`.
+* Deprecate functions `rnp_enable_debug()`/`rnp_disable_debug()`.
+
+#### CLI
+
+* Write new detailed help messages for `rnp` and `rnpkeys`.
+* Added `-` (stdin) and `env:VAR_NAME` input specifiers, as well as `-` (stdout) output specifier.
+* Do not fail with empty keyrings if those are not needed for the operation.
+* Added algorithm aliases for better usability (i.e. `SHA-256`, `SHA256`, etc.).
+* Added option `--notty` to print everything to stdout instead of TTY.
+* Added command `--edit-key` with subcommands `--check-cv25519-bits` and `--fix-cv25519-bits`.
+* Remove support for `-o someoption=somevalue`, which is unused.
+* Remove no longer used support for additional debug dumping via `--debug source.c`.
+
+### 0.15.2 [2021-07-20]
+
+#### General
+
+* Be less strict in userid validation: allow to use userids with self-signature, which has key expiration in the past.
+* Do not mark signature as invalid if key which produced it is expired now, but was valid during signing.
+* Fix incorrect key expiration calculation in some cases.
+* Fix incorrect version number in the `version.txt`.
+
+#### FFI
+
+* Add function `rnp_key_get_default_key()` to pick the default key/subkey for the specific operation.
+* Allow to pass NULL hash parameter to `rnp_key_add_uid()` to pick the default one.
+* Use the same approach as in `rnp_op_encrypt_add_recipient()` for encryption subkey selection in `rnp_key_export_autocrypt()`.
+
+#### CLI
+
+* `rnp`: Show error message if encryption failed.
+* `rnpkeys` : Add `--expiration` option to specify expiration time during key generation.
+
+### 0.15.1 [2021-05-28]
+
+#### General
+
+* Make man pages building optional.
+* Fixed updating of expiration time for a key with multiple user ids.
+* Fixed key expiry check for keys valid after the year 2038.
+* Pick up key expiration time from direct-key signature or primary userid certification if available.
+
+#### FFI
+
+* Added function `rnp_key_valid_till64()` to correctly handle keys which expire after the year 2038.
+* Added `RNP_FEATURE_*` defines to be used instead of raw strings.
+
+#### Security
+
+* Fixed issue with cleartext key data after the `rnp_key_unprotect()`/`rnp_key_protect()` calls (CVE-2021-33589).
+
+### 0.15.0 [2021-04-04]
+
+#### General
+
+* Added CMake options to allow offline builds, i.e. without Googletest/ruby-rnp downloads.
+* Removed major library version from the library name (librnp-0.so/dll -> librnp.so/dll).
+* Improved handling of cleartext signatures, when empty line between headers and contents contains some whitespace characters.
+* Relaxed requirements for the armored messages CRC (allow absence of the CRC, and issue warning instead of complete failure).
+* Updated build instructions for MSVC.
+* Improved support of 32-bit platforms (year 2038 problem).
+
+#### CLI
+
+* Added up-to-date manual pages for `rnp` and `rnpkeys`.
+* rnpkeys: added `--remove-key` command.
+
+#### FFI
+
+* Added up-to-date manual page for `librnp`.
+* Added function `rnp_signature_remove`
+* Added function `rnp_uid_remove`
+* Added function `rnp_key_remove_signatures` for batch signature removal and filtering.
+
+### 0.14.0 [2021-01-15]
+
+#### General
+
+* Improved key validation: require to have at least one valid, non-expiring self signature.
+* Added support for 'stripped' keys without userids and certifications but with valid subkey binding signature.
+* Added support for Windows via MinGW/MSYS2.
+* Added support for Windows via MSVC.
+* Fixed secret key locking when it is updated with new signatures/subkeys.
+* Fixed key expiry/flags calculation (take in account only the latest valid self-signature/subkey binding).
+* Fixed MDC reading if it appears on 8k boundary.
+* Disabled logging by default in release builds and added support for environment variable `RNP_LOG_CONSOLE` to enable it back.
+* Fixed leading zeroes for secp521r1 b & n field constants.
+* Allowed keys and signatures with invalid MPI bit count.
+* Added support for private/experimental signature subpackets, used by GnuPG and other implementations.
+* Added support for reserved/placeholder signatures.
+* Added support for zero-size userid/attr packet.
+* Relaxed packet dumping, ignoring invalid packets and allowing to find wrong packet easier.
+* Improved logging of errored keys/subkeys information for easier debugging.
+* Fixed support for old RSA sign-only/encrypt-only and ElGamal encrypt-and-sign keys.
+* Fixed support for ElGamal keys larger then 3072 bits.
+* Fixed symbol visibility so only FFI functions are exposed outside of the library.
+* Added support for unwrapping of raw literal packets.
+* Fixed crash with non-detached signature input, fed into the `rnp_op_verify_detached_create()`.
+* Significantly reduced memory usage for the keys large number of signatures.
+* Fixed long armor header lines processing.
+* Added basic support for GnuPG's offline primary keys (`gnupg --export-secret-subkeys`) and secret keys, stored on card.
+* Fixed primary key binding signature validation when hash algorithm differs from the one used in the subkey binding signature.
+* Fixed multiple memory leaks related to invalid algorithms/versions/etc.
+* Fixed possible crashes during processing of malformed armored input.
+* Limited allowed nesting levels for OpenPGP packets.
+* Fixed support for text-mode signatures.
+* Replaced strcpy calls with std::string and memcpy where applicable.
+* Removed usage of mktemp, replacing it with mkstemp.
+* Replaced usage of deprecated `botan_pbkdf()` with `botan_pwdhash()`.
+* Added support for the marker packet, issued by some implementations.
+* Added support for unknown experimental s2ks.
+* Fixed armored message contents detection (so armored revocation signature is not more reported as the public key).
+* Changed behaviour to use latest encryption subkey by default.
+* Fixed support for widechar parameters/file names on Windows.
+* Implemented userid validity checks so only certified/non-expired/non-revoked userid may be searched.
+* Fixed GnuPG compatibility issues with CR (`\r`) characters in text-mode and cleartext-signed documents.
+* Improved performance of the key/uid signatures access.
+* Migrated tests to the Python 3.
+* Migrated most of the internal code to C++.
+
+#### CLI
+
+* Do not load keyring when it is not required, avoiding extra `keyring not found` output.
+* Input/output data via the tty, if available, instead of stdin/stdout.
+* Fixed possible crash when HOME variable is not set.
+* rnpkeys: Added `--import-sigs` and changed behavior of `--import` to check whether input is key or signature.
+* rnpkeys: Added `--export-rev` command to export key's revocation, parameters `--rev-type`, `--rev-reason`.
+* rnpkeys: Added `--revoke-key` command.
+* rnpkeys: Added `--permissive` parameter to `--import-keys` command.
+* rnpkeys: Added `--password` options, allowing to specify password and/or generate unprotected key.
+
+#### FFI
+
+* Added keystore type constants `RNP_KEYSTORE_*`.
+* Added `rnp_import_signatures`.
+* Added `rnp_key_export_revocation`.
+* Added `rnp_key_revoke`.
+* Added `rnp_request_password`.
+* Added `rnp_key_set_expiration` to update key's/subkey's expiration time.
+* Added flag `RNP_LOAD_SAVE_PERMISSIVE` to `rnp_import_keys`, allowing to skip erroneous packets.
+* Added flag `RNP_LOAD_SAVE_SINGLE`, allowing to import keys one-by-one.
+* Added `rnp_op_verify_get_protection_info` to check mode and cipher used to encrypt message.
+* Added functions to retrieve recipients information (`rnp_op_verify_get_recipient_count`, `rnp_op_verify_get_symenc_count`, etc.).
+* Added flag `RNP_KEY_REMOVE_SUBKEYS` to `rnp_key_remove` function.
+* Added function `rnp_output_pipe` allowing to write data from input to the output.
+* Added function `rnp_output_armor_set_line_length` allowing to change base64 encoding line length.
+* Added function `rnp_key_export_autocrypt` to export public key in autocrypt-compatible format.
+* Added functions to retrieve information about the secret key's protection (`rnp_key_get_protection_type`, etc.).
+* Added functions `rnp_uid_get_type`, `rnp_uid_get_data`, `rnp_uid_is_primary`.
+* Added function `rnp_uid_is_valid`.
+* Added functions `rnp_key_get_revocation_signature` and `rnp_uid_get_revocation_signature`.
+* Added function `rnp_signature_get_type`.
+* Added function `rnp_signature_is_valid`.
+* Added functions `rnp_key_is_valid` and `rnp_key_valid_till`.
+* Added exception guard to FFI boundary.
+* Fixed documentation for the `rnp_unload_keys` function.
+
+#### Security
+
+* Removed version header from armored messages (see https://mailarchive.ietf.org/arch/msg/openpgp/KikdJaxvdulxIRX_yxU2_i3lQ7A/ ).
+* Enabled fuzzing via oss-fuzz and fixed reported issues.
+* Fixed a bunch of issues reported by static analyzer.
+* Require at least Botan 2.14.0.
+
+### 0.13.1 [2020-01-15]
+#### Security
+
+* rnpkeys: Fix issue #1030 where rnpkeys would generate unprotected secret keys.
+
+### 0.13.0 [2019-12-31]
+#### General
+
+* Fixed a double-free on invalid armor headers.
+* Fixed broken versioning when used as a git submodule.
+* Fixed an infinite loop on parsing truncated armored keys.
+* Fixed armored stream parsing to be more flexible and allow blank lines before trailer.
+* Fixed the armor header for detached signatures (previously MESSAGE, now SIGNATURE).
+* Improved setting of default qbits for DSA.
+* Fixed a crash when retrieving signature revocation reason.
+* Stop using expensive tests for key material validation.
+
+#### CLI
+
+* rnpkeys: Removed a few redundant commands (--get-key, --print-sigs, --trusted-keys, ...).
+* rnpkeys: Added --secret option.
+* rnpkeys: Display 'ssb' for secret subkeys.
+* rnp: Added `--list-packets` parameters (`--json`, etc.).
+* rnp: Removed `--show-keys`.
+
+#### FFI
+
+* Added `rnp_version_commit_timestamp` to retrieve the commit timestamp
+ (for non-release builds).
+* Added a new (non-JSON) key generation API (`rnp_op_generate_create` etc.).
+* Added `rnp_unload_keys` function to unload all keys.
+* Added `rnp_key_remove` to unload a single key.
+* Expanded bit length support for JSON key generation.
+* Added `rnp_key_get_subkey_count`/`rnp_key_get_subkey_at`.
+* Added various key property accessors (`rnp_key_get_bits`, `rnp_key_get_curve`).
+* Added `rnp_op_generate_set_protection_password`.
+* Added `rnp_key_packets_to_json`/`rnp_dump_packets_to_json`.
+* Added `rnp_key_get_creation`, `rnp_key_get_expiration`.
+* Added `rnp_key_get_uid_handle_at`, `rnp_uid_is_revoked`, etc.
+* Added `rnp_key_is_revoked` and related functions to check for revocation.
+* Added `rnp_output_to_path` and `rnp_output_finish`.
+* Added `rnp_import_keys`.
+* Added `rnp_calculate_iterations`.
+* Added `rnp_supports_feature`/`rnp_supported_features`.
+* Added `rnp_enable_debug`/`rnp_disable_debug`.
+* Added `rnp_key_get_primary_grip`.
+* Added `rnp_output_to_armor`.
+* Added `rnp_op_generate_set_request_password`.
+* Added `rnp_dump_packets_to_output`.
+* Added `rnp_output_write`.
+* Added `rnp_guess_contents`.
+* Implemented `rnp_op_set_file_name`/`rnp_op_set_file_mtime`.
+* Added `rnp_op_encrypt_set_aead_bits`.
+* Added `rnp_op_verify_signature_get_handle`.
+* Added `rnp_signature_packet_to_json`.
+
+#### Packaging
+
+* RPM: Split packages into librnp0, librnp0-devel, and rnp0.
+
+### 0.12.0 [2019-01-13]
+#### General
+
+* We now require Botan 2.8+.
+* Fixed key grip calculations for various key types.
+* Fixed SM2 signatures hashing the hash of the message. See comment in issue #436.
+* Added support for G10 ECC keys.
+* Fixed dumping of partial-length packets.
+* Added support for extra ECC curves:
+ * Brainpool p256, p384, p512 ECDSA/ECDH
+ * secp256k1 ECDSA/ECDH
+ * x25519
+* Fixed AEAD with newer versions of Botan.
+* Removed a lot of legacy code.
+
+#### CLI
+
+* rnp: Added -f/--keyfile option to load keys directly from a file.
+* rnp: Fixed issue with selecting G10 secret keys via userid.
+* rnpkeys: Added support for SM2 with arbitrary hashes.
+* redumper: Added -g option to dump fingerprints and grips.
+* redumper: Display key id/fingerprint/grip in packet listings.
+
+#### FFI
+
+* Added FFI examples.
+* Fixed a regression with loading subkeys directly.
+* Implemented support for per-signature hash and creation/expiration time.
+* Added AEAD support.
+
+### 0.11.0 [2018-09-16]
+#### General
+
+* Remove some old SSH key support.
+* Add support for dynamically calculating the S2K iterations.
+* Add support for extracting the public key from the secret key.
+* Add support for merging information between keys.
+
+#### CLI
+
+* Add options for custom S2K iterations/times (dynamic by default).
+
+### 0.10.0 [2018-08-20]
+#### General
+
+* Fixed some compiler warnings.
+* Switched armoring to use PRIVATE KEY instead of SECRET KEY.
+
+#### ECDSA
+
+* Use the matching hash to be used for the deterministic nonce generation.
+* Check that the input is of the expected length.
+* Removed the code to truncate the ECDSA input since this is now handled by Botan.
+
+#### FFI
+
+* Added enarmor and dearmor support.
+* Added library version retrieval.
+* Removed rnp_export_public_key, added rnp_key_export.
+
+
+### 0.9.2 [2018-08-13]
+#### General
+
+* Support for generation and verification of embedded signature subpacket for signing subkeys
+* Verification of public key signatures and key material
+* Improved performance of asymmetric operations (key material is now validated on load)
+
+#### FFI
+
+* Fixed `rnp_op_add_signature` for G10 keys
+
+
+### 0.9.1 [2018-07-12]
+#### General
+
+* Added issuer fingerprint to certifications and subkey bindings.
+
+#### CLI
+
+* Added support for keyid/fpr usage with (some) spaces and 0x prefix in
+ operations (`--sign`, etc).
+
+#### FFI
+
+* Fixed key search by fingerprint.
+
+
+### 0.9.0 [2018-06-27]
+* First official release.
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644
index 0000000..bb6d40c
--- /dev/null
+++ b/CMakeLists.txt
@@ -0,0 +1,206 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+# 3.7+ for the BZip2 target in the cmake-bundled finder (basic build)
+# 3.8+ for CPACK_RPM_MAIN_COMPONENT (RPM packaging)
+# 3.10+ for CPackFreeBSD (FreeBSD packaging)
+# 3.10+ for gtest_discover_tests (parallel rnp_tests)
+# 3.12+ for NAMELINK_COMPONENT (for better RPM packaging)
+# 3.12+ for Python3 find module
+# 3.14+ for object library link dependency propagation
+# 3.18+ for OpenSSL::applink
+cmake_minimum_required(VERSION 3.18)
+
+# contact email, other info
+include(cmake/info.cmake)
+
+# determine version
+if (NOT EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cmake")
+ file(DOWNLOAD https://raw.githubusercontent.com/rnpgp/cmake-versioning/main/version.cmake
+ "${CMAKE_CURRENT_SOURCE_DIR}/cmake/version.cmake")
+endif()
+include(cmake/version.cmake)
+determine_version("${CMAKE_CURRENT_SOURCE_DIR}" RNP)
+
+# project name, etc
+project(RNP
+ VERSION "${RNP_VERSION}"
+ LANGUAGES C CXX
+ DESCRIPTION "${PACKAGE_DESCRIPTION_SHORT}"
+)
+
+# tri-state options
+set(TRISTATE_VALUES On Off Auto)
+
+# options
+option(ENABLE_COVERAGE "Enable code coverage testing.")
+option(ENABLE_SANITIZERS "Enable ASan and other sanitizers.")
+option(ENABLE_FUZZERS "Enable fuzz targets.")
+option(DOWNLOAD_GTEST "Download Googletest" On)
+# crypto components
+function(tristate_feature_auto NAME DESCRIPTION)
+ set(${NAME} Auto CACHE STRING ${DESCRIPTION})
+ set_property(CACHE ${NAME} PROPERTY STRINGS ${TRISTATE_VALUES})
+endfunction()
+set(ENABLE_SM2 Auto CACHE STRING "Enable SM2/SM3/SM4 algorithms support.")
+set_property(CACHE ENABLE_SM2 PROPERTY STRINGS ${TRISTATE_VALUES})
+set(ENABLE_AEAD Auto CACHE STRING "Enable AEAD ciphers support.")
+set_property(CACHE ENABLE_AEAD PROPERTY STRINGS ${TRISTATE_VALUES})
+set(ENABLE_TWOFISH Auto CACHE STRING "Enable Twofish cipher support.")
+set_property(CACHE ENABLE_TWOFISH PROPERTY STRINGS ${TRISTATE_VALUES})
+set(ENABLE_BRAINPOOL Auto CACHE STRING "Enable Brainpool curves support.")
+set_property(CACHE ENABLE_BRAINPOOL PROPERTY STRINGS ${TRISTATE_VALUES})
+set(ENABLE_IDEA Auto CACHE STRING "Enable IDEA algorithm support.")
+set_property(CACHE ENABLE_IDEA PROPERTY STRINGS ${TRISTATE_VALUES})
+tristate_feature_auto(ENABLE_BLOWFISH "Enable Blowfish cipher support.")
+tristate_feature_auto(ENABLE_CAST5 "Enable CAST5 cipher support.")
+tristate_feature_auto(ENABLE_RIPEMD160 "Enable RIPEMD-160 hash support.")
+
+set(ENABLE_DOC Auto CACHE STRING "Enable building documentation.")
+set_property(CACHE ENABLE_DOC PROPERTY STRINGS ${TRISTATE_VALUES})
+
+# so we can use our bundled finders
+set(CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake/Modules")
+
+# required modules
+include(CTest)
+include(FetchContent)
+
+# default to a release build
+if (NOT CMAKE_BUILD_TYPE)
+ set(CMAKE_BUILD_TYPE Release CACHE STRING
+ "Build type. Options are: None Debug Release RelWithDebInfo MinSizeRel."
+ FORCE
+ )
+endif()
+
+# crypto backend
+if (NOT CRYPTO_BACKEND)
+ set(CRYPTO_BACKEND "botan" CACHE STRING
+ "Crypto backend. Possible values are botan and openssl (under development, only for debug builds). Default is botan."
+ FORCE
+ )
+endif()
+string(TOLOWER ${CRYPTO_BACKEND} CRYPTO_BACKEND_LOWERCASE)
+if(CRYPTO_BACKEND_LOWERCASE STREQUAL "botan")
+ # Default value;
+ set(CRYPTO_BACKEND_BOTAN 1)
+elseif(CRYPTO_BACKEND_LOWERCASE STREQUAL "openssl")
+ set(CRYPTO_BACKEND_OPENSSL 1)
+else()
+ message(FATAL_ERROR "Invalid crypto backend: ${CRYPTO_BACKEND}")
+endif()
+
+# set warning flags at the top level
+if(NOT MSVC)
+ add_compile_options(
+ -Wall -Wextra
+ -Wunreachable-code -Wpointer-arith
+ -Wmissing-declarations
+ )
+# relax some warnings a bit
+ add_compile_options(
+ -Wno-pedantic
+ -Wno-ignored-qualifiers
+ -Wno-unused-parameter
+ -Wno-missing-field-initializers
+ )
+endif(NOT MSVC)
+
+# THis works both for MSVC and CL on Windows
+if(WIN32)
+ add_compile_definitions(_CRT_SECURE_NO_WARNINGS _CRT_NONSTDC_NO_DEPRECATE)
+endif(WIN32)
+
+# set a few other things at the top level to prevent incompatibilities
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_CXX_STANDARD 11)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_CXX_EXTENSIONS OFF)
+add_definitions(-D_GNU_SOURCE)
+
+if (ENABLE_COVERAGE OR ENABLE_SANITIZERS)
+ message("Forcing build type to Debug (for code coverage or sanitizers).")
+ set(CMAKE_BUILD_TYPE Debug CACHE STRING "Build type. Forced to Debug." FORCE)
+endif()
+
+# coverage
+if (ENABLE_COVERAGE)
+ if (NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ message(FATAL_ERROR "Coverage has only been tested with the GNU compiler.")
+ endif()
+ add_compile_options(--coverage -O0)
+ link_libraries(--coverage)
+endif()
+
+# sanitizers
+if (ENABLE_SANITIZERS)
+ if (NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
+ message(FATAL_ERROR "Sanitizers have only been tested with the clang compiler.")
+ endif()
+ add_compile_options(-fsanitize=leak,address,undefined -fno-omit-frame-pointer -fno-common -O1)
+ link_libraries(-fsanitize=leak,address,undefined)
+endif()
+
+# adoc for man generation
+if (ENABLE_DOC)
+ include(AdocMan)
+endif()
+
+# everything else is in subdirs
+add_subdirectory(src/examples)
+if (ENABLE_FUZZERS)
+ add_subdirectory(src/fuzzing)
+ add_compile_options(-DFUZZERS_ENABLED=1)
+endif()
+add_subdirectory(src/common)
+
+set(WITH_SEXP_CLI OFF)
+set(WITH_SEXP_TESTS OFF)
+set(CMAKE_INSTALL_DEFAULT_COMPONENT_NAME development)
+add_subdirectory(src/libsexp EXCLUDE_FROM_ALL)
+
+add_subdirectory(src/lib)
+add_subdirectory(src/rnp)
+add_subdirectory(src/rnpkeys)
+
+# build tests, if desired
+if (BUILD_TESTING)
+ # Googletest source path
+ if (NOT GTEST_SOURCES)
+ set(GTEST_SOURCES "" CACHE STRING
+ "Path to the Googletest sources in case of download or linking to the precompiled library is disabled."
+ FORCE
+ )
+ else()
+ # Canonicalize path to the Googletest sources.
+ get_filename_component(GTEST_SOURCES_FULL ${GTEST_SOURCES} REALPATH)
+ set(GTEST_SOURCES ${GTEST_SOURCES_FULL})
+ endif()
+ add_subdirectory(src/tests)
+endif()
+
+
+# cpack packaging (RPM etc)
+include(cmake/packaging.cmake)
diff --git a/LICENSE-OCB.md b/LICENSE-OCB.md
new file mode 100644
index 0000000..6293e33
--- /dev/null
+++ b/LICENSE-OCB.md
@@ -0,0 +1,93 @@
+License for OCB Usage
+=====================
+
+Last updated: November 1, 2022
+
+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.
+
+While the license is irrevocable and does not expire, it is no longer necessary,
+since Professor Phillip Rogaway confirmed that the OCB patents are now in the
+public domain and officially abandoned, announced at the link below:
+
+* https://mailarchive.ietf.org/arch/msg/cfrg/qLTveWOdTJcLn4HP3ev-vrj05Vg/
+
+This license text remains in RNP as an acknowledgement of kindness given by
+Professor Phillip Rogaway to make OCB available for RNP users.
+
+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).
+
+
+OCB Patent License for Ribose Inc.
+----------------------------------
+
+1. Definitions
+
+1.1 "Licensor" means Phillip Rogaway, of 1212 Purdue Dr., Davis, California, USA.
+
+1.2 "Licensed Patents" means any patent that claims priority to United States
+Patent Application No. 09/918,615 entitled "Method and Apparatus for
+Facilitating Efficient Authenticated Encryption," and any utility, divisional,
+provisional, continuation, continuations in part, reexamination, reissue, or
+foreign counterpart patents that may issue with respect to the aforesaid patent
+application. This includes, but is not limited to, United States Patent No.
+7,046,802; United States Patent No. 7,200,227; United States Patent No.
+7,949,129; United States Patent No. 8,321,675; and any patent that issues out
+or United States Patent Application No. 13/669,114.
+
+1.3 "Licensee" means Ribose Inc., at Suite 1, 8/F, 10 Ice House Street,
+Central, Hong Kong, its affiliates, assignees, or successors in interest, or
+anyone using, making, copying, modifying, distributing, having made, importing,
+or having imported any program, software, or computer system including or based
+upon Open Source Software published by Ribose Inc., or their customers,
+suppliers, importers, manufacturers, distributors, or insurers.
+
+1.4 "Use in Licensee Products" means using, making, copying, modifying,
+distributing, having made, importing or having imported any program, software,
+or computer system published by Licensee, which contains or is based upon Open
+Source Software which may include any implementation of the Licensed Patents.
+
+1.5 "Open Source Software" means software whose source code is published and
+made available for inspection and use by anyone because either (a) the source
+code is subject to a license that permits recipients to copy, modify, and
+distribute the source code without payment of fees or royalties, or (b) the
+source code is in the public domain, including code released for public use
+through a CC0 waiver. All licenses certified by the Open Source Initiative at
+opensource.org as of January 1, 2017 and all Creative Commons licenses
+identified on the creativecommons.org website as of January 1, 2017, including
+the Public License Fallback of the CC0 waiver, satisfy these requirements for
+the purposes of this license.
+
+2. Grant of License
+
+2.1 Licensor hereby grants to Licensee a perpetual, worldwide, non-exclusive,
+nontransferable, non-sublicenseable, no-charge, royalty-free, irrevocable
+license to Use in Licensee Products any invention claimed in the Licensed
+Patents in any Open Source Software Implementation and in hardware as long as
+the Open Source Software incorporated in such hardware is freely licensed for
+hardware embodiment.
+
+3. Disclaimer
+
+3.1 LICENSEE'S USE OF THE LICENSED PATENTS IS AT LICENSEE'S OWN RISK AND UNLESS
+REQUIRED BY APPLICABLE LAW, LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES OF
+ANY KIND CONCERNING THE LICENSED PATENTS OR ANY PRODUCT EMBODYING ANY LICENSED
+PATENT, EXPRESS OR IMPLIED, STATUTORY OR OTHERWISE, INCLUDING, WITHOUT
+LIMITATION, WARRANTIES OF TITLE, MERCHANTIBILITY, FITNESS FOR A PARTICULAR
+PURPOSE, OR NONINFRINGEMENT. IN NO EVENT WILL LICENSOR BE LIABLE FOR ANY CLAIM,
+DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING
+FROM OR RELATED TO ANY USE OF THE LICENSED PATENTS, INCLUDING, WITHOUT
+LIMITATION, DIRECT, INDIRECT, INCIDENTAL, CONSEQUENTIAL, PUNITIVE OR SPECIAL
+DAMAGES, EVEN IF LICENSOR HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES
+PRIOR TO SUCH AN OCCURRENCE.
+
+[SIGNATURE by Phillip Rogaway]
+
+Date: August 28, 2017
diff --git a/LICENSE.md b/LICENSE.md
new file mode 100644
index 0000000..d9f32dc
--- /dev/null
+++ b/LICENSE.md
@@ -0,0 +1,178 @@
+Licenses & Copyright
+====================
+
+This license file adheres to the formatting guidelines of
+[readable-licenses](https://github.com/nevir/readable-licenses).
+
+
+Ribose's BSD 2-Clause License
+-----------------------------
+
+Copyright (c) 2017-2021, [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.
+
+
+NetBSD's BSD 2-Clause License
+-----------------------------
+
+This software contains source code originating from NetPGP, which
+carries the following copyright notice and license.
+
+Copyright (c) 2009-2016, [The NetBSD Foundation, Inc](https://www.netbsd.org).
+All rights reserved.
+
+This code is derived from software contributed to The NetBSD Foundation
+by [Alistair Crooks](mailto:agc@NetBSD.org)
+
+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 HOLDER 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.
+
+
+Nominet UK's Apache 2.0 Licence
+-------------------------------
+
+This software contains source code originating from NetPGP, which
+carries the following copyright notice and license.
+
+Copyright (c) 2005-2008 [Nominet UK](www.nic.uk)
+All rights reserved.
+
+Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+their moral rights under the UK Copyright Design and Patents Act 1988 to
+be recorded as the authors of this copyright work.
+
+Licensed under the Apache License, Version 2.0 (the "License"); you may not use
+this file except in compliance with the License. You may obtain a copy of the
+License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software distributed
+under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
+CONDITIONS OF ANY KIND, either express or implied. See the License for the
+specific language governing permissions and limitations under the License.
+
+
+Nominet UK's BSD 3-Clause License
+-------------------------------
+
+This software contains source code originating from NetPGP, which
+carries the following copyright notice and license.
+
+Copyright (c) 2005 [Nominet UK](www.nic.uk)
+All rights reserved.
+
+Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+their moral rights under the UK Copyright Design and Patents Act 1988 to
+be recorded as the authors of this copyright work.
+
+This is a BSD-style Open Source licence.
+
+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.
+
+3. The name of Nominet UK or the contributors may not be used to
+ endorse or promote products derived from this software without specific
+ prior written permission;
+
+and provided that the user accepts the terms of the following disclaimer:
+
+THIS SOFTWARE IS PROVIDED BY NOMINET UK 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 NOMINET UK 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.
+
+
+MIT License for SHA-1 collision detection code
+----------------------------------------------
+
+These files are derived from
+[sha1collisiondetection-tools](https://github.com/cr-marcstevens/sha1collisiondetection-tools),
+which is distributed with this [license](https://github.com/git/sha1collisiondetection/blob/master/LICENSE.txt)
+reproduced below.
+
+* `src/lib/crypto/sha1cd/sha1.{c,h}`
+* `src/lib/crypto/sha1cd/ubc_check.{c,h}`
+
+Copyright (c) 2017:
+
+ Marc Stevens
+ Cryptology Group
+ Centrum Wiskunde & Informatica
+ P.O. Box 94079, 1090 GB Amsterdam, Netherlands
+ marc@marc-stevens.nl
+
+ Dan Shumow
+ Microsoft Research
+ danshu@microsoft.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/README.adoc b/README.adoc
new file mode 100644
index 0000000..a0162fa
--- /dev/null
+++ b/README.adoc
@@ -0,0 +1,69 @@
+= RNP
+
+image:https://github.com/rnpgp/rnp/workflows/macos/badge.svg["macOS Build Status", link="https://github.com/rnpgp/rnp/actions?workflow=macos"]
+image:https://github.com/rnpgp/rnp/workflows/ubuntu/badge.svg["Ubuntu Build Status", link="https://github.com/rnpgp/rnp/actions?workflow=ubuntu"]
+image:https://github.com/rnpgp/rnp/workflows/centos7/badge.svg["CentOS 7 Build Status", link="https://github.com/rnpgp/rnp/actions?workflow=centos7"]
+image:https://github.com/rnpgp/rnp/workflows/windows-native/badge.svg["Windows Native Build Status", link="https://github.com/rnpgp/rnp/actions?workflow=windows-native"]
+image:https://github.com/rnpgp/rnp/workflows/windows-msys2/badge.svg["Windows MSys2 Build Status", link="https://github.com/rnpgp/rnp/actions?workflow=windows-msys2"]
+image:https://github.com/rnpgp/rnp/workflows/nix/badge.svg["Nix Build Status", link="https://github.com/rnpgp/rnp/actions?workflow=nix"]
+image:https://img.shields.io/cirrus/github/rnpgp/rnp?label=freebsd&logo=cirrus%20ci["FreeBSD Build Status", link="https://cirrus-ci.com/github/rnpgp/rnp"]
+
+image:https://img.shields.io/coverity/scan/12616.svg["Coverity Scan Build Status", link="https://scan.coverity.com/projects/rnpgp-rnp"]
+image:https://codecov.io/gh/rnpgp/rnp/branch/main/graph/badge.svg["Code coverage", link="https://codecov.io/gh/rnpgp/rnp"]
+
+== Introduction
+
+RNP is a set of OpenPGP (RFC4880) tools that works on Linux, macOS, Windows and
+*BSD built with C++.
+
+`librnp` is the library used by RNP for all OpenPGP functions, useful
+for developers to build against, different from GPGME.
+
+
+== Supported Platforms
+
+Currently supported platforms:
+
+* Fedora
+* RHEL/CentOS
+* Ubuntu
+* NixOS / Nix
+* FreeBSD
+* MacOS
+* Windows
+* Debian
+
+Upcoming supported platforms:
+
+* OpenSUSE Leap
+* SLES
+
+== link:docs/installation.adoc[Installation]
+
+== link:docs/cli-usage.adoc[Using CLI tool]
+
+== link:docs/c-usage.adoc[Using the RNP C API in your projects]
+
+== link:docs/signing-keys.adoc[PGP keys used for signing source code]
+
+== Versioning
+
+RNP follows the http://semver.org/[semantic versioning] syntax.
+
+=== Checking versions
+
+The output of `rnp --version` contains the `git` hash of
+the version the binary was built from, which value is generated when
+`cmake` runs. Consequently, a release tarball generated with `make
+dist` will contain this hash version.
+
+=== Historic information
+
+The first version of rnp started at `0.8.0` to indicate its development
+completeness (or lack thereof).
+
+RNP originated as an attempt to modernize the NetPGP codebase originally
+created by Alistair Crooks of NetBSD in 2016. RNP has been heavily rewritten,
+and carries minimal if any code from the original codebase.
+
+== link:docs/code-of-conduct.adoc[Code of Conduct]
diff --git a/_config.yml b/_config.yml
new file mode 100644
index 0000000..b849713
--- /dev/null
+++ b/_config.yml
@@ -0,0 +1 @@
+theme: jekyll-theme-leap-day \ No newline at end of file
diff --git a/ci/botan-modules b/ci/botan-modules
new file mode 100644
index 0000000..626e0fe
--- /dev/null
+++ b/ci/botan-modules
@@ -0,0 +1,45 @@
+aead
+aes
+auto_rng
+bigint
+blowfish
+camellia
+cast128
+cbc
+cfb
+crc24
+curve25519
+des
+dl_algo
+dl_group
+dsa
+eax
+ecc_key
+ecdh
+ecdsa
+ed25519
+elgamal
+eme_pkcs1
+emsa_pkcs1
+emsa_raw
+ffi
+hash
+hmac
+hmac_drbg
+idea
+kdf
+md5
+ocb
+pgp_s2k
+rfc3394
+rmd160
+rsa
+sha1
+sha2_32
+sha2_64
+sha3
+sm2
+sm3
+sm4
+sp800_56a
+twofish
diff --git a/ci/build_package_rpm.sh b/ci/build_package_rpm.sh
new file mode 100755
index 0000000..645bf5d
--- /dev/null
+++ b/ci/build_package_rpm.sh
@@ -0,0 +1,100 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+set -euxo pipefail
+
+: "${BUILD_SHARED_LIBS:=off}"
+
+: "${VERBOSE:=1}"
+
+declare packaging_dependencies_yum=(
+ rpmdevtools
+ )
+
+install_rpm_packaging_utils() {
+ yum_install \
+ "${packaging_dependencies_yum[@]}" \
+ "$@"
+}
+
+install_packaging_dependencies() {
+ case "${DIST}" in
+ centos)
+ install_rpm_packaging_utils epel-rpm-macros
+ ;;
+ *)
+ install_rpm_packaging_utils
+ esac
+}
+
+# NOTE: This should be done by install_noncacheable_dependencies.sh.
+install_build_dependencies() {
+ "${OS}_install"
+}
+
+install_dependencies() {
+ # NOTE: This is done by install_noncacheable_dependencies.sh.
+ # install_build_dependencies
+ install_packaging_dependencies
+}
+
+prepare_build_package() {
+ install_dependencies
+ rpmdev-setuptree
+ export SOURCE_PATH=rnp${RNP_VERSION:+-${RNP_VERSION}}
+ cp -a "${GITHUB_WORKSPACE}" ~/rpmbuild/SOURCES/"${SOURCE_PATH}"
+}
+
+build_package() {
+ pushd ~/rpmbuild/SOURCES/"${SOURCE_PATH}"
+
+ # XXX: debug
+ command -v asciidoctor
+
+ cpack -G RPM --config ./CPackSourceConfig.cmake
+ make package VERBOSE="${VERBOSE}"
+
+ popd
+}
+
+post_build_package() {
+ pushd ~/rpmbuild/SOURCES/"${SOURCE_PATH}"
+
+ mv ./*.src.rpm ~/rpmbuild/SRPMS/
+ # mkdir -p ~/rpmbuild/RPMS/noarch/
+ # mv *.noarch.rpm ~/rpmbuild/RPMS/noarch/
+ mkdir -p ~/rpmbuild/RPMS/x86_64/
+ mv ./*.rpm ~/rpmbuild/RPMS/x86_64/
+
+ popd
+}
+
+test_packages() {
+ yum_install ~/rpmbuild/RPMS/x86_64/*.rpm
+}
+
+main() {
+ # For asciidoctor:
+ export PATH=$HOME/bin:$PATH
+
+ . ci/env.inc.sh
+
+ prepare_build_package
+
+ export LDFLAGS='-Wl,-t' # XXX: DELETEME: for debugging only
+
+ pushd ~/rpmbuild/SOURCES/"${SOURCE_PATH}"
+
+ export cmakeopts=(
+ -DBUILD_SHARED_LIBS="${BUILD_SHARED_LIBS}"
+ -DBUILD_TESTING=no
+ -DCPACK_GENERATOR=RPM
+ )
+ build_rnp "."
+ popd
+
+ build_package
+ post_build_package
+ test_packages
+}
+
+main "$@"
diff --git a/ci/docker/Makefile b/ci/docker/Makefile
new file mode 100644
index 0000000..b5e9877
--- /dev/null
+++ b/ci/docker/Makefile
@@ -0,0 +1,11 @@
+all: fedora35.pushed
+clean:
+ rm *.built *.pushed
+.PHONY: all clean
+
+fedora35.built:
+ cd ../.. && docker build --squash -t andreyutkin/rnp-ci-fedora:35 -f ci/docker/fedora35.Dockerfile .
+ touch $@
+fedora35.pushed: fedora35.built
+ docker push andreyutkin/rnp-ci-fedora:35
+ touch $@
diff --git a/ci/docker/fedora35.Dockerfile b/ci/docker/fedora35.Dockerfile
new file mode 100644
index 0000000..dd71b05
--- /dev/null
+++ b/ci/docker/fedora35.Dockerfile
@@ -0,0 +1,52 @@
+FROM fedora:35
+RUN dnf makecache
+RUN dnf -y install \
+autoconf \
+automake \
+bison \
+botan2 \
+botan2-devel \
+byacc \
+bzip2 \
+bzip2-devel \
+clang \
+cmake \
+gcc \
+gcc-c++ \
+gettext-devel \
+git \
+gtest \
+gtest-devel \
+gzip \
+json-c \
+json-c-devel \
+libtool \
+make \
+ncurses-devel \
+openssl \
+openssl-devel \
+openssl-libs \
+python3 \
+ruby-devel \
+rubygem-asciidoctor \
+sudo \
+wget \
+zlib-devel \
+;
+RUN dnf clean all
+
+RUN useradd rnpuser
+RUN echo -e "rnpuser\tALL=(ALL)\tNOPASSWD:\tALL" > /etc/sudoers.d/rnpuser
+RUN echo -e "rnpuser\tsoft\tnproc\tunlimited\n" > /etc/security/limits.d/30-rnpuser.conf
+
+# Everything below wouldn't be needed if packaged gpg didn't fail with "Unknown elliptic curve"
+# on these tests from cli_tests.Misc:
+# test_aead_last_chunk_zero_length
+# test_clearsign_long_lines
+# test_eddsa_sig_lead_zero
+# test_text_sig_crcr
+
+COPY ci ci
+RUN export USE_STATIC_DEPENDENCIES=yes && su rnpuser -c ci/install_noncacheable_dependencies.sh
+RUN export USE_STATIC_DEPENDENCIES=yes && su rnpuser -c ci/install_cacheable_dependencies.sh
+RUN rm -rf /home/rnpuser/local-builds
diff --git a/ci/docker/fedora35.test-locally b/ci/docker/fedora35.test-locally
new file mode 100755
index 0000000..892c9a6
--- /dev/null
+++ b/ci/docker/fedora35.test-locally
@@ -0,0 +1,22 @@
+#!/bin/bash
+
+rm -rf /tmp/rnp*
+
+cat > run-this <<EOF
+#!/bin/bash
+set -e
+set -x
+
+export USE_STATIC_DEPENDENCIES=no
+
+export BUILD_MODE=normal CC=gcc CXX=g++ CRYPTO_BACKEND=openssl
+#export BUILD_MODE=sanitize CC=clang CXX=clang++ CRYPTO_BACKEND=botan
+
+cp -a /rnp /rnp.container
+cd /rnp.container
+chown -R rnpuser:rnpuser .
+su rnpuser -c ci/run.sh || bash -i
+EOF
+chmod a+x run-this
+
+docker run -v $PWD:/rnp -it andreyutkin/rnp-ci-fedora:35 /rnp/run-this
diff --git a/ci/env-common.inc.sh b/ci/env-common.inc.sh
new file mode 100644
index 0000000..11712f0
--- /dev/null
+++ b/ci/env-common.inc.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+
+: "${LOCAL_BUILDS:=$HOME/local-builds}"
+: "${LOCAL_INSTALLS:=$HOME/local-installs}"
+: "${BOTAN_INSTALL:=$LOCAL_INSTALLS/botan-install}"
+: "${JSONC_INSTALL:=$LOCAL_INSTALLS/jsonc-install}"
+: "${GPG_INSTALL:=$LOCAL_INSTALLS/gpg-install}"
+: "${RNP_INSTALL:=$LOCAL_INSTALLS/rnp-install}"
+: "${CPU:=}"
+: "${SUDO:=}"
+
+for var in LOCAL_BUILDS LOCAL_INSTALLS BOTAN_INSTALL JSONC_INSTALL \
+ GPG_INSTALL RNP_INSTALL CPU SUDO; do
+ export "${var?}"
+done
+
+: "${BUILD_MODE:=normal}"
+
+if [ "$BUILD_MODE" = "sanitize" ]; then
+ export CXX=clang++
+ export CC=clang
+fi
+
+BOTAN_MODULES=$(<ci/botan-modules tr '\n' ',')
+
+export BOTAN_MODULES
+
+# Don't clean up tempdirs when in CI runners to save time. Unset to disable.
+export RNP_KEEP_TEMP=1
diff --git a/ci/env-freebsd.inc.sh b/ci/env-freebsd.inc.sh
new file mode 100644
index 0000000..3509111
--- /dev/null
+++ b/ci/env-freebsd.inc.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env sh
+
+export PATH="/usr/local/bin:$PATH"
+export MAKE=gmake
+export SUDO=sudo
+
+: "${CORES:=$(sysctl -n hw.ncpu)}"
+export CORES
diff --git a/ci/env-linux.inc.sh b/ci/env-linux.inc.sh
new file mode 100644
index 0000000..9267972
--- /dev/null
+++ b/ci/env-linux.inc.sh
@@ -0,0 +1,46 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+
+: "${CORES:=$(grep -c '^$' /proc/cpuinfo)}"
+export CORES
+export MAKE=make
+
+DIST="$(get_linux_dist)"
+DIST_VERSION_ID="$(sh -c '. /etc/os-release && echo $VERSION_ID')"
+DIST_VERSION="${DIST}-${DIST_VERSION_ID}"
+
+export DIST
+export DIST_VERSION
+export DIST_VERSION_ID
+
+case "${DIST}" in
+ centos|fedora)
+ if command -v dnf >/dev/null; then
+ export YUM=dnf
+ else
+ export YUM=yum
+ fi
+ export SUDO=sudo
+ ;;
+ ubuntu)
+ export SUDO=sudo
+ ;;
+esac
+
+# XXX: debug function for locale
+case "${DIST}" in
+ fedora|centos)
+ debuglocale() {
+ locale -a
+ localedef --list-archive
+ if ! command -v diff >/dev/null; then
+ "${YUM}" -y -q install diffutils
+ fi
+ bash -c 'diff -u <(localedef --list-archive | sort) <(locale -a | sort) || :'
+ localedef -c -i "${LC_ALL%.*}" -f UTF-8 "${LC_ALL}"
+ # Error: character map file `UTF-8' not found: No such file or directory
+ # Error: cannot read character map directory `/usr/share/i18n/charmaps': No such file or directory
+ locale -a | grep "${LC_ALL}" || :
+ }
+ ;;
+esac
diff --git a/ci/env.inc.sh b/ci/env.inc.sh
new file mode 100644
index 0000000..ab2271f
--- /dev/null
+++ b/ci/env.inc.sh
@@ -0,0 +1,29 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1090
+# shellcheck disable=SC1091
+if [[ -z "${INCLUDED_ENV_INC_SH:-}" ]]; then
+ . ci/utils.inc.sh
+ . ci/env-common.inc.sh
+
+ OS="$(get_os)"
+ export OS
+
+ . "ci/env-${OS}.inc.sh"
+
+ : "${MAKE_PARALLEL:=$CORES}"
+ export MAKE_PARALLEL
+
+ . ci/lib/install_functions.inc.sh
+
+
+ : "${MAKE_PARALLEL:=$CORES}"
+ export MAKE_PARALLEL
+
+ : "${CTEST_PARALLEL:=$CORES}"
+ export CTEST_PARALLEL
+
+ : "${PARALLEL_TEST_PROCESSORS:=$CORES}"
+ export PARALLEL_TEST_PROCESSORS
+
+ export INCLUDED_ENV_INC_SH=1
+fi
diff --git a/ci/gha/setup-env.inc.sh b/ci/gha/setup-env.inc.sh
new file mode 100644
index 0000000..c0aca85
--- /dev/null
+++ b/ci/gha/setup-env.inc.sh
@@ -0,0 +1,60 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC2086,SC2129,SC2034
+
+set -euxo pipefail
+# execute this script in a separate, early step
+
+LOCAL_BUILDS="${GITHUB_WORKSPACE}/builds"
+
+# To install and cache our dependencies we need an absolute path
+# that does not change, is writable, and resides within
+# GITHUB_WORKSPACE.
+# On macOS GITHUB_WORKSPACE includes the github runner version,
+# so it does not remain constant.
+# This causes problems with, for example, pkgconfig files
+# referencing paths that no longer exist.
+CACHE_DIR="installs"
+mkdir -p "${CACHE_DIR}"
+
+if [[ "${RUNNER_OS}" = "Windows" ]]
+then
+ rnp_local_installs="${RUNNER_TEMP}/rnp-local-installs"
+else
+ rnp_local_installs=/tmp/rnp-local-installs
+fi
+
+ln -s "$GITHUB_WORKSPACE/installs" "${rnp_local_installs}"
+LOCAL_INSTALLS="${rnp_local_installs}"
+
+# When building packages, dependencies with non-standard installation paths must
+# be found by the (DEB) package builder.
+BOTAN_INSTALL="${rnp_local_installs}/botan-install"
+JSONC_INSTALL="${rnp_local_installs}/jsonc-install"
+GPG_INSTALL="${rnp_local_installs}/gpg-install"
+
+# set this explicitly since we don't want to cache the rnp installation
+RNP_INSTALL="${GITHUB_WORKSPACE}/rnp-install"
+
+for var in \
+ LOCAL_BUILDS \
+ CACHE_DIR \
+ LOCAL_INSTALLS \
+ BOTAN_INSTALL \
+ JSONC_INSTALL \
+ GPG_INSTALL \
+ RNP_INSTALL
+do
+ val="${!var}"
+
+ # Replace all backslashes with forward slashes, for cmake, so the following
+ # error would not come up:
+ #
+ # Invalid character escape '\a'.
+ #
+ if [[ "${RUNNER_OS}" = "Windows" ]]
+ then
+ val="${val//\\/\/}"
+ fi
+
+ echo "${var}=${val}" >> "$GITHUB_ENV"
+done
diff --git a/ci/install.sh b/ci/install.sh
new file mode 100755
index 0000000..9d90265
--- /dev/null
+++ b/ci/install.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+set -exu
+
+. ci/env.inc.sh
+
+install_static_cacheable_build_dependencies "$@"
diff --git a/ci/install_cacheable_dependencies.sh b/ci/install_cacheable_dependencies.sh
new file mode 100755
index 0000000..ea23ad6
--- /dev/null
+++ b/ci/install_cacheable_dependencies.sh
@@ -0,0 +1,7 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+set -exu
+
+. ci/env.inc.sh
+
+install_static_cacheable_build_dependencies_if_needed "$@"
diff --git a/ci/install_noncacheable_dependencies.sh b/ci/install_noncacheable_dependencies.sh
new file mode 100755
index 0000000..bfb93da
--- /dev/null
+++ b/ci/install_noncacheable_dependencies.sh
@@ -0,0 +1,8 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1091
+set -exu
+
+. ci/env.inc.sh
+
+"${OS}_install"
+install_static_noncacheable_build_dependencies_if_needed "$@"
diff --git a/ci/lib/cacheable_install_functions.inc.sh b/ci/lib/cacheable_install_functions.inc.sh
new file mode 100644
index 0000000..6ef35bb
--- /dev/null
+++ b/ci/lib/cacheable_install_functions.inc.sh
@@ -0,0 +1,242 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1090
+# shellcheck disable=SC1091
+# shellcheck disable=SC2164
+#
+# All of the following functions install things into the
+# CACHE_DIR, so they could be safely skipped in case of a
+# cache hit. Conversely, these should not attempt to export
+# environment variables (unless for self consumption), nor
+# modify other system parts (again, unless for self
+# consumption), as these will not be available in case of
+# cache hits.
+
+install_botan() {
+ # botan
+ local botan_build=${LOCAL_BUILDS}/botan
+ if [[ ! -e "${BOTAN_INSTALL}/lib/libbotan-2.so" ]] && \
+ [[ ! -e "${BOTAN_INSTALL}/lib/libbotan-2.dylib" ]] && \
+ [[ ! -e "${BOTAN_INSTALL}/lib/libbotan-2.a" ]]; then
+
+ if [[ -d "${botan_build}" ]]; then
+ rm -rf "${botan_build}"
+ fi
+
+ git clone --depth 1 --branch "${BOTAN_VERSION}" https://github.com/randombit/botan "${botan_build}"
+ pushd "${botan_build}"
+
+ local osparam=()
+ local cpuparam=()
+ local run=run
+ # Position independent code is a default for shared libraries at any xNIX platform
+ # but it makes no sense and is not supported for Windows
+ local extra_cflags="-fPIC"
+ case "${OS}" in
+ linux)
+ case "${DIST_VERSION}" in
+ centos-8|centos-9|fedora-*|debian-*)
+ run=run_in_python_venv
+ ;;
+ esac
+ ;;
+ esac
+
+ [[ -z "$CPU" ]] || cpuparam=(--cpu="$CPU" --disable-cc-tests)
+
+ local build_target="shared,cli"
+ is_use_static_dependencies && build_target="static,cli"
+
+ "${run}" ./configure.py --prefix="${BOTAN_INSTALL}" --with-debug-info --extra-cxxflags="-fno-omit-frame-pointer ${extra_cflags}" \
+ ${osparam+"${osparam[@]}"} ${cpuparam+"${cpuparam[@]}"} --without-documentation --without-openssl --build-targets="${build_target}" \
+ --minimized-build --enable-modules="$BOTAN_MODULES"
+ ${MAKE} -j"${MAKE_PARALLEL}" install
+ popd
+ fi
+}
+
+# TODO:
+# /tmp/rnp-local-installs/jsonc-install/lib
+# | If you ever happen to want to link against installed libraries
+# | in a given directory, LIBDIR, you must either use libtool, and
+# | specify the full pathname of the library, or use the '-LLIBDIR'
+# | flag during linking and do at least one of the following:
+# | - add LIBDIR to the 'LD_LIBRARY_PATH' environment variable
+# | during execution
+# | - add LIBDIR to the 'LD_RUN_PATH' environment variable
+# | during linking
+# | - use the '-Wl,-rpath -Wl,LIBDIR' linker flag
+# | - have your system administrator add LIBDIR to '/etc/ld.so.conf'
+install_jsonc() {
+ local jsonc_build=${LOCAL_BUILDS}/json-c
+ if [[ ! -e "${JSONC_INSTALL}/lib/libjson-c.so" ]] && \
+ [[ ! -e "${JSONC_INSTALL}/lib/libjson-c.dylib" ]] && \
+ [[ ! -e "${JSONC_INSTALL}/lib/libjson-c.a" ]]; then
+
+ if [ -d "${jsonc_build}" ]; then
+ rm -rf "${jsonc_build}"
+ fi
+
+ mkdir -p "${jsonc_build}"
+ pushd "${jsonc_build}"
+ wget https://s3.amazonaws.com/json-c_releases/releases/json-c-"${JSONC_VERSION}".tar.gz -O json-c.tar.gz
+ tar xzf json-c.tar.gz --strip 1
+
+ autoreconf -ivf
+ local cpuparam=()
+ [[ -z "$CPU" ]] || cpuparam=(--build="$CPU")
+ local build_type_args=(
+ "--enable-$(is_use_static_dependencies && echo 'static' || echo 'shared')"
+ "--disable-$(is_use_static_dependencies && echo 'shared' || echo 'static')"
+ )
+ env CFLAGS="-fPIC -fno-omit-frame-pointer -Wno-implicit-fallthrough -g" ./configure ${cpuparam+"${cpuparam[@]}"} "${build_type_args[@]}" --prefix="${JSONC_INSTALL}"
+ ${MAKE} -j"${MAKE_PARALLEL}" install
+ popd
+ fi
+}
+
+_install_gpg() {
+ local VERSION_SWITCH=$1
+ local NPTH_VERSION=$2
+ local LIBGPG_ERROR_VERSION=$3
+ local LIBGCRYPT_VERSION=$4
+ local LIBASSUAN_VERSION=$5
+ local LIBKSBA_VERSION=$6
+ local PINENTRY_VERSION=$7
+ local GNUPG_VERSION=$8
+
+ local gpg_build="$PWD"
+ # shellcheck disable=SC2153
+ local gpg_install="${GPG_INSTALL}"
+ mkdir -p "${gpg_build}" "${gpg_install}"
+ git clone --depth 1 https://github.com/rnpgp/gpg-build-scripts
+ pushd gpg-build-scripts
+
+ local cpuparam=()
+ [[ -z "$CPU" ]] || cpuparam=(--build="$CPU")
+
+ # configure_opts="\
+ # --prefix=${gpg_install} \
+ # --with-libgpg-error-prefix=${gpg_install} \
+ # --with-libassuan-prefix=${gpg_install} \
+ # --with-libgcrypt-prefix=${gpg_install} \
+ # --with-ksba-prefix=${gpg_install} \
+ # --with-npth-prefix=${gpg_install} \
+ # --disable-doc \
+ # --extra-cflags=\"-O3 -fomit-frame-pointer\" \
+ # --enable-pinentry-curses \
+ # --disable-pinentry-emacs \
+ # --disable-pinentry-gtk2 \
+ # --disable-pinentry-gnome3 \
+ # --disable-pinentry-qt \
+ # --disable-pinentry-qt4 \
+ # --disable-pinentry-qt5 \
+ # --disable-pinentry-tqt \
+ # --disable-pinentry-fltk \
+ # --enable-maintainer-mode \
+ # ${cpuparam+"${cpuparam[@]}"}"
+
+ local configure_opts=(
+ "--prefix=${gpg_install}"
+ "--with-libgpg-error-prefix=${gpg_install}"
+ "--with-libassuan-prefix=${gpg_install}"
+ "--with-libgcrypt-prefix=${gpg_install}"
+ "--with-ksba-prefix=${gpg_install}"
+ "--with-npth-prefix=${gpg_install}"
+ "--disable-doc"
+ "--enable-pinentry-curses"
+ "--disable-pinentry-emacs"
+ "--disable-pinentry-gtk2"
+ "--disable-pinentry-gnome3"
+ "--disable-pinentry-qt"
+ "--disable-pinentry-qt4"
+ "--disable-pinentry-qt5"
+ "--disable-pinentry-tqt"
+ "--disable-pinentry-fltk"
+ "--enable-maintainer-mode"
+ "--enable-install-gpg-error-config"
+ ${cpuparam+"${cpuparam[@]}"}
+ )
+
+ local common_args=(
+ --force-autogen
+# --verbose commented out to speed up recurring CI builds
+# --trace uncomment if you are debugging CI
+ --build-dir "${gpg_build}"
+ --configure-opts "${configure_opts[*]}"
+ )
+
+ case "${OS}" in
+ linux)
+ if [[ "${DIST}" != "ubuntu" ]]; then
+ common_args+=(--ldconfig)
+ fi
+ ;;
+ esac
+
+ # For "tee"-ing to /etc/ld.so.conf.d/gpg-from_build_scripts.conf from option `--ldconfig`
+ if [[ "${SUDO}" = "sudo" && "${DIST}" != "ubuntu" ]]; then
+ common_args+=(--sudo)
+ fi
+
+ # Workaround to correctly build pinentry on the latest GHA on macOS. Most likely there is a better solution.
+ export CFLAGS="-D_XOPEN_SOURCE_EXTENDED"
+ export CXXFLAGS="-D_XOPEN_SOURCE_EXTENDED"
+
+ # Always build GnuPG with gcc, even if we are testing clang
+ # ref https://github.com/rnpgp/rnp/issues/1669
+ export CC="gcc"
+ export CXX="g++"
+
+ for component in libgpg-error:$LIBGPG_ERROR_VERSION \
+ libgcrypt:$LIBGCRYPT_VERSION \
+ libassuan:$LIBASSUAN_VERSION \
+ libksba:$LIBKSBA_VERSION \
+ npth:$NPTH_VERSION \
+ pinentry:$PINENTRY_VERSION \
+ gnupg:$GNUPG_VERSION; do
+ local name="${component%:*}"
+ local version="${component#*:}"
+
+ ./install_gpg_component.sh \
+ --component-name "$name" \
+ --"$VERSION_SWITCH" "$version" \
+ "${common_args[@]}"
+ done
+ popd
+}
+
+
+install_gpg() {
+ local gpg_build=${LOCAL_BUILDS}/gpg
+
+ # shellcheck disable=SC2153
+ if [[ ! -e "${GPG_INSTALL}/bin/gpg" ]]; then
+ mkdir -p "${gpg_build}"
+ pushd "${gpg_build}"
+
+ # shellcheck disable=SC2153
+ case "${GPG_VERSION}" in
+ stable)
+ # npth libgpg-error libgcrypt libassuan libksba pinentry gnupg
+ _install_gpg component-version 1.6 1.46 1.10.1 2.5.5 1.6.3 1.2.1 2.4.0
+ ;;
+ lts)
+ # npth libgpg-error libgcrypt libassuan libksba pinentry gnupg
+ _install_gpg component-version 1.6 1.46 1.8.10 2.5.5 1.6.3 1.2.1 2.2.41
+ ;;
+ beta)
+ # npth libgpg-error libgcrypt libassuan libksba pinentry gnupg
+ _install_gpg component-git-ref 2501a48 f73605e d9c4183 909133b 3df0cd3 0e2e53c c6702d7
+ # _install_gpg component-git-ref 7e45b50 c66594d cf88dca 57cf9d6 4243085 6e8ad31 d4e5979
+ ;;
+ "2.3.1")
+ # npth libgpg-error libgcrypt libassuan libksba pinentry gnupg
+ _install_gpg component-version 1.6 1.42 1.9.3 2.5.5 1.6.0 1.1.1 2.3.1
+ ;;
+ *)
+ >&2 echo "\$GPG_VERSION is set to invalid value: ${GPG_VERSION}"
+ exit 1
+ esac
+ popd
+ fi
+}
diff --git a/ci/lib/install_functions.inc.sh b/ci/lib/install_functions.inc.sh
new file mode 100644
index 0000000..98acc82
--- /dev/null
+++ b/ci/lib/install_functions.inc.sh
@@ -0,0 +1,869 @@
+#!/usr/bin/env bash
+# shellcheck disable=SC1090
+# shellcheck disable=SC1091
+# shellcheck disable=SC2164
+
+: "${GPG_VERSION:=stable}"
+: "${BUILD_SHARED_LIBS:=off}"
+: "${USE_STATIC_DEPENDENCIES:=}"
+: "${OS:=}"
+: "${DIST:=}"
+: "${DIST_VERSION:=}"
+: "${DIST_VERSION_ID:=}"
+
+: "${MINIMUM_CMAKE_VERSION:=3.20.0}"
+: "${MINIMUM_RUBY_VERSION:=3.0.0}"
+
+: "${RECOMMENDED_BOTAN_VERSION:=2.18.2}"
+: "${RECOMMENDED_JSONC_VERSION:=0.12.1}"
+: "${RECOMMENDED_CMAKE_VERSION:=3.20.5}"
+: "${RECOMMENDED_PYTHON_VERSION:=3.9.2}"
+: "${RECOMMENDED_RUBY_VERSION:=3.1.1}"
+
+: "${CMAKE_VERSION:=${RECOMMENDED_CMAKE_VERSION}}"
+: "${BOTAN_VERSION:=${RECOMMENDED_BOTAN_VERSION}}"
+: "${JSONC_VERSION:=${RECOMMENDED_JSONC_VERSION}}"
+: "${PYTHON_VERSION:=${RECOMMENDED_PYTHON_VERSION}}"
+: "${RUBY_VERSION:=${RECOMMENDED_RUBY_VERSION}}"
+
+# Should minimum automake version change
+# please consider release of Ribose RPM for it
+# [https://github.com/riboseinc/rpm-spec-automake116-automake]
+
+: "${MINIMUM_AUTOMAKE_VERSION:=1.16.3}"
+: "${RECOMMENDED_AUTOMAKE_VERSION:=1.16.4}"
+: "${AUTOMAKE_VERSION:=${RECOMMENDED_AUTOMAKE_VERSION}}"
+
+: "${VERBOSE:=1}"
+
+
+if [[ "${OS}" = "freebsd" ]] || \
+ [[ "${DIST}" = "ubuntu" ]] || \
+ [[ "${DIST}" = "centos" ]] || \
+ [[ "${DIST}" = "fedora" ]]
+then
+ SUDO="${SUDO:-sudo}"
+else
+ SUDO="${SUDO:-run}"
+fi
+
+# Simply run its arguments.
+run() {
+ "$@"
+}
+
+. ci/lib/cacheable_install_functions.inc.sh
+
+freebsd_install() {
+ local packages=(
+ git
+ readline
+ bash
+ gnupg
+ devel/pkgconf
+ wget
+ cmake
+ gmake
+ autoconf
+ automake
+ libtool
+ gettext-tools
+ python
+ lang/ruby27
+ )
+
+ # Note: we assume sudo is already installed
+ "${SUDO}" pkg install -y "${packages[@]}"
+
+ cd /usr/ports/devel/ruby-gems
+ "${SUDO}" make -DBATCH RUBY_VER=2.7 install
+ cd
+
+ mkdir -p ~/.gnupg
+ echo "disable-ipv6" >> ~/.gnupg/dirmngr.conf
+ dirmngr </dev/null
+ dirmngr --daemon
+ ensure_automake
+}
+
+openbsd_install() {
+ echo ""
+}
+
+netbsd_install() {
+ echo ""
+}
+
+linux_prepare_ribose_yum_repo() {
+ "${SUDO}" rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages.pub
+ "${SUDO}" rpm --import https://github.com/riboseinc/yum/raw/master/ribose-packages-next.pub
+ "${SUDO}" curl -L https://github.com/riboseinc/yum/raw/master/ribose.repo \
+ -o /etc/yum.repos.d/ribose.repo
+}
+
+# Prepare the system by updating and optionally installing repos
+yum_prepare_repos() {
+ yum_install "${util_depedencies_yum[@]}"
+ linux_prepare_ribose_yum_repo
+ "${SUDO}" "${YUM}" -y update
+ if [[ $# -gt 0 ]]; then
+ yum_install "$@"
+ fi
+}
+
+linux_install_fedora() {
+ yum_prepare_repos
+ extra_dep=(cmake json-c-devel ruby)
+
+ yum_install_build_dependencies "${extra_dep[@]}"
+ yum_install_dynamic_build_dependencies_if_needed
+
+ ensure_automake
+ ensure_cmake
+# ensure_ruby
+ rubygem_install_build_dependencies
+}
+
+linux_install_centos() {
+ case "${DIST_VERSION}" in
+ centos-7)
+ linux_install_centos7
+ ;;
+ centos-8)
+ linux_install_centos8
+ ;;
+ centos-9)
+ linux_install_centos9
+ ;;
+ *)
+ >&2 echo "Error: unsupported CentOS version \"${DIST_VERSION_ID}\" (supported: 7, 8, 9). Aborting."
+ exit 1
+ esac
+}
+
+declare util_depedencies_yum=(
+ sudo
+ wget
+ git
+)
+
+declare basic_build_dependencies_yum=(
+ # cmake3 # XXX: Fedora 22+ only has cmake
+ clang
+ gcc
+ gcc-c++
+ make
+ autoconf
+ libtool
+ bzip2
+ gzip
+ ribose-automake116
+)
+
+declare build_dependencies_yum=(
+ bison
+ byacc
+ bzip2-devel
+ gettext-devel
+ ncurses-devel
+ python3
+# ruby-devel
+ zlib-devel
+)
+
+declare dynamic_build_dependencies_yum=(
+ botan2
+ botan2-devel
+)
+
+apt_install() {
+ local apt_command=(apt-get -y -q install "$@")
+ if command -v sudo >/dev/null; then
+ sudo "${apt_command[@]}"
+ else
+ "${apt_command[@]}"
+ fi
+}
+
+yum_install() {
+ local yum_command=("${YUM}" -y -q install "$@")
+ if command -v sudo >/dev/null; then
+ sudo "${yum_command[@]}"
+ else
+ "${yum_command[@]}"
+ fi
+}
+
+prepare_build_tool_env() {
+ enable_llvm_toolset_7
+ enable_rh_ruby30
+ enable_ribose_automake
+# prepare_rbenv_env
+}
+
+yum_install_build_dependencies() {
+ yum_install \
+ "${basic_build_dependencies_yum[@]}" \
+ "${build_dependencies_yum[@]}" \
+ "$@"
+
+ if [[ "${CRYPTO_BACKEND:-}" == "openssl" ]]; then
+ yum_install openssl-devel
+ fi
+}
+
+linux_install_centos7() {
+ yum_prepare_repos epel-release centos-release-scl centos-sclo-rh
+
+ extra_dep=(cmake3 llvm-toolset-7.0 json-c12-devel rh-ruby30)
+
+ yum_install_build_dependencies "${extra_dep[@]}"
+ yum_install_dynamic_build_dependencies_if_needed
+
+ ensure_automake
+ ensure_cmake
+# ensure_ruby
+ enable_rh_ruby30
+ rubygem_install_build_dependencies
+}
+
+linux_install_centos8() {
+ "${SUDO}" "${YUM}" -y -q install 'dnf-command(config-manager)'
+ "${SUDO}" "${YUM}" config-manager --set-enabled powertools
+ "${SUDO}" "${YUM}" module reset ruby -y
+ yum_prepare_repos epel-release
+
+ extra_dep=(cmake texinfo json-c-devel @ruby:3.0)
+
+ yum_install_build_dependencies "${extra_dep[@]}"
+ yum_install_dynamic_build_dependencies_if_needed
+
+ ensure_automake
+ ensure_cmake
+# ensure_ruby
+ ensure_symlink_to_target /usr/bin/python3 /usr/bin/python
+ rubygem_install_build_dependencies
+}
+
+linux_install_centos9() {
+ "${SUDO}" "${YUM}" -y -q install 'dnf-command(config-manager)'
+ "${SUDO}" "${YUM}" config-manager --set-enabled crb
+ yum_prepare_repos epel-release
+
+ extra_dep=(cmake texinfo json-c-devel ruby)
+
+ yum_install_build_dependencies "${extra_dep[@]}"
+ yum_install_dynamic_build_dependencies_if_needed
+
+ ensure_automake
+ ensure_cmake
+# ensure_ruby
+ rubygem_install_build_dependencies
+}
+
+is_use_static_dependencies() {
+ [[ -n "${USE_STATIC_DEPENDENCIES}" ]] && \
+ [[ no != "${USE_STATIC_DEPENDENCIES}" ]] && \
+ [[ off != "${USE_STATIC_DEPENDENCIES}" ]] && \
+ [[ false != "${USE_STATIC_DEPENDENCIES}" ]] && \
+ [[ 0 != "${USE_STATIC_DEPENDENCIES}" ]]
+}
+
+yum_install_dynamic_build_dependencies_if_needed() {
+ if ! is_use_static_dependencies; then
+ yum_install_dynamic_build_dependencies
+ fi
+}
+
+install_static_noncacheable_build_dependencies_if_needed() {
+ if is_use_static_dependencies; then
+ install_static_noncacheable_build_dependencies "$@"
+ fi
+}
+
+install_static_cacheable_build_dependencies_if_needed() {
+ if is_use_static_dependencies || [[ "$#" -gt 0 ]]; then
+ USE_STATIC_DEPENDENCIES=true
+ install_static_cacheable_build_dependencies "$@"
+ fi
+}
+
+install_static_cacheable_build_dependencies() {
+ prepare_build_tool_env
+
+ mkdir -p "$LOCAL_BUILDS"
+
+ local default=(jsonc gpg)
+ if [[ "${CRYPTO_BACKEND:-}" != "openssl" ]]; then
+ default=(botan "${default[@]}")
+ fi
+ local items=("${@:-${default[@]}}")
+ for item in "${items[@]}"; do
+ install_"$item"
+ done
+}
+
+install_static_noncacheable_build_dependencies() {
+ mkdir -p "$LOCAL_BUILDS"
+
+ local default=(asciidoctor)
+ local items=("${@:-${default[@]}}")
+ for item in "${items[@]}"; do
+ install_"$item"
+ done
+}
+
+rubygem_install_build_dependencies() {
+ install_asciidoctor
+}
+
+yum_install_dynamic_build_dependencies() {
+ yum_install \
+ "${dynamic_build_dependencies_yum[@]}"
+
+ # Work around pkg-config giving out wrong include path for json-c:
+ ensure_symlink_to_target /usr/include/json-c12 /usr/include/json-c
+}
+
+# Make sure cmake is at least 3.14+ as required by rnp
+# Also make sure ctest is available.
+# If not, build cmake from source.
+ensure_cmake() {
+ ensure_symlink_to_target /usr/bin/cmake3 /usr/bin/cmake
+ ensure_symlink_to_target /usr/bin/cpack3 /usr/bin/cpack
+
+ local cmake_version
+ cmake_version=$({
+ command -v cmake >/dev/null && command cmake --version || \
+ command -v cmake3 >/dev/null && command cmake3 --version
+ } | head -n1 | cut -f3 -d' '
+ )
+
+ local need_to_build_cmake=
+
+ # Make sure ctest is also in PATH. If not, build cmake from source.
+ # TODO: Check CentOS7 tests in GHA.
+ if ! command -v ctest >/dev/null; then
+ >&2 echo "ctest not found."
+ need_to_build_cmake=1
+ elif ! is_version_at_least cmake "${MINIMUM_CMAKE_VERSION}" echo "${cmake_version}"; then
+ >&2 echo "cmake version lower than ${MINIMUM_CMAKE_VERSION}."
+ need_to_build_cmake=1
+ fi
+
+ if [[ "${need_to_build_cmake}" != 1 ]]; then
+ CMAKE="$(command -v cmake)"
+ >&2 echo "cmake rebuild is NOT needed."
+ return
+ fi
+
+ >&2 echo "cmake rebuild is needed."
+
+ pushd "$(mktemp -d)" || return 1
+
+ install_prebuilt_cmake Linux-x86_64
+# build_and_install_cmake
+
+ command -v cmake
+
+ popd
+
+ # Abort if ctest still not found.
+ if ! command -v ctest >/dev/null; then
+ >&2 echo "Error: ctest not found. Aborting."
+ exit 1
+ fi
+}
+
+# E.g. for i386
+# NOTE: Make sure cmake's build prerequisites are installed.
+build_and_install_cmake() {
+ local cmake_build=${LOCAL_BUILDS}/cmake
+ mkdir -p "${cmake_build}"
+ pushd "${cmake_build}"
+ wget https://github.com/Kitware/CMake/releases/download/v"${CMAKE_VERSION}"/cmake-"${CMAKE_VERSION}".tar.gz -O cmake.tar.gz
+ tar xzf cmake.tar.gz --strip 1
+
+ PREFIX="${PREFIX:-/usr}"
+ mkdir -p "${PREFIX}"
+ ./configure --prefix="${PREFIX}" && ${MAKE} -j"${MAKE_PARALLEL}" && "${SUDO}" make install
+ popd
+ CMAKE="${PREFIX}"/bin/cmake
+}
+
+# 'arch' corresponds to the last segment of GitHub release URL
+install_prebuilt_cmake() {
+ local arch="${1:?Missing architecture}"
+ local cmake_build=${LOCAL_BUILDS}/cmake
+ mkdir -p "${cmake_build}"
+ pushd "${cmake_build}"
+ curl -L -o \
+ cmake.sh \
+ https://github.com/Kitware/CMake/releases/download/v"${CMAKE_VERSION}"/cmake-"${CMAKE_VERSION}"-"${arch}".sh
+
+ PREFIX="${PREFIX:-/usr}"
+ mkdir -p "${PREFIX}"
+ "${SUDO}" sh cmake.sh --skip-license --prefix="${PREFIX}"
+ popd
+ CMAKE="${PREFIX}"/bin/cmake
+}
+
+build_and_install_python() {
+ python_build=${LOCAL_BUILDS}/python
+ mkdir -p "${python_build}"
+ pushd "${python_build}"
+ curl -L -o python.tar.xz https://www.python.org/ftp/python/"${PYTHON_VERSION}"/Python-"${PYTHON_VERSION}".tar.xz
+ tar -xf python.tar.xz --strip 1
+ ./configure --enable-optimizations --prefix=/usr && ${MAKE} -j"${MAKE_PARALLEL}" && "${SUDO}" make install
+ ensure_symlink_to_target /usr/bin/python3 /usr/bin/python
+ popd
+}
+
+# Make sure automake is at least $MINIMUM_AUTOMAKE_VERSION (1.16.3) as required by GnuPG 2.3
+# - We assume that on fedora/centos ribose rpm was used (see basic_build_dependencies_yum)
+# - If automake version is less then reuired automake build it from source
+ensure_automake() {
+
+ local using_ribose_automake=
+ enable_ribose_automake
+
+ local automake_version=
+ automake_version=$({
+ command -v automake >/dev/null && command automake --version
+ } | head -n1 | cut -f4 -d' '
+ )
+
+ local need_to_build_automake=
+ if ! is_version_at_least automake "${MINIMUM_AUTOMAKE_VERSION}" echo "${automake_version}"; then
+ >&2 echo "automake version lower than ${MINIMUM_AUTOMAKE_VERSION}."
+ need_to_build_automake=1
+ fi
+
+ if [[ "${need_to_build_automake}" != 1 ]]; then
+ >&2 echo "automake rebuild is NOT needed."
+ return
+ fi
+
+ # Disable and automake116 from Ribose's repository as that may be too old.
+ if [[ "${using_ribose_automake}" == 1 ]]; then
+ >&2 echo "ribose-automake116 does not meet version requirements, disabling and removing."
+ . /opt/ribose/ribose-automake116/disable
+ "${SUDO}" rpm -e ribose-automake116
+ using_ribose_automake=0
+ fi
+
+ >&2 echo "automake rebuild is needed."
+ pushd "$(mktemp -d)" || return 1
+ build_and_install_automake
+
+ command -v automake
+ popd
+}
+
+enable_ribose_automake() {
+ case "${DIST}" in
+ centos|fedora)
+ if rpm --quiet -q ribose-automake116 && [[ "$PATH" != */opt/ribose/ribose-automake116/root/usr/bin* ]]; then
+ ACLOCAL_PATH=$(scl enable ribose-automake116 -- aclocal --print-ac-dir):$(rpm --eval '%{_datadir}/aclocal')
+ export ACLOCAL_PATH
+ . /opt/ribose/ribose-automake116/enable
+ >&2 echo "Ribose automake was enabled."
+ using_ribose_automake=1
+ fi
+ ;;
+ esac
+}
+
+enable_llvm_toolset_7() {
+ if [[ "${DIST_VERSION}" == "centos-7" ]] && \
+ rpm --quiet -q llvm-toolset-7.0 && \
+ [[ "$PATH" != */opt/rh/llvm-toolset-7.0/root/usr/bin* ]]; then
+ . /opt/rh/llvm-toolset-7.0/enable
+ fi
+}
+
+enable_rh_ruby30() {
+ if [[ "${DIST_VERSION}" == "centos-7" ]] && \
+ rpm --quiet -q rh-ruby30 && \
+ [[ "$PATH" != */opt/rh/rh-ruby30/root/usr/bin* ]]; then
+ . /opt/rh/rh-ruby30/enable
+ PATH=$HOME/bin:$PATH
+ export PATH
+ export SUDO_GEM="run"
+ fi
+}
+
+build_and_install_automake() {
+ # automake
+ automake_build=${LOCAL_BUILDS}/automake
+ mkdir -p "${automake_build}"
+ pushd "${automake_build}"
+ curl -L -o automake.tar.xz "https://ftp.gnu.org/gnu/automake/automake-${AUTOMAKE_VERSION}.tar.xz"
+ tar -xf automake.tar.xz --strip 1
+ ./configure --enable-optimizations --prefix=/usr
+ "${MAKE}" -j"${MAKE_PARALLEL}"
+ "${SUDO}" "${MAKE}" install
+ popd
+}
+
+# json-c is installed with install_jsonc
+# asciidoctor is installed with install_asciidoctor
+linux_install_ubuntu() {
+ "${SUDO}" apt-get update
+ apt_install \
+ "${util_dependencies_ubuntu[@]}" \
+ "${basic_build_dependencies_ubuntu[@]}" \
+ "${build_dependencies_ubuntu[@]}" \
+ "$@"
+
+ ubuntu_install_dynamic_build_dependencies_if_needed
+ ensure_automake
+}
+
+ubuntu_install_dynamic_build_dependencies_if_needed() {
+ if ! is_use_static_dependencies; then
+ ubuntu_install_dynamic_build_dependencies
+ fi
+}
+
+ubuntu_install_dynamic_build_dependencies() {
+ apt_install \
+ "${dynamic_build_dependencies_ubuntu[@]}"
+}
+
+declare util_dependencies_ubuntu=()
+
+declare util_dependencies_deb=(
+ sudo
+ wget
+ git
+ software-properties-common
+ # botan # Debian 9 does not have botan in default repos?
+)
+
+declare basic_build_dependencies_ubuntu=(
+ build-essential
+ cmake
+)
+
+declare basic_build_dependencies_deb=(
+ autoconf
+ automake
+ build-essential
+ curl
+ libtool
+)
+
+declare build_dependencies_ubuntu=(
+ gettext
+ libbz2-dev
+ libncurses-dev
+ python3
+ python3-venv
+ ruby-dev
+ zlib1g-dev
+)
+
+declare dynamic_build_dependencies_ubuntu=(
+ botan
+ libbotan-2-dev
+)
+
+declare build_dependencies_deb=(
+ # botan # Debian 9 does not have botan in default repos?
+ gettext
+ libbz2-dev
+ libncurses5-dev
+ libssl-dev
+ python3
+ python3-venv
+ ruby-dev
+ zlib1g-dev
+)
+
+declare ruby_build_dependencies_ubuntu=(
+ bison
+ curl
+ libbz2-dev
+ libssl-dev
+ rubygems
+ zlib1g-dev
+)
+
+declare ruby_build_dependencies_deb=(
+ bison
+ curl
+ libbz2-dev
+ libssl-dev
+ rubygems
+ zlib1g-dev
+)
+
+linux_install_debian() {
+ "${SUDO}" apt-get update
+ apt_install \
+ "${util_dependencies_deb[@]}" \
+ "${basic_build_dependencies_deb[@]}" \
+ "${build_dependencies_deb[@]}" \
+ "$@"
+
+ if [ "${CC-gcc}" = "clang" ]; then
+# Add apt.llvm.org repository and install clang
+# We may use https://packages.debian.org/stretch/clang-3.8 as well but this package gets installed to
+# /usr/lib/clang... and requires update-alternatives which would be very ugly considering CC/CXX environment
+# settings coming from yaml already
+ wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key|sudo apt-key add -
+ ${SUDO} apt-add-repository "deb http://apt.llvm.org/stretch/ llvm-toolchain-stretch main"
+ ${SUDO} apt-get install -y clang
+ fi
+
+ ensure_automake
+ ensure_ruby
+ ensure_cmake
+}
+
+linux_install() {
+ if type "linux_install_${DIST}" | grep -qwi 'function'; then
+ "linux_install_${DIST}"
+ fi
+}
+
+msys_install() {
+ local packages=(
+ tar
+ git
+ automake
+ autoconf
+ libtool
+ automake-wrapper
+ gnupg2
+ make
+ pkg-config
+ p7zip
+ mingw64/mingw-w64-x86_64-cmake
+ mingw64/mingw-w64-x86_64-python3
+ )
+
+ if [ "${CC}" = "gcc" ]; then
+ packages+=(mingw64/mingw-w64-x86_64-gcc
+ mingw64/mingw-w64-x86_64-libbotan
+ mingw64/mingw-w64-x86_64-json-c
+ )
+ else
+ packages+=(clang64/mingw-w64-clang-x86_64-clang
+ clang64/mingw-w64-clang-x86_64-openmp
+ clang64/mingw-w64-clang-x86_64-libc++
+ clang64/mingw-w64-clang-x86_64-libbotan
+ clang64/mingw-w64-clang-x86_64-json-c
+ clang64/mingw-w64-clang-x86_64-libsystre
+ )
+ fi
+
+ pacman --noconfirm -S --needed "${packages[@]}"
+
+}
+
+# Mainly for all python scripts with shebangs pointing to
+# 'python', which is
+# unavailable in CentOS 8 by default.
+#
+# This creates an environment where straight 'python' is available.
+prepare_python_virtualenv() {
+ python3 -m venv ~/.venv
+}
+
+# Run its arguments inside a python-virtualenv-enabled sub-shell.
+run_in_python_venv() {
+ if [[ ! -e ~/.venv ]] || [[ ! -f ~/.venv/bin/activate ]]; then
+ prepare_python_virtualenv
+ fi
+
+ (
+ # Avoid issues like '_OLD_VIRTUAL_PATH: unbound variable'
+ set +u
+ . ~/.venv/bin/activate
+ set -u
+ "$@"
+ )
+}
+
+install_asciidoctor() {
+ gem_install asciidoctor
+}
+
+declare ruby_build_dependencies_yum=(
+ zlib
+ zlib-devel
+ patch
+ readline-devel
+ libyaml-devel
+ libffi-devel
+ openssl-devel
+ bzip2
+ bison
+ curl
+ sqlite-devel
+ which # for rbenv-doctor
+)
+
+ensure_ruby() {
+ if is_version_at_least ruby "${MINIMUM_RUBY_VERSION}" command ruby -e 'puts RUBY_VERSION'; then
+ return
+ fi
+
+ if [[ "${DIST_VERSION}" = fedora-20 ]]; then
+ ruby_build_dependencies_yum+=(--enablerepo=updates-testing)
+ fi
+
+ case "${DIST}" in
+ centos|fedora)
+ yum_install "${ruby_build_dependencies_yum[@]}"
+ setup_rbenv
+ rbenv install -v "${RUBY_VERSION}"
+ rbenv global "${RUBY_VERSION}"
+ rbenv rehash
+ "${SUDO}" chown -R "$(whoami)" "$(rbenv prefix)"
+ ;;
+ debian)
+ apt_install "${ruby_build_dependencies_deb[@]}"
+ ;;
+ ubuntu)
+ apt_install "${ruby_build_dependencies_ubuntu[@]}"
+ ;;
+ *)
+ # TODO: handle ubuntu?
+ >&2 echo "Error: Need to install ruby ${MINIMUM_RUBY_VERSION}+"
+ exit 1
+ esac
+}
+
+
+# shellcheck disable=SC2016
+setup_rbenv() {
+ pushd "$(mktemp -d)" || return 1
+ local rbenv_rc=$HOME/setup_rbenv.sh
+ git clone https://github.com/sstephenson/rbenv.git ~/.rbenv
+ echo 'export PATH="$HOME/.rbenv/bin:$PATH"' >> "${rbenv_rc}"
+ echo 'eval "$($HOME/.rbenv/bin/rbenv init -)"' >> "${rbenv_rc}"
+
+ git clone https://github.com/sstephenson/ruby-build.git ~/.rbenv/plugins/ruby-build
+ echo 'export PATH="$HOME/.rbenv/plugins/ruby-build/bin:$PATH"' >> "${rbenv_rc}"
+ echo ". \"${rbenv_rc}\"" >> ~/.bash_profile
+ prepare_rbenv_env
+
+ # Verify rbenv is set up correctly
+ curl -fsSL https://github.com/rbenv/rbenv-installer/raw/master/bin/rbenv-doctor | bash
+ popd || return 1
+}
+
+prepare_rbenv_env() {
+ case "${DIST}" in
+ centos|fedora)
+ local rbenv_rc=$HOME/setup_rbenv.sh
+ [[ ! -r "${rbenv_rc}" ]] || . "${rbenv_rc}"
+ ;;
+ esac
+
+ if command -v rbenv >/dev/null; then
+ rbenv rehash
+ fi
+}
+
+is_version_at_least() {
+ local bin_name="${1:?Missing bin name}"; shift
+ local version_constraint="${1:?Missing version constraint}"; shift
+ local need_to_build=0
+
+ if ! command -v "${bin_name}"; then
+ >&2 echo "Warning: ${bin_name} not installed."
+ need_to_build=1
+ fi
+
+ local installed_version installed_version_major installed_version_minor #version_patch
+ installed_version="$("$@")"
+
+ # shellcheck disable=SC2181
+ # shellcheck disable=SC2295
+ if [[ $? -ne 0 ]]; then
+ need_to_build=1
+ else
+ installed_version_major="${installed_version%%.*}"
+ installed_version_minor="${installed_version#*.}"
+ installed_version_minor="${installed_version_minor%%.*}"
+ installed_version_minor="${installed_version_minor:-0}"
+ installed_version_patch="${installed_version#${installed_version_major}.}"
+ installed_version_patch="${installed_version_patch#${installed_version_minor}}"
+ installed_version_patch="${installed_version_patch#[.-]}"
+ installed_version_patch="${installed_version_patch%%[.-]*}"
+ installed_version_patch="${installed_version_patch:-0}"
+
+ local need_version_major
+ need_version_major="${version_constraint%%.*}"
+ need_version_minor="${version_constraint#*.}"
+ need_version_minor="${need_version_minor%%.*}"
+ need_version_minor="${need_version_minor:-0}"
+ need_version_patch="${version_constraint##*.}"
+ need_version_patch="${version_constraint#${need_version_major}.}"
+ need_version_patch="${need_version_patch#${need_version_minor}}"
+ need_version_patch="${need_version_patch#.}"
+ need_version_patch="${need_version_patch%%.*}"
+ need_version_patch="${need_version_patch:-0}"
+
+ # Naive semver comparison
+ if [[ "${installed_version_major}" -lt "${need_version_major}" ]] || \
+ [[ "${installed_version_major}" = "${need_version_major}" && "${installed_version_minor}" -lt "${need_version_minor}" ]] || \
+ [[ "${installed_version_major}.${installed_version_minor}" = "${need_version_major}.${need_version_minor}" && "${installed_version_patch}" -lt "${need_version_patch}" ]]; then
+ need_to_build=1
+ fi
+ fi
+
+ if [[ 1 = "${need_to_build}" ]]; then
+ >&2 echo "Warning: Need to build ${bin_name} since version constraint ${version_constraint} not met."
+ else
+ >&2 echo "No need to build ${bin_name} since version constraint ${version_constraint} is met."
+ fi
+
+ return "${need_to_build}"
+}
+
+# Install specified gem.
+# Use rbenv when available. Otherwise use system 'gem', and use 'sudo'
+# depending on OS.
+# Set SUDO_GEM to 'sudo' to force use of sudo.
+# Set SUDO_GEM to 'run' to disable sudo.
+gem_install() {
+ local gem_name="${1:?Missing gem name}"
+ local bin_name="${2:-${gem_name}}"
+ if ! command -v "${bin_name}" >/dev/null; then
+ if command -v rbenv >/dev/null; then
+ gem install "${gem_name}"
+ rbenv rehash
+ else
+ "${SUDO_GEM:-${SUDO:-run}}" gem install "${gem_name}"
+ fi
+ fi
+}
+
+build_rnp() {
+# shellcheck disable=SC2154
+ "${CMAKE:-cmake}" "${cmakeopts[@]}" "${1:-.}"
+}
+
+make_install() {
+ make -j"${MAKE_PARALLEL}" install "$@"
+}
+
+is_true_cmake_bool() {
+ local arg="${1:?Missing parameter}"
+ case "${arg}" in
+ yes|on|true|y)
+ true
+ ;;
+ no|off|false|n)
+ false
+ ;;
+ *)
+ >&2 echo "Warning: unrecognized boolean expression ($arg). Continuing and interpreting as 'false' anyway."
+ false
+ esac
+}
diff --git a/ci/local.sh b/ci/local.sh
new file mode 100755
index 0000000..db687ff
--- /dev/null
+++ b/ci/local.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -eux
+
+: "${GPG_VERSION:=stable}"
+: "${BUILD_MODE:=normal}"
+
+rsync -a /usr/local/rnp /tmp
+sudo -iu travis bash -x <<EOF
+cd /tmp/rnp
+env ${CXX:+CXX=$CXX} \
+ ${CC:+CC=$CC} \
+ GPG_VERSION=$GPG_VERSION \
+ BUILD_MODE=$BUILD_MODE \
+ ${RNP_TESTS:+RNP_TESTS=$RNP_TESTS} \
+ ci/run.sh
+EOF
diff --git a/ci/main.sh b/ci/main.sh
new file mode 100755
index 0000000..c8d80c9
--- /dev/null
+++ b/ci/main.sh
@@ -0,0 +1,109 @@
+#!/usr/bin/env bash
+set -eux
+
+. ci/env.inc.sh
+
+: "${GPG_VERSION:=stable}"
+: "${BUILD_MODE:=normal}"
+
+: "${RNP_TESTS=${RNP_TESTS-.*}}"
+: "${LD_LIBRARY_PATH:=}"
+
+: "${DIST:=}"
+: "${DIST_VERSION:=}"
+
+: "${SKIP_TESTS:=0}"
+
+prepare_build_prerequisites() {
+ CMAKE=cmake
+
+ case "${OS}-${CPU}" in
+# linux-i386) # For i386/Debian (the only 32 bit run) python3 is already installed by
+# build_and_install_python # linux_install @ install_functions.inc.sh called from install_cacheable_dependencies.sh
+# ;; # If there is a distribution that does not have python3 pre-apckeges (highly unlikely)
+ linux-*) # it shall be implemented like ensure_cmake
+ ensure_cmake
+ ;;
+ esac
+
+ export CMAKE
+}
+
+prepare_test_env() {
+ prepare_build_tool_env
+
+ export LD_LIBRARY_PATH="${GPG_INSTALL}/lib:${BOTAN_INSTALL}/lib:${JSONC_INSTALL}/lib:${RNP_INSTALL}/lib:$LD_LIBRARY_PATH"
+
+ # update dll search path for windows
+ if [[ "${OS}" = "msys" ]]; then
+ export PATH="${LOCAL_BUILDS}/rnp-build/lib:${LOCAL_BUILDS}/rnp-build/bin:${LOCAL_BUILDS}/rnp-build/src/lib:${BOTAN_INSTALL}/bin:$PATH"
+ fi
+}
+
+prepare_tests() {
+ : "${COVERITY_SCAN_BRANCH:=0}"
+ if [[ ${COVERITY_SCAN_BRANCH} = 1 ]]; then
+ exit 0
+ fi
+}
+
+build_tests() {
+ # use test costs to prioritize
+ mkdir -p "${LOCAL_BUILDS}/rnp-build/Testing/Temporary"
+ cp "${rnpsrc}/cmake/CTestCostData.txt" "${LOCAL_BUILDS}/rnp-build/Testing/Temporary"
+
+ local run=run
+ case "${DIST_VERSION}" in
+ centos-8|centos-9|fedora-*)
+ run=run_in_python_venv
+ ;;
+ esac
+
+ "${run}" ctest -j"${CTEST_PARALLEL}" -R "$RNP_TESTS" --output-on-failure
+ popd
+}
+
+main() {
+ if [[ ${SKIP_TESTS} = 0 ]]; then
+ prepare_test_env
+ fi
+ prepare_build_prerequisites
+
+ export rnpsrc="$PWD"
+
+ mkdir -p "${LOCAL_BUILDS}/rnp-build"
+ pushd "${LOCAL_BUILDS}/rnp-build"
+
+ cmakeopts=(
+ -DCMAKE_BUILD_TYPE=Release
+ -DBUILD_SHARED_LIBS=yes
+ -DCMAKE_INSTALL_PREFIX="${RNP_INSTALL}"
+ -DCMAKE_PREFIX_PATH="${BOTAN_INSTALL};${JSONC_INSTALL};${GPG_INSTALL}"
+ )
+ [[ ${SKIP_TESTS} = 1 ]] && cmakeopts+=(-DBUILD_TESTING=OFF)
+ [[ "${BUILD_MODE}" = "coverage" ]] && cmakeopts+=(-DENABLE_COVERAGE=yes)
+ [[ "${BUILD_MODE}" = "sanitize" ]] && cmakeopts+=(-DENABLE_SANITIZERS=yes)
+ [ -n "${GTEST_SOURCES:-}" ] && cmakeopts+=(-DGTEST_SOURCES="${GTEST_SOURCES}")
+ [ -n "${DOWNLOAD_GTEST:-}" ] && cmakeopts+=(-DDOWNLOAD_GTEST="${DOWNLOAD_GTEST}")
+ [ -n "${CRYPTO_BACKEND:-}" ] && cmakeopts+=(-DCRYPTO_BACKEND="${CRYPTO_BACKEND}")
+ [ -n "${ENABLE_SM2:-}" ] && cmakeopts+=(-DENABLE_SM2="${ENABLE_SM2}")
+ [ -n "${ENABLE_IDEA:-}" ] && cmakeopts+=(-DENABLE_IDEA="${ENABLE_IDEA}")
+ [ -n "${ENABLE_TWOFISH:-}" ] && cmakeopts+=(-DENABLE_TWOFISH="${ENABLE_TWOFISH}")
+ [ -n "${ENABLE_BRAINPOOL:-}" ] && cmakeopts+=(-DENABLE_BRAINPOOL="${ENABLE_BRAINPOOL}")
+
+ if [[ "${OS}" = "msys" ]]; then
+ cmakeopts+=(-G "MSYS Makefiles")
+ fi
+ build_rnp "${rnpsrc}"
+ make_install VERBOSE=0
+
+ if [[ ${SKIP_TESTS} = 0 ]]; then
+ echo "TESTS NOT SKIPPED"
+ prepare_tests
+ build_tests
+ fi
+}
+
+main "$@"
+
+exit 0
diff --git a/ci/run.sh b/ci/run.sh
new file mode 100755
index 0000000..b9e366c
--- /dev/null
+++ b/ci/run.sh
@@ -0,0 +1,5 @@
+#!/bin/sh
+set -eux
+
+ci/main.sh
+ci/success.sh
diff --git a/ci/style-check.sh b/ci/style-check.sh
new file mode 100755
index 0000000..683bbcd
--- /dev/null
+++ b/ci/style-check.sh
@@ -0,0 +1,14 @@
+#!/bin/bash
+set -eu
+
+echo Evaluating changes:
+git diff -U0 "$TRAVIS_COMMIT_RANGE"
+echo
+
+diffs=$(git diff -U0 --no-color "$TRAVIS_COMMIT_RANGE" | "$CLANG_FORMAT_DIFF" -p1 -iregex '.*\.[ch]$')
+if [ "$diffs" != "" ]; then
+ echo ==== Style changes required ====
+ echo "$diffs"
+ exit 1
+fi
+
diff --git a/ci/success.sh b/ci/success.sh
new file mode 100755
index 0000000..1a34be7
--- /dev/null
+++ b/ci/success.sh
@@ -0,0 +1,16 @@
+#!/bin/bash
+set -eu
+
+: "${BUILD_MODE:=normal}"
+
+if [ "$BUILD_MODE" = "coverage" ]; then
+ curl https://keybase.io/codecovsecurity/pgp_keys.asc | gpg --no-default-keyring --keyring trustedkeys.gpg --import # One-time step
+ curl -Os https://uploader.codecov.io/latest/linux/codecov
+ curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM
+ curl -Os https://uploader.codecov.io/latest/linux/codecov.SHA256SUM.sig
+ gpgv codecov.SHA256SUM.sig codecov.SHA256SUM
+ shasum -a 256 -c codecov.SHA256SUM
+ chmod +x codecov
+ find "${LOCAL_BUILDS}/rnp-build/" -type f -name '*.gcno' -exec gcov -p {} +
+ ./codecov
+fi
diff --git a/ci/tests/ci-tests.sh b/ci/tests/ci-tests.sh
new file mode 100755
index 0000000..1aaba03
--- /dev/null
+++ b/ci/tests/ci-tests.sh
@@ -0,0 +1,144 @@
+#! /bin/bash
+#
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+set -o errexit -o pipefail -o noclobber -o nounset
+
+DIR0="$( cd "$( dirname "$0" )" && pwd )"
+# shellcheck disable=SC2034
+SHUNIT_PARENT="$0"
+
+# Defaults applicable to 'normal' installation and not build environment
+: "${BOTAN_INSTALL:=/usr}"
+: "${JSONC_INSTALL:=/usr}"
+: "${RNP_INSTALL:=/usr}"
+
+: "${ENABLE_SM2:=}"
+: "${ENABLE_IDEA:=}"
+
+test_symbol_visibility() {
+ case "$OSTYPE" in
+ msys)
+ mkdir tmp
+ wget -O tmp/Dependencies_x64_Release.zip https://github.com/lucasg/Dependencies/releases/download/v1.10/Dependencies_x64_Release.zip
+ 7z x tmp/Dependencies_x64_Release.zip -otmp
+ tmp/Dependencies -exports "$RNP_INSTALL"/bin/librnp.dll > exports
+ rm -rf tmp
+ ;;
+ darwin*)
+ nm --defined-only -g "$RNP_INSTALL"/lib/librnp.dylib > exports
+ ;;
+ *)
+ nm --defined-only -g "$RNP_INSTALL"/lib64/librnp*.so > exports
+ esac
+
+ assertEquals "Unexpected: 'dst_close' is in exports" 0 "$(grep -c dst_close exports)"
+ assertEquals "Unexpected: 'Botan' is in exports" 0 "$(grep -c Botan exports)"
+ assertEquals "Unexpected: 'OpenSSL' is in exports" 0 "$(grep -c OpenSSL exports)"
+ assertEquals "Unexpected: 'rnp_version_string_full' is not in exports" 1 "$(grep -c rnp_version_string_full exports)"
+
+ rm -f exports
+}
+
+test_supported_features() {
+ # Make sure that we support all features which should be supported
+ supported=( RSA ELGAMAL DSA ECDH ECDSA EDDSA \
+ TRIPLEDES CAST5 BLOWFISH AES128 AES192 AES256 CAMELLIA128 CAMELLIA192 CAMELLIA256 \
+ MD5 SHA1 RIPEMD160 SHA256 SHA384 SHA512 SHA224 SHA3-256 SHA3-512 \
+ ZIP ZLIB BZip2 \
+ "NIST P-256" "NIST P-384" "NIST P-521" Ed25519 Curve25519 secp256k1 \
+ OCB)
+
+ # Old versions say ${unsupported[@]} is unbound if empty
+ unsupported=( NOOP )
+
+ botan_only=( TWOFISH EAX )
+ brainpool=( brainpoolP256r1 brainpoolP384r1 brainpoolP512r1 )
+ sm2=( SM2 SM4 SM3 "SM2 P-256" )
+
+ # SM2
+ if [[ "$ENABLE_SM2" == "Off" ]]; then
+ unsupported+=("${sm2[@]}")
+ elif [[ "${CRYPTO_BACKEND:-}" == "openssl" ]]; then
+ unsupported+=("${sm2[@]}")
+ else
+ supported+=("${sm2[@]}")
+ fi
+
+ # IDEA
+ if [[ "$ENABLE_IDEA" == "Off" ]] ;then
+ unsupported+=(IDEA)
+ else
+ supported+=(IDEA)
+ fi
+
+ case "$OSTYPE" in
+ msys)
+ so_folder="bin"
+ support+=("${brainpool[@]}")
+ ;;
+ darwin*)
+ so_folder="lib"
+ support+=("${brainpool[@]}")
+ ;;
+ *)
+ so_folder="lib64"
+ botan_only+=("${brainpool[@]}")
+ esac
+
+ if [[ "${CRYPTO_BACKEND:-}" == "openssl" ]]; then
+ unsupported+=("${botan_only[@]}")
+ library_path="${JSONC_INSTALL}/$so_folder:${RNP_INSTALL}/$so_folder"
+ else
+ supported+=("${botan_only[@]}")
+ library_path="${BOTAN_INSTALL}/$so_folder:${JSONC_INSTALL}/$so_folder:${RNP_INSTALL}/$so_folder"
+ fi
+
+ if [[ "$OSTYPE" == darwin* ]]; then
+ export DYLD_LIBRARY_PATH="$library_path"
+ else
+ export LD_LIBRARY_PATH="$library_path"
+ fi
+
+ "$RNP_INSTALL"/bin/rnp --version > rnp-version
+
+ for feature in "${supported[@]}"
+ do
+ fea="$(grep -ci "$feature" rnp-version)"
+ assertTrue "Unexpected unsupported feature: '$feature'" "[[ $fea -ge 1 ]]"
+ done
+ for feature in "${unsupported[@]}"
+ do
+ fea="$(grep -ci "$feature" rnp-version)"
+ assertTrue "Unexpected supported feature: '$feature'" "[[ $fea == 0 ]]"
+ done
+
+ rm -f rnp-version
+}
+
+# ......................................................................
+# shellcheck source=/dev/null
+. "$DIR0"/shunit2/shunit2
diff --git a/ci/tests/deb-tests.sh b/ci/tests/deb-tests.sh
new file mode 100755
index 0000000..7805702
--- /dev/null
+++ b/ci/tests/deb-tests.sh
@@ -0,0 +1,102 @@
+#! /bin/bash
+#
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+set -o errexit -o pipefail -o noclobber -o nounset
+
+DIR0="$( cd "$( dirname "$0" )" && pwd )"
+
+# Defaults applicable to 'normal' installation and not build environment
+: "${INSTALL_PREFIX:=/usr}"
+DIR_LIB="$INSTALL_PREFIX/lib/x86_64-linux-gnu"
+DIR_INC="$INSTALL_PREFIX/include/rnp"
+DIR_BIN="$INSTALL_PREFIX/bin"
+#DIR_MAN="$INSTALL_PREFIX/share/man"
+DIR_CMAKE="$INSTALL_PREFIX/lib/x86_64-linux-gnu/cmake/rnp"
+
+declare expected_libraries=(
+ "$DIR_LIB/librnp.so.0"
+)
+
+declare expected_devlibraries=(
+ "$DIR_LIB/librnp.so"
+ "$DIR_LIB/librnp.a"
+ "$DIR_LIB/libsexp.a"
+ "$DIR_LIB/pkgconfig/librnp.pc"
+)
+
+declare expected_includes=(
+ "$DIR_INC/rnp.h"
+ "$DIR_INC/rnp_err.h"
+ "$DIR_INC/rnp_export.h"
+)
+
+declare expected_cmakefiles=(
+ "$DIR_CMAKE/rnp-config.cmake"
+ "$DIR_CMAKE/rnp-config-version.cmake"
+ "$DIR_CMAKE/rnp-targets.cmake"
+ "$DIR_CMAKE/rnp-targets-release.cmake"
+)
+
+declare expected_binaries=(
+ "$DIR_BIN/rnp"
+ "$DIR_BIN/rnpkeys"
+)
+
+# Man page installation does not work as expected
+#declare expected_manuals=(
+# "$DIR_MAN/man3/librnp.3.gz"
+# "$DIR_MAN/man1/rnp.1.gz"
+# "$DIR_MAN/man1/rnpkeys.1.gz"
+#)
+
+t_installed_files() {
+ local f=
+ for f in "$@"
+ do
+ assertTrue "$f was not installed" "[ -e $f ]"
+ done
+}
+
+test_installed_files_librnp() {
+# shellcheck disable=SC2046
+ sudo dpkg -i $(ls ./*.deb) || sudo apt-get -y -f install
+
+ t_installed_files "${expected_libraries[@]}"
+ t_installed_files "${expected_devlibraries[@]}"
+ t_installed_files "${expected_includes[@]}"
+ t_installed_files "${expected_cmakefiles[@]}"
+ t_installed_files "${expected_binaries[@]}"
+
+# Man page installation does not work as expected
+# t_installed_files "${expected_manuals[@]}"
+
+ sudo dpkg -r rnp0
+}
+
+# ......................................................................
+# shellcheck source=/dev/null
+. "$DIR0"/shunit2/shunit2
diff --git a/ci/tests/pk-tests.sh b/ci/tests/pk-tests.sh
new file mode 100755
index 0000000..2b22df9
--- /dev/null
+++ b/ci/tests/pk-tests.sh
@@ -0,0 +1,144 @@
+#! /bin/bash
+#
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+set -o errexit -o pipefail -o noclobber -o nounset
+
+DIR0="$( cd "$( dirname "$0" )" && pwd )"
+
+# Defaults applicable to 'normal' installation and not build environment
+: "${INSTALL_PREFIX:=/usr}"
+
+DIR_CMAKE="$INSTALL_PREFIX/lib64/cmake/rnp"
+
+create_source_file() {
+ cat <<"EOF" > find_package_test.cpp
+ #include <rnp/rnp.h>
+
+ int main(int argc, char *argv[]) {
+ printf("RNP version: %s\n", rnp_version_string());
+ return 0;
+ }
+EOF
+}
+
+create_cmake_file() {
+ cat <<"EOF" > CMakeLists.txt
+ project(find_package_test)
+
+ find_package(PkgConfig REQUIRED)
+
+ find_package(BZip2 REQUIRED)
+ find_package(ZLIB REQUIRED)
+
+ pkg_check_modules(JSONC IMPORTED_TARGET json-c12)
+ if(NOT JSONC_FOUND)
+ pkg_check_modules(JSONC REQUIRED IMPORTED_TARGET json-c)
+ endif(NOT JSONC_FOUND)
+
+ add_library(JSON-C::JSON-C INTERFACE IMPORTED)
+ set_target_properties(JSON-C::JSON-C PROPERTIES INTERFACE_LINK_LIBRARIES PkgConfig::JSONC)
+
+ pkg_check_modules(Botan REQUIRED IMPORTED_TARGET botan-2)
+ add_library(Botan2::Botan2 INTERFACE IMPORTED)
+ set_target_properties(Botan2::Botan2 PROPERTIES INTERFACE_LINK_LIBRARIES PkgConfig::Botan)
+
+ find_package(rnp REQUIRED)
+
+ cmake_minimum_required(VERSION 3.12)
+ add_executable(find_package_test find_package_test.cpp)
+EOF
+ echo "target_link_libraries(find_package_test $1)">>CMakeLists.txt
+}
+
+test_shared_library() {
+ sudo yum -y localinstall librnp0-0*.*.rpm librnp0-devel-0*.*.rpm
+ pushd "$(mktemp -d)"
+ create_source_file
+ create_cmake_file 'rnp::librnp'
+
+# shellcheck disable=SC2251
+! cmake . -DCMAKE_MODULE_PATH="$DIR_CMAKE"/*
+ assertEquals "cmake failed at shared library test" 0 "${PIPESTATUS[0]}"
+
+# shellcheck disable=SC2251
+! make
+ assertEquals "make failed at shared library test" 0 "${PIPESTATUS[0]}"
+
+# shellcheck disable=SC2251
+! ./find_package_test
+ assertEquals "test program failed at shared library test" 0 "${PIPESTATUS[0]}"
+
+# shellcheck disable=SC2251
+! ldd find_package_test | grep librnp
+ assertEquals "no reference to shared rnp library at shared library test" 0 "${PIPESTATUS[1]}"
+
+ popd
+# shellcheck disable=SC2046
+ sudo yum -y erase $(rpm -qa | grep rnp)
+}
+
+test_static_library() {
+ sudo yum -y localinstall librnp0-0*.*.rpm librnp0-devel-0*.*.rpm
+ pushd "$(mktemp -d)"
+ create_source_file
+ create_cmake_file 'rnp::librnp-static'
+
+# shellcheck disable=SC2251
+! cmake . -DCMAKE_MODULE_PATH="$DIR_CMAKE"/*
+ assertEquals "cmake failed at static library test" 0 "${PIPESTATUS[0]}"
+
+# shellcheck disable=SC2251
+! make
+ assertEquals "make failed at static library test" 0 "${PIPESTATUS[0]}"
+
+# shellcheck disable=SC2251
+! ./find_package_test
+ assertEquals "test program failed at static library test" 0 "${PIPESTATUS[0]}"
+
+# shellcheck disable=SC2251
+! ldd find_package_test | grep librnp
+ assertNotEquals "unexpected reference to shared rnp library at static library test" 0 "${PIPESTATUS[1]}"
+
+ popd
+# shellcheck disable=SC2046
+ sudo yum -y erase $(rpm -qa | grep rnp)
+}
+
+test_no_library() {
+ pushd "$(mktemp -d)"
+ create_source_file
+ create_cmake_file 'rnp::librnp'
+
+# shellcheck disable=SC2251
+! cmake . -DCMAKE_MODULE_PATH="$DIR_CMAKE"/*
+ assertNotEquals "cmake succeeded at no library test" 0 "${PIPESTATUS[0]}"
+ popd
+}
+
+# ......................................................................
+# shellcheck source=/dev/null
+. "$DIR0"/shunit2/shunit2
diff --git a/ci/tests/pkg-tests.sh b/ci/tests/pkg-tests.sh
new file mode 100755
index 0000000..dbeaac6
--- /dev/null
+++ b/ci/tests/pkg-tests.sh
@@ -0,0 +1,102 @@
+#! /bin/bash
+#
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+set -o errexit -o pipefail -o noclobber -o nounset
+
+DIR0="$( cd "$( dirname "$0" )" && pwd )"
+
+# Defaults applicable to 'normal' installation and not build environment
+: "${INSTALL_PREFIX:=/usr/local}"
+DIR_LIB="$INSTALL_PREFIX/lib"
+DIR_INC="$INSTALL_PREFIX/include/rnp"
+DIR_BIN="$INSTALL_PREFIX/bin"
+# DIR_MAN="$INSTALL_PREFIX/share/man"
+DIR_CMAKE="$INSTALL_PREFIX/lib/cmake/rnp"
+
+declare expected_libraries=(
+ "$DIR_LIB/librnp.so.0"
+)
+
+declare expected_devlibraries=(
+ "$DIR_LIB/librnp.so"
+ "$DIR_LIB/librnp.a"
+ "$DIR_LIB/libsexp.a"
+ "$DIR_LIB/pkgconfig/librnp.pc"
+)
+
+declare expected_includes=(
+ "$DIR_INC/rnp.h"
+ "$DIR_INC/rnp_err.h"
+ "$DIR_INC/rnp_export.h"
+)
+
+declare expected_cmakefiles=(
+ "$DIR_CMAKE/rnp-config.cmake"
+ "$DIR_CMAKE/rnp-config-version.cmake"
+ "$DIR_CMAKE/rnp-targets.cmake"
+ "$DIR_CMAKE/rnp-targets-release.cmake"
+)
+
+declare expected_binaries=(
+ "$DIR_BIN/rnp"
+ "$DIR_BIN/rnpkeys"
+)
+
+# Installation of man files does not work as expected
+#declare expected_manuals=(
+# "$DIR_MAN/man3/librnp.3.gz"
+# "$DIR_MAN/man1/rnp.1.gz"
+# "$DIR_MAN/man1/rnpkeys.1.gz"
+#)
+
+t_installed_files() {
+ local f=
+ for f in "$@"
+ do
+ assertTrue "$f was not installed" "[ -e $f ]"
+ done
+}
+
+test_installed_files_librnp() {
+# shellcheck disable=SC2046
+ pkg add $(ls ./*.pkg)
+
+ t_installed_files "${expected_libraries[@]}"
+ t_installed_files "${expected_devlibraries[@]}"
+ t_installed_files "${expected_includes[@]}"
+ t_installed_files "${expected_cmakefiles[@]}"
+ t_installed_files "${expected_binaries[@]}"
+
+# Installation of man files does not work as expected
+# t_installed_files "${expected_manuals[@]}"
+
+ pkg delete rnp0
+}
+
+# ......................................................................
+# shellcheck source=/dev/null
+. "$DIR0"/shunit2/shunit2
diff --git a/ci/tests/rpm-tests.sh b/ci/tests/rpm-tests.sh
new file mode 100755
index 0000000..39f8dd0
--- /dev/null
+++ b/ci/tests/rpm-tests.sh
@@ -0,0 +1,126 @@
+#! /bin/bash
+#
+# Copyright (c) 2023 [Ribose Inc](https://www.ribose.com).
+# All rights reserved.
+# This file is a part of rnp
+#
+# 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.
+
+set -o errexit -o pipefail -o noclobber -o nounset
+
+DIR0="$( cd "$( dirname "$0" )" && pwd )"
+
+# Defaults applicable to 'normal' installation and not build environment
+: "${INSTALL_PREFIX:=/usr}"
+: "${BOTAN_INSTALL:=$INSTALL_PREFIX}"
+: "${JSONC_INSTALL:=$INSTALL_PREFIX}"
+: "${RNP_INSTALL:=$INSTALL_PREFIX}"
+
+: "${ENABLE_SM2:=}"
+: "${ENABLE_IDEA:=}"
+
+DIR_LIB="$INSTALL_PREFIX/lib64"
+DIR_INC="$INSTALL_PREFIX/include/rnp"
+DIR_BIN="$INSTALL_PREFIX/bin"
+DIR_MAN="$INSTALL_PREFIX/share/man"
+DIR_CMAKE="$INSTALL_PREFIX/lib64/cmake/rnp"
+
+declare expected_libraries=(
+ "$DIR_LIB/librnp.so.0"
+)
+
+declare expected_devlibraries=(
+ "$DIR_LIB/librnp.so"
+ "$DIR_LIB/librnp.a"
+ "$DIR_LIB/libsexp.a"
+ "$DIR_LIB/pkgconfig/librnp.pc"
+)
+
+declare expected_includes=(
+ "$DIR_INC/rnp.h"
+ "$DIR_INC/rnp_err.h"
+ "$DIR_INC/rnp_export.h"
+)
+
+declare expected_cmakefiles=(
+ "$DIR_CMAKE/rnp-config.cmake"
+ "$DIR_CMAKE/rnp-config-version.cmake"
+ "$DIR_CMAKE/rnp-targets.cmake"
+ "$DIR_CMAKE/rnp-targets-release.cmake"
+)
+
+declare expected_binaries=(
+ "$DIR_BIN/rnp"
+ "$DIR_BIN/rnpkeys"
+)
+
+declare expected_manuals=(
+ "$DIR_MAN/man3/librnp.3.gz"
+ "$DIR_MAN/man1/rnp.1.gz"
+ "$DIR_MAN/man1/rnpkeys.1.gz"
+)
+
+test_installed_files() {
+ local f=
+ for f in "$@"
+ do
+ assertTrue "$f was not installed" "[ -e $f ]"
+ done
+}
+
+test_installed_files_librnp() {
+ sudo yum -y localinstall librnp0-0*.*.rpm
+ test_installed_files "${expected_libraries[@]}"
+# shellcheck disable=SC2046
+ sudo yum -y erase $(rpm -qa | grep rnp)
+}
+
+test_installed_files_librnp-devel() {
+ sudo yum -y localinstall librnp0-0*.*.rpm librnp0-devel-0*.*.rpm
+ test_installed_files "${expected_libraries[@]}"
+ test_installed_files "${expected_devlibraries[@]}"
+ test_installed_files "${expected_includes[@]}"
+ test_installed_files "${expected_cmakefiles[@]}"
+# shellcheck disable=SC2046
+ sudo yum -y erase $(rpm -qa | grep rnp)
+}
+
+test_installed_files_rnp() {
+ sudo yum -y localinstall librnp0-0*.*.rpm rnp0-0*.*.rpm
+ test_installed_files "${expected_libraries[@]}"
+ test_installed_files "${expected_binaries[@]}"
+# shellcheck disable=SC2046
+ sudo yum -y erase $(rpm -qa | grep rnp)
+}
+
+test_installed_files_doc() {
+# in case the nodocs transaction flag is set in the yum configuration
+ sudo yum --setopt=tsflags='' -y install man-db
+ sudo yum --setopt=tsflags='' -y localinstall rnp-*-doc.rpm
+ test_installed_files "${expected_manuals[@]}"
+# shellcheck disable=SC2046
+ sudo yum -y erase $(rpm -qa | grep rnp)
+}
+
+# ......................................................................
+# shellcheck source=/dev/null
+. "$DIR0"/shunit2/shunit2
diff --git a/ci/utils.inc.sh b/ci/utils.inc.sh
new file mode 100644
index 0000000..618df78
--- /dev/null
+++ b/ci/utils.inc.sh
@@ -0,0 +1,43 @@
+#!/usr/bin/env bash
+# Derived from: https://gist.github.com/marcusandre/4b88c2428220ea255b83
+get_os() {
+ local ostype
+ ostype=$(<<< "$OSTYPE" tr '[:upper:]' '[:lower:]')
+ if [ -z "$ostype" ]; then
+ ostype=$(uname | tr '[:upper:]' '[:lower:]')
+ fi
+
+ case $ostype in
+ freebsd*) echo "freebsd" ;;
+ netbsd*) echo "netbsd" ;;
+ openbsd*) echo "openbsd" ;;
+ darwin*) echo "macos" ;;
+ linux*) echo "linux" ;;
+ cygwin*) echo "cygwin" ;;
+ msys*) echo "msys" ;;
+ mingw*) echo "win" ;;
+ *) echo "unknown"; exit 1 ;;
+ esac
+}
+
+get_linux_dist() {
+ if [[ -f /etc/os-release ]]; then
+ sh -c '. /etc/os-release && echo $ID'
+ elif type lsb_release >/dev/null 2>&1; then
+ lsb_release -si | tr '[:upper:]' '[:lower:]'
+ fi
+}
+
+# If target does not exist, create symlink from source to target.
+ensure_symlink_to_target() {
+ local from="${1:?Missing source}"
+ local to="${2:?Missing target}"
+
+ if [[ -e "${from}" && ! -e "${to}" ]]; then
+ if ! sudo ln -s "${from}" "${to}"
+ then
+ >&2 echo "Error: ${to} still not available after symlink. Aborting."
+ exit 1
+ fi
+ fi
+}
diff --git a/cmake/CTestCostData.txt b/cmake/CTestCostData.txt
new file mode 100644
index 0000000..1a00177
--- /dev/null
+++ b/cmake/CTestCostData.txt
@@ -0,0 +1,159 @@
+rnp_tests.hash_test_success 1 0.0138223
+rnp_tests.cipher_test_success 1 0.0120679
+rnp_tests.pkcs1_rsa_test_success 1 0.0714414
+rnp_tests.rnp_test_eddsa 1 0.014394
+rnp_tests.rnp_test_x25519 1 0.0197451
+rnp_tests.raw_elgamal_random_key_test_success 1 0.725944
+rnp_tests.ecdsa_signverify_success 1 0.0561304
+rnp_tests.ecdh_roundtrip 1 0.0383161
+rnp_tests.ecdh_decryptionNegativeCases 1 0.0201392
+rnp_tests.sm2_roundtrip 1 0.0214206
+rnp_tests.sm2_sm3_signature_test 1 0.027973
+rnp_tests.sm2_sha256_signature_test 1 0.0269801
+rnp_tests.test_dsa_roundtrip 1 24.2023
+rnp_tests.test_dsa_verify_negative 1 0.800061
+rnp_tests.s2k_iteration_tuning 1 0.515675
+rnp_tests.s2k_iteration_encode_decode 1 0.0134686
+rnp_tests.test_validate_key_material 1 1.68337
+rnp_tests.test_cli_rnp_keyfile 1 0.859851
+rnp_tests.test_cli_g10_operations 1 2.50301
+rnp_tests.test_cli_rnp 1 0.0972449
+rnp_tests.test_cli_examples 1 1.3149
+rnp_tests.test_cli_rnpkeys 1 0.115061
+rnp_tests.test_cli_dump 1 0.0361111
+rnp_tests.test_cli_logname 1 0.0116097
+rnp_tests.rnpkeys_exportkey_verifyUserId 1 0.118051
+rnp_tests.test_ffi_homedir 1 0.0241839
+rnp_tests.test_ffi_detect_key_format 1 0.0129409
+rnp_tests.test_ffi_load_keys 1 0.0493167
+rnp_tests.test_ffi_clear_keys 1 0.0259738
+rnp_tests.test_ffi_save_keys 1 0.0316757
+rnp_tests.test_ffi_keygen_json_pair 1 0.0736696
+rnp_tests.test_ffi_keygen_json_pair_dsa_elg 1 12.7012
+rnp_tests.test_ffi_keygen_json_primary 1 0.0194215
+rnp_tests.test_ffi_keygen_json_sub 1 0.0573184
+rnp_tests.test_ffi_key_generate_misc 1 0.985612
+rnp_tests.test_ffi_key_generate_rsa 1 0.342821
+rnp_tests.test_ffi_key_generate_dsa 1 2.13331
+rnp_tests.test_ffi_key_generate_ecdsa 1 0.0177323
+rnp_tests.test_ffi_key_generate_eddsa 1 0.0143182
+rnp_tests.test_ffi_key_generate_sm2 1 0.0175289
+rnp_tests.test_ffi_key_generate_ex 1 5.74498
+rnp_tests.test_ffi_key_generate_algnamecase 1 2.62221
+rnp_tests.test_ffi_key_generate_protection 1 1.40079
+rnp_tests.test_ffi_add_userid 1 0.0404918
+rnp_tests.test_ffi_keygen_json_sub_pass_required 1 0.382914
+rnp_tests.test_ffi_encrypt_pass 1 0.429726
+rnp_tests.test_ffi_encrypt_pass_provider 1 0.342456
+rnp_tests.test_ffi_encrypt_pk 1 0.0482677
+rnp_tests.test_ffi_encrypt_pk_key_provider 1 0.0339246
+rnp_tests.test_ffi_encrypt_and_sign 1 0.0917957
+rnp_tests.test_ffi_signatures_memory 1 0.0518454
+rnp_tests.test_ffi_signatures 1 0.0488163
+rnp_tests.test_ffi_signatures_detached_memory 1 0.0492941
+rnp_tests.test_ffi_signatures_detached 1 0.04846
+rnp_tests.test_ffi_signatures_dump 1 0.0168647
+rnp_tests.test_ffi_key_to_json 1 0.0164411
+rnp_tests.test_ffi_key_iter 1 0.0261607
+rnp_tests.test_ffi_locate_key 1 0.0161026
+rnp_tests.test_ffi_signatures_detached_memory_g10 1 0.0427063
+rnp_tests.test_ffi_enarmor_dearmor 1 0.0373763
+rnp_tests.test_ffi_version 1 0.0140884
+rnp_tests.test_ffi_key_export 1 0.0250205
+rnp_tests.test_ffi_key_dump 1 0.0256317
+rnp_tests.test_ffi_pkt_dump 1 0.0167862
+rnp_tests.test_ffi_rsa_v3_dump 1 0.0158175
+rnp_tests.test_ffi_load_userattr 1 0.0177854
+rnp_tests.test_ffi_revocations 1 0.0191412
+rnp_tests.test_ffi_file_output 1 0.0292883
+rnp_tests.test_ffi_key_signatures 1 0.0211163
+rnp_tests.test_ffi_keys_import 1 0.577245
+rnp_tests.test_ffi_import_keys_check_pktlen 1 0.0151679
+rnp_tests.test_ffi_calculate_iterations 1 0.0279029
+rnp_tests.test_ffi_supported_features 1 0.0149266
+rnp_tests.test_ffi_enable_debug 1 0.010948
+rnp_tests.test_ffi_rnp_key_get_primary_grip 1 0.0178161
+rnp_tests.test_ffi_output_to_armor 1 0.01893
+rnp_tests.test_ffi_rnp_guess_contents 1 0.0121287
+rnp_tests.test_ffi_literal_filename 1 0.0552985
+rnp_tests.test_ffi_op_set_hash 1 0.0528855
+rnp_tests.test_ffi_op_set_compression 1 0.0730627
+rnp_tests.test_ffi_aead_params 1 0.241513
+rnp_tests.test_ffi_detached_verify_input 1 0.0228228
+rnp_tests.test_ffi_op_verify_sig_count 1 0.0491328
+rnp_tests.rnpkeys_generatekey_testSignature 1 1.18378
+rnp_tests.rnpkeys_generatekey_testEncryption 1 0.275621
+rnp_tests.rnpkeys_generatekey_verifySupportedHashAlg 1 1.41153
+rnp_tests.rnpkeys_generatekey_verifyUserIdOption 1 0.616167
+rnp_tests.rnpkeys_generatekey_verifykeyHomeDirOption 1 0.188337
+rnp_tests.rnpkeys_generatekey_verifykeyKBXHomeDirOption 1 0.205113
+rnp_tests.rnpkeys_generatekey_verifykeyHomeDirNoPermission 1 0.149937
+rnp_tests.rnpkeys_generatekey_testExpertMode 1 2.63892
+rnp_tests.generatekeyECDSA_explicitlySetSmallOutputDigest_DigestAlgAdjusted 1 0.0200285
+rnp_tests.generatekey_multipleUserIds_ShouldFail 1 0.0110169
+rnp_tests.generatekeyECDSA_explicitlySetBiggerThanNeededDigest_ShouldSuceed 1 0.0187109
+rnp_tests.generatekeyECDSA_explicitlySetUnknownDigest_ShouldFail 1 0.0133541
+rnp_tests.test_generated_key_sigs 1 0.128008
+rnp_tests.test_kbx_nsigs 1 4.07201
+rnp_tests.test_key_add_userid 1 0.0324633
+rnp_tests.key_grip 1 0.0621873
+rnp_tests.test_key_prefs 1 0.0115654
+rnp_tests.test_key_protect_load_pgp 1 0.330786
+rnp_tests.test_key_store_search 1 0.0108802
+rnp_tests.test_key_store_search_by_name 1 0.0138023
+rnp_tests.test_key_unlock_pgp 1 0.0629279
+rnp_tests.test_key_validate 1 0.0313496
+rnp_tests.test_forged_key_validate 1 0.0350325
+rnp_tests.test_key_validity 1 0.0179677
+rnp_tests.test_large_packet 1 12.6562
+rnp_tests.test_load_g10 1 0.493759
+rnp_tests.test_load_v3_keyring_pgp 1 0.012287
+rnp_tests.test_load_v4_keyring_pgp 1 0.015202
+rnp_tests.test_load_keyring_and_count_pgp 1 0.0166739
+rnp_tests.test_load_check_bitfields_and_times 1 0.0145676
+rnp_tests.test_load_check_bitfields_and_times_v3 1 0.0141121
+rnp_tests.test_load_armored_pub_sec 1 0.0233942
+rnp_tests.test_load_merge 1 0.148499
+rnp_tests.test_load_public_from_secret 1 0.0321099
+rnp_tests.test_key_import 1 0.256372
+rnp_tests.test_load_subkey 1 0.0191733
+rnp_tests.test_partial_length_public_key 1 0.0120663
+rnp_tests.test_partial_length_signature 1 0.0141925
+rnp_tests.test_partial_length_first_packet_256 1 0.017635
+rnp_tests.test_partial_length_zero_last_chunk 1 0.0177394
+rnp_tests.test_partial_length_largest 1 3.20183
+rnp_tests.test_partial_length_first_packet_length 1 0.0300068
+rnp_tests.test_s2k_iterations 1 7.229982
+rnp_tests.test_stream_memory 1 0.0125394
+rnp_tests.test_stream_memory_discard 1 0.0111196
+rnp_tests.test_stream_file 1 0.0168583
+rnp_tests.test_stream_signatures 1 0.0373302
+rnp_tests.test_stream_signatures_revoked_key 1 0.012537
+rnp_tests.test_stream_key_load 1 0.0130797
+rnp_tests.test_stream_key_load_errors 1 0.727713
+rnp_tests.test_stream_key_decrypt 1 0.511516
+rnp_tests.test_stream_key_encrypt 1 0.487256
+rnp_tests.test_stream_key_signatures 1 0.0199383
+rnp_tests.test_stream_key_signature_validate 1 0.108001
+rnp_tests.test_stream_verify_no_key 1 0.0301321
+rnp_tests.test_stream_dumper 1 0.0180775
+rnp_tests.test_stream_z 1 21.1022
+rnp_tests.test_stream_814_dearmor_double_free 1 0.0119918
+rnp_tests.test_stream_825_dearmor_blank_line 1 0.0155773
+rnp_tests.test_stream_dearmor_edge_cases 1 0.0119838
+rnp_tests.test_load_user_prefs 1 0.0142821
+rnp_tests.test_utils_list 1 0.0107737
+rnp_tests.test_rnpcfg 1 0.0108745
+rnp_tests.issue_1030_rnpkeys_secret_keys_unprotected 1 0.31969
+setupTestData 1 0.0175373
+cli_tests-Keystore 1 5.56182
+cli_tests-SignECDSA 1 3.92326
+cli_tests-Compression 1 137.827
+cli_tests-Encryption 1 329.997
+cli_tests-Misc 1 35.6212
+cli_tests-SignDefault 1 16.0034
+cli_tests-EncryptEcdh 1 4.19268
+cli_tests-SignDSA 1 6.13993
+cli_tests-EncryptSignRSA 1 2.47967
+cli_tests-EncryptElgamal 1 11.2535
+---
diff --git a/cmake/Modules/AdocMan.cmake b/cmake/Modules/AdocMan.cmake
new file mode 100644
index 0000000..96c5c93
--- /dev/null
+++ b/cmake/Modules/AdocMan.cmake
@@ -0,0 +1,135 @@
+# 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.
+
+#.adoc:
+# add_adoc_man
+# -----------
+#
+# Convert adoc manual page to troff and install it via the custom target.
+#
+# Parameters
+# ^^^^^^^^^^
+# Required parameter is source with markdown file. Must have md extension with man category prepended, i.e. something like ${CMAKE_SOURCE_DIR}/src/utility.1.adoc
+# DST - optional parameter, which overrides where generated man will be stored.
+# If not specified then will be automatically set to ${CMAKE_BINARY_DIR}/src/utility.1
+#
+# Generated man page will be installed via the target, named man_utility
+#
+
+set(ADOCCOMMAND_FOUND 0)
+find_program(ADOCCOMMAND_PATH
+ NAMES asciidoctor
+ DOC "Path to AsciiDoc processor. Used to generate man pages from AsciiDoc."
+)
+
+if(NOT EXISTS ${ADOCCOMMAND_PATH})
+ set(ADOC_MISSING_MSG "AsciiDoc processor not found, man pages will not be generated. Install asciidoctor or use the CMAKE_PROGRAM_PATH variable.")
+
+ string(TOLOWER "${ENABLE_DOC}" ENABLE_DOC)
+ if (ENABLE_DOC STREQUAL "auto")
+ message(WARNING ${ADOC_MISSING_MSG})
+ elseif(ENABLE_DOC)
+ message(FATAL_ERROR ${ADOC_MISSING_MSG})
+ endif()
+else()
+ set(ADOCCOMMAND_FOUND 1)
+endif()
+
+function(add_adoc_man SRC COMPONENT_VERSION)
+ if (NOT ${ADOCCOMMAND_FOUND})
+ return()
+ endif()
+
+ cmake_parse_arguments(
+ ARGS
+ ""
+ "DST"
+ ""
+ ${ARGN}
+ )
+
+ set(ADOC_EXT ".adoc")
+ get_filename_component(FILE_NAME ${SRC} NAME)
+
+ # The following procedures check against the expected file name
+ # pattern: "{name}.{man-number}.adoc", and builds to a
+ # destination file "{name}.{man-number}".
+
+ # Check SRC extension
+ get_filename_component(END_EXT ${SRC} LAST_EXT)
+ string(COMPARE EQUAL ${END_EXT} ${ADOC_EXT} _equal)
+ if (NOT _equal)
+ message(FATAL_ERROR "SRC must have ${ADOC_EXT} extension.")
+ endif()
+
+ # Check man number
+ get_filename_component(EXTS ${SRC} EXT)
+ string(REGEX MATCH "^\.([1-9])\.+$" _matches ${EXTS})
+ set(MAN_NUM ${CMAKE_MATCH_1})
+ if (NOT _matches)
+ message(FATAL_ERROR "Man file with wrong name pattern: ${FILE_NAME} must be in format {name}.[0-9]${ADOC_EXT}.")
+ endif()
+
+ # Set target name
+ get_filename_component(TARGET_NAME ${SRC} NAME_WE)
+ string(PREPEND TARGET_NAME "man_")
+
+ # Build output path if not specified.
+ if(NOT DST)
+ get_filename_component(SRC_PREFIX ${SRC} DIRECTORY)
+
+ # Ensure that SRC_PREFIX is within CMAKE_SOURCE_DIR
+ if(NOT(SRC_PREFIX MATCHES "^${CMAKE_SOURCE_DIR}"))
+ message(FATAL_ERROR "Cannot build DST path as SRC is outside of the CMake sources dir.")
+ endif()
+ STRING(REGEX REPLACE "^${CMAKE_SOURCE_DIR}/" "" SUBDIR_PATH ${SRC})
+
+ # Strip '.adoc' from the output subpath
+ get_filename_component(SUBDIR_PATH_NAME_WLE ${SUBDIR_PATH} NAME_WLE)
+ get_filename_component(SUBDIR_PATH_DIRECTORY ${SUBDIR_PATH} DIRECTORY)
+ set(DST "${CMAKE_BINARY_DIR}/${SUBDIR_PATH_DIRECTORY}/${SUBDIR_PATH_NAME_WLE}")
+ endif()
+
+ # Check conformance of destination file name to pattern
+ get_filename_component(FILE_NAME_WE ${SRC} NAME_WE)
+ get_filename_component(MAN_FILE_NAME ${DST} NAME)
+ if(NOT(MAN_FILE_NAME MATCHES "^${FILE_NAME_WE}.${MAN_NUM}$"))
+ message(FATAL_ERROR "File name of a man page must be in the format {name}.{man-number}${ADOC_EXT}.")
+ endif()
+
+ add_custom_command(
+ OUTPUT ${DST}
+ COMMAND ${ADOCCOMMAND_PATH} -b manpage ${SRC} -o ${DST} -a component-version=${COMPONENT_VERSION}
+ DEPENDS ${SRC}
+ WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
+ COMMENT "Generating man page ${SUBDIR_PATH_DIRECTORY}/${SUBDIR_PATH_NAME_WLE}"
+ VERBATIM
+ )
+
+ add_custom_target("${TARGET_NAME}" ALL DEPENDS ${DST})
+ install(FILES ${DST}
+ DESTINATION "${CMAKE_INSTALL_FULL_MANDIR}/man${MAN_NUM}"
+ COMPONENT doc
+ )
+endfunction(add_adoc_man)
diff --git a/cmake/Modules/FindBotan2.cmake b/cmake/Modules/FindBotan2.cmake
new file mode 100644
index 0000000..2708491
--- /dev/null
+++ b/cmake/Modules/FindBotan2.cmake
@@ -0,0 +1,131 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+#.rst:
+# FindBotan2
+# -----------
+#
+# Find the botan-2 library.
+#
+# IMPORTED Targets
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines :prop_tgt:`IMPORTED` targets:
+#
+# ``Botan2::Botan2``
+# The botan-2 library, if found.
+#
+# Result variables
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines the following variables:
+#
+# ::
+#
+# BOTAN2_FOUND - true if the headers and library were found
+# BOTAN2_INCLUDE_DIRS - where to find headers
+# BOTAN2_LIBRARIES - list of libraries to link
+# BOTAN2_VERSION - library version that was found, if any
+
+# use pkg-config to get the directories and then use these values
+# in the find_path() and find_library() calls
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_BOTAN2 QUIET botan-2)
+
+# find the headers
+find_path(BOTAN2_INCLUDE_DIR
+ NAMES botan/version.h
+ HINTS
+ ${PC_BOTAN2_INCLUDEDIR}
+ ${PC_BOTAN2_INCLUDE_DIRS}
+ PATH_SUFFIXES botan-2
+)
+
+# find the library
+if(MSVC)
+ find_library(BOTAN2_LIBRARY
+ NAMES botan
+ HINTS
+ ${PC_BOTAN2_LIBDIR}
+ ${PC_BOTAN2_LIBRARY_DIRS}
+ )
+else()
+ find_library(BOTAN2_LIBRARY
+ NAMES botan-2 libbotan-2
+ HINTS
+ ${PC_BOTAN2_LIBDIR}
+ ${PC_BOTAN2_LIBRARY_DIRS}
+ )
+endif()
+
+# determine the version
+if(PC_BOTAN2_VERSION)
+ set(BOTAN2_VERSION ${PC_BOTAN2_VERSION})
+elseif(BOTAN2_INCLUDE_DIR AND EXISTS "${BOTAN2_INCLUDE_DIR}/botan/build.h")
+ file(STRINGS "${BOTAN2_INCLUDE_DIR}/botan/build.h" botan2_version_str
+ REGEX "^#define[\t ]+(BOTAN_VERSION_[A-Z]+)[\t ]+[0-9]+")
+
+ string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MAJOR[\t ]+([0-9]+).*"
+ "\\1" _botan2_version_major "${botan2_version_str}")
+ string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_MINOR[\t ]+([0-9]+).*"
+ "\\1" _botan2_version_minor "${botan2_version_str}")
+ string(REGEX REPLACE ".*#define[\t ]+BOTAN_VERSION_PATCH[\t ]+([0-9]+).*"
+ "\\1" _botan2_version_patch "${botan2_version_str}")
+ set(BOTAN2_VERSION "${_botan2_version_major}.${_botan2_version_minor}.${_botan2_version_patch}"
+ CACHE INTERNAL "The version of Botan which was detected")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(Botan2
+ REQUIRED_VARS BOTAN2_LIBRARY BOTAN2_INCLUDE_DIR
+ VERSION_VAR BOTAN2_VERSION
+)
+
+if (BOTAN2_FOUND)
+ set(BOTAN2_INCLUDE_DIRS ${BOTAN2_INCLUDE_DIR} ${PC_BOTAN2_INCLUDE_DIRS})
+ set(BOTAN2_LIBRARIES ${BOTAN2_LIBRARY})
+endif()
+
+if (BOTAN2_FOUND AND NOT TARGET Botan2::Botan2)
+ # create the new library target
+ add_library(Botan2::Botan2 UNKNOWN IMPORTED)
+ # set the required include dirs for the target
+ if (BOTAN2_INCLUDE_DIRS)
+ set_target_properties(Botan2::Botan2
+ PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${BOTAN2_INCLUDE_DIRS}"
+ )
+ endif()
+ # set the required libraries for the target
+ if (EXISTS "${BOTAN2_LIBRARY}")
+ set_target_properties(Botan2::Botan2
+ PROPERTIES
+ IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+ IMPORTED_LOCATION "${BOTAN2_LIBRARY}"
+ )
+ endif()
+endif()
+
+mark_as_advanced(BOTAN2_INCLUDE_DIR BOTAN2_LIBRARY)
+
diff --git a/cmake/Modules/FindGnuPG.cmake b/cmake/Modules/FindGnuPG.cmake
new file mode 100644
index 0000000..ed92027
--- /dev/null
+++ b/cmake/Modules/FindGnuPG.cmake
@@ -0,0 +1,137 @@
+# Copyright (c) 2018 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.
+
+#.rst:
+# FindGnuPG
+# -----------
+#
+# Find GnuPG executables.
+#
+# Imported targets
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines the following :prop_tgt:`IMPORTED` targets:
+#
+# ::
+#
+# GnuPG::<COMPONENT> - the component executable that was requested (default is just 'gpg')
+#
+## Result variables
+# ^^^^^^^^^^^^^^^^
+#
+# This module always defines the following variables:
+#
+# ::
+#
+# GNUPG_VERSION - version that was found
+#
+# Depending on components requested, this module will also define variables like:
+#
+# ::
+#
+# GPG_EXECUTABLE - path to the gpg executable
+# <COMPONENT>_EXECUTABLE - path to the component executable
+#
+
+# helper that will call <utility_name> --version and extract the version string
+function(_get_gpg_version utility_name exe_path var_prefix)
+ execute_process(
+ COMMAND "${exe_path}" --version
+ OUTPUT_VARIABLE version
+ RESULT_VARIABLE exit_code
+ ERROR_QUIET
+ )
+ if (NOT exit_code)
+ string(REGEX MATCH "${utility_name} \\(GnuPG\\) (([0-9]+)\\.([0-9]+)\\.([0-9]+))" version "${version}")
+ if (CMAKE_MATCH_1)
+ set(${var_prefix}_VERSION "${CMAKE_MATCH_1}" PARENT_SCOPE)
+ endif()
+ endif()
+endfunction()
+
+# default to finding gpg
+if (NOT GnuPG_FIND_COMPONENTS)
+ set(GnuPG_FIND_COMPONENTS gpg)
+endif()
+
+foreach(_comp IN LISTS GnuPG_FIND_COMPONENTS)
+ # we also check for an executable with the 2 suffix when appropriate
+ set(_names "${_comp}")
+ if (_comp STREQUAL "gpg" OR _comp STREQUAL "gpgv")
+ if (NOT ${GnuPG_FIND_VERSION})
+ set(_names "${_comp}2" ${_comp})
+ elseif (${GnuPG_FIND_VERSION} VERSION_GREATER_EQUAL 2.2)
+ # 2.2+ defaults to gpg/gpgv, but supports gpg2/gpgv2
+ set(_names ${_comp} "${_comp}2")
+ elseif(${GnuPG_FIND_VERSION} VERSION_GREATER_EQUAL 2.0)
+ # 2.0-2.2 or so used a temporary naming of gpg2/gpgv2
+ set(_names "${_comp}2" ${_comp})
+ endif()
+ endif()
+ string(TOUPPER "${_comp}" _comp_upper)
+ find_program(${_comp_upper}_EXECUTABLE NAMES ${_names})
+ unset(_names)
+ mark_as_advanced(${_comp_upper}_EXECUTABLE)
+
+ # if we found an executable, check the version
+ if (${_comp_upper}_EXECUTABLE)
+ _get_gpg_version(${_comp} ${${_comp_upper}_EXECUTABLE} _${_comp})
+ if (_${_comp}_VERSION)
+ if (NOT GNUPG_VERSION)
+ # this is the first component found, so set the version to match
+ set(GNUPG_VERSION ${_${_comp}_VERSION})
+ endif()
+ # see if the version matches the previous components found
+ if(_${_comp}_VERSION VERSION_EQUAL ${GNUPG_VERSION} AND NOT TARGET GnuPG::${_comp})
+ add_executable(GnuPG::${_comp} IMPORTED GLOBAL)
+ set_target_properties(GnuPG::${_comp} PROPERTIES
+ IMPORTED_LOCATION "${${_comp_upper}_EXECUTABLE}"
+ )
+ endif()
+ endif()
+ unset(_${_comp}_VERSION)
+ endif()
+
+ # mark our components as found or not found
+ if (TARGET GnuPG::${_comp})
+ set(GnuPG_${_comp}_FOUND TRUE)
+ else()
+ set(GnuPG_${_comp}_FOUND FALSE)
+ unset(${_comp_upper}_EXECUTABLE)
+ endif()
+
+ if (GnuPG_FIND_REQUIRED_${_comp})
+ list(APPEND _GnuPG_REQUIRED_VARS ${_comp_upper}_EXECUTABLE)
+ endif()
+endforeach()
+unset(_comp)
+unset(_comp_upper)
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(GnuPG
+ REQUIRED_VARS ${_GnuPG_REQUIRED_VARS}
+ VERSION_VAR GNUPG_VERSION
+ HANDLE_COMPONENTS
+)
+
diff --git a/cmake/Modules/FindJSON-C.cmake b/cmake/Modules/FindJSON-C.cmake
new file mode 100644
index 0000000..e66a011
--- /dev/null
+++ b/cmake/Modules/FindJSON-C.cmake
@@ -0,0 +1,123 @@
+# Copyright (c) 2018 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.
+
+#.rst:
+# FindJSON-C
+# -----------
+#
+# Find the json-c library.
+#
+# IMPORTED Targets
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines :prop_tgt:`IMPORTED` targets:
+#
+# ``JSON-C::JSON-C``
+# The json-c library, if found.
+#
+# Result variables
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines the following variables:
+#
+# ::
+#
+# JSON-C_FOUND - true if the headers and library were found
+# JSON-C_INCLUDE_DIRS - where to find headers
+# JSON-C_LIBRARIES - list of libraries to link
+# JSON-C_VERSION - library version that was found, if any
+
+# use pkg-config to get the directories and then use these values
+# in the find_path() and find_library() calls
+find_package(PkgConfig QUIET)
+pkg_check_modules(PC_JSON-C QUIET json-c)
+
+# RHEL-based systems may have json-c12
+if (NOT PC_JSON-C_FOUND)
+ pkg_check_modules(PC_JSON-C QUIET json-c12)
+endif()
+
+# find the headers
+find_path(JSON-C_INCLUDE_DIR
+ NAMES json_c_version.h
+ HINTS
+ ${PC_JSON-C_INCLUDEDIR}
+ ${PC_JSON-C_INCLUDE_DIRS}
+ PATH_SUFFIXES json-c
+)
+
+# find the library
+find_library(JSON-C_LIBRARY
+ NAMES json-c libjson-c json-c12 libjson-c12
+ HINTS
+ ${PC_JSON-C_LIBDIR}
+ ${PC_JSON-C_LIBRARY_DIRS}
+)
+
+# determine the version
+if(PC_JSON-C_VERSION)
+ set(JSON-C_VERSION ${PC_JSON-C_VERSION})
+elseif(JSON-C_INCLUDE_DIR AND EXISTS "${JSON-C_INCLUDE_DIR}/json_c_version.h")
+ file(STRINGS "${JSON-C_INCLUDE_DIR}/json_c_version.h" _json-c_version_h
+ REGEX "^#define[\t ]+JSON_C_VERSION[\t ]+\"[^\"]*\"$")
+
+ string(REGEX REPLACE ".*#define[\t ]+JSON_C_VERSION[\t ]+\"([^\"]*)\".*"
+ "\\1" _json-c_version_str "${_json-c_version_h}")
+ set(JSON-C_VERSION "${_json-c_version_str}"
+ CACHE INTERNAL "The version of json-c which was detected")
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(JSON-C
+ REQUIRED_VARS JSON-C_LIBRARY JSON-C_INCLUDE_DIR JSON-C_VERSION
+ VERSION_VAR JSON-C_VERSION
+)
+
+if (JSON-C_FOUND)
+ set(JSON-C_INCLUDE_DIRS ${JSON-C_INCLUDE_DIR} ${PC_JSON-C_INCLUDE_DIRS})
+ set(JSON-C_LIBRARIES ${JSON-C_LIBRARY})
+endif()
+
+if (JSON-C_FOUND AND NOT TARGET JSON-C::JSON-C)
+ # create the new library target
+ add_library(JSON-C::JSON-C UNKNOWN IMPORTED)
+ # set the required include dirs for the target
+ if (JSON-C_INCLUDE_DIRS)
+ set_target_properties(JSON-C::JSON-C
+ PROPERTIES
+ INTERFACE_INCLUDE_DIRECTORIES "${JSON-C_INCLUDE_DIRS}"
+ )
+ endif()
+ # set the required libraries for the target
+ if (EXISTS "${JSON-C_LIBRARY}")
+ set_target_properties(JSON-C::JSON-C
+ PROPERTIES
+ IMPORTED_LINK_INTERFACE_LANGUAGES "C"
+ IMPORTED_LOCATION "${JSON-C_LIBRARY}"
+ )
+ endif()
+endif()
+
+mark_as_advanced(JSON-C_INCLUDE_DIR JSON-C_LIBRARY)
+
diff --git a/cmake/Modules/FindOpenSSLFeatures.cmake b/cmake/Modules/FindOpenSSLFeatures.cmake
new file mode 100644
index 0000000..6967764
--- /dev/null
+++ b/cmake/Modules/FindOpenSSLFeatures.cmake
@@ -0,0 +1,171 @@
+# 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.
+
+#.rst:
+# FindOpenSSLFeatures
+# -----------
+#
+# Find OpenSSL features: supported hashes, ciphers, curves and public-key algorithms.
+# Requires FindOpenSSL to be included first, and C compiler to be set as module
+# compiles and executes program which do checks against installed OpenSSL library.
+#
+# Result variables
+# ^^^^^^^^^^^^^^^^
+#
+# This module defines the following variables:
+#
+# ::
+#
+# OPENSSL_SUPPORTED_HASHES - list of the supported hash algorithms
+# OPENSSL_SUPPORTED_CIPHERS - list of the supported ciphers
+# OPENSSL_SUPPORTED_CURVES - list of the supported elliptic curves
+# OPENSSL_SUPPORTED_PUBLICKEY - list of the supported public-key algorithms
+# OPENSSL_SUPPORTED_FEATURES - all previous lists, glued together
+#
+# Functions
+# ^^^^^^^^^
+# OpenSSLHasFeature(FEATURE <VARIABLE>)
+# Check whether OpenSSL has corresponding feature (hash/curve/public-key algorithm name, elliptic curve).
+# Result is stored in VARIABLE as boolean value, i.e. TRUE or FALSE
+#
+if (NOT OPENSSL_FOUND)
+ message(FATAL_ERROR "OpenSSL is not found. Please make sure that you call find_package(OpenSSL) first.")
+endif()
+
+message(STATUS "Querying OpenSSL features")
+
+# Copy and build findopensslfeatures.c in fossl-build subfolder.
+set(_fossl_work_dir "${CMAKE_BINARY_DIR}/fossl")
+file(MAKE_DIRECTORY "${_fossl_work_dir}")
+file(COPY "${CMAKE_CURRENT_LIST_DIR}/findopensslfeatures.c"
+ DESTINATION "${_fossl_work_dir}"
+)
+# As it's short enough let's keep it here.
+# Reuse OPENSSL parameters from the upstream project
+# otherwise there is a good chance to find another instance of openssl
+# We assume that OpenSSL root is one level up openssl include directory
+# This does not look as a good solution, however it is the only one that
+# works with all Windows configuration options
+
+message(STATUS "Using OpenSSL root directory at ${OPENSSL_INCLUDE_DIR}/..")
+
+file(WRITE "${_fossl_work_dir}/CMakeLists.txt"
+"cmake_minimum_required(VERSION 3.18)\n\
+project(findopensslfeatures LANGUAGES C)\n\
+set(CMAKE_C_STANDARD 99)\n\
+include(FindOpenSSL)\n\
+find_package(OpenSSL REQUIRED)\n\
+add_executable(findopensslfeatures findopensslfeatures.c)\n\
+target_include_directories(findopensslfeatures PRIVATE ${OPENSSL_INCLUDE_DIR})\n\
+target_link_libraries(findopensslfeatures PRIVATE OpenSSL::Crypto)\n\
+if (OpenSSL::applink)\n\
+ target_link_libraries(findopensslfeatures PRIVATE OpenSSL::applink)\n\
+endif(OpenSSL::applink)\n"
+)
+
+set(MKF ${MKF} "-DCMAKE_BUILD_TYPE=Release" "-DOPENSSL_ROOT_DIR=${OPENSSL_INCLUDE_DIR}/..")
+
+if(CMAKE_PREFIX_PATH)
+ set(MKF ${MKF} "-DCMAKE_PREFIX_PATH=${CMAKE_PREFIX_PATH}")
+endif(CMAKE_PREFIX_PATH)
+
+if(CMAKE_TOOLCHAIN_FILE)
+ set(MKF ${MKF} "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}")
+endif(CMAKE_TOOLCHAIN_FILE)
+
+if(CMAKE_GENERATOR_PLATFORM)
+ set(MKF ${MKF} "-A" "${CMAKE_GENERATOR_PLATFORM}")
+endif(CMAKE_GENERATOR_PLATFORM)
+
+if(CMAKE_GENERATOR_TOOLSET)
+ set(MKF ${MKF} "-T" "${CMAKE_GENERATOR_TOOLSET}")
+endif(CMAKE_GENERATOR_TOOLSET)
+
+execute_process(
+ COMMAND "${CMAKE_COMMAND}" "-Bbuild" ${MKF} "."
+ WORKING_DIRECTORY "${_fossl_work_dir}"
+ OUTPUT_VARIABLE output
+ ERROR_VARIABLE error
+ RESULT_VARIABLE result
+ COMMAND_ECHO STDOUT
+ ECHO_OUTPUT_VARIABLE
+ ECHO_ERROR_VARIABLE
+)
+
+if (NOT ${result} EQUAL 0)
+ message(FATAL_ERROR "Error configuring findopensslfeatures")
+endif()
+
+execute_process(
+ COMMAND "${CMAKE_COMMAND}" "--build" "build" --config "Release"
+ WORKING_DIRECTORY "${_fossl_work_dir}"
+ OUTPUT_VARIABLE output
+ ERROR_VARIABLE error
+ RESULT_VARIABLE result
+ COMMAND_ECHO STDOUT
+ ECHO_OUTPUT_VARIABLE
+ ECHO_ERROR_VARIABLE
+)
+
+if (NOT ${result} EQUAL 0)
+ message(FATAL_ERROR "Error building findopensslfeatures")
+endif()
+
+set(OPENSSL_SUPPORTED_FEATURES "")
+if(WIN32 AND NOT MINGW)
+ set(FOF "build/Release/findopensslfeatures")
+else(WIN32 AND NOT MINGW)
+ set(FOF "build/findopensslfeatures")
+endif(WIN32 AND NOT MINGW)
+
+foreach(feature "hashes" "ciphers" "curves" "publickey")
+ execute_process(
+ COMMAND "${FOF}" "${feature}"
+ WORKING_DIRECTORY "${_fossl_work_dir}"
+ OUTPUT_VARIABLE feature_val
+ ERROR_VARIABLE error
+ RESULT_VARIABLE result
+ )
+
+ if(NOT ${result} EQUAL 0)
+ message(FATAL_ERROR "Error getting supported OpenSSL ${feature}: ${result}\n${error}")
+ endif()
+
+ string(TOUPPER ${feature} feature_up)
+ string(TOUPPER ${feature_val} feature_val)
+ string(REPLACE "\n" ";" feature_val ${feature_val})
+ set(OPENSSL_SUPPORTED_${feature_up} ${feature_val})
+ list(LENGTH OPENSSL_SUPPORTED_${feature_up} ${feature}_len)
+ list(APPEND OPENSSL_SUPPORTED_FEATURES ${OPENSSL_SUPPORTED_${feature_up}})
+endforeach()
+
+message(STATUS "Fetched OpenSSL features: ${hashes_len} hashes, ${ciphers_len} ciphers, ${curves_len} curves, ${publickey_len} publickey.")
+
+function(OpenSSLHasFeature FEATURE VARIABLE)
+ string(TOUPPER ${FEATURE} _feature_up)
+ set(${VARIABLE} FALSE PARENT_SCOPE)
+ if (${_feature_up} IN_LIST OPENSSL_SUPPORTED_FEATURES)
+ set(${VARIABLE} TRUE PARENT_SCOPE)
+ endif()
+endfunction(OpenSSLHasFeature)
diff --git a/cmake/Modules/FindWindowsSDK.cmake b/cmake/Modules/FindWindowsSDK.cmake
new file mode 100644
index 0000000..d18e979
--- /dev/null
+++ b/cmake/Modules/FindWindowsSDK.cmake
@@ -0,0 +1,662 @@
+# - Find the Windows SDK aka Platform SDK
+# taken from https://github.com/ampl/mp/blob/master/support/cmake/FindWindowsSDK.cmake
+#
+# Relevant Wikipedia article: http://en.wikipedia.org/wiki/Microsoft_Windows_SDK
+#
+# Pass "COMPONENTS tools" to ignore Visual Studio version checks: in case
+# you just want the tool binaries to run, rather than the libraries and headers
+# for compiling.
+#
+# Variables:
+# WINDOWSSDK_FOUND - if any version of the windows or platform SDK was found that is usable with the current version of visual studio
+# WINDOWSSDK_LATEST_DIR
+# WINDOWSSDK_LATEST_NAME
+# WINDOWSSDK_FOUND_PREFERENCE - if we found an entry indicating a "preferred" SDK listed for this visual studio version
+# WINDOWSSDK_PREFERRED_DIR
+# WINDOWSSDK_PREFERRED_NAME
+#
+# WINDOWSSDK_DIRS - contains no duplicates, ordered most recent first.
+# WINDOWSSDK_PREFERRED_FIRST_DIRS - contains no duplicates, ordered with preferred first, followed by the rest in descending recency
+#
+# Functions:
+# GetUMWindowsSDKLibraryDir(<output variable>) - Find the latest SDK user mode (um) library directory,
+# architecture dependent
+# GetUMWindowsSDKIncludeDir(<output variable>) - Find the latest SDK user mode (um) include directory
+#
+# windowssdk_name_lookup(<directory> <output variable>) - Find the name corresponding with the SDK directory you pass in, or
+# NOTFOUND if not recognized. Your directory must be one of WINDOWSSDK_DIRS for this to work.
+#
+# windowssdk_build_lookup(<directory> <output variable>) - Find the build version number corresponding with the SDK directory you pass in, or
+# NOTFOUND if not recognized. Your directory must be one of WINDOWSSDK_DIRS for this to work.
+#
+# get_windowssdk_from_component(<file or dir> <output variable>) - Given a library or include dir,
+# find the Windows SDK root dir corresponding to it, or NOTFOUND if unrecognized.
+#
+# get_windowssdk_library_dirs(<directory> <output variable>) - Find the architecture-appropriate
+# library directories corresponding to the SDK directory you pass in (or NOTFOUND if none)
+#
+# get_windowssdk_library_dirs_multiple(<output variable> <directory> ...) - Find the architecture-appropriate
+# library directories corresponding to the SDK directories you pass in, in order, skipping those not found. NOTFOUND if none at all.
+# Good for passing WINDOWSSDK_DIRS or WINDOWSSDK_DIRS to if you really just want a file and don't care where from.
+#
+# get_windowssdk_include_dirs(<directory> <output variable>) - Find the
+# include directories corresponding to the SDK directory you pass in (or NOTFOUND if none)
+#
+# get_windowssdk_include_dirs_multiple(<output variable> <directory> ...) - Find the
+# include directories corresponding to the SDK directories you pass in, in order, skipping those not found. NOTFOUND if none at all.
+# Good for passing WINDOWSSDK_DIRS or WINDOWSSDK_DIRS to if you really just want a file and don't care where from.
+#
+# Requires these CMake modules:
+# FindPackageHandleStandardArgs (known included with CMake >=2.6.2)
+#
+# Original Author:
+# 2012 Ryan Pavlik <rpavlik@iastate.edu> <abiryan@ryand.net>
+# http://academic.cleardefinition.com
+# Iowa State University HCI Graduate Program/VRAC
+#
+# Copyright Iowa State University 2012.
+# Distributed under the Boost Software License, Version 1.0.
+# (See accompanying file LICENSE_1_0.txt or copy at
+# http://www.boost.org/LICENSE_1_0.txt)
+
+set(_preferred_sdk_dirs) # pre-output
+set(_win_sdk_dirs) # pre-output
+set(_win_sdk_versanddirs) # pre-output
+set(_win_sdk_buildsanddirs) # pre-output
+set(_winsdk_vistaonly) # search parameters
+set(_winsdk_kits) # search parameters
+
+
+set(_WINDOWSSDK_ANNOUNCE OFF)
+if(NOT WINDOWSSDK_FOUND AND (NOT WindowsSDK_FIND_QUIETLY))
+ set(_WINDOWSSDK_ANNOUNCE ON)
+endif()
+macro(_winsdk_announce)
+ if(_WINSDK_ANNOUNCE)
+ message(STATUS ${ARGN})
+ endif()
+endmacro()
+
+
+set(_winsdk_win10vers
+ 10.0.18362.0 # Windows 10 SDK for 2019 Update
+ 10.0.17763.0 # Windows 10 SDK for October 2018 Update
+ 10.0.17133.0 # Redstone 4 aka Win10 1803 "April 1018 Update"
+ 10.0.16299.0 # Redstone 3 aka Win10 1709 "Fall Creators Update"
+ 10.0.15063.0 # Redstone 2 aka Win10 1703 "Creators Update"
+ 10.0.14393.0 # Redstone aka Win10 1607 "Anniversary Update"
+ 10.0.10586.0 # TH2 aka Win10 1511
+ 10.0.10240.0 # Win10 RTM
+ 10.0.10150.0 # just ucrt
+ 10.0.10056.0
+)
+
+if(WindowsSDK_FIND_COMPONENTS MATCHES "tools")
+ set(_WINDOWSSDK_IGNOREMSVC ON)
+ _winsdk_announce("Checking for tools from Windows/Platform SDKs...")
+else()
+ set(_WINDOWSSDK_IGNOREMSVC OFF)
+ _winsdk_announce("Checking for Windows/Platform SDKs...")
+endif()
+
+# Appends to the three main pre-output lists used only if the path exists
+# and is not already in the list.
+function(_winsdk_conditional_append _vername _build _path)
+ if(("${_path}" MATCHES "registry") OR (NOT EXISTS "${_path}"))
+ # Path invalid - do not add
+ return()
+ endif()
+ list(FIND _win_sdk_dirs "${_path}" _win_sdk_idx)
+ if(_win_sdk_idx GREATER -1)
+ # Path already in list - do not add
+ return()
+ endif()
+ _winsdk_announce( " - ${_vername}, Build ${_build} @ ${_path}")
+ # Not yet in the list, so we'll add it
+ list(APPEND _win_sdk_dirs "${_path}")
+ set(_win_sdk_dirs "${_win_sdk_dirs}" CACHE INTERNAL "" FORCE)
+ list(APPEND
+ _win_sdk_versanddirs
+ "${_vername}"
+ "${_path}")
+ set(_win_sdk_versanddirs "${_win_sdk_versanddirs}" CACHE INTERNAL "" FORCE)
+ list(APPEND
+ _win_sdk_buildsanddirs
+ "${_build}"
+ "${_path}")
+ set(_win_sdk_buildsanddirs "${_win_sdk_buildsanddirs}" CACHE INTERNAL "" FORCE)
+endfunction()
+
+# Appends to the "preferred SDK" lists only if the path exists
+function(_winsdk_conditional_append_preferred _info _path)
+ if(("${_path}" MATCHES "registry") OR (NOT EXISTS "${_path}"))
+ # Path invalid - do not add
+ return()
+ endif()
+
+ get_filename_component(_path "${_path}" ABSOLUTE)
+
+ list(FIND _win_sdk_preferred_sdk_dirs "${_path}" _win_sdk_idx)
+ if(_win_sdk_idx GREATER -1)
+ # Path already in list - do not add
+ return()
+ endif()
+ _winsdk_announce( " - Found \"preferred\" SDK ${_info} @ ${_path}")
+ # Not yet in the list, so we'll add it
+ list(APPEND _win_sdk_preferred_sdk_dirs "${_path}")
+ set(_win_sdk_preferred_sdk_dirs "${_win_sdk_dirs}" CACHE INTERNAL "" FORCE)
+
+ # Just in case we somehow missed it:
+ _winsdk_conditional_append("${_info}" "" "${_path}")
+endfunction()
+
+# Given a version like v7.0A, looks for an SDK in the registry under "Microsoft SDKs".
+# If the given version might be in both HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows
+# and HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots aka "Windows Kits",
+# use this macro first, since these registry keys usually have more information.
+#
+# Pass a "default" build number as an extra argument in case we can't find it.
+function(_winsdk_check_microsoft_sdks_registry _winsdkver)
+ set(SDKKEY "HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows\\${_winsdkver}")
+ get_filename_component(_sdkdir
+ "[${SDKKEY};InstallationFolder]"
+ ABSOLUTE)
+
+ set(_sdkname "Windows SDK ${_winsdkver}")
+
+ # Default build number passed as extra argument
+ set(_build ${ARGN})
+ # See if the registry holds a Microsoft-mutilated, err, designated, product name
+ # (just using get_filename_component to execute the registry lookup)
+ get_filename_component(_sdkproductname
+ "[${SDKKEY};ProductName]"
+ NAME)
+ if(NOT "${_sdkproductname}" MATCHES "registry")
+ # Got a product name
+ set(_sdkname "${_sdkname} (${_sdkproductname})")
+ endif()
+
+ # try for a version to augment our name
+ # (just using get_filename_component to execute the registry lookup)
+ get_filename_component(_sdkver
+ "[${SDKKEY};ProductVersion]"
+ NAME)
+ if(NOT "${_sdkver}" MATCHES "registry" AND NOT MATCHES)
+ # Got a version
+ if(NOT "${_sdkver}" MATCHES "\\.\\.")
+ # and it's not an invalid one with two dots in it:
+ # use to override the default build
+ set(_build ${_sdkver})
+ if(NOT "${_sdkname}" MATCHES "${_sdkver}")
+ # Got a version that's not already in the name, let's use it to improve our name.
+ set(_sdkname "${_sdkname} (${_sdkver})")
+ endif()
+ endif()
+ endif()
+ _winsdk_conditional_append("${_sdkname}" "${_build}" "${_sdkdir}")
+endfunction()
+
+# Given a name for identification purposes, the build number, and a key (technically a "value name")
+# corresponding to a Windows SDK packaged as a "Windows Kit", look for it
+# in HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots
+# Note that the key or "value name" tends to be something weird like KitsRoot81 -
+# no easy way to predict, just have to observe them in the wild.
+# Doesn't hurt to also try _winsdk_check_microsoft_sdks_registry for these:
+# sometimes you get keys in both parts of the registry (in the wow64 portion especially),
+# and the non-"Windows Kits" location is often more descriptive.
+function(_winsdk_check_windows_kits_registry _winkit_name _winkit_build _winkit_key)
+ get_filename_component(_sdkdir
+ "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;${_winkit_key}]"
+ ABSOLUTE)
+ _winsdk_conditional_append("${_winkit_name}" "${_winkit_build}" "${_sdkdir}")
+endfunction()
+
+# Given a name for identification purposes and the build number
+# corresponding to a Windows 10 SDK packaged as a "Windows Kit", look for it
+# in HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots
+# Doesn't hurt to also try _winsdk_check_microsoft_sdks_registry for these:
+# sometimes you get keys in both parts of the registry (in the wow64 portion especially),
+# and the non-"Windows Kits" location is often more descriptive.
+function(_winsdk_check_win10_kits _winkit_build)
+ get_filename_component(_sdkdir
+ "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Windows Kits\\Installed Roots;KitsRoot10]"
+ ABSOLUTE)
+ if(("${_sdkdir}" MATCHES "registry") OR (NOT EXISTS "${_sdkdir}"))
+ return() # not found
+ endif()
+ if(EXISTS "${_sdkdir}/Include/${_winkit_build}/um")
+ _winsdk_conditional_append("Windows Kits 10 (Build ${_winkit_build})" "${_winkit_build}" "${_sdkdir}")
+ endif()
+endfunction()
+
+# Given a name for identification purposes, the build number, and the associated package GUID,
+# look in the registry under both HKLM and HKCU in \\SOFTWARE\\Microsoft\\MicrosoftSDK\\InstalledSDKs\\
+# for that guid and the SDK it points to.
+function(_winsdk_check_platformsdk_registry _platformsdkname _build _platformsdkguid)
+ foreach(_winsdk_hive HKEY_LOCAL_MACHINE HKEY_CURRENT_USER)
+ get_filename_component(_sdkdir
+ "[${_winsdk_hive}\\SOFTWARE\\Microsoft\\MicrosoftSDK\\InstalledSDKs\\${_platformsdkguid};Install Dir]"
+ ABSOLUTE)
+ _winsdk_conditional_append("${_platformsdkname} (${_build})" "${_build}" "${_sdkdir}")
+ endforeach()
+endfunction()
+
+###
+# Detect toolchain information: to know whether it's OK to use Vista+ only SDKs
+###
+set(_winsdk_vistaonly_ok OFF)
+if(MSVC AND NOT _WINDOWSSDK_IGNOREMSVC)
+ # VC 10 and older has broad target support
+ if(MSVC_VERSION LESS 1700)
+ # VC 11 by default targets Vista and later only, so we can add a few more SDKs that (might?) only work on vista+
+ elseif("${CMAKE_VS_PLATFORM_TOOLSET}" MATCHES "_xp")
+ # This is the XP-compatible v110+ toolset
+ elseif("${CMAKE_VS_PLATFORM_TOOLSET}" STREQUAL "v100" OR "${CMAKE_VS_PLATFORM_TOOLSET}" STREQUAL "v90")
+ # This is the VS2010/VS2008 toolset
+ else()
+ # OK, we're VC11 or newer and not using a backlevel or XP-compatible toolset.
+ # These versions have no XP (and possibly Vista pre-SP1) support
+ set(_winsdk_vistaonly_ok ON)
+ if(_WINDOWSSDK_ANNOUNCE AND NOT _WINDOWSSDK_VISTAONLY_PESTERED)
+ set(_WINDOWSSDK_VISTAONLY_PESTERED ON CACHE INTERNAL "" FORCE)
+ message(STATUS "FindWindowsSDK: Detected Visual Studio 2012 or newer, not using the _xp toolset variant: including SDK versions that drop XP support in search!")
+ endif()
+ endif()
+endif()
+if(_WINDOWSSDK_IGNOREMSVC)
+ set(_winsdk_vistaonly_ok ON)
+endif()
+
+###
+# MSVC version checks - keeps messy conditionals in one place
+# (messy because of _WINDOWSSDK_IGNOREMSVC)
+###
+set(_winsdk_msvc_greater_1200 OFF)
+if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION GREATER 1200)))
+ set(_winsdk_msvc_greater_1200 ON)
+endif()
+# Newer than VS .NET/VS Toolkit 2003
+set(_winsdk_msvc_greater_1310 OFF)
+if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION GREATER 1310)))
+ set(_winsdk_msvc_greater_1310 ON)
+endif()
+
+# VS2005/2008
+set(_winsdk_msvc_less_1600 OFF)
+if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (MSVC_VERSION LESS 1600)))
+ set(_winsdk_msvc_less_1600 ON)
+endif()
+
+# VS2013+
+set(_winsdk_msvc_not_less_1800 OFF)
+if(_WINDOWSSDK_IGNOREMSVC OR (MSVC AND (NOT MSVC_VERSION LESS 1800)))
+ set(_winsdk_msvc_not_less_1800 ON)
+endif()
+
+###
+# START body of find module
+###
+if(_winsdk_msvc_greater_1310) # Newer than VS .NET/VS Toolkit 2003
+ ###
+ # Look for "preferred" SDKs
+ ###
+
+ # Environment variable for SDK dir
+ if(EXISTS "$ENV{WindowsSDKDir}" AND (NOT "$ENV{WindowsSDKDir}" STREQUAL ""))
+ _winsdk_conditional_append_preferred("WindowsSDKDir environment variable" "$ENV{WindowsSDKDir}")
+ endif()
+
+ if(_winsdk_msvc_less_1600)
+ # Per-user current Windows SDK for VS2005/2008
+ get_filename_component(_sdkdir
+ "[HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]"
+ ABSOLUTE)
+ _winsdk_conditional_append_preferred("Per-user current Windows SDK" "${_sdkdir}")
+
+ # System-wide current Windows SDK for VS2005/2008
+ get_filename_component(_sdkdir
+ "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]"
+ ABSOLUTE)
+ _winsdk_conditional_append_preferred("System-wide current Windows SDK" "${_sdkdir}")
+ endif()
+
+ ###
+ # Begin the massive list of SDK searching!
+ ###
+ if(_winsdk_vistaonly_ok AND _winsdk_msvc_not_less_1800)
+ # These require at least Visual Studio 2013 (VC12)
+
+ _winsdk_check_microsoft_sdks_registry(v10.0A)
+
+ # Windows Software Development Kit (SDK) for Windows 10
+ # Several different versions living in the same directory - if nothing else we can assume RTM (10240)
+ _winsdk_check_microsoft_sdks_registry(v10.0 10.0.10240.0)
+ foreach(_win10build ${_winsdk_win10vers})
+ _winsdk_check_win10_kits(${_win10build})
+ endforeach()
+ endif() # vista-only and 2013+
+
+ # Included in Visual Studio 2013
+ # Includes the v120_xp toolset
+ _winsdk_check_microsoft_sdks_registry(v8.1A 8.1.51636)
+
+ if(_winsdk_vistaonly_ok AND _winsdk_msvc_not_less_1800)
+ # Windows Software Development Kit (SDK) for Windows 8.1
+ # http://msdn.microsoft.com/en-gb/windows/desktop/bg162891
+ _winsdk_check_microsoft_sdks_registry(v8.1 8.1.25984.0)
+ _winsdk_check_windows_kits_registry("Windows Kits 8.1" 8.1.25984.0 KitsRoot81)
+ endif() # vista-only and 2013+
+
+ if(_winsdk_vistaonly_ok)
+ # Included in Visual Studio 2012
+ _winsdk_check_microsoft_sdks_registry(v8.0A 8.0.50727)
+
+ # Microsoft Windows SDK for Windows 8 and .NET Framework 4.5
+ # This is the first version to also include the DirectX SDK
+ # http://msdn.microsoft.com/en-US/windows/desktop/hh852363.aspx
+ _winsdk_check_microsoft_sdks_registry(v8.0 6.2.9200.16384)
+ _winsdk_check_windows_kits_registry("Windows Kits 8.0" 6.2.9200.16384 KitsRoot)
+ endif() # vista-only
+
+ # Included with VS 2012 Update 1 or later
+ # Introduces v110_xp toolset
+ _winsdk_check_microsoft_sdks_registry(v7.1A 7.1.51106)
+ if(_winsdk_vistaonly_ok)
+ # Microsoft Windows SDK for Windows 7 and .NET Framework 4
+ # http://www.microsoft.com/downloads/en/details.aspx?FamilyID=6b6c21d2-2006-4afa-9702-529fa782d63b
+ _winsdk_check_microsoft_sdks_registry(v7.1 7.1.7600.0.30514)
+ endif() # vista-only
+
+ # Included with VS 2010
+ _winsdk_check_microsoft_sdks_registry(v7.0A 6.1.7600.16385)
+
+ # Windows SDK for Windows 7 and .NET Framework 3.5 SP1
+ # Works with VC9
+ # http://www.microsoft.com/en-us/download/details.aspx?id=18950
+ _winsdk_check_microsoft_sdks_registry(v7.0 6.1.7600.16385)
+
+ # Two versions call themselves "v6.1":
+ # Older:
+ # Windows Vista Update & .NET 3.0 SDK
+ # http://www.microsoft.com/en-us/download/details.aspx?id=14477
+
+ # Newer:
+ # Windows Server 2008 & .NET 3.5 SDK
+ # may have broken VS9SP1? they recommend v7.0 instead, or a KB...
+ # http://www.microsoft.com/en-us/download/details.aspx?id=24826
+ _winsdk_check_microsoft_sdks_registry(v6.1 6.1.6000.16384.10)
+
+ # Included in VS 2008
+ _winsdk_check_microsoft_sdks_registry(v6.0A 6.1.6723.1)
+
+ # Microsoft Windows Software Development Kit for Windows Vista and .NET Framework 3.0 Runtime Components
+ # http://blogs.msdn.com/b/stanley/archive/2006/11/08/microsoft-windows-software-development-kit-for-windows-vista-and-net-framework-3-0-runtime-components.aspx
+ _winsdk_check_microsoft_sdks_registry(v6.0 6.0.6000.16384)
+endif()
+
+# Let's not forget the Platform SDKs, which sometimes are useful!
+if(_winsdk_msvc_greater_1200)
+ _winsdk_check_platformsdk_registry("Microsoft Platform SDK for Windows Server 2003 R2" "5.2.3790.2075.51" "D2FF9F89-8AA2-4373-8A31-C838BF4DBBE1")
+ _winsdk_check_platformsdk_registry("Microsoft Platform SDK for Windows Server 2003 SP1" "5.2.3790.1830.15" "8F9E5EF3-A9A5-491B-A889-C58EFFECE8B3")
+endif()
+###
+# Finally, look for "preferred" SDKs
+###
+if(_winsdk_msvc_greater_1310) # Newer than VS .NET/VS Toolkit 2003
+
+
+ # Environment variable for SDK dir
+ if(EXISTS "$ENV{WindowsSDKDir}" AND (NOT "$ENV{WindowsSDKDir}" STREQUAL ""))
+ _winsdk_conditional_append_preferred("WindowsSDKDir environment variable" "$ENV{WindowsSDKDir}")
+ endif()
+
+ if(_winsdk_msvc_less_1600)
+ # Per-user current Windows SDK for VS2005/2008
+ get_filename_component(_sdkdir
+ "[HKEY_CURRENT_USER\\Software\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]"
+ ABSOLUTE)
+ _winsdk_conditional_append_preferred("Per-user current Windows SDK" "${_sdkdir}")
+
+ # System-wide current Windows SDK for VS2005/2008
+ get_filename_component(_sdkdir
+ "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]"
+ ABSOLUTE)
+ _winsdk_conditional_append_preferred("System-wide current Windows SDK" "${_sdkdir}")
+ endif()
+endif()
+
+
+function(windowssdk_name_lookup _dir _outvar)
+ list(FIND _win_sdk_versanddirs "${_dir}" _diridx)
+ math(EXPR _idx "${_diridx} - 1")
+ if(${_idx} GREATER -1)
+ list(GET _win_sdk_versanddirs ${_idx} _ret)
+ else()
+ set(_ret "NOTFOUND")
+ endif()
+ set(${_outvar} "${_ret}" PARENT_SCOPE)
+endfunction()
+
+function(windowssdk_build_lookup _dir _outvar)
+ list(FIND _win_sdk_buildsanddirs "${_dir}" _diridx)
+ math(EXPR _idx "${_diridx} - 1")
+ if(${_idx} GREATER -1)
+ list(GET _win_sdk_buildsanddirs ${_idx} _ret)
+ else()
+ set(_ret "NOTFOUND")
+ endif()
+ set(${_outvar} "${_ret}" PARENT_SCOPE)
+endfunction()
+
+# If we found something...
+if(_win_sdk_dirs)
+ list(GET _win_sdk_dirs 0 WINDOWSSDK_LATEST_DIR)
+ windowssdk_name_lookup("${WINDOWSSDK_LATEST_DIR}"
+ WINDOWSSDK_LATEST_NAME)
+ set(WINDOWSSDK_DIRS ${_win_sdk_dirs})
+
+ # Fallback, in case no preference found.
+ set(WINDOWSSDK_PREFERRED_DIR "${WINDOWSSDK_LATEST_DIR}")
+ set(WINDOWSSDK_PREFERRED_NAME "${WINDOWSSDK_LATEST_NAME}")
+ set(WINDOWSSDK_PREFERRED_FIRST_DIRS ${WINDOWSSDK_DIRS})
+ set(WINDOWSSDK_FOUND_PREFERENCE OFF)
+endif()
+
+# If we found indications of a user preference...
+if(_win_sdk_preferred_sdk_dirs)
+ list(GET _win_sdk_preferred_sdk_dirs 0 WINDOWSSDK_PREFERRED_DIR)
+ windowssdk_name_lookup("${WINDOWSSDK_PREFERRED_DIR}"
+ WINDOWSSDK_PREFERRED_NAME)
+ set(WINDOWSSDK_PREFERRED_FIRST_DIRS
+ ${_win_sdk_preferred_sdk_dirs}
+ ${_win_sdk_dirs})
+ list(REMOVE_DUPLICATES WINDOWSSDK_PREFERRED_FIRST_DIRS)
+ set(WINDOWSSDK_FOUND_PREFERENCE ON)
+endif()
+
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(WindowsSDK
+ "No compatible version of the Windows SDK or Platform SDK found."
+ WINDOWSSDK_DIRS)
+
+if(WINDOWSSDK_FOUND)
+ # Internal: Architecture-appropriate library directory names.
+ if("${CMAKE_VS_PLATFORM_NAME}" STREQUAL "ARM")
+ if(CMAKE_SIZEOF_VOID_P MATCHES "8")
+ # Only supported in Win10 SDK and up.
+ set(_winsdk_arch8 arm64) # what the WDK for Win8+ calls this architecture
+ else()
+ set(_winsdk_archbare /arm) # what the architecture used to be called in oldest SDKs
+ set(_winsdk_arch arm) # what the architecture used to be called
+ set(_winsdk_arch8 arm) # what the WDK for Win8+ calls this architecture
+ endif()
+ else()
+ if(CMAKE_SIZEOF_VOID_P MATCHES "8")
+ set(_winsdk_archbare /x64) # what the architecture used to be called in oldest SDKs
+ set(_winsdk_arch amd64) # what the architecture used to be called
+ set(_winsdk_arch8 x64) # what the WDK for Win8+ calls this architecture
+ else()
+ set(_winsdk_archbare ) # what the architecture used to be called in oldest SDKs
+ set(_winsdk_arch i386) # what the architecture used to be called
+ set(_winsdk_arch8 x86) # what the WDK for Win8+ calls this architecture
+ endif()
+ endif()
+
+ function(get_windowssdk_from_component _component _var)
+ get_filename_component(_component "${_component}" ABSOLUTE)
+ file(TO_CMAKE_PATH "${_component}" _component)
+ foreach(_sdkdir ${WINDOWSSDK_DIRS})
+ get_filename_component(_sdkdir "${_sdkdir}" ABSOLUTE)
+ string(LENGTH "${_sdkdir}" _sdklen)
+ file(RELATIVE_PATH _rel "${_sdkdir}" "${_component}")
+ # If we don't have any "parent directory" items...
+ if(NOT "${_rel}" MATCHES "[.][.]")
+ set(${_var} "${_sdkdir}" PARENT_SCOPE)
+ return()
+ endif()
+ endforeach()
+ # Fail.
+ set(${_var} "NOTFOUND" PARENT_SCOPE)
+ endfunction()
+ function(get_windowssdk_library_dirs _winsdk_dir _var)
+ set(_dirs)
+ set(_suffixes
+ "lib${_winsdk_archbare}" # SDKs like 7.1A
+ "lib/${_winsdk_arch}" # just because some SDKs have x86 dir and root dir
+ "lib/w2k/${_winsdk_arch}" # Win2k min requirement
+ "lib/wxp/${_winsdk_arch}" # WinXP min requirement
+ "lib/wnet/${_winsdk_arch}" # Win Server 2003 min requirement
+ "lib/wlh/${_winsdk_arch}"
+ "lib/wlh/um/${_winsdk_arch8}" # Win Vista ("Long Horn") min requirement
+ "lib/win7/${_winsdk_arch}"
+ "lib/win7/um/${_winsdk_arch8}" # Win 7 min requirement
+ )
+ foreach(_ver
+ wlh # Win Vista ("Long Horn") min requirement
+ win7 # Win 7 min requirement
+ win8 # Win 8 min requirement
+ winv6.3 # Win 8.1 min requirement
+ )
+
+ list(APPEND _suffixes
+ "lib/${_ver}/${_winsdk_arch}"
+ "lib/${_ver}/um/${_winsdk_arch8}"
+ "lib/${_ver}/km/${_winsdk_arch8}"
+ )
+ endforeach()
+
+ # Look for WDF libraries in Win10+ SDK
+ foreach(_mode umdf kmdf)
+ file(GLOB _wdfdirs RELATIVE "${_winsdk_dir}" "${_winsdk_dir}/lib/wdf/${_mode}/${_winsdk_arch8}/*")
+ if(_wdfdirs)
+ list(APPEND _suffixes ${_wdfdirs})
+ endif()
+ endforeach()
+
+ # Look in each Win10+ SDK version for the components
+ foreach(_win10ver ${_winsdk_win10vers})
+ foreach(_component um km ucrt mmos)
+ list(APPEND _suffixes "lib/${_win10ver}/${_component}/${_winsdk_arch8}")
+ endforeach()
+ endforeach()
+
+ foreach(_suffix ${_suffixes})
+ # Check to see if a library actually exists here.
+ file(GLOB _libs "${_winsdk_dir}/${_suffix}/*.lib")
+ if(_libs)
+ list(APPEND _dirs "${_winsdk_dir}/${_suffix}")
+ endif()
+ endforeach()
+ if("${_dirs}" STREQUAL "")
+ set(_dirs NOTFOUND)
+ else()
+ list(REMOVE_DUPLICATES _dirs)
+ endif()
+ set(${_var} ${_dirs} PARENT_SCOPE)
+ endfunction()
+ function(get_windowssdk_include_dirs _winsdk_dir _var)
+ set(_dirs)
+
+ set(_subdirs shared um winrt km wdf mmos ucrt)
+ set(_suffixes Include)
+
+ foreach(_dir ${_subdirs})
+ list(APPEND _suffixes "Include/${_dir}")
+ endforeach()
+
+ foreach(_ver ${_winsdk_win10vers})
+ foreach(_dir ${_subdirs})
+ list(APPEND _suffixes "Include/${_ver}/${_dir}")
+ endforeach()
+ endforeach()
+
+ foreach(_suffix ${_suffixes})
+ # Check to see if a header file actually exists here.
+ file(GLOB _headers "${_winsdk_dir}/${_suffix}/*.h")
+ if(_headers)
+ list(APPEND _dirs "${_winsdk_dir}/${_suffix}")
+ endif()
+ endforeach()
+ if("${_dirs}" STREQUAL "")
+ set(_dirs NOTFOUND)
+ else()
+ list(REMOVE_DUPLICATES _dirs)
+ endif()
+ set(${_var} ${_dirs} PARENT_SCOPE)
+ endfunction()
+
+
+
+ function(get_windowssdk_library_dirs_multiple _var)
+ set(_dirs)
+ foreach(_sdkdir ${ARGN})
+ get_windowssdk_library_dirs("${_sdkdir}" _current_sdk_libdirs)
+ if(_current_sdk_libdirs)
+ list(APPEND _dirs ${_current_sdk_libdirs})
+ endif()
+ endforeach()
+ if("${_dirs}" STREQUAL "")
+ set(_dirs NOTFOUND)
+ else()
+ list(REMOVE_DUPLICATES _dirs)
+ endif()
+ set(${_var} ${_dirs} PARENT_SCOPE)
+ endfunction()
+ function(get_windowssdk_include_dirs_multiple _var)
+ set(_dirs)
+ foreach(_sdkdir ${ARGN})
+ get_windowssdk_include_dirs("${_sdkdir}" _current_sdk_incdirs)
+ if(_current_sdk_libdirs)
+ list(APPEND _dirs ${_current_sdk_incdirs})
+ endif()
+ endforeach()
+ if("${_dirs}" STREQUAL "")
+ set(_dirs NOTFOUND)
+ else()
+ list(REMOVE_DUPLICATES _dirs)
+ endif()
+ set(${_var} ${_dirs} PARENT_SCOPE)
+ endfunction()
+endif()
+
+
+function(FindFirstStringMatching list reg matching)
+ foreach(l ${${list}})
+ if(${l} MATCHES ${reg})
+ set(${matching} ${l} PARENT_SCOPE)
+ break()
+ endif()
+ endforeach()
+endfunction()
+
+function(GetUMWindowsSDKLibraryDir library_dir)
+ get_windowssdk_library_dirs(${WINDOWSSDK_LATEST_DIR} WIN_LIBRARY_DIRS)
+ FindFirstStringMatching(WIN_LIBRARY_DIRS "[\\/]um[\\/]" WINDOWSKIT_LIBRARY_DIR)
+ set(${library_dir} ${WINDOWSKIT_LIBRARY_DIR} PARENT_SCOPE)
+endfunction()
+
+function(GetUMWindowsSDKIncludeDir include_dir)
+ get_windowssdk_include_dirs(${WINDOWSSDK_LATEST_DIR} WIN_INCLUDE_DIRS)
+ FindFirstStringMatching(WIN_INCLUDE_DIRS "[\\/]um[\\/]" WIN_INCLUDE_DIR)
+ set(${include_dir} ${WIN_INCLUDE_DIR} PARENT_SCOPE)
+endfunction()
diff --git a/cmake/Modules/findopensslfeatures.c b/cmake/Modules/findopensslfeatures.c
new file mode 100644
index 0000000..390f1d2
--- /dev/null
+++ b/cmake/Modules/findopensslfeatures.c
@@ -0,0 +1,101 @@
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <openssl/ec.h>
+#include <openssl/objects.h>
+#include <openssl/evp.h>
+
+int
+list_curves()
+{
+ size_t len = EC_get_builtin_curves(NULL, 0);
+ EC_builtin_curve *curves = OPENSSL_malloc(sizeof(EC_builtin_curve) * len);
+ if (!curves) {
+ fprintf(stderr, "Allocation failed.\n");
+ return 1;
+ }
+ if (!EC_get_builtin_curves(curves, len)) {
+ OPENSSL_free(curves);
+ fprintf(stderr, "Failed to get curves.\n");
+ return 1;
+ }
+ for (size_t i = 0; i < len; i++) {
+ const char *sname = OBJ_nid2sn(curves[i].nid);
+ if (!sname) {
+ continue;
+ }
+ printf("%s\n", sname);
+ }
+ OPENSSL_free(curves);
+ return 0;
+}
+
+static void
+print_hash(const EVP_MD *md, const char *from, const char *to, void *arg)
+{
+ if (!md) {
+ return;
+ }
+ if (strstr(from, "rsa") || strstr(from, "RSA")) {
+ return;
+ }
+ printf("%s\n", from);
+}
+
+int
+list_hashes()
+{
+ EVP_MD_do_all_sorted(print_hash, NULL);
+ return 0;
+}
+
+static void
+print_cipher(const EVP_CIPHER *cipher, const char *from, const char *to, void *x)
+{
+ if (!cipher) {
+ return;
+ }
+ printf("%s\n", from);
+}
+
+int
+list_ciphers()
+{
+ EVP_CIPHER_do_all_sorted(print_cipher, NULL);
+ return 0;
+}
+
+int
+list_publickey()
+{
+ for (size_t i = 0; i < EVP_PKEY_meth_get_count(); i++) {
+ const EVP_PKEY_METHOD *pmeth = EVP_PKEY_meth_get0(i);
+ int id = 0;
+ EVP_PKEY_meth_get0_info(&id, NULL, pmeth);
+ printf("%s\n", OBJ_nid2ln(id));
+ }
+ return 0;
+}
+
+int
+main(int argc, char *argv[])
+{
+ if (argc != 2) {
+ fprintf(stderr, "Usage: opensslfeatures [curves|hashes|ciphers|publickey]\n");
+ return 1;
+ }
+ if (!strcmp(argv[1], "hashes")) {
+ return list_hashes();
+ }
+ if (!strcmp(argv[1], "ciphers")) {
+ return list_ciphers();
+ }
+ if (!strcmp(argv[1], "curves")) {
+ return list_curves();
+ }
+ if (!strcmp(argv[1], "publickey")) {
+ return list_publickey();
+ }
+ fprintf(stderr, "Unknown command: %s\n", argv[1]);
+ return 1;
+}
diff --git a/cmake/info.cmake b/cmake/info.cmake
new file mode 100644
index 0000000..21ea680
--- /dev/null
+++ b/cmake/info.cmake
@@ -0,0 +1,41 @@
+# Copyright (c) 2018 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.
+
+# this file contains things that are likely to change occasionally
+set(PACKAGE_VENDOR "Ribose Inc.")
+set(PACKAGE_URL "https://github.com/rnpgp/rnp")
+
+set(PACKAGING_EMAIL "Ribose Inc. <rnpgp@ribose.com>")
+set(BUGREPORT_EMAIL "${PACKAGING_EMAIL}")
+
+set(PACKAGE_DESCRIPTION [=[
+A set of OpenPGP tools for encrypting, decrypting, signing, and \
+verifying files.
+]=]
+)
+set(PACKAGE_DESCRIPTION_SHORT "Freely-licensed OpenPGP library and command-line tools")
+set(PACKAGE_LICENSE "BSD")
+
+set(RPM_RELEASE_NUM 1)
+set(DEB_RELEASE_NUM 1)
diff --git a/cmake/librnp.pc.in b/cmake/librnp.pc.in
new file mode 100644
index 0000000..2eb84bf
--- /dev/null
+++ b/cmake/librnp.pc.in
@@ -0,0 +1,13 @@
+prefix=@CMAKE_INSTALL_PREFIX@
+exec_prefix=${prefix}
+libdir=@PKGCONFIG_LIBDIR@
+includedir=@PKGCONFIG_INCLUDEDIR@
+
+Name: rnp
+Description: @PACKAGE_DESCRIPTION_SHORT@
+Version: @PROJECT_VERSION@
+
+Libs: -L${libdir} -l@LIBRNP_OUTPUT_NAME@
+Libs.private: @LIBRNP_PRIVATE_LIBS@
+Cflags: -I${includedir}
+
diff --git a/cmake/packaging.cmake b/cmake/packaging.cmake
new file mode 100644
index 0000000..2180845
--- /dev/null
+++ b/cmake/packaging.cmake
@@ -0,0 +1,79 @@
+# Copyright (c) 2018, 2023 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.
+
+# this file contains packaging items that aren't likely to change much
+
+# general
+set(CPACK_PACKAGE_VENDOR "${PACKAGE_VENDOR}")
+set(CPACK_PACKAGE_CONTACT "${PACKAGING_EMAIL}")
+set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "${PACKAGE_DESCRIPTION_SHORT}")
+set(CPACK_PACKAGE_VERSION "${PROJECT_VERSION}")
+set(CPACK_PACKAGE_FILE_NAME "rnp-${CPACK_PACKAGE_VERSION}")
+set(CPACK_PACKAGE_NAME "rnp${PROJECT_VERSION_MAJOR}")
+
+set(CPACK_SOURCE_IGNORE_FILES "/installs/;/build/;/\\\\.git/;\\\\.#;/#")
+
+# deb-specific
+set(CPACK_DEBIAN_PACKAGE_HOMEPAGE "${PACKAGE_URL}")
+set(CPACK_DEBIAN_PACKAGE_RELEASE "${DEB_RELEASE_NUM}")
+set(CPACK_DEBIAN_PACKAGE_SHLIBDEPS ON)
+
+# rpm-specific
+set(CPACK_RPM_PACKAGE_LICENSE "${PACKAGE_LICENSE}")
+set(CPACK_RPM_PACKAGE_URL "${PACKAGE_URL}")
+set(CPACK_RPM_PACKAGE_RELEASE "${RPM_RELEASE_NUM}${RNP_VERSION_SUFFIX}")
+set(CPACK_RPM_PACKAGE_RELEASE_DIST ON)
+set(CPACK_RPM_PACKAGE_GROUP "Applications/System")
+set(CPACK_RPM_PACKAGE_DESCRIPTION "${PACKAGE_DESCRIPTION}")
+set(CPACK_RPM_PACKAGE_AUTOREQPROV ON)
+file(WRITE "${PROJECT_BINARY_DIR}/rpm-ldconfig" "/sbin/ldconfig")
+set(CPACK_RPM_POST_INSTALL_SCRIPT_FILE "${PROJECT_BINARY_DIR}/rpm-ldconfig")
+set(CPACK_RPM_POST_UNINSTALL_SCRIPT_FILE "${PROJECT_BINARY_DIR}/rpm-ldconfig")
+# rnp - obsolete the original package name, now preferring to append the major ver
+# rnp0 < [...] - obsolete the monolithic RPM generated by previous versions
+set(CPACK_RPM_PACKAGE_OBSOLETES "rnp, rnp0 < %{version}-%{release}")
+# rpm component packages
+set(CPACK_RPM_COMPONENT_INSTALL ON)
+set(CPACK_RPM_MAIN_COMPONENT "cli")
+# runtime library
+set(CPACK_RPM_RUNTIME_PACKAGE_NAME "librnp${PROJECT_VERSION_MAJOR}")
+set(CPACK_RPM_RUNTIME_FILE_NAME "${CPACK_RPM_RUNTIME_PACKAGE_NAME}-%{version}-%{release}.rpm")
+set(CPACK_RPM_RUNTIME_PACKAGE_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY} (runtime)")
+# development files
+set(CPACK_RPM_DEVELOPMENT_PACKAGE_NAME "${CPACK_RPM_RUNTIME_PACKAGE_NAME}-devel")
+set(CPACK_RPM_DEVELOPMENT_FILE_NAME "${CPACK_RPM_DEVELOPMENT_PACKAGE_NAME}-%{version}-%{release}.rpm")
+set(CPACK_RPM_DEVELOPMENT_PACKAGE_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY} (development files)")
+set(CPACK_RPM_DEVELOPMENT_PACKAGE_DESCRIPTION "Development files for the rnp library")
+set(CPACK_RPM_DEVELOPMENT_PACKAGE_REQUIRES "${CPACK_RPM_RUNTIME_PACKAGE_NAME}")
+# cli utils
+set(CPACK_RPM_CLI_FILE_NAME RPM-DEFAULT)
+set(CPACK_RPM_CLI_PACKAGE_SUMMARY "${CPACK_PACKAGE_DESCRIPTION_SUMMARY} (command-line utilities)")
+
+# bsd-specific
+set(CPACK_FREEBSD_PACKAGE_MAINTAINER "${PACKAGING_EMAIL}")
+set(CPACK_FREEBSD_PACKAGE_ORIGIN "security/rnp")
+set(CPACK_FREEBSD_PACKAGE_CATEGORIES security)
+set(CPACK_FREEBSD_PACKAGE_DEPS bzip2 json-c botan2)
+
+include(CPack)
diff --git a/cmake/rnp-config.cmake.in b/cmake/rnp-config.cmake.in
new file mode 100644
index 0000000..04f4f26
--- /dev/null
+++ b/cmake/rnp-config.cmake.in
@@ -0,0 +1,31 @@
+# Copyright (c) 2018 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.
+
+@PACKAGE_INIT@
+
+include(CMakeFindDependencyMacro)
+
+if(NOT TARGET rnp::librnp)
+ include("${CMAKE_CURRENT_LIST_DIR}/rnp-targets.cmake")
+endif()
diff --git a/cmake/rnp_tests_discover.cmake b/cmake/rnp_tests_discover.cmake
new file mode 100644
index 0000000..9f05293
--- /dev/null
+++ b/cmake/rnp_tests_discover.cmake
@@ -0,0 +1,40 @@
+set(script)
+
+function(add_command NAME)
+ set(_args "")
+ foreach(_arg ${ARGN})
+ set(_args "${_args} [==[${_arg}]==]")
+ endforeach()
+ set(script "${script}${NAME}(${_args})\n" PARENT_SCOPE)
+endfunction()
+
+if(NOT EXISTS "${TEST_EXECUTABLE}")
+ message(FATAL_ERROR "Executable does not exist: ${TEST_EXECUTABE}")
+endif()
+execute_process(
+ COMMAND "${TEST_EXECUTABLE}" list-tests
+ WORKING_DIRECTORY "${TEST_WORKING_DIR}"
+ OUTPUT_VARIABLE output
+ RESULT_VARIABLE result
+)
+if(NOT ${result} EQUAL 0)
+ message(FATAL_ERROR "Error running executable: ${TEST_EXECUTABE}")
+endif()
+
+string(REPLACE "\n" ";" output "${output}")
+
+foreach(line ${output})
+ set(test "${line}")
+ add_command(add_test
+ "rnp_tests-${test}"
+ "${TEST_EXECUTABLE}"
+ "${test}"
+ )
+ add_command(set_tests_properties
+ "rnp_tests-${test}"
+ PROPERTIES ${TEST_PROPERTIES}
+ )
+endforeach()
+
+file(WRITE "${CTEST_FILE}" "${script}")
+
diff --git a/cmake/version.cmake b/cmake/version.cmake
new file mode 100644
index 0000000..f74126e
--- /dev/null
+++ b/cmake/version.cmake
@@ -0,0 +1,163 @@
+# Copyright (c) 2018-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.
+
+# desired length of commit hash
+set(GIT_REV_LEN 7)
+
+# call git, store output in var (can fail)
+macro(_git var)
+ execute_process(
+ COMMAND "${GIT_EXECUTABLE}" ${ARGN}
+ WORKING_DIRECTORY "${source_dir}"
+ RESULT_VARIABLE _git_ec
+ OUTPUT_VARIABLE ${var}
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+ ERROR_QUIET
+ )
+endmacro()
+
+function(extract_version_info version var_prefix)
+ # extract the main components
+ # v1.9.0-3-g5b92266+1546836556
+ # v1.9.0-3-g5b92266-dirty+1546836556
+ string(REGEX MATCH "^v?([0-9]+\\.[0-9]+\\.[0-9]+)(-([0-9]+)-g([0-9a-f]+)(-dirty)?)?(\\+([0-9]+))?$" matches "${version}")
+ if (NOT matches)
+ message(FATAL_ERROR "Failed to extract version components from ${version}.")
+ endif()
+ set(${var_prefix}_VERSION "${CMAKE_MATCH_1}" PARENT_SCOPE) # 1.9.0
+ if (NOT CMAKE_MATCH_3)
+ set(CMAKE_MATCH_3 "0")
+ endif()
+ set(${var_prefix}_VERSION_NCOMMITS "${CMAKE_MATCH_3}" PARENT_SCOPE) # 3
+ if (NOT CMAKE_MATCH_4)
+ set(CMAKE_MATCH_4 "0")
+ endif()
+ set(${var_prefix}_VERSION_GIT_REV "${CMAKE_MATCH_4}" PARENT_SCOPE) # 5b92266
+ if (CMAKE_MATCH_5 STREQUAL "-dirty")
+ set(${var_prefix}_VERSION_IS_DIRTY TRUE PARENT_SCOPE)
+ else()
+ set(${var_prefix}_VERSION_IS_DIRTY FALSE PARENT_SCOPE)
+ endif()
+ # timestamp is optional, default to 0
+ if (NOT CMAKE_MATCH_7)
+ set(CMAKE_MATCH_7 "0")
+ endif()
+ set(${var_prefix}_VERSION_COMMIT_TIMESTAMP "${CMAKE_MATCH_7}" PARENT_SCOPE) # 1546836556
+endfunction()
+
+function(determine_version source_dir var_prefix)
+ set(has_release_tag NO)
+ set(has_version_txt NO)
+ set(local_prefix "_determine_ver")
+ # find out base version via version.txt
+ set(base_version "0.0.0")
+ if (EXISTS "${source_dir}/version.txt")
+ set(has_version_txt YES)
+ file(STRINGS "${source_dir}/version.txt" version_file)
+ extract_version_info("${version_file}" "${local_prefix}")
+ set(base_version "${${local_prefix}_VERSION}")
+ message(STATUS "Found version.txt with ${version_file}")
+ else()
+ message(STATUS "Found no version.txt.")
+ endif()
+ # for GIT_EXECUTABLE
+ find_package(Git)
+ # get a description of the version, something like:
+ # v1.9.1-0-g38ffe82 (a tagged release)
+ # v1.9.1-0-g38ffe82-dirty (a tagged release with local modifications)
+ # v1.9.0-3-g5b92266 (post-release snapshot)
+ # v1.9.0-3-g5b92266-dirty (post-release snapshot with local modifications)
+ _git(version describe --abbrev=${GIT_REV_LEN} --match "v[0-9]*" --long --dirty)
+ if (NOT _git_ec EQUAL 0)
+ # no annotated tags, fake one
+ message(STATUS "Found no annotated tags.")
+ _git(revision rev-parse --short=${GIT_REV_LEN} --verify HEAD)
+ if (_git_ec EQUAL 0)
+ set(version "v${base_version}-0-g${revision}")
+ # check if dirty (this won't detect untracked files, but should be ok)
+ _git(changes diff-index --quiet HEAD --)
+ if (NOT _git_ec EQUAL 0)
+ string(APPEND version "-dirty")
+ endif()
+ # append the commit timestamp of the most recent commit (only
+ # in non-release branches -- typically master)
+ _git(commit_timestamp show -s --format=%ct)
+ if (_git_ec EQUAL 0)
+ string(APPEND version "+${commit_timestamp}")
+ endif()
+ elseif(has_version_txt)
+ # Nothing to get from git - so use version.txt completely
+ set(version "${version_file}")
+ else()
+ # Sad case - no git, no version.txt
+ set(version "v${base_version}")
+ endif()
+ else()
+ set(has_release_tag YES)
+ message(STATUS "Found annotated tag ${version}")
+ endif()
+ extract_version_info("${version}" "${local_prefix}")
+ if ("${has_version_txt}" AND NOT ${base_version} STREQUAL ${local_prefix}_VERSION)
+ message(WARNING "Tagged version ${${local_prefix}_VERSION} doesn't match one from the version.txt: ${base_version}")
+ if (${base_version} VERSION_GREATER ${local_prefix}_VERSION)
+ set(${local_prefix}_VERSION ${base_version})
+ endif()
+ endif()
+ foreach(suffix VERSION VERSION_NCOMMITS VERSION_GIT_REV VERSION_IS_DIRTY VERSION_COMMIT_TIMESTAMP)
+ if (NOT DEFINED ${local_prefix}_${suffix})
+ message(FATAL_ERROR "Unable to determine version.")
+ endif()
+ set(${var_prefix}_${suffix} "${${local_prefix}_${suffix}}" PARENT_SCOPE)
+ message(STATUS "${var_prefix}_${suffix}: ${${local_prefix}_${suffix}}")
+ endforeach()
+ # Set VERSION_SUFFIX and VERSION_FULL. When making changes, be aware that
+ # this is used in packaging as well and will affect ordering.
+ # | state | version_full |
+ # |-----------------------------------------------------|
+ # | exact tag | 0.9.0 |
+ # | exact tag, dirty | 0.9.0+git20180604 |
+ # | after tag | 0.9.0+git20180604.1.085039f |
+ # | no tag, version.txt | 0.9.0+git20180604.2ee02af |
+ # | no tag, no version.txt| 0.0.0+git20180604.2ee02af |
+ string(TIMESTAMP date "%Y%m%d" UTC)
+ set(version_suffix "")
+ if (NOT ${local_prefix}_VERSION_NCOMMITS EQUAL 0)
+ # 0.9.0+git20150604.4.289818b
+ string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_NCOMMITS}.${${local_prefix}_VERSION_GIT_REV}")
+ elseif ((NOT has_release_tag) AND ((NOT has_version_txt) OR ("${base_version}" STREQUAL "0.0.0") OR (NOT "${revision}" STREQUAL "")))
+ # 0.9.0+git20150604.289818b
+ string(APPEND version_suffix "+git${date}.${${local_prefix}_VERSION_GIT_REV}")
+ elseif(${local_prefix}_VERSION_IS_DIRTY)
+ # 0.9.0+git20150604
+ string(APPEND version_suffix "+git${date}")
+ endif()
+ set(version_full "${${local_prefix}_VERSION}${version_suffix}")
+ # set the results
+ set(${var_prefix}_VERSION_SUFFIX "${version_suffix}" PARENT_SCOPE)
+ set(${var_prefix}_VERSION_FULL "${version_full}" PARENT_SCOPE)
+ # for informational purposes
+ message(STATUS "${var_prefix}_VERSION_SUFFIX: ${version_suffix}")
+ message(STATUS "${var_prefix}_VERSION_FULL: ${version_full}")
+endfunction()
+
diff --git a/default.nix b/default.nix
new file mode 100644
index 0000000..1ab6b1f
--- /dev/null
+++ b/default.nix
@@ -0,0 +1,44 @@
+{ pkgs ? import <nixpkgs> { }
+, lib ? pkgs.lib
+, stdenv ? pkgs.stdenv
+}:
+
+stdenv.mkDerivation rec {
+ pname = "rnp";
+ version = "unstable";
+
+ src = ./.;
+
+ buildInputs = with pkgs; [ zlib bzip2 json_c botan2 ];
+
+ cmakeFlags = [
+ "-DCMAKE_INSTALL_PREFIX=${placeholder "out"}"
+ "-DBUILD_SHARED_LIBS=on"
+ "-DBUILD_TESTING=on"
+ "-DDOWNLOAD_GTEST=off"
+ ];
+
+ nativeBuildInputs = with pkgs; [ asciidoctor cmake gnupg gtest pkg-config python3 ];
+
+ # NOTE: check-only inputs should ideally be moved to checkInputs, but it
+ # would fail during buildPhase.
+ # checkInputs = [ gtest python3 ];
+
+ outputs = [ "out" "lib" "dev" ];
+
+ preConfigure = ''
+ commitEpoch=$(date +%s)
+ baseVersion=$(cat version.txt)
+ echo "v$baseVersion-0-g0-dirty+$commitEpoch" > version.txt
+ # For generating the correct timestamp in cmake
+ export SOURCE_DATE_EPOCH=$commitEpoch
+ '';
+
+ meta = with lib; {
+ homepage = "https://github.com/rnpgp/rnp";
+ description = "High performance C++ OpenPGP library, fully compliant to RFC 4880";
+ license = licenses.bsd2;
+ platforms = platforms.all;
+ maintainers = with maintainers; [ ribose-jeffreylau ];
+ };
+} \ No newline at end of file
diff --git a/doc/tests/README.md b/doc/tests/README.md
new file mode 100644
index 0000000..a116569
--- /dev/null
+++ b/doc/tests/README.md
@@ -0,0 +1,72 @@
+# Test Case Guidelines for `rnp`
+
+The document aims to describe and capture various use cases for `rnp` in
+the form of the test cases. These can be used as acceptance tests for
+the maintenance of the project.
+
+
+## Naming conventions
+
+The test case name is composed of the three parts.
+
+* First being the module under test,
+* Second being the feature and third details the motivation of the test.
+
+Naming structure looks like: `<module>_<component>_<test-motivation>`.
+
+For example, when testing the `generatekey` feature of `rnpkeys`, the
+test case name would be `rnpkeys.generatekey.<test-motivation>`.
+
+
+## Test Case Specification Template
+
+The following template **SHOULD** be used for describing a test case.
+
+
+~~~~~~ md
+# <test-case-name>
+
+Component
+: <component-name>
+
+Feature
+: <feature-name>
+
+## Objective
+
+% Objective of test case
+
+## Description
+
+% Describe test case briefly
+
+## Preconditions
+
+% List of conditions prior to testing
+
+* condition 1
+* condition 2
+* condition 3
+
+## Test steps and expected behavior
+
+1. Test step 1
+
+1. Test step 2
+
+Expectation: expectation here
+
+## Verification steps and logic
+
+1. Verification step 1
+ * Rationale: verification logic
+
+1. Verification step 2
+ * Rationale: verification logic
+
+## Comments
+
+% if any
+
+~~~~~~
+
diff --git a/doc/tests/rnpkeys-generate-key.md b/doc/tests/rnpkeys-generate-key.md
new file mode 100644
index 0000000..113a9e6
--- /dev/null
+++ b/doc/tests/rnpkeys-generate-key.md
@@ -0,0 +1,192 @@
+# rnpkeys_generatekey_verifySupportedHashAlg
+
+Component
+: rnpkeys
+
+Feature
+: Generate-key
+
+## Objective
+
+Verify SupportedHashAlg.
+
+## Description
+
+The test aims to test key generation with all possible hash algorithm.
+Following hash algorithm are tested for the key generation:
+
+* `MD5`
+* `SHA-1`
+* `RIPEMD160`
+* `SHA256`
+* `SHA384`
+* `SHA512`
+* `SHA224`
+
+## Preconditions
+
+* Initialize RNP
+* Set the default value for `res`, `format`, `hash` via `rnp_setvar()`.
+
+## Test steps and expected behavior
+
+1. Set the hash algorithm via `rnp_setvar`
+
+1. Call the API to generate key (`rnp_generate_key`)
+
+Expectation: key is generated using options set via `rnp_setvar`
+
+## Verification steps and logic
+
+1. Load the newly generated RNP keys
+ * Rationale: Ensures keys are loaded in the `rnp` control structure
+ for verification.
+
+1. Find existence of key via `userId`.
+
+ * **Note**: If `userid` variable is not set, default is `always`.
+ * Rationale: Ensures the key exists by finding it.
+
+## Comments
+
+It is required to delete the old keys if the test case iterates over the
+hashing algorithm.
+
+
+# rnpkeys_generatekey_VerifyUserIdOption
+
+Component
+: rnpkeys
+
+Feature
+: Generate-key
+
+## Objective
+
+Verify `UserIdOption`
+
+## Description
+
+The test aims to test key generation with command line option `UserId`.
+
+Following different `userid`s are tested:
+
+* `rnpkeys_Generatekey_VerifyUserIdOption_MD5`
+* `rnpkeys_Generatekey_VerifyUserIdOption_SHA-1`
+* `rnpkeys_Generatekey_VerifyUserIdOption_RIPEMD160`
+* `rnpkeys_Generatekey_VerifyUserIdOption_SHA256`
+* `rnpkeys_Generatekey_VerifyUserIdOption_SHA384`
+* `rnpkeys_Generatekey_VerifyUserIdOption_SHA512`
+* `rnpkeys_Generatekey_VerifyUserIdOption_SHA224`
+
+
+## Preconditions
+
+* Initialize RNP
+* Set the default value for res, format, hash via `rnp_setvar`.
+
+## Test steps and expected behavior
+
+1. Set the userId via `rnp_setvar`
+
+1. Call the API to generate key (`rnp_generate_key`)
+
+Expectation: key is generated using options set via `rnp_setvar`
+
+## Verification steps and logic
+
+1. Load the newly generated RNP keys
+ * Rationale: Ensures keys are loaded in the rnp control structure for
+ verification.
+
+1. Find the existence of the key via finding the key with the userId.
+
+
+# rnpkeys_generatekey_verifykeyRingOptions
+
+Component
+: rnpkeys
+
+Feature
+: Generate-key
+
+## Objective
+
+Verify keyRingOptions.
+
+## Description
+
+The test aims to test key generation with the user specified keyring.
+
+## Preconditions
+
+* Initialize RNP
+* Set the default value for `res`, `format`, `hash` via `rnp_setvar()`.
+
+## Test steps and expected behavior
+
+1. Set the keyring via `rnp_setvar`
+
+1. Call the API to generate key (`rnp_generate_key`)
+
+Expectation: key is generated using options set via `rnp_setvar`
+
+## Verification steps and logic
+
+1. Delete the default keyring i.e. `pubring.gpg` and `secring.gpg` found
+ in the homedir
+
+ * Rationale: To ensure that default keyring is **NOT** available.
+
+1. Load the newly generated RNP keys
+
+ * Rationale: Ensures keys are loaded in the `rnp` control structure
+ for verification.
+
+1. Find existence of key via `userId`.
+
+ * **Note**: If `userid` variable is not set, default is `always`.
+ * Rationale: Ensures the key exists by finding it.
+
+
+# rnpkeys_generatekey_verifykeyHomeDirOption
+
+Component
+: rnpkeys
+
+Feature
+: Generate-key
+
+## Objective
+
+Verify keyHomeDirOption.
+
+## Description
+
+The test aims to test key generation with the user specified keyring.
+
+## Preconditions
+
+* Create new home dir with read/write permissions.
+* Delete the keys (if any) in the previous default directory.
+* Initialize RNP
+* Set the default value for `res`, `format`, `hash` via `rnp_setvar()`.
+
+## Test steps and expected behavior
+
+1. Call the API to generate key (`rnp_generate_key`)
+
+Expectation: key is generated using options set via `rnp_setvar`
+
+## Verification steps and logic
+
+1. Load the newly generated RNP keys
+
+ * Rationale: Ensures keys are loaded in the rnp control structure for
+ verification.
+
+1. Find existence of key via `userId`.
+
+ * **Note**: If `userid` variable is not set, default is `always`.
+ * Rationale: Ensures the key exists by finding it.
+
diff --git a/docs/c-usage.adoc b/docs/c-usage.adoc
new file mode 100644
index 0000000..b45f9b9
--- /dev/null
+++ b/docs/c-usage.adoc
@@ -0,0 +1,129 @@
+= Using the RNP C API
+
+This document is for developers who wish to use RNP as a library in C.
+
+Examples are given below to demonstrate such usage.
+
+== Examples
+
+[TIP]
+.Location of examples
+====
+The source code of these examples can be found under
+`https://github.com/rnpgp/rnp/blob/main/src/examples/[src/examples]`.
+
+If you are planning to build from source, these examples are built
+together with the RNP library and will be available under `src/examples`
+within your build folder.
+====
+
+[TIP]
+====
+All samples below use APIs exposed via the header file
+`https://github.com/rnpgp/rnp/blob/main/include/rnp/rnp.h[include/rnp/rnp.h]`.
+For further details please refer to the file directly.
+====
+
+The following example applications are available:
+
+`generate`:: Demonstrates generating keys, save/load of keyrings, exporting keys.
+
+`encrypt`:: Demonstrates how to encrypt a file using a password and/or key.
+
+`decrypt`:: Demonstrates how to decrypt OpenPGP data using a key and/or password.
+
+`sign`:: Demonstrates how to sign messages, using one or more keys from a loaded keyring.
+
+`verify`:: Demonstrates how to verify signed messages using dynamic keys fetching
+ (using a sample key provider implementation).
+
+`dump`:: Demonstrates how to dump OpenPGP packet information.
+
+
+=== generate.c
+
+Location: https://github.com/rnpgp/rnp/blob/main/src/examples/generate.c
+
+This example is composed from 2 functions:
+
+* `ffi_generate_keys()`: Demonstrates how to generate and save different key types
+ (RSA and EDDSA/Curve25519) using JSON key description.
+ Also demonstrates usage of the password provider.
++
+Keyrings will be saved to files `pubring.pgp` and `secring.pgp` in the current directory.
+You can use `rnp --list-packets pubring.pgp` to check the properties of the generated key(s).
+
+* `ffi_output_keys()`: Demonstrates how to load keyrings,
+ search for keys (in helper functions `ffi_print_key()`/`ffi_export_key()`),
+ and how to export them to memory or file in armored format.
+
+=== encrypt.c
+
+Location: https://github.com/rnpgp/rnp/blob/main/src/examples/encrypt.c
+
+This code example does the following:
+
+* first loads a public keyring (`pubring.pgp`) (created by the `generate.c` example)
+* then creates an encryption operation structure and
+* configures it with various options (including the setup of password encryption and public-key encryption).
+
+The result is the encrypted and armored (for easier reading) message
+`RNP encryption sample message`.
+
+This message is saved to the file `encrypted.asc` in current directory.
+
+What you can do after:
+
+* Inspect the message with `rnp --list-packets encrypted.asc`.
+* Decrypt the saved file via `rnp --keyfile secring.pgp -d encrypted.asc`.
+
+=== decrypt.c
+
+Location: https://github.com/rnpgp/rnp/blob/main/src/examples/decrypt.c
+
+This example uses keyrings generated from the `generate.c` example
+to decrypt messages encrypted by the `encrypt.c` example.
+
+This example demonstrates how to decrypt message with a password or with a key,
+and implements a custom password provider for decryption via key or key password.
+
+The decrypted message is saved to memory and then printed to the `stdout`.
+
+=== sign.c
+
+Location: https://github.com/rnpgp/rnp/blob/main/src/examples/sign.c
+
+This example uses keyrings generated in the preceding `generate.c` example.
+
+It demonstrates configuration of a signing context, signing of the message,
+and the saving of the detached signature to the `signed.asc` file.
+
+Then the attached signature is used: i.e. the data is encapsulated into
+the resulting message.
+
+What you can do after:
+
+* Inspect the signed message with `rnp --list-packets signed.asc`.
+* Verify the message with `rnp --keyfile pubring.pgp -v signed.asc`.
+
+=== verify.c
+
+Location: https://github.com/rnpgp/rnp/blob/main/src/examples/verify.c
+
+This example uses keyrings generated in the `generate.c` example.
+
+However, instead of loading the whole keyring, it implements dynamic key fetching
+via custom key provider (see function `example_key_provider`).
+
+After verification, it outputs the verified embedded message
+and all signatures to `stdout` (with signing key IDs and statuses).
+
+=== dump.c
+
+Location: https://github.com/rnpgp/rnp/blob/main/src/examples/dump.c
+
+This example dumps OpenPGP packet information from the input stream
+(via `stdin` or filename), tuned with flags passed via the
+command-line interface.
+
+The resulting human-readable text or JSON is printed to `stdout`.
diff --git a/docs/cli-usage.adoc b/docs/cli-usage.adoc
new file mode 100644
index 0000000..7039381
--- /dev/null
+++ b/docs/cli-usage.adoc
@@ -0,0 +1,174 @@
+= Using the RNP command-line interface
+
+== Generating an RSA private key
+
+By default, `rnpkeys --generate-key` generates a 2048-bit RSA key.
+
+[source,console]
+----
+export keydir=/tmp
+rnpkeys --generate-key --homedir=${keydir}
+----
+
+=>
+
+[source,console]
+----
+rnpkeys: generated keys in directory ${keydir}/6ed2d908150b82e7
+----
+
+NOTE: Here `6ed2d...` is the key fingerprint.
+
+In order to use fully-featured key-pair generation, the `--expert` flag
+should be used.
+
+With this flag added to `rnpkeys --generate-key`, the user will be
+able to generate a key-pair for any supported algorithm and/or key size.
+
+Example:
+
+[source,console]
+----
+> export keydir=/tmp
+> rnpkeys --generate-key --expert --homedir=${keydir}
+
+Please select what kind of key you want:
+ (1) RSA (Encrypt or Sign)
+ (19) ECDSA
+ (22) EDDSA
+> 19
+
+Please select which elliptic curve you want:
+ (1) NIST P-256
+ (2) NIST P-384
+ (3) NIST P-521
+> 2
+
+Generating a new key...
+signature 384/ECDSA d45592277b75ada1 2017-06-21
+Key fingerprint: 4244 2969 07ca 42f7 b6d8 1636 d455 9227 7b75 ada1
+uid ECDSA 384-bit key <flowher@localhost>
+rnp: generated keys in directory /tmp/.rnp
+Enter password for d45592277b75ada1:
+Repeat password for d45592277b75ada1:
+>
+----
+
+
+== Listing keys
+
+[source,console]
+----
+export keyringdir=${keydir}/MYFINGERPRINT
+rnpkeys --list-keys --homedir=${keyringdir}
+
+----
+
+=>
+
+[source,console]
+----
+1 key found
+...
+----
+
+
+== Signing a file
+
+
+=== Signing in binary format
+
+[source,console]
+----
+rnp --sign --homedir=${keyringdir} ${filename}
+----
+
+=>
+
+Creates `${filename}.gpg` which is an OpenPGP message that includes the
+message together with the signature as a 'signed message'.
+
+This type of file can be verified with:
+
+* `rnp --verify --homedir=${keyringdir} ${filename}.gpg`
+
+
+=== Signing in binary detached format
+
+[source,console]
+----
+rnp --sign --detach --homedir=${keyringdir} ${filename}
+----
+
+=>
+
+Creates `${filename}.sig` which is an OpenPGP message in binary
+format, that only contains the signature.
+
+This type of file can be verified with:
+
+* `rnp --verify --homedir=${keyringdir} ${filename}.sig`
+
+
+=== Signing in armored ("`ASCII-armored`") format
+
+[source,console]
+----
+rnp --sign --armor --homedir=${keyringdir} ${filename}
+----
+
+=>
+
+Creates `${filename}.asc` which is an OpenPGP message in ASCII-armored
+format, including the message together with the signature as a
+"`signed message`".
+
+This type of file can be verified with:
+
+* `rnp --verify --homedir=${keyringdir} ${filename}.asc`
+
+
+=== Other options
+
+`--clearsign`::
+appends a separate OpenPGP signature to the end of the newly
+signed message.
+
+`--detach`::
+saves the OpenPGP signature in a separate file from the newly
+signed message.
+
+
+== Encrypt
+
+
+[source,console]
+----
+rnp --encrypt --homedir=${keyringdir} ${filename}
+----
+
+=>
+
+Creates `${filename}.gpg`, which is an encrypted OpenPGP message.
+
+
+== Decrypt
+
+[source,console]
+----
+rnp --decrypt --homedir=${keyringdir} ${filename}.gpg
+----
+
+=>
+
+Creates `${filename}`, the decrypted form of the `${filename}.gpg`
+encrypted OpenPGP message.
+
+
+== Check version
+
+The output of `rnp --version` contains the `git` hash of the version
+the binary was built from, of which value is generated when `cmake` runs.
+
+Consequently, a release tarball generated with `make dist` will
+contain this hash version.
diff --git a/docs/code-of-conduct.adoc b/docs/code-of-conduct.adoc
new file mode 100644
index 0000000..14f5e03
--- /dev/null
+++ b/docs/code-of-conduct.adoc
@@ -0,0 +1,128 @@
+
+= Contributor Covenant Code of Conduct
+
+== Our Pledge
+
+We as members, contributors, and leaders pledge to make participation in our
+community a harassment-free experience for everyone, regardless of age, body
+size, visible or invisible disability, ethnicity, sex characteristics, gender
+identity and expression, level of experience, education, socio-economic status,
+nationality, personal appearance, race, religion, or sexual identity
+and orientation.
+
+We pledge to act and interact in ways that contribute to an open, welcoming,
+diverse, inclusive, and healthy community.
+
+== Our Standards
+
+Examples of behavior that contributes to a positive environment for our
+community include:
+
+* Demonstrating empathy and kindness toward other people
+* Being respectful of differing opinions, viewpoints, and experiences
+* Giving and gracefully accepting constructive feedback
+* Accepting responsibility and apologizing to those affected by our mistakes,
+ and learning from the experience
+* Focusing on what is best not just for us as individuals, but for the
+ overall community
+
+Examples of unacceptable behavior include:
+
+* The use of sexualized language or imagery, and sexual attention or
+ advances of any kind
+* Trolling, insulting or derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or email
+ address, without their explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+ professional setting
+
+== Enforcement Responsibilities
+
+Community leaders are responsible for clarifying and enforcing our standards of
+acceptable behavior and will take appropriate and fair corrective action in
+response to any behavior that they deem inappropriate, threatening, offensive,
+or harmful.
+
+Community leaders have the right and responsibility to remove, edit, or reject
+comments, commits, code, wiki edits, issues, and other contributions that are
+not aligned to this Code of Conduct, and will communicate reasons for moderation
+decisions when appropriate.
+
+== Scope
+
+This Code of Conduct applies within all community spaces, and also applies when
+an individual is officially representing the community in public spaces.
+Examples of representing our community include using an official e-mail address,
+posting via an official social media account, or acting as an appointed
+representative at an online or offline event.
+
+== Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported to the community leaders responsible for enforcement at
+open.source@ribose.com.
+All complaints will be reviewed and investigated promptly and fairly.
+
+All community leaders are obligated to respect the privacy and security of the
+reporter of any incident.
+
+== Enforcement Guidelines
+
+Community leaders will follow these Community Impact Guidelines in determining
+the consequences for any action they deem in violation of this Code of Conduct:
+
+=== 1. Correction
+
+**Community Impact**: Use of inappropriate language or other behavior deemed
+unprofessional or unwelcome in the community.
+
+**Consequence**: A private, written warning from community leaders, providing
+clarity around the nature of the violation and an explanation of why the
+behavior was inappropriate. A public apology may be requested.
+
+=== 2. Warning
+
+**Community Impact**: A violation through a single incident or series
+of actions.
+
+**Consequence**: A warning with consequences for continued behavior. No
+interaction with the people involved, including unsolicited interaction with
+those enforcing the Code of Conduct, for a specified period of time. This
+includes avoiding interactions in community spaces as well as external channels
+like social media. Violating these terms may lead to a temporary or
+permanent ban.
+
+=== 3. Temporary Ban
+
+**Community Impact**: A serious violation of community standards, including
+sustained inappropriate behavior.
+
+**Consequence**: A temporary ban from any sort of interaction or public
+communication with the community for a specified period of time. No public or
+private interaction with the people involved, including unsolicited interaction
+with those enforcing the Code of Conduct, is allowed during this period.
+Violating these terms may lead to a permanent ban.
+
+=== 4. Permanent Ban
+
+**Community Impact**: Demonstrating a pattern of violation of community
+standards, including sustained inappropriate behavior, harassment of an
+individual, or aggression toward or disparagement of classes of individuals.
+
+**Consequence**: A permanent ban from any sort of public interaction within
+the community.
+
+== Attribution
+
+This Code of Conduct is adapted from the
+https://www.contributor-covenant.org[Contributor Covenant],
+version 2.0, available at
+https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
+
+Community Impact Guidelines were inspired by
+https://github.com/mozilla/diversity[Mozilla's code of conduct enforcement ladder].
+
+For answers to common questions about this code of conduct, see the FAQ at
+https://www.contributor-covenant.org/faq. Translations are available at
+https://www.contributor-covenant.org/translations.
diff --git a/docs/develop.adoc b/docs/develop.adoc
new file mode 100644
index 0000000..e0c0e41
--- /dev/null
+++ b/docs/develop.adoc
@@ -0,0 +1,435 @@
+= RNP development guide
+
+The following are a set of conventions and items that are relevant to
+contributors.
+
+== Contributing
+
+=== Pull Requests
+
+See also: https://github.com/thoughtbot/guides/tree/master/code-review[Thoughtbot’s Code Review guide]
+
+Pull Requests should be used for any non-trivial changes. This presents
+an opportunity for feedback and allows the CI tests to complete prior to
+merging.
+
+The `master` branch should generally always be in a buildable and
+functional state.
+
+Pull Requests should be:
+
+* Focused. Do not include changes that are unrelated to the main purpose
+ of the PR.
+* As small as possible. Sometimes large pull requests may be necessary
+ for adding complex features, but generally they should be kept as small
+ as possible to ensure a quick and thorough review process.
+* Related to a GH issue to which you are assigned. If there is none,
+ file one (but search first!). This ensures there is no duplication of
+ effort and allows for a discussion prior to beginning work.
+ (This may not be necessary for PRs that are purely documentation updates)
+* Approved by **2** reviewers before merging.
+ (Updates related to policies, like this section, should be approved by
+ the project owner)
+* Merged by a reviewer via the most appropriate method
+ (see https://github.com/rnpgp/guides/tree/master/protocol/git[here]).
+
+=== Branches
+
+See also: https://github.com/rnpgp/guides/tree/master/protocol/git[Guides - Protocol / Git]
+
+Git branches should be used generously. Most branches should be topic branches,
+created for adding a specific feature or fixing a specific bug.
+
+Keep branches short-lived (treat them as disposable/transient) and try to
+avoid long-running branches.
+
+A good example of using a branch would be:
+
+* User `@joe` notices a bug where a NULL pointer is dereferenced during
+ key export. He creates GH issue `#500`.
+* He creates a new branch to fix this bug named
+ `joe-500-fix-null-deref-in-pgp_export_key`.
+* Joe commits a fix for the issue to this new branch.
+* Joe creates a Pull Request to merge this branch in to main.
+* Once merged, Joe deletes the branch since it is no longer useful.
+
+Branch names may vary but should be somewhat descriptive, with words
+separated by hyphens. It is also helpful to start the branch name with
+your GitHub username, to make it clear who created the branch and
+prevent naming conflicts.
+
+Remember that branch names may be preserved permanently in the commit
+history of `main`, depending on how they are merged.
+
+=== Commits
+
+* Try to keep commits as small as possible. This may be difficult or
+ impractical at times, so use your best judgement.
+* Each commit should be buildable and should pass all tests. This helps
+ to ensure that git bisect remains a useful method of pinpointing issues.
+* Commit messages should follow 50/72 rule.
+* When integrating pull requests, merge function should be preferred over
+ 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 (Github Actions)
+
+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`.
+
+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
+
+If tests fail in CI, you may attempt to reproduce those locally via `ctest` command:
+
+[source,console]
+--
+ctest -j4 -V -R rnp_tests
+--
+
+Or, more specific:
+
+[source,console]
+--
+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/
+
+== Security / Bug Hunting
+
+=== Static Analysis
+
+==== Coverity Scan
+
+Coverity Scan is used for static analysis of the code base.
+It is run daily on the main branch via the Github actions.
+See `.github/workflows/coverity.yml` for the details.
+
+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 `main` branch, issues may already be resolved,
+etc.
+
+==== Clang Static Analyzer
+
+Clang includes a useful static analyzer that can also be used to locate
+potential bugs.
+
+Note: It is normal for the build time to increase significantly when using this static analyzer.
+
+[source,console]
+--
+# it's important to start fresh for this!
+rm -rf build && mkdir build && cd build
+scan-build cmake .. && scan-build make -j8
+[...]
+scan-build: 61 bugs found.
+scan-build: Run 'scan-view /tmp/scan-build-2018-09-17-085354-22998-1' to examine bug reports.
+--
+
+Then use `scan-view`, as indicated above, to start a web server and use
+your web browser to view the results.
+
+=== Dynamic Analysis
+
+==== Fuzzing
+
+It is often useful to utilize a fuzzer like
+http://lcamtuf.coredump.cx/afl/["american fuzzy lop" ("AFL")] or
+https://llvm.org/docs/LibFuzzer.html["libfuzzer"] to find
+ways to improve the robustness of the code base.
+
+Presently, rnp builds in
+https://github.com/google/oss-fuzz/tree/master/projects/rnp["OSS-Fuzz"]
+and certain fuzzers are enabled there.
+
+In the `src/fuzzing` directory, we have the fuzzers that run in OSS-Fuzz.
+Setting `-DENABLE_SANITIZERS=1 -DENABLE_FUZZERS=1` will build these fuzzers
+with the libfuzzer engine; and running the resulting executables will perform
+the fuzzing.
+
+To build and run fuzzers locally, or reproduce an issue, see https://google.github.io/oss-fuzz/advanced-topics/reproducing/
+
+===== Further Reading
+
+* AFL's `README`, `parallel_fuzzing.txt`, and other bundled documentation.
+* See https://fuzzing-project.org/tutorial3.html[Tutorial: Instrumented fuzzing with american fuzzy lop]
+
+==== Clang Sanitizer
+
+Clang and GCC both support a number of sanitizers that can help locate
+issues in the code base during runtime.
+
+To use them, you should rebuild with the sanitizers enabled, and then
+run the tests (or any executable):
+
+[source,console]
+--
+env CXX=clang++ CXXFLAGS="-fsanitize=address,undefined" LDFLAGS="-fsanitize=address,undefined" ./configure
+make -j4
+src/tests/rnp_tests
+--
+
+Here we are using the
+https://clang.llvm.org/docs/AddressSanitizer.html[AddressSanitizer]
+and
+https://clang.llvm.org/docs/UndefinedBehaviorSanitizer.html[UndefinedBehaviorSanitizer].
+
+This will produce output showing any memory leaks, heap overflows, or
+other issues.
+
+== Code Conventions
+
+C is a very flexible and powerful language. Because of this, it is
+important to establish a set of conventions to avoid common problems and
+to maintain a consistent code base.
+
+=== Code Formatting
+
+`clang-format` (v9.0.0) can be used to format the code base, utilizing
+the `.clang-format` file included in the repository.
+
+==== clang-format git hook
+
+A git pre-commit hook exists to perform this task automatically, and can
+be enabled like so:
+
+[source,console]
+--
+cd rnp
+git-hooks/enable.sh
+--
+
+If you do not have clang-format v9.0.0 available, you can use a docker
+container for this purpose by setting `USE_DOCKER="yes"` in
+`git-hooks/pre-commit.sh`.
+
+This should generally work if you commit from the command line.
+
+Note that if you have unstaged changes on some of the files you are
+attempting to commit, which have formatting issues detected, you will
+have to resolve this yourself (the script will inform you of this).
+
+If your commit does not touch any `.c`/`.h` files, you can skip the
+pre-commit hook with git's `--no-verify`/`-n` option.
+
+==== clang-format (manually)
+
+If you are not able to use the git hook, you can run `clang-format`
+manually in a docker container.
+
+Create a suitable container image with:
+
+[source,console]
+--
+docker run --name=clang-format alpine:latest apk --no-cache add clang
+docker commit clang-format clang-format
+docker rm clang-format
+--
+
+You can then reformat a file (say, `src/lib/crypto/bn.cpp`) like so:
+
+[source,console]
+--
+cd rnp
+docker run --rm -v $PWD:/rnp -w /rnp clang-format clang-format -style=file -i src/lib/crypto/bn.cpp
+--
+
+Also you may wish to reformat all modified uncommitted files:
+
+[source,console]
+--
+docker run --rm -v $PWD:/rnp -w /rnp clang-format clang-format -style=file -i `git ls-files -m |grep "\.\(c\|h\|cpp\)\$"`
+--
+
+...or files, modified since referenced commit, say `54c5476`:
+
+[source,console]
+--
+docker run --rm -v $PWD:/rnp -w /rnp clang-format clang-format -style=file -i `git diff --name-only 54c5476..HEAD |grep "\.\(c\|h\|cpp\)\$"`
+--
+
+=== Style Guide
+
+In order to keep the code base consistent, we should define and adhere
+to a single style.
+
+When in doubt, consult the existing code base.
+
+==== Naming
+
+The following are samples that demonstrate the style for naming
+different things.
+
+* Functions: `some_function`
+* Variables: `some_variable`
+* Filenames: `packet-parse.c` `packet-parse.h`
+* Struct: `pgp_key_t`
+* Typedefed Enums: `pgp_pubkey_alg_t`
+* Enum Values: `PGP_PKA_RSA = 1`
+* Constants (macro): `RNP_BUFSIZ`
+
+==== General Guidelines
+
+Do:
+
+* Do use header guards (`#ifndef SOME_HEADER_H [...]`) in headers.
+* Do use `sizeof(variable)`, rather than `sizeof(type)`. Or
+ `sizeof(*variable)` as appropriate.
+* Do use commit messages that close GitHub issues automatically, when
+ applicable. `Fix XYZ. Closes #78.` See
+ https://help.github.com/articles/closing-issues-via-commit-messages/[here].
+* Do declare functions `static` when they do not need to be referenced
+ outside the current source file.
+* Do always use braces for conditionals, even if the block only contains a
+ single statement.
++
+[source,c]
+--
+if (something) {
+ return val;
+}
+--
+
+* Do use a default failure (not success) value for `ret` variables. Example:
++
+[source,c]
+--
+rnp_result_t ret = RNP_ERROR_GENERIC;
+// ...
+
+return ret;
+--
+
+Do not:
+
+* Do not use the static storage class for local variables, *unless* they
+ are constant.
++
+**Not OK**
++
+[source,c]
+--
+int somefunc() {
+ static char buffer[256];
+ //...
+}
+--
++
+**OK**
++
+[source,c]
+--
+int somefunc() {
+ static const uint16_t some_data[] = {
+ 0x00, 0x01, 0x02, //...
+ };
+}
+--
+
+* Do not use `pragma`, and try to avoid `__attribute__` as well.
+
+* Do not use uninitialized memory. Try to ensure your code will not cause any errors in valgrind and other memory checkers.
+
+==== Documentation
+
+Documentation is done in Doxygen comments format, which must be put in header files.
+
+Exception are static or having only definition functions - it is not required to document them,
+however if they are documented then this should be done in the source file and using the @private tag.
+
+Comments should use doxygen markdown style, like the following example:
+
+[source,c]
+--
+/** Some comments regarding the file purpose, like 'PGP packet parsing utilities'
+ * @file
+ */
+
+/** brief description of the sample function which does something, keyword 'brief' is omitted
+ * Which may be continued here
+ *
+ * After an empty line you may add detailed description in case it is needed. You may put
+ * details about the memory allocation, what happens if function fails and so on.
+ *
+ * @param param1 first parameter, null-terminated string which should not be NULL
+ * @param param2 integer, some number representing something
+ * @param size number of bytes available to store in buffer
+ * @param buffer buffer to store results, may be NULL. In this case size can be used to
+ * obtain the required buffer length
+ * @return 0 if operation succeeds, or error code otherwise. If operation succeeds then buffer
+ * is populated with the resulting data, and size contains the length of this data.
+ * if error code is E_BUF_TOOSMALL then size will contain the required size to store
+ * the result
+ **/
+rnp_result_t
+rnp_do_operation(const char *param1, const int param2, int *size, char *buffer);
+--
+
+== OpenPGP protocol specification
+
+During development you'll need to reference OpenPGP protocol and related documents.
+Here is the list of RFCs and Internet Drafts available at the moment:
+
+* https://www.ietf.org/rfc/rfc1991.txt[RFC 1991]: PGP Message Exchange Formats. Now obsolete, but may have some historical interest.
+* https://www.ietf.org/rfc/rfc2440.txt[RFC 2440]: OpenPGP Message Format. Superseded by RFC 4880.
+* https://www.ietf.org/rfc/rfc4880.txt[RFC 4880]: OpenPGP Message Format. Latest RFC available at the moment, however has a lot of suggested changes via RFC 4880bis
+* https://tools.ietf.org/rfc/rfc5581.txt[RFC 5581]: The Camellia cipher in OpenPGP.
+* https://www.ietf.org/id/draft-ietf-openpgp-rfc4880bis-09.txt[RFC 4880bis-09]: OpenPGP Message Format. Latest suggested update to the RFC 4880.
+
+More information sources:
+
+* https://mailarchive.ietf.org/arch/browse/openpgp/[OpenPGP Working Group mailing list]. Here you can pick up all the latest discussions and suggestions regarding the update of RFC 4880
+* https://gitlab.com/openpgp-wg/rfc4880bis[OpenPGP Working Group gitlab]. Latest work on RFC update is available here.
+
+== Reviewers and Responsibility areas
+
+The individuals are responsible for the following areas of `rnp`.
+When submitting a Pull Request please seek reviews by whoever is
+responsible according to this list.
+
+General:
+
+* Code style: @dewyatt
+* Algorithms: @randombit, @dewyatt, @flowher, @catap, @ni4
+* Performance: @catap, @ni4
+* CLI: @ni4
+* GnuPG compatibility: @MohitKumarAgniotri, @frank-trampe, @ni4
+* Security Testing/Analysis: @MohitKumarAgniotri, @flowher
+* Autotools: @randombit, @zgyarmati, @catap
+
+Data formats:
+
+* OpenPGP Packet: @randombit, @catap, @ni4
+* Keystore: @catap
+* JSON: @zgyarmati
+* SSH: @ni4
+
+Bindings:
+
+* FFI: @dewyatt
+* Ruby: @dewyatt
+* Java/JNI: @catap
+* Obj-C/Swift: @ni4
+* Python: @dewyatt, @ni4
+
+Platforms:
+
+* RHEL/CentOS: @dewyatt
+* BSD:
+* Windows: @rrrooommmaaa
+* macOS / iOS / Homebrew: @ni4
+* Debian: @zgyarmati
diff --git a/docs/develop/cpp-usage.adoc b/docs/develop/cpp-usage.adoc
new file mode 100644
index 0000000..e0c1842
--- /dev/null
+++ b/docs/develop/cpp-usage.adoc
@@ -0,0 +1,49 @@
+= Usage of {cpp} within RNP
+
+This is a provisional document reflecting the recent conversion from C
+to {cpp}. It should be revisited as experience with using {cpp} within RNP
+codebase increases.
+
+== Encouraged Features
+
+These are features which seem broadly useful, their downsides are minimal
+and well understood.
+
+ - STL types std::vector, std::string, std::unique_ptr, std::map
+
+ - RAII techniques (destructors, smart pointers) to minimize use of
+ goto to handle cleanup.
+
+ - Value types, that is to say types which simply encapsulate some
+ data.
+
+ - std::function or virtual functions to replace function pointers.
+
+ - Prefer virtual functions only on "interface" classes (with no data),
+ and derive only one level of classes from this interface class.
+
+ - Anonymous namespaces are an alternative to `static` functions.
+
+== Questionable Features
+
+These are features that may be useful in certain situations, but should
+be used carefully.
+
+ - Exceptions. While convenient, they do have a non-zero cost in runtime
+ and binary size.
+
+== Forbidden Features
+
+These are {cpp} features that simply should be avoided, at least until a
+very clear use case for them has been identified and no other approach
+suffices.
+
+ - RTTI. This has a significant runtime cost and usually there are
+ better alternatives.
+
+ - Multiple inheritance. This leads to many confusing and problematic
+ scenarios.
+
+ - Template metaprogramming. If you have a problem, and you think
+ template metaprogramming will solve it, now you have two problems,
+ and one of them is incomprehensible.
diff --git a/docs/develop/packaging.adoc b/docs/develop/packaging.adoc
new file mode 100644
index 0000000..917c9aa
--- /dev/null
+++ b/docs/develop/packaging.adoc
@@ -0,0 +1,78 @@
+= Packaging
+
+== CentOS 7
+
+=== 1. Retrieve the source
+
+==== Tarball
+
+[source,console]
+--
+curl -LO https://github.com/rnpgp/rnp/archive/v0.9.0.tar.gz
+tar xzf v0.9.0.tar.gz
+cd rnp-0.9.0
+--
+
+==== Git
+
+[source,console]
+--
+git clone https://github.com/rnpgp/rnp
+cd rnp
+git checkout v0.9.0
+--
+
+=== 2. Launch a container
+
+[source,console]
+--
+docker run -ti --rm -v $PWD:/usr/local/rnp centos:7 bash
+--
+
+From this point, all commands are executed in the container.
+
+==== 3. Install pre-requisites
+
+[source,console]
+--
+# for newer cmake and other things
+yum -y install epel-release
+
+# rnp
+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
+
+[source,console]
+--
+yum -y install rpm-build
+mkdir ~/build
+cd ~/build
+cmake3 -DBUILD_SHARED_LIBS=on -DBUILD_TESTING=off -DCPACK_GENERATOR=RPM /usr/local/rnp
+make package
+--
+
+=== 5. Check and Install the RPM
+
+It may be helpful to run `rpmlint` on the RPM and note new warnings or errors.
+
+[source,console]
+--
+yum -y install rpmlint
+rpmlint rnp-0.9.0-1.el7.centos.x86_64.rpm
+--
+
+At this point, you can test that the RPM installs successfully:
+
+[source,console]
+--
+yum localinstall rnp-0.9.0-1.el7.centos.x86_64.rpm
+--
diff --git a/docs/develop/release-workflow.adoc b/docs/develop/release-workflow.adoc
new file mode 100644
index 0000000..40f4203
--- /dev/null
+++ b/docs/develop/release-workflow.adoc
@@ -0,0 +1,122 @@
+= Releases
+
+== General notes
+
+* Avoid tagging commits in the `main` branch.
+* Release branches should have annotated tags and a CHANGELOG.md.
+* The steps below detail creation of a brand new 1.0.0 release.
+ Some steps would be omitted for minor releases.
+
+== Creating an initial release
+
+=== Update documentation
+
+Update references to version numbers in relevant documentation to the new
+version you intend to release.
+
+[source,console]
+----
+git checkout main
+vim docs/installation.adoc
+git add docs/installation.adoc
+git commit
+git push
+----
+
+=== Create branch
+
+Release branches have names of the form `release/N.x`, where N is the major
+version (and `x` is a literal -- not a placeholder).
+
+[source,console]
+----
+git checkout -b release/1.x main
+----
+
+[[update-changelog-and-version]]
+=== Update CHANGELOG and version
+
+[source,console]
+----
+vim CHANGELOG.md
+# Add/update CHANGELOG entry for the new version
+git add CHANGELOG.md
+
+echo 1.0.0 > version.txt
+git add -f version.txt
+
+git commit
+----
+
+=== Create tag
+
+An initial release would be tagged as follows:
+
+[source,console]
+----
+git tag -a v1.0.0 -m ''
+----
+
+=== Push branch and tag
+
+[source,console]
+----
+# push the branch
+git push origin release/1.x
+
+# push the tag
+git push origin v1.0.0
+----
+
+=== Edit tagged release description on GitHub
+
+. Navigate to the link:#https://github.com/rnpgp/rnp/releases[Releases] page;
+
+. Edit the tag that was just pushed;
+
+. Fill the tag's description with data from the corresponding `CHANGELOG`
+ entries of the same tag version;
+
+. Publish the release.
+
+
+== Creating a new release
+
+Maintaining a release branch involves cherry-picking hotfixes and
+similar commits from the `main` branch, while following the rules for
+Semantic Versioning.
+
+The steps below will show the release of version 1.0.1.
+
+=== Add desired changes
+
+Cherry-pick the appropriate commits into the appropriate `release/N.x` branch.
+
+To see what commits are in `main` that are not in the release branch, you
+can observe the lines starting with `+` in:
+
+[source,console]
+----
+git cherry -v release/1.x main
+----
+
+It is often useful to pick a range of commits. For example:
+
+[source,console]
+----
+git checkout release/0.x
+git cherry-pick a57b36f^..e23352c
+----
+
+If there are merge commits in this range, this will not work.
+Instead, try:
+
+[source,console]
+----
+git checkout release/0.x
+git cherry release/0.x main | grep '^+ ' | cut -c 3-9 | \
+ while read commit; do git cherry-pick $commit; done
+----
+
+From here, you can follow the steps for an initial release,
+starting with <<update-changelog-and-version>>.
diff --git a/docs/installation.adoc b/docs/installation.adoc
new file mode 100644
index 0000000..991fa85
--- /dev/null
+++ b/docs/installation.adoc
@@ -0,0 +1,262 @@
+= Installing RNP
+
+Binaries that will be installed:
+
+* `rnp`
+* `rnpkeys`
+
+
+== On NixOS or Nix package manager
+
+We provide a Nix package for easy installation on NixOS and any OS with Nix
+installed (including Linux and macOS, even NixOS on WSL).
+
+[source,console]
+----
+nix-env -iA nixpkgs.rnp
+----
+
+== With Nix Flakes
+
+We provide a Nix flake.
+
+[source,console]
+----
+nix profile install github:rnpgp/rnp
+----
+
+== On macOS using Homebrew
+
+We provide a Homebrew tap for easy installation of RNP on macOS.
+
+[source,console]
+----
+brew tap rnpgp/rnp
+brew install rnp
+----
+
+== On RHEL and CentOS via YUM
+
+We provide pre-built packages for RHEL and CentOS at our YUM repository hosted
+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
+
+Prerequisites: 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.17.0
+
+Please ensure that you clone with submodules if you use a version higher then 0.16.2
+git clone https://github.com/rnpgp/rnp.git --recurse-submodules --shallow-submodules
+
+# Install required packages
+sudo apt install g++-8 cmake libbz2-dev zlib1g-dev libjson-c-dev build-essential python-minimal
+
+# Download, build and install Botan2
+wget -qO- https://botan.randombit.net/releases/Botan-2.18.2.tar.xz | tar xvJ
+cd Botan-2.18.2
+./configure.py --prefix=/usr
+make
+sudo make install
+cd ..
+
+# CMake encourages building outside of the source directory.
+mkdir rnp-build
+cd rnp-build
+
+# Run CMake
+cmake -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=on -DBUILD_TESTING=off ../rnp/
+
+# Compile
+make
+
+# Install
+sudo make install
+----
+
+== On Debian
+
+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.17.0
+
+Please ensure that you clone with submodules if you use a version higher then 0.16.2
+git clone https://github.com/rnpgp/rnp.git --recurse-submodules --shallow-submodules
+
+# Enable access to `testing` packages by editing /etc/apt/sources.list
+# deb http://deb.debian.org/debian testing main
+
+# Install required packages
+sudo apt install g++-8 cmake libbz2-dev zlib1g-dev libjson-c-dev \
+ libbotan-2-dev build-essential
+
+# Cmake recommend out-of-source builds
+mkdir rnp-build
+cd rnp-build
+
+# Cmake it
+cmake -DCMAKE_INSTALL_PREFIX=/usr -DBUILD_SHARED_LIBS=on -DBUILD_TESTING=off ../rnp/
+
+# Compile and install
+sudo make install
+----
+
+== On Gentoo Linux
+
+RNP ebuilds are available from an overlay repository named `rnp`.
+
+=== Using eselect-repository (the current way)
+
+Prerequisite: ensure `eselect-repository` is installed on your system.
+
+[source,console]
+----
+eselect repository enable rnp
+emaint sync -r rnp
+emerge -av app-crypt/rnp
+----
+
+=== Using layman (the old way)
+
+Prerequisite: ensure `layman` is installed on your system.
+
+[source,console]
+----
+layman -a rnp
+layman -s rnp
+emerge -av app-crypt/rnp
+----
+
+== Compile from source
+
+Clone this repo, or download a release and expand it.
+
+Enter the source folder and run the following commands:
+
+[source,console]
+----
+cmake -DCMAKE_INSTALL_PREFIX=/usr/local -DBUILD_SHARED_LIBS=on -DBUILD_TESTING=off .
+
+make install
+----
+
+== On Windows
+
+=== Using MSYS/MinGW
+
+From a clean MSYS2 install, please first update `pacman` and install required
+packages via the `msys` console.
+
+[source,console]
+----
+pacman -Syu --noconfirm --needed
+
+# Most likely you'll need to close msys console and run it again:
+pacman -Syu --noconfirm --needed
+
+# Install packages
+pacman --noconfirm -S --needed tar zlib-devel libbz2-devel git automake autoconf libtool automake-wrapper make pkg-config mingw64/mingw-w64-x86_64-cmake mingw64/mingw-w64-x86_64-gcc mingw64/mingw-w64-x86_64-json-c mingw64/mingw-w64-x86_64-libbotan mingw64/mingw-w64-x86_64-python3
+----
+
+Then clone the RNP repository and build it.
+
+Please ensure that you clone with submodules if you use a version higher then 0.16.2
+git clone https://github.com/rnpgp/rnp.git --recurse-submodules --shallow-submodules
+
+[source,console]
+----
+# CMake encourages building outside of the source directory.
+mkdir rnp-build
+cd rnp-build
+
+# Add paths to PATH so dependency dll/lib files can be found
+export PATH="/c/msys64/mingw64/lib:/c/msys64/mingw64/bin:$PWD/bin:$PATH"
+
+# Run CMake
+cmake -DBUILD_SHARED_LIBS=yes -G "MSYS Makefiles" -DBUILD_TESTING=off ../rnp
+
+# Compile and install
+make && make install
+----
+
+=== Using Microsoft Visual Studio 2019 and vcpkg
+
+Install `vcpkg` according to
+https://docs.microsoft.com/en-us/cpp/build/install-vcpkg?view=msvc-160&tabs=windows[these instructions]:
+
+Set the `VCPKG_ROOT` environment variable to the `vcpkg` root folder.
+
+For botan backend:
+[source,console]
+----
+vcpkg install --triplet x64-windows bzip2 zlib botan json-c getopt dirent python3[core,enable-shared]
+----
+
+For openssl backend:
+[source,console]
+----
+vcpkg install --triplet x64-windows bzip2 zlib botan json-c getopt dirent python3[core,enable-shared]
+----
+
+If you need to target 32-bit platform you'll need to to replace `x64-windows` with `x86-windows`.
+
+* The following steps will perform a console build for CMake using Visual Studio 2019 CMake generator: +
++
+--
+[source,console]
+----
+cmake -B build -G "Visual Studio 16 2019" -A x64 -DCMAKE_TOOLCHAIN_FILE=%VCPKG_ROOT%\scripts\buildsystems\vcpkg.cmake \
+ -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=off -DCRYPTO_BACKEND="botan" .
+cmake --build . --config Release
+cmake --install .
+----
+--
+Replace CRYPTO_BACKEND parameter to "openssl" if you target this backend.
+
+Ensure that the following dependencies are available on path:
+
+* `librnp.dll`
+* `botan.dll` or `libcrypto.dll` depending on target backend and architecture
+* `bz2.dll`
+* `getopt.dll`
+* `json-c.dll`
+* `zlib1.dll`
+
+=== Using Microsoft Visual Studio 2019 and pre-installed libraries
+
+Install dependencies and make them available either on PATH or using CMAKE_TARGET_PREFIX parameter:
+
+* Botan(2.14+) or Crypto (OpenSSL 1.1.1+) depending on target backend
+* BZip2
+* GetOpt
+* JSON-C (0.12.1+)
+* ZLIB
+
+If openssl backend is used note that your environment may have another ("default") openssl installation.
+In such case use OPENSSL_ROOT_DIR.
+
+* The following steps will perform a console build for CMake using Visual Studio 2019 CMake generator: +
++
+--
+[source,console]
+----
+cmake -B build -G "Visual Studio 16 2019" -A x64 -DOPENSSL_ROOT_DIR=<openssl root> -DCMAKE_TARGET_PREFIX=<target prefix> \
+ -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=off -DCRYPTO_BACKEND="botan" .
+cmake --build . --config Release
+cmake --install .
+----
+--
+Replace CRYPTO_BACKEND parameter to "openssl" if you target this backend, use OPENSSL_ROOT_DIR and CMAKE_TARGET_PREFIX optionally as explained above
diff --git a/docs/navigation.adoc b/docs/navigation.adoc
new file mode 100644
index 0000000..385992c
--- /dev/null
+++ b/docs/navigation.adoc
@@ -0,0 +1,18 @@
+---
+items:
+- { title: Installation, path: installation/ }
+- { title: Command-line usage, path: cli-usage/ }
+- { title: C API usage, path: c-usage/ }
+- title: Developing RNP
+ path: develop/
+ items:
+ - { title: "C++ usage", path: develop/cpp-usage/ }
+ - { title: "Packaging", path: develop/packaging/ }
+ - { title: "Release workflow", path: develop/release-workflow/ }
+ # - title: "Acceptance tests"
+ # path: develop/acceptance-tests/
+ # items:
+ # - { title: "rnpkeys-generate-key", path: develop/acceptance-tests/rnpkeys-generate-key }
+---
+
+= Navigation
diff --git a/docs/signing-keys.adoc b/docs/signing-keys.adoc
new file mode 100644
index 0000000..0ebf7ff
--- /dev/null
+++ b/docs/signing-keys.adoc
@@ -0,0 +1,5 @@
+
+= PGP keys used for signing source code
+
+The current OpenPGP key used for signing source code for RNP is hosted at
+https://www.rnpgp.org/openpgp_keys/[rnpgp.org^].
diff --git a/flake.lock b/flake.lock
new file mode 100644
index 0000000..5a119b9
--- /dev/null
+++ b/flake.lock
@@ -0,0 +1,42 @@
+{
+ "nodes": {
+ "flake-utils": {
+ "locked": {
+ "lastModified": 1678901627,
+ "narHash": "sha256-U02riOqrKKzwjsxc/400XnElV+UtPUQWpANPlyazjH0=",
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "rev": "93a2b84fc4b70d9e089d029deacc3583435c2ed6",
+ "type": "github"
+ },
+ "original": {
+ "owner": "numtide",
+ "repo": "flake-utils",
+ "type": "github"
+ }
+ },
+ "nixpkgs": {
+ "locked": {
+ "lastModified": 1679685564,
+ "narHash": "sha256-PgzKcXZWCf9MjU4F+9WCQyQER5Mf1qbi2dh65QTD5Vc=",
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "rev": "0dbcdf71be50604de48a67e6cf1d0a9a5911f145",
+ "type": "github"
+ },
+ "original": {
+ "owner": "nixos",
+ "repo": "nixpkgs",
+ "type": "github"
+ }
+ },
+ "root": {
+ "inputs": {
+ "flake-utils": "flake-utils",
+ "nixpkgs": "nixpkgs"
+ }
+ }
+ },
+ "root": "root",
+ "version": 7
+}
diff --git a/flake.nix b/flake.nix
new file mode 100644
index 0000000..c9ddc20
--- /dev/null
+++ b/flake.nix
@@ -0,0 +1,26 @@
+{
+ description = "High performance C++ OpenPGP library, fully compliant to RFC 4880";
+
+ inputs = {
+ nixpkgs.url = "github:nixos/nixpkgs";
+ flake-utils.url = "github:numtide/flake-utils";
+ };
+
+ outputs = { self, nixpkgs, flake-utils }:
+ flake-utils.lib.eachDefaultSystem (system:
+ let
+ pkgs = nixpkgs.legacyPackages.${system};
+ thePackage = pkgs.callPackage ./default.nix { };
+ in
+ rec {
+ defaultApp = flake-utils.lib.mkApp {
+ drv = defaultPackage;
+ };
+ defaultPackage = thePackage;
+ devShell = pkgs.mkShell {
+ buildInputs = [
+ thePackage
+ ];
+ };
+ });
+} \ No newline at end of file
diff --git a/git-hooks/enable.sh b/git-hooks/enable.sh
new file mode 100755
index 0000000..7a32117
--- /dev/null
+++ b/git-hooks/enable.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+ln -sf ../../git-hooks/pre-commit.sh "$(git rev-parse --show-toplevel)/.git/hooks/pre-commit"
+
diff --git a/git-hooks/pre-commit.sh b/git-hooks/pre-commit.sh
new file mode 100755
index 0000000..5cab632
--- /dev/null
+++ b/git-hooks/pre-commit.sh
@@ -0,0 +1,125 @@
+#!/bin/bash
+set -eEu
+
+# Path to clang-format. If it's in your PATH, "clang-format" is fine.
+# If you do not have it (or the version is too old), see USE_DOCKER.
+CLANG_FORMAT="/usr/local/bin/clang-format"
+
+# Set to "yes" if you want to create and use a docker container
+# for clang-format.
+USE_DOCKER="no"
+
+# Set this to "yes" if you typically commit from the command line
+# and wish to be prompted on whether to apply any patches
+# automatically.
+INTERACTIVE="yes"
+
+# Leave this stuff
+STASH_NAME="pre-commit-$(date +%s)"
+CONTAINER_NAME="clang-format-$(date +%s)"
+CLANG_FORMAT_VERSION="11.1.0"
+
+apply_patch() {
+ local patchfile=$1
+ git apply --index "$patchfile"
+ rm -f "$patchfile"
+ exit 0
+}
+
+stash() {
+ git stash save -q --keep-index "$STASH_NAME"
+}
+
+unstash() {
+ if git stash list | head -n 1 | grep -Fq "$STASH_NAME"; then
+ git stash pop -q || true
+ fi
+}
+
+cleanup() {
+ ec=$?
+ unstash
+ if [ $USE_DOCKER == "yes" ]; then
+ docker kill "$CONTAINER_NAME" >/dev/null 2>&1 || true
+ fi
+ if [ $ec -ne 0 ]; then
+ echo Aborted.
+ fi
+ exit $ec
+}
+
+if [ $USE_DOCKER == "yes" ]; then
+ if [ ! "$(docker images clang-format --format '{{.Repository}}')" ]; then
+ echo "Creating docker image for clang-format (one time only)..."
+ docker run --name=clang-format alpine:latest apk --no-cache add clang >/dev/null 2>&1
+ docker commit clang-format clang-format >/dev/null 2>&1
+ docker rm clang-format
+ fi
+ CLANG_FORMAT="docker exec $CONTAINER_NAME clang-format"
+ docker run --rm --name "$CONTAINER_NAME" -t -d -v "$(git rev-parse --show-toplevel)":/src -w /src clang-format tail -f /dev/null >/dev/null 2>&1
+ # alternatively (slower for multiple files)
+ #CLANG_FORMAT="docker run --rm -v $(git rev-parse --show-toplevel):/src -w /src clang-format clang-format"
+fi
+
+if git rev-parse --verify HEAD >/dev/null 2>&1
+then
+ against=HEAD
+else
+ # Initial commit: diff against an empty tree object
+ against=4b825dc642cb6eb9a060e54bf8d69288fbee4904
+fi
+
+exec 1>&2
+
+$CLANG_FORMAT -version | grep -Fq "$CLANG_FORMAT_VERSION" || (echo Incorrect version of clang-format.; exit 1)
+
+patchfile=$(mktemp -t git-clang-format.XXXXXX.patch)
+stash
+trap "cleanup" SIGHUP SIGINT SIGTERM EXIT ERR
+
+git diff-index --cached --diff-filter=ACMR --name-only $against -- | grep "\.[ch]$" | while read -r file;
+do
+ $CLANG_FORMAT -style=file "$file" | \
+ diff -u "$file" - | \
+ sed -e "1s|--- |--- a/|" -e "2s|+++ -|+++ b/$file|" >> "$patchfile"
+
+ # cat is just here to ignore the exit status of diff
+ $CLANG_FORMAT -style=file "$file" | diff -u "$file" - | cat
+done
+unstash
+
+if [ ! -s "$patchfile" ]; then
+ rm -f "$patchfile"
+ exit 0
+fi
+
+echo
+echo Formatting changes requested.
+echo "See $patchfile"
+echo
+
+if ! git apply --index --check "$patchfile"; then
+ echo You may have unstaged changes to files that require formatting updates.
+ echo It is not safe for this script to apply the patch automatically.
+ exit 1
+fi
+
+if [ $INTERACTIVE == "yes" ]; then
+ while true; do
+ exec < /dev/tty
+ read -rp "Apply now (y/n)? " yn
+ exec <&-
+ case $yn in
+ [Yy]* ) apply_patch "$patchfile"; exit 0;;
+ [Nn]* ) break;;
+ * ) echo Please answer yes or no. ;;
+ esac
+ done
+fi
+
+echo
+echo You should manually apply the patch with:
+echo "git apply --index \"$patchfile\""
+echo "Or use another method, and then restage."
+exit 1
+
diff --git a/include/rekey/rnp_key_store.h b/include/rekey/rnp_key_store.h
new file mode 100644
index 0000000..04faabf
--- /dev/null
+++ b/include/rekey/rnp_key_store.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2017, [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).
+ *
+ * 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 KEY_STORE_H_
+#define KEY_STORE_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include "rnp.h"
+#include "librepgp/stream-common.h"
+#include "pgp-key.h"
+#include <string>
+#include <list>
+#include <map>
+#include <unordered_map>
+#include <memory>
+#include "librekey/kbx_blob.hpp"
+#include "sec_profile.hpp"
+
+/* Key import status. Order of elements is important. */
+typedef enum pgp_key_import_status_t {
+ PGP_KEY_IMPORT_STATUS_UNKNOWN = 0,
+ PGP_KEY_IMPORT_STATUS_UNCHANGED,
+ PGP_KEY_IMPORT_STATUS_UPDATED,
+ PGP_KEY_IMPORT_STATUS_NEW,
+} pgp_key_import_status_t;
+
+typedef enum pgp_sig_import_status_t {
+ PGP_SIG_IMPORT_STATUS_UNKNOWN = 0,
+ PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY,
+ PGP_SIG_IMPORT_STATUS_UNCHANGED,
+ PGP_SIG_IMPORT_STATUS_NEW
+} pgp_sig_import_status_t;
+
+typedef std::unordered_map<pgp_fingerprint_t, std::list<pgp_key_t>::iterator> pgp_key_fp_map_t;
+
+typedef struct rnp_key_store_t {
+ std::string path;
+ pgp_key_store_format_t format;
+ rnp::SecurityContext & secctx;
+ bool disable_validation =
+ false; /* do not automatically validate keys, added to this key store */
+
+ std::list<pgp_key_t> keys;
+ pgp_key_fp_map_t keybyfp;
+ std::vector<std::unique_ptr<kbx_blob_t>> blobs;
+
+ ~rnp_key_store_t();
+ rnp_key_store_t(rnp::SecurityContext &ctx)
+ : path(""), format(PGP_KEY_STORE_UNKNOWN), secctx(ctx){};
+ rnp_key_store_t(pgp_key_store_format_t format,
+ const std::string & path,
+ rnp::SecurityContext & ctx);
+ /* make sure we use only empty constructor */
+ rnp_key_store_t(rnp_key_store_t &&src) = delete;
+ rnp_key_store_t &operator=(rnp_key_store_t &&) = delete;
+ rnp_key_store_t(const rnp_key_store_t &src) = delete;
+ rnp_key_store_t &operator=(const rnp_key_store_t &) = delete;
+} rnp_key_store_t;
+
+bool rnp_key_store_load_from_path(rnp_key_store_t *, const pgp_key_provider_t *key_provider);
+bool rnp_key_store_load_from_src(rnp_key_store_t *,
+ pgp_source_t *,
+ const pgp_key_provider_t *key_provider);
+
+bool rnp_key_store_write_to_path(rnp_key_store_t *);
+bool rnp_key_store_write_to_dst(rnp_key_store_t *, pgp_dest_t *);
+
+void rnp_key_store_clear(rnp_key_store_t *);
+
+size_t rnp_key_store_get_key_count(const rnp_key_store_t *);
+
+/**
+ * @brief Add key to the keystore, copying it.
+ *
+ * @param keyring allocated keyring, cannot be NULL.
+ * @param key key to be added, cannot be NULL.
+ * @return pointer to the added key or NULL if failed.
+ */
+pgp_key_t *rnp_key_store_add_key(rnp_key_store_t *keyring, pgp_key_t *key);
+
+pgp_key_t *rnp_key_store_import_key(rnp_key_store_t *,
+ pgp_key_t *,
+ bool,
+ pgp_key_import_status_t *);
+
+/**
+ * @brief Get signer's key from key store.
+ *
+ * @param store populated key store, cannot be NULL.
+ * @param sig signature, cannot be NULL.
+ * @return pointer to pgp_key_t structure if key was found or NULL otherwise.
+ */
+pgp_key_t *rnp_key_store_get_signer_key(rnp_key_store_t *store, const pgp_signature_t *sig);
+
+pgp_sig_import_status_t rnp_key_store_import_key_signature(rnp_key_store_t * keyring,
+ pgp_key_t * key,
+ const pgp_signature_t *sig);
+
+/**
+ * @brief Import revocation or direct-key signature to the keyring.
+ *
+ * @param keyring populated keyring, cannot be NULL.
+ * @param sig signature to import.
+ * @param status signature import status will be put here, if not NULL.
+ * @return pointer to the key to which this signature belongs (or NULL if key was not found)
+ */
+pgp_key_t *rnp_key_store_import_signature(rnp_key_store_t * keyring,
+ const pgp_signature_t * sig,
+ pgp_sig_import_status_t *status);
+
+bool rnp_key_store_remove_key(rnp_key_store_t *, const pgp_key_t *, bool);
+
+bool rnp_key_store_get_key_grip(const pgp_key_material_t *, pgp_key_grip_t &grip);
+
+const pgp_key_t *rnp_key_store_get_key_by_fpr(const rnp_key_store_t *,
+ const pgp_fingerprint_t &fpr);
+pgp_key_t * rnp_key_store_get_key_by_fpr(rnp_key_store_t *, const pgp_fingerprint_t &fpr);
+pgp_key_t * rnp_key_store_get_primary_key(rnp_key_store_t *, const pgp_key_t *);
+pgp_key_t *rnp_key_store_search(rnp_key_store_t *, const pgp_key_search_t *, pgp_key_t *);
+
+#endif /* KEY_STORE_H_ */
diff --git a/include/repgp/repgp_def.h b/include/repgp/repgp_def.h
new file mode 100644
index 0000000..218736c
--- /dev/null
+++ b/include/repgp/repgp_def.h
@@ -0,0 +1,505 @@
+/*
+ * Copyright (c) 2017-2020, 2023 [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).
+ *
+ * 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 REPGP_DEF_H_
+#define REPGP_DEF_H_
+
+#include <cstdint>
+
+/************************************/
+/* Packet Tags - RFC4880, 4.2 */
+/************************************/
+
+/** Packet Tag - Bit 7 Mask (this bit is always set).
+ * The first byte of a packet is the "Packet Tag". It always
+ * has bit 7 set. This is the mask for it.
+ *
+ * \see RFC4880 4.2
+ */
+#define PGP_PTAG_ALWAYS_SET 0x80
+
+/** Packet Tag - New Format Flag.
+ * Bit 6 of the Packet Tag is the packet format indicator.
+ * If it is set, the new format is used, if cleared the
+ * old format is used.
+ *
+ * \see RFC4880 4.2
+ */
+#define PGP_PTAG_NEW_FORMAT 0x40
+
+/** Old Packet Format: Mask for content tag.
+ * In the old packet format bits 5 to 2 (including)
+ * are the content tag. This is the mask to apply
+ * to the packet tag. Note that you need to
+ * shift by #PGP_PTAG_OF_CONTENT_TAG_SHIFT bits.
+ *
+ * \see RFC4880 4.2
+ */
+#define PGP_PTAG_OF_CONTENT_TAG_MASK 0x3c
+/** Old Packet Format: Offset for the content tag.
+ * As described at #PGP_PTAG_OF_CONTENT_TAG_MASK the
+ * content tag needs to be shifted after being masked
+ * out from the Packet Tag.
+ *
+ * \see RFC4880 4.2
+ */
+#define PGP_PTAG_OF_CONTENT_TAG_SHIFT 2
+/** Old Packet Format: Mask for length type.
+ * Bits 1 and 0 of the packet tag are the length type
+ * in the old packet format.
+ *
+ * See #pgp_ptag_of_lt_t for the meaning of the values.
+ *
+ * \see RFC4880 4.2
+ */
+#define PGP_PTAG_OF_LENGTH_TYPE_MASK 0x03
+
+/* Maximum block size for symmetric crypto */
+#define PGP_MAX_BLOCK_SIZE 16
+
+/* Maximum key size for symmetric crypto */
+#define PGP_MAX_KEY_SIZE 32
+
+/* Salt size for hashing */
+#define PGP_SALT_SIZE 8
+
+/* 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 * 2) + 1
+
+/* Size of the key grip */
+#define PGP_KEY_GRIP_SIZE 20
+
+/* PGP marker packet contents */
+#define PGP_MARKER_CONTENTS "PGP"
+#define PGP_MARKER_LEN 3
+
+/** Old Packet Format Lengths.
+ * Defines the meanings of the 2 bits for length type in the
+ * old packet format.
+ *
+ * \see RFC4880 4.2.1
+ */
+typedef enum {
+ PGP_PTAG_OLD_LEN_1 = 0x00, /* Packet has a 1 byte length -
+ * header is 2 bytes long. */
+ PGP_PTAG_OLD_LEN_2 = 0x01, /* Packet has a 2 byte length -
+ * header is 3 bytes long. */
+ PGP_PTAG_OLD_LEN_4 = 0x02, /* Packet has a 4 byte
+ * length - header is 5 bytes
+ * long. */
+ PGP_PTAG_OLD_LEN_INDETERMINATE = 0x03 /* Packet has a
+ * indeterminate length. */
+} pgp_ptag_of_lt_t;
+
+/** New Packet Format: Mask for content tag.
+ * In the new packet format the 6 rightmost bits
+ * are the content tag. This is the mask to apply
+ * to the packet tag. Note that you need to
+ * shift by #PGP_PTAG_NF_CONTENT_TAG_SHIFT bits.
+ *
+ * \see RFC4880 4.2
+ */
+#define PGP_PTAG_NF_CONTENT_TAG_MASK 0x3f
+/** New Packet Format: Offset for the content tag.
+ * As described at #PGP_PTAG_NF_CONTENT_TAG_MASK the
+ * content tag needs to be shifted after being masked
+ * out from the Packet Tag.
+ *
+ * \see RFC4880 4.2
+ */
+#define PGP_PTAG_NF_CONTENT_TAG_SHIFT 0
+
+#define MDC_PKT_TAG 0xd3
+#define MDC_V1_SIZE 22
+
+typedef enum : uint8_t {
+ PGP_REVOCATION_NO_REASON = 0,
+ PGP_REVOCATION_SUPERSEDED = 1,
+ PGP_REVOCATION_COMPROMISED = 2,
+ PGP_REVOCATION_RETIRED = 3,
+ PGP_REVOCATION_NO_LONGER_VALID = 0x20
+} pgp_revocation_type_t;
+
+/**
+ * @brief OpenPGP packet tags. See section 4.3 of RFC4880 for the detailed description.
+ *
+ */
+typedef enum : uint8_t {
+ PGP_PKT_RESERVED = 0, /* Reserved - a packet tag must not have this value */
+ PGP_PKT_PK_SESSION_KEY = 1, /* Public-Key Encrypted Session Key Packet */
+ PGP_PKT_SIGNATURE = 2, /* Signature Packet */
+ PGP_PKT_SK_SESSION_KEY = 3, /* Symmetric-Key Encrypted Session Key Packet */
+ PGP_PKT_ONE_PASS_SIG = 4, /* One-Pass Signature Packet */
+ PGP_PKT_SECRET_KEY = 5, /* Secret Key Packet */
+ PGP_PKT_PUBLIC_KEY = 6, /* Public Key Packet */
+ PGP_PKT_SECRET_SUBKEY = 7, /* Secret Subkey Packet */
+ PGP_PKT_COMPRESSED = 8, /* Compressed Data Packet */
+ PGP_PKT_SE_DATA = 9, /* Symmetrically Encrypted Data Packet */
+ PGP_PKT_MARKER = 10, /* Marker Packet */
+ PGP_PKT_LITDATA = 11, /* Literal Data Packet */
+ PGP_PKT_TRUST = 12, /* Trust Packet */
+ PGP_PKT_USER_ID = 13, /* User ID Packet */
+ PGP_PKT_PUBLIC_SUBKEY = 14, /* Public Subkey Packet */
+ PGP_PKT_RESERVED2 = 15, /* Reserved */
+ PGP_PKT_RESERVED3 = 16, /* Reserved */
+ PGP_PKT_USER_ATTR = 17, /* User Attribute Packet */
+ PGP_PKT_SE_IP_DATA = 18, /* Sym. Encrypted and Integrity Protected Data Packet */
+ PGP_PKT_MDC = 19, /* Modification Detection Code Packet */
+ PGP_PKT_AEAD_ENCRYPTED = 20 /* AEAD Encrypted Data Packet, RFC 4880bis */
+} pgp_pkt_type_t;
+
+/** Public Key Algorithm Numbers.
+ * OpenPGP assigns a unique Algorithm Number to each algorithm that is part of OpenPGP.
+ *
+ * This lists algorithm numbers for public key algorithms.
+ *
+ * \see RFC4880 9.1
+ */
+typedef enum : uint8_t {
+ PGP_PKA_NOTHING = 0, /* No PKA */
+ PGP_PKA_RSA = 1, /* RSA (Encrypt or Sign) */
+ PGP_PKA_RSA_ENCRYPT_ONLY = 2, /* RSA Encrypt-Only (deprecated -
+ * \see RFC4880 13.5) */
+ PGP_PKA_RSA_SIGN_ONLY = 3, /* RSA Sign-Only (deprecated -
+ * \see RFC4880 13.5) */
+ PGP_PKA_ELGAMAL = 16, /* Elgamal (Encrypt-Only) */
+ PGP_PKA_DSA = 17, /* DSA (Digital Signature Algorithm) */
+ PGP_PKA_ECDH = 18, /* ECDH public key algorithm */
+ PGP_PKA_ECDSA = 19, /* ECDSA public key algorithm [FIPS186-3] */
+ PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN = 20, /* Elgamal Encrypt or Sign. Implementation MUST not
+ generate such keys and elgamal signatures. */
+ PGP_PKA_RESERVED_DH = 21, /* Reserved for Diffie-Hellman
+ * (X9.42, as defined for
+ * IETF-S/MIME) */
+ PGP_PKA_EDDSA = 22, /* EdDSA from draft-ietf-openpgp-rfc4880bis */
+ PGP_PKA_SM2 = 99, /* SM2 encryption/signature schemes */
+
+ PGP_PKA_PRIVATE00 = 100, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE01 = 101, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE02 = 102, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE03 = 103, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE04 = 104, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE05 = 105, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE06 = 106, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE07 = 107, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE08 = 108, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE09 = 109, /* Private/Experimental Algorithm */
+ PGP_PKA_PRIVATE10 = 110 /* Private/Experimental Algorithm */
+} pgp_pubkey_alg_t;
+
+/**
+ * Enumeration of elliptic curves used by PGP.
+ *
+ * \see RFC4880-bis01 9.2. ECC Curve OID
+ *
+ * Values in this enum correspond to order in ec_curve array (in ec.c)
+ */
+typedef enum {
+ PGP_CURVE_UNKNOWN = 0,
+ PGP_CURVE_NIST_P_256,
+ PGP_CURVE_NIST_P_384,
+ PGP_CURVE_NIST_P_521,
+ PGP_CURVE_ED25519,
+ PGP_CURVE_25519,
+ PGP_CURVE_BP256,
+ PGP_CURVE_BP384,
+ PGP_CURVE_BP512,
+ PGP_CURVE_P256K1,
+
+ PGP_CURVE_SM2_P_256,
+
+ // Keep always last one
+ PGP_CURVE_MAX
+} pgp_curve_t;
+
+/** Symmetric Key Algorithm Numbers.
+ * OpenPGP assigns a unique Algorithm Number to each algorithm that is
+ * part of OpenPGP.
+ *
+ * This lists algorithm numbers for symmetric key algorithms.
+ *
+ * \see RFC4880 9.2
+ */
+typedef enum {
+ PGP_SA_PLAINTEXT = 0, /* Plaintext or unencrypted data */
+ PGP_SA_IDEA = 1, /* IDEA */
+ PGP_SA_TRIPLEDES = 2, /* TripleDES */
+ PGP_SA_CAST5 = 3, /* CAST5 */
+ PGP_SA_BLOWFISH = 4, /* Blowfish */
+ PGP_SA_AES_128 = 7, /* AES with 128-bit key (AES) */
+ PGP_SA_AES_192 = 8, /* AES with 192-bit key */
+ PGP_SA_AES_256 = 9, /* AES with 256-bit key */
+ PGP_SA_TWOFISH = 10, /* Twofish with 256-bit key (TWOFISH) */
+ PGP_SA_CAMELLIA_128 = 11, /* Camellia with 128-bit key (CAMELLIA) */
+ PGP_SA_CAMELLIA_192 = 12, /* Camellia with 192-bit key */
+ PGP_SA_CAMELLIA_256 = 13, /* Camellia with 256-bit key */
+
+ PGP_SA_SM4 = 105, /* RNP extension - SM4 */
+ PGP_SA_UNKNOWN = 255
+} pgp_symm_alg_t;
+
+typedef enum {
+ PGP_CIPHER_MODE_NONE = 0,
+ PGP_CIPHER_MODE_CFB = 1,
+ PGP_CIPHER_MODE_CBC = 2,
+ PGP_CIPHER_MODE_OCB = 3,
+} pgp_cipher_mode_t;
+
+typedef enum {
+ PGP_AEAD_NONE = 0,
+ PGP_AEAD_EAX = 1,
+ PGP_AEAD_OCB = 2,
+ PGP_AEAD_UNKNOWN = 255
+} pgp_aead_alg_t;
+
+/** s2k_usage_t
+ */
+typedef enum {
+ PGP_S2KU_NONE = 0,
+ PGP_S2KU_ENCRYPTED_AND_HASHED = 254,
+ PGP_S2KU_ENCRYPTED = 255
+} pgp_s2k_usage_t;
+
+/** s2k_specifier_t
+ */
+typedef enum : uint8_t {
+ PGP_S2KS_SIMPLE = 0,
+ PGP_S2KS_SALTED = 1,
+ PGP_S2KS_ITERATED_AND_SALTED = 3,
+ PGP_S2KS_EXPERIMENTAL = 101
+} pgp_s2k_specifier_t;
+
+typedef enum {
+ PGP_S2K_GPG_NONE = 0,
+ PGP_S2K_GPG_NO_SECRET = 1,
+ PGP_S2K_GPG_SMARTCARD = 2
+} pgp_s2k_gpg_extension_t;
+
+/** Signature Type.
+ * OpenPGP defines different signature types that allow giving
+ * different meanings to signatures. Signature types include 0x10 for
+ * generitc User ID certifications (used when Ben signs Weasel's key),
+ * Subkey binding signatures, document signatures, key revocations,
+ * etc.
+ *
+ * Different types are used in different places, and most make only
+ * sense in their intended location (for instance a subkey binding has
+ * no place on a UserID).
+ *
+ * \see RFC4880 5.2.1
+ */
+typedef enum : uint8_t {
+ PGP_SIG_BINARY = 0x00, /* Signature of a binary document */
+ PGP_SIG_TEXT = 0x01, /* Signature of a canonical text document */
+ PGP_SIG_STANDALONE = 0x02, /* Standalone signature */
+
+ PGP_CERT_GENERIC = 0x10, /* Generic certification of a User ID and
+ * Public Key packet */
+ PGP_CERT_PERSONA = 0x11, /* Persona certification of a User ID and
+ * Public Key packet */
+ PGP_CERT_CASUAL = 0x12, /* Casual certification of a User ID and
+ * Public Key packet */
+ PGP_CERT_POSITIVE = 0x13, /* Positive certification of a
+ * User ID and Public Key packet */
+
+ PGP_SIG_SUBKEY = 0x18, /* Subkey Binding Signature */
+ PGP_SIG_PRIMARY = 0x19, /* Primary Key Binding Signature */
+ PGP_SIG_DIRECT = 0x1f, /* Signature directly on a key */
+
+ PGP_SIG_REV_KEY = 0x20, /* Key revocation signature */
+ PGP_SIG_REV_SUBKEY = 0x28, /* Subkey revocation signature */
+ PGP_SIG_REV_CERT = 0x30, /* Certification revocation signature */
+
+ PGP_SIG_TIMESTAMP = 0x40, /* Timestamp signature */
+
+ PGP_SIG_3RD_PARTY = 0x50 /* Third-Party Confirmation signature */
+} pgp_sig_type_t;
+
+/** Signature Subpacket Type
+ * Signature subpackets contains additional information about the signature
+ *
+ * \see RFC4880 5.2.3.1-5.2.3.26
+ */
+
+typedef enum {
+ PGP_SIG_SUBPKT_UNKNOWN = 0,
+ PGP_SIG_SUBPKT_RESERVED_1 = 1,
+ PGP_SIG_SUBPKT_CREATION_TIME = 2, /* signature creation time */
+ PGP_SIG_SUBPKT_EXPIRATION_TIME = 3, /* signature expiration time */
+ PGP_SIG_SUBPKT_EXPORT_CERT = 4, /* exportable certification */
+ PGP_SIG_SUBPKT_TRUST = 5, /* trust signature */
+ PGP_SIG_SUBPKT_REGEXP = 6, /* regular expression */
+ PGP_SIG_SUBPKT_REVOCABLE = 7, /* revocable */
+ PGP_SIG_SUBPKT_RESERVED_8 = 8,
+ PGP_SIG_SUBPKT_KEY_EXPIRY = 9, /* key expiration time */
+ PGP_SIG_SUBPKT_PLACEHOLDER = 10, /* placeholder for backward compatibility */
+ PGP_SIG_SUBPKT_PREFERRED_SKA = 11, /* preferred symmetric algs */
+ PGP_SIG_SUBPKT_REVOCATION_KEY = 12, /* revocation key */
+ PGP_SIG_SUBPKT_RESERVED_13 = 13,
+ PGP_SIG_SUBPKT_RESERVED_14 = 14,
+ PGP_SIG_SUBPKT_RESERVED_15 = 15,
+ PGP_SIG_SUBPKT_ISSUER_KEY_ID = 16, /* issuer key ID */
+ PGP_SIG_SUBPKT_RESERVED_17 = 17,
+ PGP_SIG_SUBPKT_RESERVED_18 = 18,
+ PGP_SIG_SUBPKT_RESERVED_19 = 19,
+ PGP_SIG_SUBPKT_NOTATION_DATA = 20, /* notation data */
+ PGP_SIG_SUBPKT_PREFERRED_HASH = 21, /* preferred hash algs */
+ PGP_SIG_SUBPKT_PREF_COMPRESS = 22, /* preferred compression algorithms */
+ PGP_SIG_SUBPKT_KEYSERV_PREFS = 23, /* key server preferences */
+ PGP_SIG_SUBPKT_PREF_KEYSERV = 24, /* preferred key Server */
+ PGP_SIG_SUBPKT_PRIMARY_USER_ID = 25, /* primary user ID */
+ PGP_SIG_SUBPKT_POLICY_URI = 26, /* policy URI */
+ PGP_SIG_SUBPKT_KEY_FLAGS = 27, /* key flags */
+ PGP_SIG_SUBPKT_SIGNERS_USER_ID = 28, /* signer's user ID */
+ PGP_SIG_SUBPKT_REVOCATION_REASON = 29, /* reason for revocation */
+ PGP_SIG_SUBPKT_FEATURES = 30, /* features */
+ PGP_SIG_SUBPKT_SIGNATURE_TARGET = 31, /* signature target */
+ PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE = 32, /* embedded signature */
+ PGP_SIG_SUBPKT_ISSUER_FPR = 33, /* issuer fingerprint */
+ PGP_SIG_SUBPKT_PREFERRED_AEAD = 34, /* preferred AEAD algorithms */
+ PGP_SIG_SUBPKT_PRIVATE_100 = 100, /* private/experimental subpackets */
+ PGP_SIG_SUBPKT_PRIVATE_101 = 101,
+ PGP_SIG_SUBPKT_PRIVATE_102 = 102,
+ PGP_SIG_SUBPKT_PRIVATE_103 = 103,
+ PGP_SIG_SUBPKT_PRIVATE_104 = 104,
+ PGP_SIG_SUBPKT_PRIVATE_105 = 105,
+ PGP_SIG_SUBPKT_PRIVATE_106 = 106,
+ PGP_SIG_SUBPKT_PRIVATE_107 = 107,
+ PGP_SIG_SUBPKT_PRIVATE_108 = 108,
+ PGP_SIG_SUBPKT_PRIVATE_109 = 109,
+ PGP_SIG_SUBPKT_PRIVATE_110 = 110
+} pgp_sig_subpacket_type_t;
+
+/** Key Flags
+ *
+ * \see RFC4880 5.2.3.21
+ */
+typedef enum {
+ PGP_KF_CERTIFY = 0x01, /* This key may be used to certify other keys. */
+ PGP_KF_SIGN = 0x02, /* This key may be used to sign data. */
+ PGP_KF_ENCRYPT_COMMS = 0x04, /* This key may be used to encrypt communications. */
+ PGP_KF_ENCRYPT_STORAGE = 0x08, /* This key may be used to encrypt storage. */
+ PGP_KF_SPLIT = 0x10, /* The private component of this key may have been split
+ by a secret-sharing mechanism. */
+ PGP_KF_AUTH = 0x20, /* This key may be used for authentication. */
+ PGP_KF_SHARED = 0x80, /* The private component of this key may be in the
+ possession of more than one person. */
+ /* pseudo flags */
+ PGP_KF_NONE = 0x00,
+ PGP_KF_ENCRYPT = PGP_KF_ENCRYPT_COMMS | PGP_KF_ENCRYPT_STORAGE,
+} pgp_key_flags_t;
+
+typedef enum {
+ PGP_KEY_FEATURE_MDC = 0x01,
+ PGP_KEY_FEATURE_AEAD = 0x02,
+ PGP_KEY_FEATURE_V5 = 0x04
+} pgp_key_feature_t;
+
+/** Types of Compression */
+typedef enum {
+ PGP_C_NONE = 0,
+ PGP_C_ZIP = 1,
+ PGP_C_ZLIB = 2,
+ PGP_C_BZIP2 = 3,
+ PGP_C_UNKNOWN = 255
+} pgp_compression_type_t;
+
+enum { PGP_SE_IP_DATA_VERSION = 1, PGP_PKSK_V3 = 3, PGP_SKSK_V4 = 4, PGP_SKSK_V5 = 5 };
+
+/** Version.
+ * OpenPGP has two different protocol versions: version 3 and version 4.
+ *
+ * \see RFC4880 5.2
+ */
+typedef enum {
+ PGP_VUNKNOWN = 0,
+ PGP_V2 = 2, /* Version 2 (essentially the same as v3) */
+ PGP_V3 = 3, /* Version 3 */
+ PGP_V4 = 4 /* Version 4 */
+} pgp_version_t;
+
+typedef enum pgp_op_t {
+ PGP_OP_UNKNOWN = 0,
+ PGP_OP_ADD_SUBKEY = 1, /* adding a subkey, primary key password required */
+ PGP_OP_SIGN = 2, /* signing file or data */
+ PGP_OP_DECRYPT = 3, /* decrypting file or data */
+ PGP_OP_UNLOCK = 4, /* unlocking a key with key->unlock() */
+ PGP_OP_PROTECT = 5, /* adding protection to a key */
+ PGP_OP_UNPROTECT = 6, /* removing protection from a (locked) key */
+ PGP_OP_DECRYPT_SYM = 7, /* symmetric decryption */
+ PGP_OP_ENCRYPT_SYM = 8, /* symmetric encryption */
+ PGP_OP_VERIFY = 9, /* signature verification */
+ PGP_OP_ADD_USERID = 10, /* adding a userid */
+ PGP_OP_MERGE_INFO = 11, /* merging information from one key to another */
+ PGP_OP_ENCRYPT = 12, /* public-key encryption */
+ PGP_OP_CERTIFY = 13 /* key certification */
+} pgp_op_t;
+
+/** Hashing Algorithm Numbers.
+ * OpenPGP assigns a unique Algorithm Number to each algorithm that is
+ * part of OpenPGP.
+ *
+ * This lists algorithm numbers for hash algorithms.
+ *
+ * \see RFC4880 9.4
+ */
+typedef enum : uint8_t {
+ PGP_HASH_UNKNOWN = 0, /* used to indicate errors */
+ PGP_HASH_MD5 = 1,
+ PGP_HASH_SHA1 = 2,
+ PGP_HASH_RIPEMD = 3,
+
+ PGP_HASH_SHA256 = 8,
+ PGP_HASH_SHA384 = 9,
+ PGP_HASH_SHA512 = 10,
+ PGP_HASH_SHA224 = 11,
+ PGP_HASH_SHA3_256 = 12,
+ PGP_HASH_SHA3_512 = 14,
+
+ /* Private range */
+ PGP_HASH_SM3 = 105,
+} pgp_hash_alg_t;
+
+typedef enum pgp_key_store_format_t {
+ PGP_KEY_STORE_UNKNOWN = 0,
+ PGP_KEY_STORE_GPG,
+ PGP_KEY_STORE_KBX,
+ PGP_KEY_STORE_G10,
+} pgp_key_store_format_t;
+
+namespace rnp {
+enum class AuthType { None, MDC, AEADv1 };
+}
+
+#endif
diff --git a/include/rnp.h b/include/rnp.h
new file mode 100644
index 0000000..0d666db
--- /dev/null
+++ b/include/rnp.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RNP_RNP_H
+#define RNP_RNP_H
+
+#include "types.h"
+#include "pass-provider.h"
+#include "key-provider.h"
+#include "crypto/rng.h"
+#include <rnp/rnp_def.h>
+#include "utils.h"
+
+#endif // RNP_RNP_H
diff --git a/include/rnp/rnp.h b/include/rnp/rnp.h
new file mode 100644
index 0000000..6667169
--- /dev/null
+++ b/include/rnp/rnp.h
@@ -0,0 +1,3150 @@
+/*-
+ * Copyright (c) 2017-2021 Ribose Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp_export.h>
+#include <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+#include <stdio.h>
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+/**
+ * Function return type. 0 == SUCCESS, all other values indicate an error.
+ */
+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)
+#define RNP_KEY_UNLOAD_SECRET (1U << 1)
+
+/**
+ * Flags for optional details to include in JSON.
+ */
+#define RNP_JSON_PUBLIC_MPIS (1U << 0)
+#define RNP_JSON_SECRET_MPIS (1U << 1)
+#define RNP_JSON_SIGNATURES (1U << 2)
+#define RNP_JSON_SIGNATURE_MPIS (1U << 3)
+
+/**
+ * Flags to include additional data in packet dumping
+ */
+#define RNP_JSON_DUMP_MPI (1U << 0)
+#define RNP_JSON_DUMP_RAW (1U << 1)
+#define RNP_JSON_DUMP_GRIP (1U << 2)
+
+#define RNP_DUMP_MPI (1U << 0)
+#define RNP_DUMP_RAW (1U << 1)
+#define RNP_DUMP_GRIP (1U << 2)
+
+/**
+ * Flags for the key loading/saving functions.
+ */
+#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
+ */
+
+#define RNP_KEY_SIGNATURE_INVALID (1U << 0)
+#define RNP_KEY_SIGNATURE_UNKNOWN_KEY (1U << 1)
+#define RNP_KEY_SIGNATURE_NON_SELF_SIG (1U << 2)
+
+#define RNP_KEY_SIGNATURE_KEEP (0U)
+#define RNP_KEY_SIGNATURE_REMOVE (1U)
+
+/**
+ * Flags for output structure creation.
+ */
+#define RNP_OUTPUT_FILE_OVERWRITE (1U << 0)
+#define RNP_OUTPUT_FILE_RANDOM (1U << 1)
+
+/**
+ * Flags for default key selection.
+ */
+#define RNP_KEY_SUBKEYS_ONLY (1U << 0)
+
+/**
+ * User id type
+ */
+#define RNP_USER_ID (1U)
+#define RNP_USER_ATTR (2U)
+
+/**
+ * Predefined feature security levels
+ */
+#define RNP_SECURITY_PROHIBITED (0U)
+#define RNP_SECURITY_INSECURE (1U)
+#define RNP_SECURITY_DEFAULT (2U)
+
+/**
+ * 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)
+#define RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT (1U << 2)
+
+/**
+ * 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();
+RNP_API const char *rnp_version_string_full();
+
+/** return a value representing the version of librnp
+ *
+ * This function is only useful for releases. For non-releases,
+ * it will return 0.
+ *
+ * The value returned can be used in comparisons by utilizing
+ * rnp_version_for.
+ *
+ * @return a value representing the librnp version
+ **/
+RNP_API uint32_t rnp_version();
+
+/** return a value representing a specific version of librnp
+ *
+ * This value can be used in comparisons.
+ *
+ * @return a value representing a librnp version
+ **/
+RNP_API uint32_t rnp_version_for(uint32_t major, uint32_t minor, uint32_t patch);
+
+/** return the librnp major version
+ *
+ * @return
+ **/
+RNP_API uint32_t rnp_version_major(uint32_t version);
+
+/** return the librnp minor version
+ *
+ * @return
+ **/
+RNP_API uint32_t rnp_version_minor(uint32_t version);
+
+/** return the librnp patch version
+ *
+ * @return
+ **/
+RNP_API uint32_t rnp_version_patch(uint32_t version);
+
+/** return a unix timestamp of the last commit, if available
+ *
+ * This function is only useful for non-releases. For releases,
+ * it will return 0.
+ *
+ * The intended usage is to provide a form of versioning for the main
+ * branch.
+ *
+ * @return the unix timestamp of the last commit, or 0 if unavailable
+ **/
+RNP_API uint64_t rnp_version_commit_timestamp();
+
+#ifndef RNP_NO_DEPRECATED
+/** @brief This function is deprecated and should not be used anymore. It would just silently
+ * return RNP_SUCCESS.
+ *
+ * @param file name of the sourcer file. Use 'all' to enable debug for all code.
+ *
+ */
+RNP_API RNP_DEPRECATED rnp_result_t rnp_enable_debug(const char *file);
+
+/**
+ * @brief This function is deprecated and should not be used anymore. It would just silently
+ * return RNP_SUCCESS.
+ *
+ */
+RNP_API RNP_DEPRECATED rnp_result_t rnp_disable_debug();
+#endif
+
+/*
+ * Opaque structures
+ */
+typedef struct rnp_ffi_st * rnp_ffi_t;
+typedef struct rnp_key_handle_st * rnp_key_handle_t;
+typedef struct rnp_input_st * rnp_input_t;
+typedef struct rnp_output_st * rnp_output_t;
+typedef struct rnp_op_generate_st * rnp_op_generate_t;
+typedef struct rnp_op_sign_st * rnp_op_sign_t;
+typedef struct rnp_op_sign_signature_st * rnp_op_sign_signature_t;
+typedef struct rnp_op_verify_st * rnp_op_verify_t;
+typedef struct rnp_op_verify_signature_st *rnp_op_verify_signature_t;
+typedef struct rnp_op_encrypt_st * rnp_op_encrypt_t;
+typedef struct rnp_identifier_iterator_st *rnp_identifier_iterator_t;
+typedef struct rnp_uid_handle_st * rnp_uid_handle_t;
+typedef struct rnp_signature_handle_st * rnp_signature_handle_t;
+typedef struct rnp_recipient_handle_st * rnp_recipient_handle_t;
+typedef struct rnp_symenc_handle_st * rnp_symenc_handle_t;
+
+/* Callbacks */
+/**
+ * @brief Callback, used to read data from the source.
+ *
+ * @param app_ctx custom parameter, passed back to the function.
+ * @param buf on successful call data should be put here. Cannot be NULL,
+ * and must be capable to store at least len bytes.
+ * @param len number of bytes to read.
+ * @param read on successful call number of read bytes must be put here.
+ * @return true on success (including EOF condition), or false on read error.
+ * EOF case is indicated by zero bytes read on non-zero read call.
+ */
+typedef bool rnp_input_reader_t(void *app_ctx, void *buf, size_t len, size_t *read);
+/**
+ * @brief Callback, used to close input stream.
+ *
+ * @param app_ctx custom parameter, passed back to the function.
+ * @return void
+ */
+typedef void rnp_input_closer_t(void *app_ctx);
+/**
+ * @brief Callback, used to write data to the output stream.
+ *
+ * @param app_ctx custom parameter, passed back to the function.
+ * @param buf buffer with data, cannot be NULL.
+ * @param len number of bytes to write.
+ * @return true if call was successful and all data is written, or false otherwise.
+ */
+typedef bool rnp_output_writer_t(void *app_ctx, const void *buf, size_t len);
+
+/**
+ * @brief Callback, used to close output stream.
+ *
+ * @param app_ctx custom parameter, passed back to the function.
+ * @param discard true if the already written data should be deleted.
+ * @return void
+ */
+typedef void rnp_output_closer_t(void *app_ctx, bool discard);
+
+/**
+ * Callback used for getting a password.
+ *
+ * @param ffi
+ * @param app_ctx provided by application
+ * @param key the key, if any, for which the password is being requested.
+ * Note: this key handle should not be held by the application,
+ * it is destroyed after the callback. It should only be used to
+ * retrieve information like the userids, grip, etc.
+ * @param pgp_context a descriptive string on why the password is being
+ * requested, may have one of the following values:
+ * - "add subkey": add subkey to the encrypted secret key
+ * - "add userid": add userid to the encrypted secret key
+ * - "sign": sign data
+ * - "decrypt": decrypt data using the encrypted secret key
+ * - "unlock": temporary unlock secret key (decrypting its fields), so it may be used
+ * later without need to decrypt
+ * - "protect": encrypt secret key fields
+ * - "unprotect": decrypt secret key fields, leaving those in a raw format
+ * - "decrypt (symmetric)": decrypt data, using the password
+ * - "encrypt (symmetric)": encrypt data, using the password
+ * @param buf to which the callback should write the returned password, NULL terminated.
+ * @param buf_len the size of buf
+ * @return true if a password was provided, false otherwise
+ */
+typedef bool (*rnp_password_cb)(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char buf[],
+ size_t buf_len);
+
+/** callback used to signal the application that a key is needed
+ *
+ * The application should use the appropriate functions (rnp_load_public_keys, etc)
+ * to load the requested key.
+ *
+ * This may be called multiple times for the same key. For example, if attempting
+ * to verify a signature, the signer's keyid may be used first to request the key.
+ * If that is not successful, the signer's fingerprint (if available) may be used.
+ *
+ * Please note that there is a special case with 'hidden' recipient, with all-zero keyid. In
+ * this case implementation should load all available secret keys for the decryption attempt
+ * (or do nothing, in this case decryption to the hidden recipient would fail).
+ *
+ * Situations in which this callback would be used include:
+ * - When decrypting data that includes a public-key encrypted session key,
+ * and the key is not found in the keyrings.
+ * - When attempting to verify a signature, when the signer's key is not found in
+ * the keyrings.
+ *
+ * @param ffi
+ * @param app_ctx provided by application in rnp_keyring_open
+ * @param identifier_type the type of identifier ("userid", "keyid", "grip")
+ * @param identifier the identifier for locating the key
+ * @param secret true if a secret key is being requested
+ */
+typedef void (*rnp_get_key_cb)(rnp_ffi_t ffi,
+ void * app_ctx,
+ const char *identifier_type,
+ const char *identifier,
+ bool secret);
+
+/**
+ * @brief callback used to report back signatures from the function
+ * rnp_key_remove_signatures(). This may be used to implement custom signature filtering
+ * code or record information about the signatures which are removed.
+ * @param ffi
+ * @param app_ctx custom context, provided by application.
+ * @param sig signature handle to retrieve information about the signature. Callback must not
+ * call rnp_signature_handle_destroy() on it.
+ * @param action action which will be performed on the signature. Currently defined are
+ * RNP_KEY_SIGNATURE_KEEP an RNP_KEY_SIGNATURE_REMOVE.
+ * Callback may overwrite this value.
+ *
+ */
+typedef void (*rnp_key_signatures_cb)(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_signature_handle_t sig,
+ uint32_t * action);
+
+/** create the top-level object used for interacting with the library
+ *
+ * @param ffi pointer that will be set to the created ffi object
+ * @param pub_format the format of the public keyring, RNP_KEYSTORE_GPG or other
+ * RNP_KEYSTORE_* constant
+ * @param sec_format the format of the secret keyring, RNP_KEYSTORE_GPG or other
+ * RNP_KEYSTORE_* constant
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_ffi_create(rnp_ffi_t * ffi,
+ const char *pub_format,
+ const char *sec_format);
+
+/** destroy the top-level object used for interacting with the library
+ *
+ * Note that this invalidates key handles, keyrings, and any other
+ * objects associated with this particular object.
+ *
+ * @param ffi the ffi object
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_ffi_destroy(rnp_ffi_t ffi);
+
+RNP_API rnp_result_t rnp_ffi_set_log_fd(rnp_ffi_t ffi, int fd);
+
+/**
+ * @brief Set key provider callback. This callback would be called in case when required public
+ * or secret key is not loaded to the keyrings.
+ *
+ * @param ffi initialized ffi object, cannot be NULL.
+ * @param getkeycb callback function. See rnp_get_key_cb documentation for details.
+ * @param getkeycb_ctx implementation-specific context, which would be passed to the getkeycb
+ * on invocation.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_ffi_set_key_provider(rnp_ffi_t ffi,
+ rnp_get_key_cb getkeycb,
+ void * getkeycb_ctx);
+RNP_API rnp_result_t rnp_ffi_set_pass_provider(rnp_ffi_t ffi,
+ rnp_password_cb getpasscb,
+ void * getpasscb_ctx);
+
+/* 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_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.
+ * @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_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_destroy.
+ * @param sec_format pointer that will be set to the format of the secret keyring.
+ * 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_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_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.
+ * Number of iterations is used to derive encryption key from password.
+ *
+ * @param hash hash algorithm to try
+ * @param msec number of milliseconds which will be needed to derive key from the password.
+ * Since it depends on CPU speed the calculated value will make sense only for the
+ * system it was calculated for.
+ * @param iterations approximate number of iterations to satisfy time complexity.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_calculate_iterations(const char *hash,
+ size_t msec,
+ size_t * iterations);
+
+/** Check whether rnp supports specific feature (algorithm, elliptic curve, whatever else).
+ *
+ * @param type string with the feature type. See RNP_FEATURE_* defines for the supported
+ * values.
+ * @param name value of the feature to check whether it is supported.
+ * @param supported will contain true or false depending whether feature is supported or not.
+ * @return RNP_SUCCESS on success or any other value on error.
+ */
+RNP_API rnp_result_t rnp_supports_feature(const char *type, const char *name, bool *supported);
+
+/** Get the JSON with array of supported rnp feature values (algorithms, curves, etc) by type.
+ *
+ * @param type type of the feature. See RNP_FEATURE_* defines for the supported values.
+ * @param result after successful execution will contain the JSON with supported feature
+ * values. You must destroy it using the rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS on success or any other value on error.
+ */
+RNP_API rnp_result_t rnp_supported_features(const char *type, char **result);
+
+/**
+ * @brief Add new security rule to the FFI. Security rules allows to override default algorithm
+ * security settings by disabling them or marking as insecure. After creation of FFI
+ * object default rules are added, however caller may add more strict rules or
+ * completely overwrite rule table by calling rnp_remove_security_rule().
+ * Note: key signature validation status is cached, so rules should be changed before
+ * keyrings are loaded or keyring should be reloaded after updating rules.
+ *
+ * @param ffi initialized FFI object.
+ * @param type type of the feature, cannot be NULL. Currently only RNP_FEATURE_HASH_ALG is
+ * supported.
+ * @param name name of the feature, i.e. SHA1, MD5. The same values are used in
+ * rnp_supports_feature()/rnp_supported_features().
+ * @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
+ * security level (i.e., valid), whereas all signatures made after 2021-01-01 will
+ * be marked as 'insecure' (i.e., invalid).
+ * @param level security level of the rule. Currently the following ones are defined:
+ * - RNP_SECURITY_PROHIBITED : feature (for instance, MD5 algorithm) is completely
+ * disabled, so no processing can be done. In terms of signature check, that
+ * would mean the check will fail right after the hashing begins.
+ * Note: Currently it works in the same way as RNP_SECURITY_INSECURE.
+ * - RNP_SECURITY_INSECURE : feature (for instance, SHA1 algorithm) is marked as
+ * insecure. So even valid signatures, produced later than `from`, will be
+ * marked as invalid.
+ * - RNP_SECURITY_DEFAULT : feature is secure enough. Default value when there are
+ * no other rules for feature.
+ *
+ * @return RNP_SUCCESS or any other value on error.
+ */
+RNP_API rnp_result_t rnp_add_security_rule(rnp_ffi_t ffi,
+ const char *type,
+ const char *name,
+ uint32_t flags,
+ uint64_t from,
+ uint32_t level);
+
+/**
+ * @brief Get security rule applicable for the corresponding feature value and timestamp.
+ * Note: if there is no matching rule, it will fall back to the default security level
+ * with empty flags and `from`.
+ *
+ * @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. 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,
+ const char *type,
+ const char *name,
+ uint64_t time,
+ uint32_t * flags,
+ uint64_t * from,
+ uint32_t * level);
+
+/**
+ * @brief Remove security rule(s), matching the parameters.
+ * Note: use this with caution, as this may also clear default security rules, so
+ * all affected features would be considered of the default security level.
+ *
+ * @param ffi populated FFI structure, cannot be NULL.
+ * @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
+ * matched, execution will be marked as successful. Use the `removed` parameter to
+ * check for this case.
+ */
+RNP_API rnp_result_t rnp_remove_security_rule(rnp_ffi_t ffi,
+ const char *type,
+ const char *name,
+ uint32_t level,
+ uint32_t flags,
+ uint64_t from,
+ size_t * removed);
+
+/**
+ * @brief Request password via configured FFI's callback
+ *
+ * @param ffi initialized FFI structure
+ * @param key key handle for which password is requested. May be NULL.
+ * @param context string describing the purpose of password request. See description of
+ * rnp_password_cb for the list of possible values. Also you may use any
+ * custom one as far as your password callback handles it.
+ * @param password password will be put here on success. Must be destroyed via
+ * rnp_buffer_destroy(), also it is good idea to securely clear it via
+ * rnp_buffer_clear().
+ * @return RNP_SUCCESS or other value on error.
+ */
+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
+ * @param format the key format of the data (GPG, KBX, G10). Must not be NULL.
+ * @param input source to read from.
+ * @param flags the flags. See RNP_LOAD_SAVE_*.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_load_keys(rnp_ffi_t ffi,
+ const char *format,
+ rnp_input_t input,
+ uint32_t flags);
+
+/** unload public and/or secret keys
+ * Note: After unloading all key handles will become invalid and must be destroyed.
+ * @param ffi
+ * @param flags choose which keys should be unloaded (pubic, secret or both).
+ * See RNP_KEY_UNLOAD_PUBLIC/RNP_KEY_UNLOAD_SECRET.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_unload_keys(rnp_ffi_t ffi, uint32_t flags);
+
+/** import keys to the keyring and receive JSON list of the new/updated keys.
+ * Note: this will work only with keys in OpenPGP format, use rnp_load_keys for other formats.
+ * @param ffi
+ * @param input source to read from. Cannot be NULL.
+ * @param flags see RNP_LOAD_SAVE_* constants. If RNP_LOAD_SAVE_PERMISSIVE is specified
+ * then import process will skip unrecognized or bad keys/signatures instead of
+ * failing the whole operation.
+ * 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)
+ * any other value on error.
+ */
+RNP_API rnp_result_t rnp_import_keys(rnp_ffi_t ffi,
+ rnp_input_t input,
+ uint32_t flags,
+ char ** results);
+
+/** import standalone signatures to the keyring and receive JSON list of the updated keys.
+ *
+ * @param ffi
+ * @param input source to read from. Cannot be NULL.
+ * @param flags additional import flags, currently must be 0.
+ * @param results if not NULL then after the successful execution will contain JSON with
+ * information about the updated keys. You must free it using the
+ * rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_import_signatures(rnp_ffi_t ffi,
+ rnp_input_t input,
+ uint32_t flags,
+ char ** results);
+
+/** save keys
+ *
+ * Note that for G10, the output must be a directory (which must already exist).
+ *
+ * @param ffi
+ * @param format the key format of the data (GPG, KBX, G10). Must not be NULL.
+ * @param output the output destination to write to.
+ * @param flags the flags. See RNP_LOAD_SAVE_*.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_save_keys(rnp_ffi_t ffi,
+ const char * format,
+ rnp_output_t output,
+ uint32_t flags);
+
+RNP_API rnp_result_t rnp_get_public_key_count(rnp_ffi_t ffi, size_t *count);
+RNP_API rnp_result_t rnp_get_secret_key_count(rnp_ffi_t ffi, size_t *count);
+
+/** Search for the key
+ * Note: only valid userids are checked while searching by userid.
+ *
+ * @param ffi
+ * @param identifier_type string with type of the identifier: userid, keyid, fingerprint, grip
+ * @param identifier for userid is the userid string, for other search types - hex string
+ * representation of the value
+ * @param key if key was found then the resulting key handle will be stored here, otherwise it
+ * will contain NULL value. You must free handle after use with rnp_key_handle_destroy.
+ * @return RNP_SUCCESS on success (including case where key is not found), or any other value
+ * on error
+ */
+RNP_API rnp_result_t rnp_locate_key(rnp_ffi_t ffi,
+ const char * identifier_type,
+ const char * identifier,
+ rnp_key_handle_t *key);
+
+RNP_API rnp_result_t rnp_key_handle_destroy(rnp_key_handle_t key);
+
+/** generate a key or pair of keys using a JSON description
+ *
+ * Notes:
+ * - When generating a subkey, the pass provider may be required.
+ *
+ * @param ffi
+ * @param json the json data that describes the key generation.
+ * Must not be NULL.
+ * @param results pointer that will be set to the JSON results.
+ * 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_generate_key_json(rnp_ffi_t ffi, const char *json, char **results);
+
+/* Key operations */
+
+/** Shortcut function for rsa key-subkey pair generation. See rnp_generate_key_ex() for the
+ * detailed parameters description.
+ */
+RNP_API rnp_result_t rnp_generate_key_rsa(rnp_ffi_t ffi,
+ uint32_t bits,
+ uint32_t subbits,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key);
+
+/** Shortcut function for DSA/ElGamal key-subkey pair generation. See rnp_generate_key_ex() for
+ * the detailed parameters description.
+ */
+RNP_API rnp_result_t rnp_generate_key_dsa_eg(rnp_ffi_t ffi,
+ uint32_t bits,
+ uint32_t subbits,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key);
+
+/** Shortcut function for ECDSA/ECDH key-subkey pair generation. See rnp_generate_key_ex() for
+ * the detailed parameters description.
+ */
+RNP_API rnp_result_t rnp_generate_key_ec(rnp_ffi_t ffi,
+ const char * curve,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key);
+
+/** Shortcut function for EdDSA/x25519 key-subkey pair generation. See rnp_generate_key_ex()
+ * for the detailed parameters description.
+ */
+RNP_API rnp_result_t rnp_generate_key_25519(rnp_ffi_t ffi,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key);
+
+/** Shortcut function for SM2/SM2 key-subkey pair generation. See rnp_generate_key_ex() for
+ * for the detailed parameters description.
+ */
+RNP_API rnp_result_t rnp_generate_key_sm2(rnp_ffi_t ffi,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key);
+
+/**
+ * @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.
+ * Must be zero for EC-based primary key algorithm (use curve instead).
+ * @param sub_bits size of subkey in bits. If zero then default value will be used.
+ * Must be zero for EC-based subkey algorithm (use scurve instead).
+ * @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,
+ const char * key_alg,
+ const char * sub_alg,
+ uint32_t key_bits,
+ uint32_t sub_bits,
+ const char * key_curve,
+ const char * sub_curve,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key);
+
+/** Create key generation context for the primary key.
+ * To generate a subkey use function rnp_op_generate_subkey_create() instead.
+ * Note: pass provider is required if generated key needs protection.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param ffi
+ * @param alg key algorithm as string. Must be able to sign. Currently the following algorithms
+ * are supported (case-insensetive) : 'rsa', 'dsa', 'ecdsa', 'eddsa', 'sm2'.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_create(rnp_op_generate_t *op,
+ rnp_ffi_t ffi,
+ const char * alg);
+
+/** Create key generation context for the subkey.
+ * Note: you need to have primary key before calling this function. It can be loaded from
+ * keyring or generated via the function rnp_op_generate_create(). Also pass provider is needed
+ * if primary key is encrypted (protected and locked).
+ *
+ * @param op pointer to opaque key generation context.
+ * @param ffi
+ * @param primary primary key handle, must have secret part.
+ * @param alg key algorithm as string. Currently the following algorithms are supported
+ * (case-insensetive) : 'rsa', 'dsa', 'elgamal', 'ecdsa', 'eddsa', 'ecdh', 'sm2'.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_subkey_create(rnp_op_generate_t *op,
+ rnp_ffi_t ffi,
+ rnp_key_handle_t primary,
+ const char * alg);
+
+/** Set bits of the generated key or subkey.
+ * Note: this is applicable only to rsa, dsa and el-gamal keys.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param bits number of bits
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_bits(rnp_op_generate_t op, uint32_t bits);
+
+/** Set hash algorithm used in self signature or subkey binding signature.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param hash string with hash algorithm name. Following hash algorithms are supported:
+ * "MD5", "SHA1", "RIPEMD160", "SHA256", "SHA384", "SHA512", "SHA224", "SM3"
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_hash(rnp_op_generate_t op, const char *hash);
+
+/** Set size of q parameter for DSA key.
+ * Note: appropriate default value will be set, depending on key bits. However you may
+ * override it if needed.
+ * @param op pointer to opaque key generation context.
+ * @param qbits number of bits
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_dsa_qbits(rnp_op_generate_t op, uint32_t qbits);
+
+/** Set the curve used for ECC key
+ * Note: this is only applicable for ECDSA, ECDH and SM2 keys.
+ * @param op pointer to opaque key generation context.
+ * @param curve string with curve name. Following curve names may be used:
+ * "NIST P-256", "NIST P-384", "NIST P-521", "Curve25519" (ECDH only),
+ * "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "secp256k1",
+ * "SM2 P-256" (SM2 only)
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_curve(rnp_op_generate_t op, const char *curve);
+
+/** Set password, used to encrypt secret key data. If this method is not called then
+ * key will be generated without protection (unencrypted).
+ *
+ * @param op pointer to opaque key generation context.
+ * @param password string with password, could not be NULL. Will be copied internally so may
+ * be safely freed after the call.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_protection_password(rnp_op_generate_t op,
+ const char * password);
+
+/**
+ * @brief Enable or disable password requesting via ffi's password provider. This password
+ * then will be used for key encryption.
+ * Note: this will be ignored if password was set via
+ * rnp_op_generate_set_protection_password().
+ *
+ * @param op pointer to opaque key generation context.
+ * @param request true to enable password requesting or false otherwise. Default value is false
+ * (i.e. key will be generated unencrypted).
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_request_password(rnp_op_generate_t op, bool request);
+
+/** Set cipher used to encrypt secret key data. If not called then default one will be used.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param cipher string with cipher name. Following ciphers are supported:
+ * "Idea", "Tripledes", "Cast5", "Blowfish", "AES128", "AES192", "AES256",
+ * "Twofish", "Camellia128", "Camellia192", "Camellia256", "SM4".
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_protection_cipher(rnp_op_generate_t op,
+ const char * cipher);
+
+/** Set hash algorithm, used to derive key from password for secret key data encryption.
+ * If not called then default one will be used.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param hash string with hash algorithm, see rnp_op_generate_set_hash() for the whole list.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_protection_hash(rnp_op_generate_t op,
+ const char * hash);
+
+/** Set encryption mode, used for secret key data encryption.
+ * Note: currently this makes sense only for G10 key format
+ *
+ * @param op pointer to opaque key generation context.
+ * @param mode string with mode name: "CFB", "CBC", "OCB"
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_protection_mode(rnp_op_generate_t op,
+ const char * mode);
+
+/** Set number of iterations used to derive key from password for secret key encryption.
+ * If not called then default one will be used.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param iterations number of iterations
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_protection_iterations(rnp_op_generate_t op,
+ uint32_t iterations);
+
+/** Add key usage flag to the key or subkey.
+ * Note: use it only if you need to override defaults, which depend on primary key or subkey,
+ * and public key algorithm.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param usage string, representing key usage. Following values are supported: "sign",
+ * "certify", "encrypt", "authenticate".
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_add_usage(rnp_op_generate_t op, const char *usage);
+
+/** Reset key usage flags, so default ones will be used during key/subkey generation
+ *
+ * @param op pointer to opaque key generation context.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_clear_usage(rnp_op_generate_t op);
+
+/** Set the userid which will represent the generate key.
+ * Note: Makes sense only for primary key generation.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param userid NULL-terminated string with userid.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_userid(rnp_op_generate_t op, const char *userid);
+
+/** Set the key or subkey expiration time.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param expiration expiration time in seconds. 0 value means that key doesn't expire.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_expiration(rnp_op_generate_t op, uint32_t expiration);
+
+/** Add preferred hash to user preferences.
+ * Note: the first added hash algorithm has the highest priority, then the second and so on.
+ * Applicable only for the primary key generation.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param hash string, representing the hash algorithm. See the rnp_op_generate_set_hash()
+ * function description for the list of possible values.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_add_pref_hash(rnp_op_generate_t op, const char *hash);
+
+/** Clear the preferred hash algorithms list, so default ones will be used.
+ *
+ * @param op pointer to opaque key generation context.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_clear_pref_hashes(rnp_op_generate_t op);
+
+/** Add preferred compression algorithm to user preferences.
+ * Note: the first added algorithm has the highest priority, then the second and so on.
+ * Applicable only for the primary key generation.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param compression string, representing the compression algorithm. Possible values are:
+ * "zip", "zlib", "bzip2"
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_add_pref_compression(rnp_op_generate_t op,
+ const char * compression);
+
+/** Clear the preferred compression algorithms list, so default ones will be used.
+ *
+ * @param op pointer to opaque key generation context.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_clear_pref_compression(rnp_op_generate_t op);
+
+/** Add preferred encryption algorithm to user preferences.
+ * Note: the first added algorithm has the highest priority, then the second and so on.
+ * Applicable only for the primary key generation.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param cipher string, representing the encryption algorithm.
+ * See the rnp_op_generate_set_protection_cipher() function description for
+ * the list of possible values.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_add_pref_cipher(rnp_op_generate_t op, const char *cipher);
+
+/** Clear the preferred encryption algorithms list, so default ones will be used.
+ *
+ * @param op pointer to opaque key generation context.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_clear_pref_ciphers(rnp_op_generate_t op);
+
+/** Set the preferred key server. Applicable only for the primary key.
+ *
+ * @param op pointer to opaque key generation context.
+ * @param keyserver NULL-terminated string with key server's URL, or NULL to delete it from
+ * user preferences.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_set_pref_keyserver(rnp_op_generate_t op,
+ const char * keyserver);
+
+/** Execute the prepared key or subkey generation operation.
+ * Note: if you set protection algorithm, then you need to specify ffi password provider to
+ * be able to request password for key encryption.
+ *
+ * @param op pointer to opaque key generation context.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_op_generate_execute(rnp_op_generate_t op);
+
+/** Get the generated key's handle. Should be called only after successful execution of
+ * rnp_op_generate_execute().
+ *
+ * @param op pointer to opaque key generation context.
+ * @param handle pointer to key handle will be stored here.
+ * 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 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.
+ */
+RNP_API rnp_result_t rnp_op_generate_destroy(rnp_op_generate_t op);
+
+/** export a key
+ *
+ * @param key the key to export
+ * @param output the stream to write to
+ * @param flags see RNP_KEY_EXPORT_*.
+ * @return RNP_SUCCESS on success, or any other value on error
+ **/
+RNP_API rnp_result_t rnp_key_export(rnp_key_handle_t key, rnp_output_t output, uint32_t flags);
+
+/**
+ * @brief Export minimal key for autocrypt feature (just 5 packets: key, uid, signature,
+ * encryption subkey, signature)
+ *
+ * @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. 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,
+ rnp_output_t output,
+ uint32_t flags);
+
+/**
+ * @brief Generate and export primary key revocation signature.
+ * Note: to revoke a key you'll need to import this signature into the keystore or use
+ * 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 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
+ * string.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_key_export_revocation(rnp_key_handle_t key,
+ rnp_output_t output,
+ uint32_t flags,
+ const char * hash,
+ const char * code,
+ const char * reason);
+
+/**
+ * @brief revoke a key or subkey by generating and adding revocation signature.
+ * @param key key or subkey to be revoked. For primary key must have secret key, otherwise
+ * keyrings will be searched for the authorized to issue revocation signatures
+ * secret key. For subkey keyrings must have primary secret key.
+ * If secret key is locked then password will be asked via password provider.
+ * @param flags currently must be 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
+ * string.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_key_revoke(rnp_key_handle_t key,
+ uint32_t flags,
+ const char * hash,
+ const char * code,
+ const char * reason);
+
+/**
+ * @brief Check whether Curve25519 secret key's bits are correctly set, i.e. 3 least
+ * significant bits are zero and key is exactly 255 bits in size. See RFC 7748, section
+ * 5 for the details. RNP interpreted RFC requirements in the way that Curve25519 secret
+ * key is random 32-byte string, which bits are correctly tweaked afterwards within
+ * secret key operation. However, for compatibility reasons, it would be more correct to
+ * store/transfer secret key with bits already tweaked.
+ *
+ * Note: this operation requires unlocked secret key, so make sure to call
+ * rnp_key_lock() afterwards.
+ *
+ * @param key key handle, cannot be NULL. Must be ECDH Curve25519 unlocked secret key.
+ * @param result true will be stored here if secret key's low/high bits are not correctly set.
+ * In this case you may need to call `rnp_key_25519_bits_tweak()` on it to set
+ * bits to correct values so exported secret key will be compatible with
+ * implementations which do not tweak these bits automatically.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_25519_bits_tweaked(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Make sure Curve25519 secret key's least significant and most significant bits are
+ * correctly set, see rnp_key_25519_bits_tweaked() documentation for the details.
+ * Note: this operation requires unprotected secret key since it would modify secret
+ * key's data, so make sure to call rnp_key_protect() afterwards.
+ *
+ * @param key key handle, cannot be NULL. Must be ECDH Curve25519 unprotected secret key.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_25519_bits_tweak(rnp_key_handle_t key);
+
+/** remove a key from keyring(s)
+ * Note: you need to call rnp_save_keys() to write updated keyring(s) out.
+ * Other handles of the same key should not be used after this call.
+ * @param key pointer to the key handle.
+ * @param flags see RNP_KEY_REMOVE_* constants. Flag RNP_KEY_REMOVE_SUBKEYS will work only for
+ * primary key, and remove all of its subkeys as well.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_remove(rnp_key_handle_t key, uint32_t flags);
+
+/**
+ * @brief Remove unneeded signatures from the key, it's userids and subkeys if any.
+ * May be called on subkey handle as well.
+ * Note: you'll need to call rnp_save_keys() to write updated keyring(s) out.
+ * Any signature handles related to this key, it's uids or subkeys should not be used
+ * after this call.
+ *
+ * @param key key handle, cannot be NULL.
+ * @param flags flags, controlling which signatures to remove. Signature will be removed if it
+ * matches at least one of these flags.
+ * Currently following signature matching flags are defined:
+ * - RNP_KEY_SIGNATURE_INVALID : signature is invalid and was never valid. Note,
+ * that this will not remove invalid signature if there is no signer's public
+ * key in the keyring.
+ * - RNP_KEY_SIGNATURE_UNKNOWN_KEY : signature is made by the key which is not
+ * known/available.
+ * - RNP_KEY_SIGNATURE_NON_SELF_SIG : signature is not a self-signature (i.e. made
+ * by the key itself or corresponding primary key).
+ *
+ * Note: if RNP_KEY_SIGNATURE_NON_SELF_SIG is not specified then function will
+ * attempt to validate all the signatures, and look up for the signer's public key
+ * via keyring/key provider.
+ *
+ * @param sigcb callback, used to record information about the removed signatures, or further
+ * filter out the signatures. May be NULL.
+ * @param app_ctx context information, passed to sigcb. May be NULL.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_remove_signatures(rnp_key_handle_t key,
+ uint32_t flags,
+ rnp_key_signatures_cb sigcb,
+ void * app_ctx);
+
+/**
+ * @brief Guess contents of the OpenPGP data stream.
+ * Note: This call just peeks data from the stream, so stream is still usable for
+ * the further processing.
+ * @param input stream with data. Must be opened and cannot be NULL.
+ * @param contents string with guessed data format will be stored here.
+ * Possible values: 'message', 'public key', 'secret key', 'signature',
+ * 'unknown'. May be used as type in rnp_enarmor() function. Must be
+ * deallocated with rnp_buffer_destroy() call.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_guess_contents(rnp_input_t input, char **contents);
+
+/** Add ASCII Armor
+ *
+ * @param input stream to read data from
+ * @param output stream to write armored data to
+ * @param type the type of armor to add ("message", "public key",
+ * "secret key", "signature", "cleartext"). Use NULL to try
+ * to guess the type.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_enarmor(rnp_input_t input, rnp_output_t output, const char *type);
+
+/** Remove ASCII Armor
+ *
+ * @param input stream to read armored data from
+ * @param output stream to write dearmored data to
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_dearmor(rnp_input_t input, rnp_output_t output);
+
+/** Get key's primary user id.
+ * Note: userid considered as primary if it has marked as primary in self-certification, and
+ * is valid (i.e. both certification and key are valid, not expired and not revoked). If
+ * there is no userid marked as primary then the first valid userid handle will be
+ * returned.
+ * @param key key handle.
+ * @param uid pointer to the string with primary user id will be stored here.
+ * You must free it using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_primary_uid(rnp_key_handle_t key, char **uid);
+
+/** Get number of the key's user ids.
+ *
+ * @param key key handle.
+ * @param count number of user ids will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_uid_count(rnp_key_handle_t key, size_t *count);
+
+/** Get key's user id by its index.
+ *
+ * @param key key handle.
+ * @param idx zero-based index of the userid.
+ * @param uid pointer to the string with user id will be stored here.
+ * You must free it using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_uid_at(rnp_key_handle_t key, size_t idx, char **uid);
+
+/** Get key's user id handle by its index.
+ * Note: user id handle may become invalid once corresponding user id or key is removed.
+ *
+ * @param key key handle
+ * @param idx zero-based index of the userid.
+ * @param uid user id handle will be stored here on success. You must destroy it
+ * using the rnp_uid_handle_destroy().
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_uid_handle_at(rnp_key_handle_t key,
+ size_t idx,
+ rnp_uid_handle_t *uid);
+
+/** Get userid's type. Currently two possible values are defined:
+ * - RNP_USER_ID - string representation of user's name and email.
+ * - RNP_USER_ATTR - binary photo of the user
+
+ * @param uid uid handle, cannot be NULL.
+ * @param type on success userid type will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_get_type(rnp_uid_handle_t uid, uint32_t *type);
+
+/** Get userid's data. Representation of data depends on userid type (see rnp_uid_get_type()
+ * function)
+ *
+ * @param uid uid handle, cannot be NULL.
+ * @param data cannot be NULL. On success pointer to the allocated buffer with data will be
+ * stored here. Must be deallocated by caller via rnp_buffer_destroy().
+ * @param size cannot be NULL. On success size of the data will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_get_data(rnp_uid_handle_t uid, void **data, size_t *size);
+
+/** Check whether uid is marked as primary.
+ *
+ * @param uid uid handle, cannot be NULL
+ * @param primary cannot be NULL. On success true or false will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_is_primary(rnp_uid_handle_t uid, bool *primary);
+
+/** Get userid validity status. Userid is considered as valid if key itself is valid, and
+ * userid has at least one valid, non-expired self-certification.
+ * Note: - userid still may be valid even if a primary key is invalid - expired, revoked, etc.
+ * - up to the RNP version 0.15.1 uid was not considered as valid if it's latest
+ * self-signature has key expiration in the past.
+ *
+ * @param uid user id handle.
+ * @param valid validity status will be stored here on success.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_is_valid(rnp_uid_handle_t uid, bool *valid);
+
+/** Get number of key's signatures.
+ * Note: this will not count user id certifications and subkey(s) signatures if any.
+ * I.e. it will return only number of direct-key and key revocation signatures for the
+ * primary key, and number of subkey bindings/revocation signatures for the subkey.
+ * Use rnp_uid_get_signature_count() or call this function on subkey's handle.
+ *
+ * @param key key handle
+ * @param count number of key's signatures will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_signature_count(rnp_key_handle_t key, size_t *count);
+
+/** Get key's signature, based on its index.
+ * Note: see the rnp_key_get_signature_count() description for the details.
+ *
+ * @param key key handle
+ * @param idx zero-based signature index.
+ * @param sig signature handle will be stored here on success. You must free it after use with
+ * the rnp_signature_handle_destroy() function.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_signature_at(rnp_key_handle_t key,
+ size_t idx,
+ rnp_signature_handle_t *sig);
+
+/**
+ * @brief Get key's revocation signature handle, if any.
+ *
+ * @param key key handle
+ * @param sig signature handle or NULL will be stored here on success. NULL will be stored in
+ * case when there is no valid revocation signature.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_revocation_signature(rnp_key_handle_t key,
+ rnp_signature_handle_t *sig);
+
+/** Get the number of user id's signatures.
+ *
+ * @param uid user id handle.
+ * @param count number of uid's signatures will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_get_signature_count(rnp_uid_handle_t uid, size_t *count);
+
+/** Get user id's signature, based on its index.
+ *
+ * @param uid uid handle.
+ * @param idx zero-based signature index.
+ * @param sig signature handle will be stored here on success. You must free it after use with
+ * the rnp_signature_handle_destroy() function.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_get_signature_at(rnp_uid_handle_t uid,
+ size_t idx,
+ rnp_signature_handle_t *sig);
+
+/**
+ * @brief Get signature's type.
+ *
+ * @param sig signature handle.
+ * @param type on success string with signature type will be saved here. Cannot be NULL.
+ * You must free it using the rnp_buffer_destroy().
+ * Currently defined values are:
+ * - 'binary' : signature of a binary document
+ * - 'text' : signature of a canonical text document
+ * - 'standalone' : standalone signature
+ * - 'certification (generic)` : generic certification of a user id
+ * - 'certification (persona)' : persona certification of a user id
+ * - 'certification (casual)' : casual certification of a user id
+ * - 'certification (positive)' : positive certification of a user id
+ * - 'subkey binding' : subkey binding signature
+ * - 'primary key binding' : primary key binding signature
+ * - 'direct' : direct-key signature
+ * - 'key revocation' : primary key revocation signature
+ * - 'subkey revocation' : subkey revocation signature
+ * - 'certification revocation' : certification revocation signature
+ * - 'timestamp' : timestamp signature
+ * - 'third-party' : third party confirmation signature
+ * - 'uknown: 0..255' : unknown signature with its type specified as number
+ *
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_get_type(rnp_signature_handle_t sig, char **type);
+
+/** Get signature's algorithm.
+ *
+ * @param sig signature handle.
+ * @param alg on success string with algorithm name will be saved here. Cannot be NULL.
+* You must free it using the rnp_buffer_destroy().
+
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_get_alg(rnp_signature_handle_t sig, char **alg);
+
+/** Get signature's hash algorithm.
+ *
+ * @param sig signature handle.
+ * @param alg on success string with algorithm name will be saved here. Cannot be NULL.
+ * You must free it using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_get_hash_alg(rnp_signature_handle_t sig, char **alg);
+
+/** Get the signature creation time as number of seconds since Jan, 1 1970 UTC
+ *
+ * @param sig signature handle.
+ * @param create on success result will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_get_creation(rnp_signature_handle_t sig, uint32_t *create);
+
+/** Get the signature expiration time as number of seconds after creation time
+ *
+ * @param sig signature handle.
+ * @param expires on success result will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_get_expiration(rnp_signature_handle_t sig,
+ uint32_t * expires);
+
+/** Get signer's key id from the signature.
+ * Note: if key id is not available from the signature then NULL value will
+ * be stored to result.
+ * @param sig signature handle
+ * @param result hex-encoded key id 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_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.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_get_signer(rnp_signature_handle_t sig,
+ rnp_key_handle_t * key);
+
+/**
+ * @brief Get signature validity, revalidating it if didn't before.
+ *
+ * @param sig key/userid signature handle
+ * @param flags validation flags, currently must be zero.
+ * @return Following error codes represents the validation status:
+ * RNP_SUCCESS : operation succeeds and signature is valid
+ * RNP_ERROR_KEY_NOT_FOUND : signer's key not found
+ * RNP_ERROR_VERIFICATION_FAILED: verification failed, so validity cannot be checked
+ * RNP_ERROR_SIGNATURE_EXPIRED: signature is valid but expired
+ * RNP_ERROR_SIGNATURE_INVALID: signature is invalid (corrupted, malformed, was issued
+ * by invalid key, whatever else.)
+ *
+ * Please also note that other error codes may be returned because of wrong
+ * function call (included, but not limited to):
+ * RNP_ERROR_NULL_POINTER: sig as well as some of its fields are NULL
+ * RNP_ERROR_BAD_PARAMETERS: invalid parameter value (unsupported flag, etc).
+ */
+RNP_API rnp_result_t rnp_signature_is_valid(rnp_signature_handle_t sig, uint32_t flags);
+
+/** Dump signature packet to JSON, obtaining the whole information about it.
+ *
+ * @param sig sigmature handle, cannot be NULL
+ * @param flags include additional fields in JSON (see RNP_JSON_DUMP_MPI and other
+ * RNP_JSON_DUMP_* flags)
+ * @param result resulting JSON string will be stored here. You must free it using the
+ * rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_signature_packet_to_json(rnp_signature_handle_t sig,
+ uint32_t flags,
+ char ** json);
+
+/**
+ * @brief Remove a signature.
+ *
+ * @param key key handle, cannot be NULL.
+ * @param sig signature handle, cannot be NULL. Must be obtained via the key handle or one of
+ * its userids. You still need to call rnp_signature_handle_destroy afterwards to
+ * destroy handle itself. All other handles of the same signature, if any, should
+ * not be used after the call is made.
+ * @return RNP_SUCCESS if signature was successfully deleted, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_signature_remove(rnp_key_handle_t key, rnp_signature_handle_t sig);
+
+/**
+ * @brief Export a signature.
+ *
+ * @param sig signature handle, cannot be NULL.
+ * @param output destination of the data stream.
+ * @param flags must be RNP_KEY_EXPORT_ARMORED or 0.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_export(rnp_signature_handle_t sig,
+ rnp_output_t output,
+ uint32_t flags);
+
+/** Free signature handle.
+ *
+ * @param sig signature handle.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_signature_handle_destroy(rnp_signature_handle_t sig);
+
+/** Check whether user id is revoked.
+ *
+ * @param uid user id handle, should not be NULL.
+ * @param result boolean result will be stored here on success. Cannot be NULL.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_is_revoked(rnp_uid_handle_t uid, bool *result);
+
+/** Retrieve uid revocation signature, if any.
+ *
+ * @param uid user id handle, should not be NULL.
+ * @param sig on success signature handle or NULL will be stored here. NULL will be stored in
+ * case when uid is not revoked.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_get_revocation_signature(rnp_uid_handle_t uid,
+ rnp_signature_handle_t *sig);
+
+/**
+ * @brief Remove userid with all of its signatures from the key
+ *
+ * @param key key handle, cannot be NULL and must own the uid.
+ * @param uid uid handle, cannot be NULL. Still must be destroyed afterwards via the
+ * rnp_uid_handle_destroy(). All other handles pointing to the same uid will
+ * become invalid and should not be used.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_uid_remove(rnp_key_handle_t key, rnp_uid_handle_t uid);
+
+/** Destroy previously allocated user id handle.
+ *
+ * @param uid user id handle.
+ * @return RNP_SUCCESS or error code
+ */
+RNP_API rnp_result_t rnp_uid_handle_destroy(rnp_uid_handle_t uid);
+
+/** Get number of the key's subkeys.
+ *
+ * @param key key handle.
+ * @param count number of subkeys will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_subkey_count(rnp_key_handle_t key, size_t *count);
+
+/** Get the handle of one of the key's subkeys, using its index in the list.
+ *
+ * @param key handle of the primary key.
+ * @param idx zero-based index of the subkey.
+ * @param subkey on success handle for the subkey will be stored here. You must free it
+ * using the rnp_key_handle_destroy() function.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_subkey_at(rnp_key_handle_t key,
+ size_t idx,
+ rnp_key_handle_t *subkey);
+
+/** Get default key for specified usage. Accepts primary key
+ * and returns one of its subkeys suitable for desired usage.
+ * May return the same primary key if it is suitable for requested
+ * usage and flag RNP_KEY_SUBKEYS_ONLY is not set.
+ *
+ * @param primary_key handle of the primary key.
+ * @param usage desired key usage i.e. "sign", "certify", etc,
+ * see rnp_op_generate_add_usage() function description for all possible values.
+ * @param flags possible values: RNP_KEY_SUBKEYS_ONLY - select only subkeys,
+ * otherwise if flags is 0, primary key can be returned if
+ * it is suitable for specified usage.
+ * @param default_key on success resulting key handle will be stored here, otherwise it
+ * will contain NULL value. You must free this handle after use with
+ * rnp_key_handle_destroy().
+ * @return RNP_SUCCESS on success, RNP_ERROR_KEY_NOT_FOUND if no key with desired usage
+ * was found or any other error code.
+ */
+RNP_API rnp_result_t rnp_key_get_default_key(rnp_key_handle_t primary_key,
+ const char * usage,
+ uint32_t flags,
+ rnp_key_handle_t *default_key);
+
+/** Get the key's algorithm.
+ *
+ * @param key key handle
+ * @param alg string with algorithm name will be stored here. You must free it using the
+ * rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_alg(rnp_key_handle_t key, char **alg);
+
+/** Get number of bits in the key. For EC-based keys it will return size of the curve.
+ *
+ * @param key key handle
+ * @param bits number of bits will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_bits(rnp_key_handle_t key, uint32_t *bits);
+
+/** Get the number of bits in q parameter of the DSA key. Makes sense only for DSA keys.
+ *
+ * @param key key handle
+ * @param qbits number of bits will be stored here.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_dsa_qbits(rnp_key_handle_t key, uint32_t *qbits);
+
+/** Get the curve of EC-based key.
+ *
+ * @param key key handle
+ * @param curve string with name of the curve will be stored here. You must free it using the
+ * rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_get_curve(rnp_key_handle_t key, char **curve);
+
+/** Add a new user identifier to a key
+ *
+ * @param ffi
+ * @param key the key to add - must be a secret key
+ * @param uid the UID to add
+ * @param hash name of the hash function to use for the uid binding
+ * signature (eg "SHA256"). If NULL, default hash algorithm
+ * will be used.
+ * @param expiration time when this user id expires
+ * @param key_flags usage flags, see section 5.2.3.21 of RFC 4880
+ * or just provide zero to indicate no special handling.
+ * @param primary indicates if this is the primary UID
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_add_uid(rnp_key_handle_t key,
+ const char * uid,
+ const char * hash,
+ uint32_t expiration,
+ uint8_t key_flags,
+ bool primary);
+
+/* The following output hex encoded strings */
+
+/**
+ * @brief Get key's fingerprint as hex-encoded string.
+ *
+ * @param key key handle, should not be NULL
+ * @param fprint pointer to the NULL-terminated string with hex-encoded fingerprint will be
+ * stored here. You must free it later using rnp_buffer_destroy function.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_get_fprint(rnp_key_handle_t key, char **fprint);
+
+/**
+ * @brief Get key's id as hex-encoded string
+ *
+ * @param key key handle, should not be NULL
+ * @param keyid pointer to the NULL-terminated string with hex-encoded key id will be
+ * stored here. You must free it later using rnp_buffer_destroy function.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_get_keyid(rnp_key_handle_t key, char **keyid);
+
+/**
+ * @brief Get key's grip as hex-encoded string
+ *
+ * @param key key handle, should not be NULL
+ * @param grip pointer to the NULL-terminated string with hex-encoded key grip will be
+ * stored here. You must free it later using rnp_buffer_destroy function.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_get_grip(rnp_key_handle_t key, char **grip);
+
+/**
+ * @brief Get primary's key grip for the subkey, if available.
+ *
+ * @param key key handle, should not be NULL
+ * @param grip pointer to the NULL-terminated string with hex-encoded key grip 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.
+ */
+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 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 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.
+ *
+ * @param key key handle, should not be NULL
+ * @param usage string describing the key usage. For the list of allowed values see the
+ * rnp_op_generate_add_usage() function description.
+ * @param result function result will be stored here. Could not be NULL.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_allows_usage(rnp_key_handle_t key,
+ const char * usage,
+ bool * result);
+
+/**
+ * @brief Get the key's creation time.
+ *
+ * @param key key handle, should not be NULL.
+ * @param result creation time will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_get_creation(rnp_key_handle_t key, uint32_t *result);
+
+/**
+ * @brief Get the key's expiration time in seconds.
+ * Note: 0 means that the key doesn't expire.
+ *
+ * @param key key handle, should not be NULL
+ * @param result expiration time will be stored here. Could not be NULL.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_get_expiration(rnp_key_handle_t key, uint32_t *result);
+
+/**
+ * @brief Set the key's expiration time in seconds.
+ * Note: this will require re-signing, which requires availability of the secret key (or
+ * secret primary key for the subkey). If the secret key is locked then may ask for
+ * key's password via FFI callback.
+ *
+ * @param key key's handle.
+ * @param expiry expiration time in seconds (or 0 if key doesn't expire). Please note that it
+ * is calculated from the key creation time, not from the current time.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_set_expiration(rnp_key_handle_t key, uint32_t expiry);
+
+/**
+ * @brief Check whether public key is valid. This includes checks of the self-signatures,
+ * expiration times, revocations and so on.
+ * Note: it doesn't take in account secret key, if it is available.
+ *
+ * @param key key's handle.
+ * @param result on success true or false will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_is_valid(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Get the timestamp till which key can be considered as valid.
+ * Note: this will take into account not only key's expiration, but revocations as well.
+ * For the subkey primary key's validity time will be also checked.
+ * While in OpenPGP key creation and expiration times are 32-bit, their sum may overflow
+ * 32 bits, so rnp_key_valid_till64 function should be used.
+ * In case of 32 bit overflow result will be set to the UINT32_MAX - 1.
+ * @param key key's handle.
+ * @param result on success timestamp will be stored here. If key doesn't expire then maximum
+ * value (UINT32_MAX or UINT64_MAX) will be stored here. If key was never valid
+ * then zero value will be stored here.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_valid_till(rnp_key_handle_t key, uint32_t *result);
+RNP_API rnp_result_t rnp_key_valid_till64(rnp_key_handle_t key, uint64_t *result);
+
+/**
+ * @brief Check whether key is revoked.
+ *
+ * @param key key handle, should not be NULL
+ * @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_revoked(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Get textual description of the key's revocation reason (if any)
+ *
+ * @param key key handle, should not be NULL
+ * @param result on success pointer to the NULL-terminated string will be stored here.
+ * You must free it later using rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS or error code on failure.
+ */
+RNP_API rnp_result_t rnp_key_get_revocation_reason(rnp_key_handle_t key, char **result);
+
+/**
+ * @brief Check whether revoked key was superseded by other key.
+ *
+ * @param key key handle, should not be NULL
+ * @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_superseded(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Check whether revoked key's material was compromised.
+ *
+ * @param key key handle, should not be NULL
+ * @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_compromised(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Check whether revoked key was retired.
+ *
+ * @param key key handle, should not be NULL
+ * @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.
+ * @return RNP_SUCCESS on success, or any other value on error
+ **/
+RNP_API rnp_result_t rnp_key_is_locked(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Get type of protection, used for secret key data.
+ *
+ * @param key key handle, cannot be NULL and should have secret part (see function
+ * rnp_key_have_secret()).
+ * @param type on success protection type will be stored here. Cannot be NULL.
+ * Must be freed by caller via rnp_buffer_destroy() call.
+ * Currently defined values are:
+ * - "None" : secret key data is stored in plaintext.
+ * - "Encrypted" : secret key data is encrypted, using just CRC as integrity
+ * protection.
+ * - "Encrypted-Hashed" : secret key data is encrypted, using the SHA1 hash as
+ * an integrity protection.
+ * - "GPG-None" : secret key data is not available at all (this would happen if
+ * secret key is exported from GnuPG via --export-secret-subkeys)
+ * - "GPG-Smartcard" : secret key data is stored on smartcard by GnuPG, so is not
+ * available
+ * - "Unknown" : key protection type is unknown, so secret key data is not
+ * available
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_key_get_protection_type(rnp_key_handle_t key, char **type);
+
+/**
+ * @brief Get mode in which secret key data is encrypted.
+ *
+ * @param key key handle, cannot be NULL and should have secret part (see function
+ * rnp_key_have_secret()).
+ * @param mode on success secret key protection mode name will be stored here. Cannot be NULL.
+ * Must be freed by caller via rnp_buffer_destroy() call.
+ * Currently defined values are:
+ * - "None" : secret key data is not encrypted at all
+ * - "Unknown" : it is not known how secret key data is encrypted, so there is no
+ * way to unlock/unprotect the key.
+ * - "CFB" : secret key data is encrypted in CFB mode, using the password
+ * - "CBC" : secret key data is encrypted in CBC mode, using the password
+ * (only for G10 keys)
+ * - "OCB" : secert key data is encrypted in OCB mode, using the password
+ * (only for G10 keys)
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_get_protection_mode(rnp_key_handle_t key, char **mode);
+
+/**
+ * @brief Get cipher, used to encrypt secret key data.
+ * Note: this call will return an error if secret key data is not available or secret
+ * key is not encrypted.
+ *
+ * @param key key handle, cannot be NULL and should have secret part.
+ * @param cipher on success cipher name will be stored here. See
+ * rnp_op_generate_set_protection_cipher for possible values. Cannot be NULL.
+ * Must be freed by caller via rnp_buffer_destroy() call.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_get_protection_cipher(rnp_key_handle_t key, char **cipher);
+
+/**
+ * @brief Get hash, used to derive secret key data encrypting key from the password.
+ * Note: this call will return an error if secret key data is not available or secret
+ * key is not encrypted.
+ * @param key key handle, cannot be NULL and should have secret part.
+ * @param hash on success hash name will be stored here. See rnp_op_generate_set_hash() for the
+ * whole list of possible values. Cannot be NULL.
+ * Must be freed by caller via rnp_buffer_destroy() call.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_get_protection_hash(rnp_key_handle_t key, char **hash);
+
+/**
+ * @brief Get number of iterations used to derive encrypting key from password, using the hash
+ * function.
+ * Note: this call will return an error if secret key data is not available or secret
+ * key is not encrypted.
+ *
+ * @param key key handle, cannot be NULL and should have secret part.
+ * @param iterations on success number of iterations will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_get_protection_iterations(rnp_key_handle_t key,
+ size_t * iterations);
+
+/** lock the key
+ *
+ * A locked key does not have the secret key material immediately
+ * available for use. A locked and protected (aka encrypted) key
+ * is safely encrypted in memory and requires a password for
+ * performing any operations involving the secret key material.
+ *
+ * Generally lock/unlock are not useful for unencrypted (not protected) keys.
+ *
+ * @param key
+ * @return RNP_SUCCESS on success, or any other value on error
+ **/
+RNP_API rnp_result_t rnp_key_lock(rnp_key_handle_t key);
+
+/** unlock the key
+ *
+ * An unlocked key has unencrypted secret key material available for use
+ * without a password.
+ *
+ * Generally lock/unlock are not useful for unencrypted (not protected) keys.
+ *
+ * @param key
+ * @param password the password to unlock the key. If NULL, the password
+ * provider will be used.
+ * @return RNP_SUCCESS on success, or any other value on error
+ **/
+RNP_API rnp_result_t rnp_key_unlock(rnp_key_handle_t key, const char *password);
+
+/** check if a key is currently protected
+ *
+ * A protected key is one that is encrypted and can be safely held in memory
+ * and locked/unlocked as needed.
+ *
+ * @param key
+ * @param result pointer to hold the result. This will be set to true if
+ * the key is currently protected, or false otherwise. Must not be NULL.
+ * @return RNP_SUCCESS on success, or any other value on error
+ **/
+RNP_API rnp_result_t rnp_key_is_protected(rnp_key_handle_t key, bool *result);
+
+/** protect the key
+ *
+ * This can be used to set a new password on a key or to protect an unprotected
+ * key.
+ *
+ * Note that the only required parameter is "password".
+ *
+ * @param key
+ * @param password the new password to encrypt/re-encrypt the key with.
+ * Must not be NULL.
+ * @param cipher the cipher (AES256, etc) used to encrypt the key. May be NULL,
+ * in which case a default will be used.
+ * @param cipher_mode the cipher mode (CFB, CBC, OCB). This parameter is not
+ * well supported currently and is mostly relevant for G10.
+ * May be NULL.
+ * @param hash the hash algorithm (SHA512, etc) used for the String-to-Key key
+ * derivation. May be NULL, in which case a default will be used.
+ * @param iterations the number of iterations used for the String-to-Key key
+ * derivation. Use 0 to select a reasonable default.
+ * @return RNP_SUCCESS on success, or any other value on error
+ **/
+RNP_API rnp_result_t rnp_key_protect(rnp_key_handle_t handle,
+ const char * password,
+ const char * cipher,
+ const char * cipher_mode,
+ const char * hash,
+ size_t iterations);
+
+/** unprotect the key
+ *
+ * This removes the encryption from the key.
+ *
+ * @param key
+ * @param password the password to unlock the key. If NULL, the password
+ * provider will be used.
+ * @return RNP_SUCCESS on success, or any other value on error
+ **/
+RNP_API rnp_result_t rnp_key_unprotect(rnp_key_handle_t key, const char *password);
+
+/**
+ * @brief Check whether key is primary key.
+ *
+ * @param key key handle, cannot be NULL.
+ * @param result true or false will be stored here on success.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_is_primary(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Check whether key is subkey.
+ *
+ * @param key key handle, cannot be NULL.
+ * @param result true or false will be stored here on success.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_is_sub(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Check whether key has secret part.
+ *
+ * @param key key handle, cannot be NULL.
+ * @param result true will be stored here on success, or false otherwise.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_have_secret(rnp_key_handle_t key, bool *result);
+
+/**
+ * @brief Check whether key has public part. Generally all keys would have public part.
+ *
+ * @param key key handle, cannot be NULL.
+ * @param result true will be stored here on success, or false otherwise.
+ * @return RNP_SUCCESS on success, or any other value on error.
+ */
+RNP_API rnp_result_t rnp_key_have_public(rnp_key_handle_t key, bool *result);
+
+/** Get the information about key packets in JSON string.
+ * Note: this will not work for G10 keys.
+ *
+ * @param key key's handle, cannot be NULL
+ * @param secret dump secret key instead of public
+ * @param flags include additional fields in JSON (see RNP_JSON_DUMP_MPI and other
+ * RNP_JSON_DUMP_* flags)
+ * @param result resulting JSON string will be stored here. You must free it using the
+ * rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_key_packets_to_json(rnp_key_handle_t key,
+ bool secret,
+ uint32_t flags,
+ char ** result);
+
+/** Dump OpenPGP packets stream information to the JSON string.
+ * @param input source with OpenPGP data
+ * @param flags include additional fields in JSON (see RNP_JSON_DUMP_MPI and other
+ * RNP_JSON_DUMP_* flags)
+ * @result resulting JSON string will be stored here. You must free it using the
+ * rnp_buffer_destroy() function.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_dump_packets_to_json(rnp_input_t input,
+ uint32_t flags,
+ char ** result);
+
+/** Dump OpenPGP packets stream information to output in humand-readable format.
+ * @param input source with OpenPGP data
+ * @param output text, describing packet sequence, will be written here
+ * @param flags see RNP_DUMP_MPI and other RNP_DUMP_* constants.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_dump_packets_to_output(rnp_input_t input,
+ rnp_output_t output,
+ uint32_t flags);
+
+/* Signing operations */
+
+/** @brief Create signing operation context. This method should be used for embedded
+ * signatures of binary data. For detached and cleartext signing corresponding
+ * function should be used.
+ * @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.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_create(rnp_op_sign_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t output);
+
+/** @brief Create cleartext signing operation context. Input should be text data. Output will
+ * contain source data with additional headers and armored signature.
+ * @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.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_cleartext_create(rnp_op_sign_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t output);
+
+/** @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 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,
+ rnp_output_t signature);
+
+/** @brief Add information about the signature so it could be calculated later in execute
+ * function call. Multiple signatures could be added.
+ * @param op opaque signing context. Must be successfully initialized with one of the
+ * rnp_op_sign_*_create functions.
+ * @param key handle of the private key. Private key should be capable for signing.
+ * @param sig pointer to opaque structure holding the signature information. May be NULL.
+ * You should not free it as it will be destroyed together with signing context.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_add_signature(rnp_op_sign_t op,
+ rnp_key_handle_t key,
+ rnp_op_sign_signature_t *sig);
+
+/** @brief Set hash algorithm used during signature calculation instead of default one, or one
+ * set by rnp_op_encrypt_set_hash/rnp_op_sign_set_hash
+ * @param sig opaque signature context, returned via rnp_op_sign_add_signature
+ * @param hash hash algorithm to be used
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_signature_set_hash(rnp_op_sign_signature_t sig,
+ const char * hash);
+
+/** @brief Set signature creation time. By default current time is used or value set by
+ * rnp_op_encrypt_set_creation_time/rnp_op_sign_set_creation_time
+ * @param sig opaque signature context, returned via rnp_op_sign_add_signature
+ * @param create creation time in seconds since Jan, 1 1970 UTC
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_signature_set_creation_time(rnp_op_sign_signature_t sig,
+ uint32_t create);
+
+/** @brief Set signature expiration time. By default is set to never expire or to value set by
+ * rnp_op_encrypt_set_expiration_time/rnp_op_sign_set_expiration_time
+ * @param sig opaque signature context, returned via rnp_op_sign_add_signature
+ * @param expire expiration time in seconds since the creation time. 0 value is used to mark
+ * signature as non-expiring (default value)
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_signature_set_expiration_time(rnp_op_sign_signature_t sig,
+ uint32_t expires);
+
+/** @brief Set data compression parameters. Makes sense only for embedded signatures.
+ * @param op opaque signing context. Must be initialized with rnp_op_sign_create function
+ * @param compression compression algorithm (zlib, zip, bzip2)
+ * @param level compression level, 0-9. 0 disables compression.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_set_compression(rnp_op_sign_t op,
+ const char * compression,
+ int level);
+
+/** @brief Enabled or disable armored (textual) output. Doesn't make sense for cleartext sign.
+ * @param op opaque signing context. Must be initialized with rnp_op_sign_create or
+ * rnp_op_sign_detached_create function.
+ * @param armored true if armoring should be used (it is disabled by default)
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_set_armor(rnp_op_sign_t op, bool armored);
+
+/** @brief Set hash algorithm used during signature calculation. This will set hash function
+ * for all signature. To change it for a single signature use
+ * rnp_op_sign_signature_set_hash function.
+ * @param op opaque signing context. Must be successfully initialized with one of the
+ * rnp_op_sign_*_create functions.
+ * @param hash hash algorithm to be used
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_set_hash(rnp_op_sign_t op, const char *hash);
+
+/** @brief Set signature creation time. By default current time is used.
+ * @param op opaque signing context. Must be successfully initialized with one of the
+ * rnp_op_sign_*_create functions.
+ * @param create creation time in seconds since Jan, 1 1970 UTC. 32 bit unsigned integer
+ * datatype is used here instead of 64 bit (like modern timestamps do) because
+ * in OpenPGP messages times are stored as 32-bit unsigned integers.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_set_creation_time(rnp_op_sign_t op, uint32_t create);
+
+/** @brief Set signature expiration time.
+ * @param op opaque signing context. Must be successfully initialized with one of the
+ * rnp_op_sign_*_create functions.
+ * @param expire expiration time in seconds since the creation time. 0 value is used to mark
+ * signature as non-expiring (default value)
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_set_expiration_time(rnp_op_sign_t op, uint32_t expire);
+
+/** @brief Set input's file name. Makes sense only for embedded signature.
+ * @param op opaque signing context. Must be initialized with rnp_op_sign_create function
+ * @param filename source data file name. Special value _CONSOLE may be used to mark message
+ * as 'for your eyes only', i.e. it should not be stored anywhere but only displayed
+ * to the receiver. Default is the empty string.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_set_file_name(rnp_op_sign_t op, const char *filename);
+
+/** @brief Set input's file modification date. Makes sense only for embedded signature.
+ * @param op opaque signing context. Must be initialized with rnp_op_sign_create function
+ * @param mtime modification time in seconds since Jan, 1 1970 UTC. 32 bit unsigned integer
+ * datatype is used here instead of 64 bit (like modern timestamps do) because
+ * in OpenPGP messages times are stored as 32-bit unsigned integers.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_sign_set_file_mtime(rnp_op_sign_t op, uint32_t mtime);
+
+/** @brief Execute previously initialized signing operation.
+ * @param op opaque signing context. Must be successfully initialized with one of the
+ * rnp_op_sign_*_create functions. At least one signing key should be added.
+ * @return RNP_SUCCESS or error code if failed. On success output stream, passed in the create
+ * function call, will be populated with signed data
+ */
+RNP_API rnp_result_t rnp_op_sign_execute(rnp_op_sign_t op);
+
+/** @brief Free resources associated with signing operation.
+ * @param op opaque signing context. Must be successfully initialized with one of the
+ * rnp_op_sign_*_create functions.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+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, 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.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_verify_create(rnp_op_verify_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t output);
+
+/** @brief Create verification operation context for detached signature.
+ * @param op pointer to opaque verification context
+ * @param ffi
+ * @param input stream with raw data. Could not be NULL.
+ * @param signature stream with detached signature data
+ * @return RNP_SUCCESS or error code if failed
+ */
+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().
+ * RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT - allow hidden recipient during the
+ * decryption.
+ *
+ * Note: all flags are set at once, if some flag is not present in the subsequent
+ * call then it will be unset.
+ * @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 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.
+ * @param count result will be stored here on success.
+ * @return RNP_SUCCESS if call succeeded.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_signature_count(rnp_op_verify_t op, size_t *count);
+
+/** @brief Get single signature information based on its index.
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param sig opaque signature context data will be stored here on success.
+ * @return RNP_SUCCESS if call succeeded.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_signature_at(rnp_op_verify_t op,
+ size_t idx,
+ rnp_op_verify_signature_t *sig);
+
+/** @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_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,
+ uint32_t * mtime);
+
+/**
+ * @brief Get data protection (encryption) mode, used in processed message.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param mode on success string with mode will be stored here. Caller is responsible for
+ * freeing it using the rnp_buffer_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_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.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_protection_info(rnp_op_verify_t op,
+ char ** mode,
+ char ** cipher,
+ bool * valid);
+
+/**
+ * @brief Get number of public keys (recipients) to whom message was encrypted to.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param count on success number of keys will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_recipient_count(rnp_op_verify_t op, size_t *count);
+
+/**
+ * @brief Get the recipient's handle, used to decrypt message.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param recipient pointer to the opaque handle context. Cannot be NULL. If recipient's key
+ * was used to decrypt a message then handle will be stored here, otherwise
+ * it will be set to NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_used_recipient(rnp_op_verify_t op,
+ rnp_recipient_handle_t *recipient);
+
+/**
+ * @brief Get the recipient's handle by index.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param idx zero-based index in array.
+ * @param recipient pointer to the opaque handle context. Cannot be NULL. On success handle
+ * will be stored here.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_recipient_at(rnp_op_verify_t op,
+ size_t idx,
+ rnp_recipient_handle_t *recipient);
+
+/**
+ * @brief Get recipient's keyid.
+ *
+ * @param recipient recipient's handle, obtained via rnp_op_verify_get_used_recipient() or
+ * rnp_op_verify_get_recipient_at() function call. Cannot be NULL.
+ * @param keyid on success pointer to NULL-terminated string with hex-encoded keyid will be
+ * stored here. Cannot be NULL. Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_recipient_get_keyid(rnp_recipient_handle_t recipient, char **keyid);
+
+/**
+ * @brief Get recipient's key algorithm.
+ *
+ * @param recipient recipient's handle, obtained via rnp_op_verify_get_used_recipient() or
+ * rnp_op_verify_get_recipient_at() function call. Cannot be NULL.
+ * @param alg on success pointer to NULL-terminated string with algorithm will be stored here.
+ * Cannot be NULL. Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_recipient_get_alg(rnp_recipient_handle_t recipient, char **alg);
+
+/**
+ * @brief Get number of symenc entries (i.e. passwords), to which message was encrypted.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param count on success number of keys will be stored here. Cannot be NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_symenc_count(rnp_op_verify_t op, size_t *count);
+
+/**
+ * @brief Get the symenc handle, used to decrypt a message.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param symenc pointer to the opaque symenc context. Cannot be NULL. If password was used to
+ * decrypt a message then handle will be stored here, otherwise it will be set to
+ * NULL.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_used_symenc(rnp_op_verify_t op,
+ rnp_symenc_handle_t *symenc);
+
+/**
+ * @brief Get the symenc handle by index.
+ *
+ * @param op opaque verification context. Must be initialized and have execute() called on it.
+ * @param idx zero-based index in array.
+ * @param symenc pointer to the opaque handle context. Cannot be NULL. On success handle
+ * will be stored here.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_verify_get_symenc_at(rnp_op_verify_t op,
+ size_t idx,
+ rnp_symenc_handle_t *symenc);
+
+/**
+ * @brief Get the symmetric cipher, used to encrypt data encryption key.
+ * Note: if message is encrypted with only one passphrase and without public keys, then
+ * key, derived from password, may be used to encrypt the whole message.
+ * @param symenc opaque handle, cannot be NULL.
+ * @param cipher NULL-terminated string with cipher's name will be stored here. Cannot be NULL.
+ * Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_symenc_get_cipher(rnp_symenc_handle_t symenc, char **cipher);
+
+/**
+ * @brief Get AEAD algorithm if it was used to encrypt data encryption key.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param alg NULL-terminated string with AEAD algorithm name will be stored here. If AEAD was
+ * not used then it will contain string 'None'. Must be freed using the
+ * rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_symenc_get_aead_alg(rnp_symenc_handle_t symenc, char **alg);
+
+/**
+ * @brief Get hash algorithm, used to derive key from the passphrase.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param alg NULL-terminated string with hash algorithm name will be stored here. Cannot be
+ * NULL. Must be freed using the rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_symenc_get_hash_alg(rnp_symenc_handle_t symenc, char **alg);
+
+/**
+ * @brief Get string-to-key type, used to derive password.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param type NULL-terminated string with s2k type will be stored here. Currently following
+ * types are available: 'Simple', 'Salted', 'Iterated and salted'. Please note that
+ * first two are considered weak and should not be used. Must be freed using the
+ * rnp_buffer_destroy().
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_symenc_get_s2k_type(rnp_symenc_handle_t symenc, char **type);
+
+/**
+ * @brief Get number of iterations in iterated-and-salted S2K, if it was used.
+ *
+ * @param symenc opaque handle, cannot be NULL.
+ * @param iterations on success number of iterations will be stored here. Cannot be NULL.
+ * If non-iterated s2k was used then will be set to 0.
+ * @return RNP_SUCCESS if call succeeded, or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_symenc_get_s2k_iterations(rnp_symenc_handle_t symenc,
+ uint32_t * iterations);
+
+/** @brief Free resources allocated in verification context.
+ * @param op opaque verification context. Must be initialized.
+ * @return RNP_SUCCESS if call succeeded.
+ */
+RNP_API rnp_result_t rnp_op_verify_destroy(rnp_op_verify_t op);
+
+/** @brief Get signature verification status.
+ * @param sig opaque signature context obtained via rnp_op_verify_get_signature_at call.
+ * @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.
+ * @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_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);
+
+/** @brief Get key used for signing
+ * @param sig opaque signature context obtained via rnp_op_verify_get_signature_at call.
+ * @param key pointer to opaque key handle structure.
+ * @return RNP_SUCCESS or error code otherwise
+ */
+RNP_API rnp_result_t rnp_op_verify_signature_get_key(rnp_op_verify_signature_t sig,
+ rnp_key_handle_t * key);
+
+/** @brief Get signature creation and expiration times
+ * @param sig opaque signature context obtained via rnp_op_verify_get_signature_at call.
+ * @param create signature creation time will be put here. It is number of seconds since
+ * Jan, 1 1970 UTC. May be NULL if called doesn't need this data.
+ * @param expires signature expiration time will be stored here. It is number of seconds since
+ * the creation time or 0 if signature never expires. May be NULL.
+ * @return RNP_SUCCESS or error code otherwise
+ */
+RNP_API rnp_result_t rnp_op_verify_signature_get_times(rnp_op_verify_signature_t sig,
+ uint32_t * create,
+ uint32_t * expires);
+
+/**
+ * @brief Free buffer allocated by a function in this header.
+ *
+ * @param ptr previously allocated buffer. May be NULL, then nothing is done.
+ */
+RNP_API void rnp_buffer_destroy(void *ptr);
+
+/**
+ * @brief Securely clear buffer contents.
+ *
+ * @param ptr pointer to the buffer contents, may be NULL.
+ * @param size number of bytes in buffer.
+ */
+RNP_API void rnp_buffer_clear(void *ptr, size_t size);
+
+/**
+ * @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
+ */
+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
+ * @param do_copy if true then the buffer will be copied internally. If
+ * false then the application should ensure that the buffer
+ * is valid and not modified during the lifetime of this object.
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise
+ */
+RNP_API rnp_result_t rnp_input_from_memory(rnp_input_t * input,
+ const uint8_t buf[],
+ size_t buf_len,
+ bool do_copy);
+
+/**
+ * @brief Initialize input struct to read via callbacks
+ *
+ * @param input pointer to the input opaque structure
+ * @param reader callback used for reading
+ * @param closer callback used to close the stream
+ * @param app_ctx context to pass as parameter to reader and closer
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise
+ */
+RNP_API rnp_result_t rnp_input_from_callback(rnp_input_t * input,
+ rnp_input_reader_t *reader,
+ rnp_input_closer_t *closer,
+ void * app_ctx);
+
+/**
+ * @brief Close previously opened input and free all corresponding resources
+ *
+ * @param input previously opened input structure
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise
+ */
+RNP_API rnp_result_t rnp_input_destroy(rnp_input_t input);
+
+/**
+ * @brief Initialize output structure to write to a path. If path is a file
+ * 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.
+ */
+RNP_API rnp_result_t rnp_output_to_path(rnp_output_t *output, const char *path);
+
+/**
+ * @brief Initialize structure to write to a file.
+ * Note: it doesn't allow output to directory like rnp_output_to_path does, but
+ * allows additional options to be specified.
+ * When RNP_OUTPUT_FILE_RANDOM flag is included then you may want to call
+ * rnp_output_finish() to make sure that final rename succeeded.
+ * @param output pointer to the opaque output structure. After use you must free it using the
+ * rnp_output_destroy() function.
+ * @param path path to the file.
+ * @param flags additional flags, see RNP_OUTPUT_* flags.
+ * @return RNP_SUCCESS if file was opened successfully and ready for writing or error code
+ * otherwise.
+ */
+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.
+ */
+RNP_API rnp_result_t rnp_output_to_memory(rnp_output_t *output, size_t max_alloc);
+
+/**
+ * @brief Output data to armored stream (and then output to other destination), allowing
+ * streamed output.
+ *
+ * @param base initialized output structure, where armored data will be written to.
+ * @param output pointer to the opaque output structure. You must free it later using the
+ * rnp_output_destroy() function.
+ * @param type type of the armored stream. See rnp_enarmor() for possible values.
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_output_to_armor(rnp_output_t base,
+ rnp_output_t *output,
+ const char * type);
+
+/**
+ * @brief Get the pointer to the buffer of output, initialized by rnp_output_to_memory
+ *
+ * @param output output structure, initialized by rnp_output_to_memory and populated with data
+ * @param buf pointer to the buffer will be stored here, could not be NULL
+ * @param len number of bytes in buffer will be stored here, could not be NULL
+ * @param do_copy if true then a newly-allocated buffer will be returned and the application
+ * will be responsible for freeing it with rnp_buffer_destroy. If false
+ * then the internal buffer is returned and the application must not modify the
+ * buffer or access it after this object is destroyed.
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_output_memory_get_buf(rnp_output_t output,
+ uint8_t ** buf,
+ size_t * len,
+ bool do_copy);
+
+/**
+ * @brief Initialize output structure to write to callbacks.
+ *
+ * @param output pointer to the opaque output structure.
+ * @param writer write callback.
+ * @param closer close callback.
+ * @param app_ctx context parameter which will be passed to writer and closer.
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_output_to_callback(rnp_output_t * output,
+ rnp_output_writer_t *writer,
+ rnp_output_closer_t *closer,
+ void * app_ctx);
+
+/**
+ * @brief Initialize output structure which will discard all data
+ *
+ * @param output pointer to the opaque output structure.
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_output_to_null(rnp_output_t *output);
+
+/**
+ * @brief write some data to the output structure.
+ *
+ * @param output pointer to the initialized opaque output structure.
+ * @param data pointer to data which should be written.
+ * @param size number of bytes to write.
+ * @param written on success will contain the number of bytes written. May be NULL.
+ * @return rnp_result_t RNP_SUCCESS if operation succeeded or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_output_write(rnp_output_t output,
+ const void * data,
+ size_t size,
+ size_t * written);
+
+/**
+ * @brief Finish writing to the output.
+ * Note: on most output types you'll need just to call rnp_output_destroy().
+ * However, for file output with RNP_OUTPUT_FILE_RANDOM flag, you need to call this
+ * to make sure that rename from random to required name succeeded.
+ *
+ * @param output pointer to the opaque output structure.
+ * @return RNP_SUCCESS if operation succeeded or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_output_finish(rnp_output_t output);
+
+/**
+ * @brief Close previously opened output and free all associated data.
+ *
+ * @param output previously opened output structure.
+ * @return RNP_SUCCESS if operation succeeds or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_output_destroy(rnp_output_t output);
+
+/* encrypt */
+RNP_API rnp_result_t rnp_op_encrypt_create(rnp_op_encrypt_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t output);
+
+/**
+ * @brief Add recipient's public key to encrypting context.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param key public key, used for encryption. Key is not checked for
+ * validity or expiration.
+ * @return RNP_SUCCESS if operation succeeds or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_encrypt_add_recipient(rnp_op_encrypt_t op, rnp_key_handle_t key);
+
+/**
+ * @brief Add signature to encrypting context, so data will be encrypted and signed.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param key private key, used for signing.
+ * @param sig pointer to the newly added signature will be stored here. May be NULL.
+ * @return RNP_SUCCESS if signature was added or error code otherwise.
+ */
+RNP_API rnp_result_t rnp_op_encrypt_add_signature(rnp_op_encrypt_t op,
+ rnp_key_handle_t key,
+ rnp_op_sign_signature_t *sig);
+
+/**
+ * @brief Set hash function used for signature calculation. Makes sense if encrypt-and-sign is
+ * used. To set hash function for each signature separately use rnp_op_sign_signature_set_hash.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param hash hash algorithm to be used as NULL-terminated string. Following values are
+ * supported: "MD5", "SHA1", "RIPEMD160", "SHA256", "SHA384", "SHA512", "SHA224", "SM3".
+ * However, some signature types may require specific hash function or hash function
+ * output length.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_hash(rnp_op_encrypt_t op, const char *hash);
+
+/**
+ * @brief Set signature creation time. By default current time is used.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param create creation time in seconds since Jan, 1 1970 UTC
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_creation_time(rnp_op_encrypt_t op, uint32_t create);
+
+/**
+ * @brief Set signature expiration time. By default signatures do not expire.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param expire expiration time in seconds since the creation time. 0 value is used to mark
+ * signature as non-expiring
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_expiration_time(rnp_op_encrypt_t op, uint32_t expire);
+
+/**
+ * @brief Add password which is used to encrypt data. Multiple passwords can be added.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param password NULL-terminated password string, or NULL if password should be requested
+ * via password provider.
+ * @param s2k_hash hash algorithm, used in key-from-password derivation. Pass NULL for default
+ * value. See rnp_op_encrypt_set_hash for possible values.
+ * @param iterations number of iterations, used in key derivation function.
+ * According to RFC 4880, chapter 3.7.1.3, only 256 distinct values within the range
+ * [1024..0x3e00000] can be encoded. Thus, the number will be increased to the closest
+ * encodable value. In case it exceeds the maximum encodable value, it will be decreased
+ * to the maximum encodable value.
+ * If 0 is passed, an optimal number (greater or equal to 1024) will be calculated based
+ * on performance measurement.
+ * @param s2k_cipher symmetric cipher, used for key encryption. Pass NULL for default value.
+ * See rnp_op_encrypt_set_cipher for possible values.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_add_password(rnp_op_encrypt_t op,
+ const char * password,
+ const char * s2k_hash,
+ size_t iterations,
+ const char * s2k_cipher);
+
+/**
+ * @brief Set whether output should be ASCII-armored, or binary.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param armored true for armored, false for binary
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_armor(rnp_op_encrypt_t op, bool armored);
+
+/**
+ * @brief set the encryption algorithm
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param cipher NULL-terminated string with cipher's name. One of the "IDEA", "TRIPLEDES",
+ * "CAST5", "BLOWFISH", "AES128", "AES192", "AES256", "TWOFISH", "CAMELLIA128",
+ * "CAMELLIA192", "CAMELLIA256", "SM4".
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_cipher(rnp_op_encrypt_t op, const char *cipher);
+
+/**
+ * @brief set AEAD mode algorithm or disable AEAD usage. By default it is disabled.
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param alg NULL-terminated AEAD algorithm name. Use "None" to disable AEAD, or "EAX", "OCB"
+ * to use the corresponding algorithm.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_aead(rnp_op_encrypt_t op, const char *alg);
+
+/**
+ * @brief set chunk length for AEAD mode via number of chunk size bits (refer to the OpenPGP
+ * specification for the details).
+ *
+ * @param op opaque encrypting context. Must be allocated and initialized.
+ * @param bits number of bits, currently it must be from 0 to 16.
+ * @return RNP_SUCCESS or error code if failed
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_aead_bits(rnp_op_encrypt_t op, int bits);
+
+/**
+ * @brief set the compression algorithm and level for the inner raw data
+ *
+ * @param op opaque encrypted context. Must be allocated and initialized
+ * @param compression compression algorithm name. Can be one of the "Uncompressed", "ZIP",
+ * "ZLIB", "BZip2". Please note that ZIP is not PkWare's ZIP file format but just a
+ * DEFLATE compressed data (RFC 1951).
+ * @param level 0 - 9, where 0 is no compression and 9 is maximum compression level.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+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.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_op_encrypt_set_file_name(rnp_op_encrypt_t op, const char *filename);
+
+/**
+ * @brief set the internally stored file modification date for the data being encrypted
+ *
+ * @param op opaque encrypted context. Must be allocated and initialized
+ * @param mtime time in seconds since Jan, 1 1970. 32 bit unsigned integer datatype is used
+ * here instead of 64 bit (like modern timestamps do) because in OpenPGP messages
+ * times are stored as 32-bit unsigned integers.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+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.
+ *
+ * @param handle the key handle
+ * @param buf
+ * @param buf_len
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_get_public_key_data(rnp_key_handle_t handle,
+ uint8_t ** buf,
+ size_t * buf_len);
+
+/** retrieve the raw data for a secret key
+ *
+ * If this is a G10 key, this will be the s-expr data. Otherwise, it will
+ * be PGP packets.
+ *
+ * Note that this result will never include ASCII armor.
+ *
+ * @param handle the key handle
+ * @param buf
+ * @param buf_len
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_get_secret_key_data(rnp_key_handle_t handle,
+ uint8_t ** buf,
+ size_t * buf_len);
+
+/** output key information to JSON structure and serialize it to the string
+ *
+ * @param handle the key handle, could not be NULL
+ * @param flags controls which key data is printed, see RNP_JSON_* constants.
+ * @param result pointer to the resulting string will be stored here on success. You must
+ * release it afterwards via rnp_buffer_destroy() function call.
+ * @return RNP_SUCCESS or error code if failed.
+ */
+RNP_API rnp_result_t rnp_key_to_json(rnp_key_handle_t handle, uint32_t flags, char **result);
+
+/** 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", "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);
+
+/** retrieve the next item from an iterator
+ *
+ * @param it the iterator
+ * @param identifier pointer that will be set to the identifier value.
+ * Must not be NULL. This buffer should not be freed by the application.
+ * It will be modified by subsequent calls to this function, and its
+ * life is tied to the iterator.
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_identifier_iterator_next(rnp_identifier_iterator_t it,
+ const char ** identifier);
+
+/** destroy an identifier iterator
+ *
+ * @param it the iterator object
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_identifier_iterator_destroy(rnp_identifier_iterator_t it);
+
+/** Read from input and write to output
+ *
+ * @param input stream to read data from
+ * @param output stream to write data to
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_output_pipe(rnp_input_t input, rnp_output_t output);
+
+/** Set line length for armored output
+ *
+ * @param output stream to configure
+ * @param llen line length in characters [16..76]
+ * @return RNP_SUCCESS on success, or any other value on error
+ */
+RNP_API rnp_result_t rnp_output_armor_set_line_length(rnp_output_t output, size_t llen);
+
+/**
+ * @brief Return cryptographic backend library name.
+ *
+ * @return Backend name string. Currently supported
+ * backends are "Botan" and "OpenSSL".
+ */
+RNP_API const char *rnp_backend_string();
+
+/**
+ * @brief Return cryptographic backend library version.
+ *
+ * @return Version string.
+ */
+RNP_API const char *rnp_backend_version();
+
+#if defined(__cplusplus)
+}
+
+#endif
+
+/**
+ * Feature strings.
+ */
+#ifndef RNP_FEATURE_SYMM_ALG
+
+#define RNP_FEATURE_SYMM_ALG "symmetric algorithm"
+#define RNP_FEATURE_AEAD_ALG "aead algorithm"
+#define RNP_FEATURE_PROT_MODE "protection mode"
+#define RNP_FEATURE_PK_ALG "public key algorithm"
+#define RNP_FEATURE_HASH_ALG "hash algorithm"
+#define RNP_FEATURE_COMP_ALG "compression algorithm"
+#define RNP_FEATURE_CURVE "elliptic curve"
+
+#endif
+
+/** Algorithm Strings
+ */
+#ifndef RNP_ALGNAME_PLAINTEXT
+
+#define RNP_ALGNAME_PLAINTEXT "PLAINTEXT"
+#define RNP_ALGNAME_RSA "RSA"
+#define RNP_ALGNAME_ELGAMAL "ELGAMAL"
+#define RNP_ALGNAME_DSA "DSA"
+#define RNP_ALGNAME_ECDH "ECDH"
+#define RNP_ALGNAME_ECDSA "ECDSA"
+#define RNP_ALGNAME_EDDSA "EDDSA"
+#define RNP_ALGNAME_IDEA "IDEA"
+#define RNP_ALGNAME_TRIPLEDES "TRIPLEDES"
+#define RNP_ALGNAME_CAST5 "CAST5"
+#define RNP_ALGNAME_BLOWFISH "BLOWFISH"
+#define RNP_ALGNAME_TWOFISH "TWOFISH"
+#define RNP_ALGNAME_AES_128 "AES128"
+#define RNP_ALGNAME_AES_192 "AES192"
+#define RNP_ALGNAME_AES_256 "AES256"
+#define RNP_ALGNAME_CAMELLIA_128 "CAMELLIA128"
+#define RNP_ALGNAME_CAMELLIA_192 "CAMELLIA192"
+#define RNP_ALGNAME_CAMELLIA_256 "CAMELLIA256"
+#define RNP_ALGNAME_SM2 "SM2"
+#define RNP_ALGNAME_SM3 "SM3"
+#define RNP_ALGNAME_SM4 "SM4"
+#define RNP_ALGNAME_MD5 "MD5"
+#define RNP_ALGNAME_SHA1 "SHA1"
+#define RNP_ALGNAME_SHA256 "SHA256"
+#define RNP_ALGNAME_SHA384 "SHA384"
+#define RNP_ALGNAME_SHA512 "SHA512"
+#define RNP_ALGNAME_SHA224 "SHA224"
+#define RNP_ALGNAME_SHA3_256 "SHA3-256"
+#define RNP_ALGNAME_SHA3_512 "SHA3-512"
+#define RNP_ALGNAME_RIPEMD160 "RIPEMD160"
+#define RNP_ALGNAME_CRC24 "CRC24"
+
+/* SHA1 is not considered secured anymore and SHOULD NOT be used to create messages (as per
+ * Appendix C of RFC 4880-bis-02). SHA2 MUST be implemented.
+ * Let's preempt this by specifying SHA256 - gpg interoperates just fine with SHA256 - agc,
+ * 20090522
+ */
+#define DEFAULT_HASH_ALG RNP_ALGNAME_SHA256
+
+/* Default symmetric algorithm */
+#define DEFAULT_SYMM_ALG RNP_ALGNAME_AES_256
+
+/* Keystore format: GPG, KBX (pub), G10 (sec), GPG21 ( KBX for pub, G10 for sec) */
+#define RNP_KEYSTORE_GPG ("GPG")
+#define RNP_KEYSTORE_KBX ("KBX")
+#define RNP_KEYSTORE_G10 ("G10")
+#define RNP_KEYSTORE_GPG21 ("GPG21")
+
+#endif
diff --git a/include/rnp/rnp_def.h b/include/rnp/rnp_def.h
new file mode 100644
index 0000000..5b099d9
--- /dev/null
+++ b/include/rnp/rnp_def.h
@@ -0,0 +1,35 @@
+/*
+ * Copyright (c) 2017, [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 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 RNP_DEF_H_
+#define RNP_DEF_H_
+
+#include <stdint.h>
+#include "rnp_err.h"
+
+/* rnp_result_t is the type used for return codes from the APIs. */
+typedef uint32_t rnp_result_t;
+
+#endif
diff --git a/include/rnp/rnp_err.h b/include/rnp/rnp_err.h
new file mode 100644
index 0000000..f4ff179
--- /dev/null
+++ b/include/rnp/rnp_err.h
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2017-2019, [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 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 RNP_ERR_H_
+#define RNP_ERR_H_
+
+/*
+ * Error code definitions
+ */
+enum {
+
+ RNP_SUCCESS = 0x00000000,
+
+ /* Common error codes */
+ RNP_ERROR_GENERIC = 0x10000000,
+ RNP_ERROR_BAD_FORMAT,
+ RNP_ERROR_BAD_PARAMETERS,
+ RNP_ERROR_NOT_IMPLEMENTED,
+ RNP_ERROR_NOT_SUPPORTED,
+ RNP_ERROR_OUT_OF_MEMORY,
+ RNP_ERROR_SHORT_BUFFER,
+ RNP_ERROR_NULL_POINTER,
+
+ /* Storage */
+ RNP_ERROR_ACCESS = 0x11000000,
+ RNP_ERROR_READ,
+ RNP_ERROR_WRITE,
+
+ /* Crypto */
+ RNP_ERROR_BAD_STATE = 0x12000000,
+ RNP_ERROR_MAC_INVALID,
+ RNP_ERROR_SIGNATURE_INVALID,
+ RNP_ERROR_KEY_GENERATION,
+ RNP_ERROR_BAD_PASSWORD,
+ RNP_ERROR_KEY_NOT_FOUND,
+ RNP_ERROR_NO_SUITABLE_KEY,
+ RNP_ERROR_DECRYPT_FAILED,
+ RNP_ERROR_RNG,
+ 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,
+ RNP_ERROR_NO_USERID,
+ RNP_ERROR_EOF
+
+};
+
+#endif
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
new file mode 100644
index 0000000..1ea3445
--- /dev/null
+++ b/src/common/CMakeLists.txt
@@ -0,0 +1,64 @@
+# Copyright (c) 2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+add_library(rnp-common OBJECT
+ str-utils.cpp
+ file-utils.cpp
+ time-utils.cpp
+)
+
+if(MSVC)
+ find_path(GETOPT_INCLUDE_DIR
+ NAMES getopt.h
+ )
+ find_library(GETOPT_LIBRARY
+ NAMES getopt
+ )
+ find_path(DIRENT_INCLUDE_DIR
+ NAMES dirent.h
+ )
+endif()
+
+target_include_directories(rnp-common
+ PUBLIC
+ "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src/lib>"
+ "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
+ "$<INSTALL_INTERFACE:include>"
+ PRIVATE
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ "${PROJECT_SOURCE_DIR}/src"
+)
+if(MSVC)
+ target_include_directories(rnp-common
+ PRIVATE
+ "${GETOPT_INCLUDE_DIR}"
+ "${DIRENT_INCLUDE_DIR}"
+ )
+endif()
+
+set_target_properties(rnp-common PROPERTIES
+ POSITION_INDEPENDENT_CODE ON
+ CXX_VISIBILITY_PRESET hidden
+)
+
diff --git a/src/common/file-utils.cpp b/src/common/file-utils.cpp
new file mode 100644
index 0000000..d9cdb40
--- /dev/null
+++ b/src/common/file-utils.cpp
@@ -0,0 +1,359 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+/** File utilities
+ * @file
+ */
+
+#include "file-utils.h"
+#include "config.h"
+#ifdef _MSC_VER
+#include <stdlib.h>
+#include <stdio.h>
+#include "uniwin.h"
+#include <errno.h>
+#else
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#endif // !_MSC_VER
+#include "str-utils.h"
+#include <algorithm>
+#ifdef _WIN32
+#include <random> // for rnp_mkstemp
+#define CATCH_AND_RETURN(v) \
+ catch (...) \
+ { \
+ errno = ENOMEM; \
+ return v; \
+ }
+#else
+#include <string.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#include <stdarg.h>
+
+int
+rnp_unlink(const char *filename)
+{
+#ifdef _WIN32
+ try {
+ return _wunlink(wstr_from_utf8(filename).c_str());
+ }
+ CATCH_AND_RETURN(-1)
+#else
+ return unlink(filename);
+#endif
+}
+
+bool
+rnp_file_exists(const char *path)
+{
+ struct stat st;
+ return rnp_stat(path, &st) == 0 && S_ISREG(st.st_mode);
+}
+
+bool
+rnp_dir_exists(const char *path)
+{
+ struct stat st;
+ return rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode);
+}
+
+int
+rnp_open(const char *filename, int oflag, int pmode)
+{
+#ifdef _WIN32
+ try {
+ return _wopen(wstr_from_utf8(filename).c_str(), oflag, pmode);
+ }
+ CATCH_AND_RETURN(-1)
+#else
+ return open(filename, oflag, pmode);
+#endif
+}
+
+FILE *
+rnp_fopen(const char *filename, const char *mode)
+{
+#ifdef _WIN32
+ try {
+ return _wfopen(wstr_from_utf8(filename).c_str(), wstr_from_utf8(mode).c_str());
+ }
+ CATCH_AND_RETURN(NULL)
+#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 {
+ return _waccess(wstr_from_utf8(path).c_str(), mode);
+ }
+ CATCH_AND_RETURN(-1)
+#else
+ return access(path, mode);
+#endif
+}
+
+int
+rnp_stat(const char *filename, struct stat *statbuf)
+{
+#ifdef _WIN32
+ static_assert(sizeof(struct stat) == sizeof(struct _stat64i32),
+ "stat is expected to match _stat64i32");
+ try {
+ return _wstat64i32(wstr_from_utf8(filename).c_str(), (struct _stat64i32 *) statbuf);
+ }
+ CATCH_AND_RETURN(-1)
+#else
+ return stat(filename, statbuf);
+#endif
+}
+
+#ifdef _WIN32
+int
+rnp_mkdir(const char *path)
+{
+ try {
+ return _wmkdir(wstr_from_utf8(path).c_str());
+ }
+ CATCH_AND_RETURN(-1)
+}
+#endif
+
+int
+rnp_rename(const char *oldpath, const char *newpath)
+{
+#ifdef _WIN32
+ try {
+ return _wrename(wstr_from_utf8(oldpath).c_str(), wstr_from_utf8(newpath).c_str());
+ }
+ CATCH_AND_RETURN(-1)
+#else
+ return rename(oldpath, newpath);
+#endif
+}
+
+#ifdef _WIN32
+_WDIR *
+#else
+DIR *
+#endif
+rnp_opendir(const char *path)
+{
+#ifdef _WIN32
+ try {
+ return _wopendir(wstr_from_utf8(path).c_str());
+ }
+ CATCH_AND_RETURN(NULL)
+#else
+ return opendir(path);
+#endif
+}
+
+std::string
+#ifdef _WIN32
+rnp_readdir_name(_WDIR *dir)
+{
+ _wdirent *ent;
+ for (;;) {
+ if ((ent = _wreaddir(dir)) == NULL) {
+ return std::string();
+ }
+ if (wcscmp(ent->d_name, L".") && wcscmp(ent->d_name, L"..")) {
+ break;
+ }
+ }
+ try {
+ return wstr_to_utf8(ent->d_name);
+ }
+ CATCH_AND_RETURN(std::string())
+#else
+rnp_readdir_name(DIR *dir)
+{
+ dirent *ent;
+ for (;;) {
+ if ((ent = readdir(dir)) == NULL) {
+ return std::string();
+ }
+ if (strcmp(ent->d_name, ".") && strcmp(ent->d_name, "..")) {
+ break;
+ }
+ }
+ return std::string(ent->d_name);
+#endif
+}
+
+/* return the file modification time */
+int64_t
+rnp_filemtime(const char *path)
+{
+ struct stat st;
+
+ if (rnp_stat(path, &st) != 0) {
+ return 0;
+ } else {
+ return st.st_mtime;
+ }
+}
+
+#ifdef _WIN32
+static const char letters[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
+
+/** @private
+ * generate a temporary file name based on TMPL.
+ *
+ * @param tmpl filename template in UTF-8 ending in XXXXXX
+ * @return file descriptor of newly created and opened file, or -1 on error
+ **/
+int
+rnp_mkstemp(char *tmpl)
+{
+ try {
+ int save_errno = errno;
+ const int mask_length = 6;
+ int len = strlen(tmpl);
+ if (len < mask_length || strcmp(&tmpl[len - mask_length], "XXXXXX")) {
+ errno = EINVAL;
+ return -1;
+ }
+ std::wstring tmpl_w = wstr_from_utf8(tmpl, tmpl + len - mask_length);
+
+ /* This is where the Xs start. */
+ char *XXXXXX = &tmpl[len - mask_length];
+
+ std::random_device rd;
+ std::mt19937_64 rng(rd());
+
+ for (unsigned int countdown = TMP_MAX; --countdown;) {
+ unsigned long long v = rng();
+
+ XXXXXX[0] = letters[v % 36];
+ v /= 36;
+ XXXXXX[1] = letters[v % 36];
+ v /= 36;
+ XXXXXX[2] = letters[v % 36];
+ v /= 36;
+ XXXXXX[3] = letters[v % 36];
+ v /= 36;
+ XXXXXX[4] = letters[v % 36];
+ v /= 36;
+ XXXXXX[5] = letters[v % 36];
+
+ int flags = O_WRONLY | O_CREAT | O_EXCL | O_BINARY;
+ int fd =
+ _wopen((tmpl_w + wstr_from_utf8(XXXXXX)).c_str(), flags, _S_IREAD | _S_IWRITE);
+ if (fd != -1) {
+ errno = save_errno;
+ return fd;
+ } else if (errno != EEXIST)
+ return -1;
+ }
+
+ // We got out of the loop because we ran out of combinations to try.
+ errno = EEXIST;
+ return -1;
+ }
+ CATCH_AND_RETURN(-1)
+}
+#endif // _WIN32
+
+namespace rnp {
+namespace path {
+inline char
+separator()
+{
+#ifdef _WIN32
+ return '\\';
+#else
+ return '/';
+#endif
+}
+
+bool
+exists(const std::string &path, bool is_dir)
+{
+ return is_dir ? rnp_dir_exists(path.c_str()) : rnp_file_exists(path.c_str());
+}
+
+bool
+empty(const std::string &path)
+{
+ auto dir = rnp_opendir(path.c_str());
+ if (!dir) {
+ return true;
+ }
+ bool empty = rnp_readdir_name(dir).empty();
+ rnp_closedir(dir);
+ return empty;
+}
+
+std::string
+HOME(const std::string &sdir)
+{
+ 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();
+}
+
+std::string
+append(const std::string &path, const std::string &name)
+{
+ 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/src/common/file-utils.h b/src/common/file-utils.h
new file mode 100644
index 0000000..1993d17
--- /dev/null
+++ b/src/common/file-utils.h
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RNP_FILE_UTILS_H_
+#define RNP_FILE_UTILS_H_
+
+#include <stdint.h>
+#include <stdio.h>
+#include <dirent.h>
+#include <string>
+
+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);
+
+#ifdef _WIN32
+#define rnp_closedir _wclosedir
+int rnp_mkdir(const char *path);
+_WDIR * rnp_opendir(const char *path);
+std::string rnp_readdir_name(_WDIR *dir);
+#else
+#define rnp_closedir closedir
+DIR * rnp_opendir(const char *path);
+std::string rnp_readdir_name(DIR *dir);
+#endif
+#ifdef _WIN32
+#define RNP_MKDIR(pathname, mode) rnp_mkdir(pathname)
+#else
+#define RNP_MKDIR(pathname, mode) mkdir(pathname, mode)
+#endif
+
+#ifdef _MSC_VER
+#define R_OK 4 /* Test for read permission. */
+#define W_OK 2 /* Test for write permission. */
+#define F_OK 0 /* Test for existence. */
+#endif
+
+/** @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
+ *
+ * @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/src/common/getoptwin.h b/src/common/getoptwin.h
new file mode 100644
index 0000000..84a9583
--- /dev/null
+++ b/src/common/getoptwin.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#ifndef _GETOPTWIN_H
+#define _GETOPTWIN_H 1
+
+#include <getopt.h>
+
+#define _BEGIN_EXTERN_C extern "C" {
+#define _END_EXTERN_C }
+
+#endif /* getoptwin.h */ \ No newline at end of file
diff --git a/src/common/str-utils.cpp b/src/common/str-utils.cpp
new file mode 100644
index 0000000..245e31e
--- /dev/null
+++ b/src/common/str-utils.cpp
@@ -0,0 +1,217 @@
+/*
+ * Copyright (c) 2017 [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.
+ */
+/** String utilities
+ * @file
+ */
+
+#include <cstddef>
+#include <cstring>
+#include <cctype>
+#include <stdexcept>
+#include "str-utils.h"
+#ifdef _WIN32
+#include <locale>
+#include <codecvt>
+#endif
+
+using std::size_t;
+using std::strlen;
+
+namespace rnp {
+char *
+strip_eol(char *s)
+{
+ size_t len = strlen(s);
+
+ while ((len > 0) && ((s[len - 1] == '\n') || (s[len - 1] == '\r'))) {
+ s[--len] = '\0';
+ }
+
+ return s;
+}
+
+bool
+strip_eol(std::string &s)
+{
+ size_t len = s.size();
+ while (len && ((s[len - 1] == '\n') || (s[len - 1] == '\r'))) {
+ len--;
+ }
+ if (len == s.size()) {
+ return false;
+ }
+ s.resize(len);
+ return true;
+}
+
+bool
+is_blank_line(const char *line, size_t len)
+{
+ for (size_t i = 0; i < len && line[i]; i++) {
+ if (line[i] != ' ' && line[i] != '\t' && line[i] != '\r') {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+str_case_eq(const char *s1, const char *s2)
+{
+ while (*s1 && *s2) {
+ if (std::tolower(*s1) != std::tolower(*s2)) {
+ return false;
+ }
+ 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());
+}
+
+static size_t
+hex_prefix_len(const std::string &str)
+{
+ if ((str.length() >= 2) && (str[0] == '0') && ((str[1] == 'x') || (str[1] == 'X'))) {
+ return 2;
+ }
+ return 0;
+}
+
+bool
+is_hex(const std::string &s)
+{
+ for (size_t i = hex_prefix_len(s); i < s.length(); i++) {
+ auto &ch = s[i];
+ if ((ch >= '0') && (ch <= '9')) {
+ continue;
+ }
+ if ((ch >= 'a') && (ch <= 'f')) {
+ continue;
+ }
+ if ((ch >= 'A') && (ch <= 'F')) {
+ continue;
+ }
+ if ((ch == ' ') || (ch == '\t')) {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+std::string
+strip_hex(const std::string &s)
+{
+ std::string res = "";
+ for (size_t idx = hex_prefix_len(s); idx < s.length(); idx++) {
+ auto ch = s[idx];
+ if ((ch == ' ') || (ch == '\t')) {
+ continue;
+ }
+ res.push_back(ch);
+ }
+ return res;
+}
+
+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;
+ }
+ }
+ try {
+ val = std::stoi(s);
+ } catch (std::out_of_range const &ex) {
+ return false;
+ }
+ return true;
+}
+
+bool
+is_slash(char c)
+{
+ return (c == '/') || (c == '\\');
+}
+
+} // namespace rnp
+
+#ifdef _WIN32
+std::wstring
+wstr_from_utf8(const char *s)
+{
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8conv;
+ return utf8conv.from_bytes(s);
+}
+
+std::wstring
+wstr_from_utf8(const char *first, const char *last)
+{
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8conv;
+ return utf8conv.from_bytes(first, last);
+}
+
+std::wstring
+wstr_from_utf8(const std::string &s)
+{
+ return wstr_from_utf8(s.c_str());
+}
+
+std::string
+wstr_to_utf8(const wchar_t *ws)
+{
+ std::wstring_convert<std::codecvt_utf8<wchar_t>> utf8conv;
+ return utf8conv.to_bytes(ws);
+}
+
+std::string
+wstr_to_utf8(const std::wstring &ws)
+{
+ return wstr_to_utf8(ws.c_str());
+}
+#endif
diff --git a/src/common/str-utils.h b/src/common/str-utils.h
new file mode 100644
index 0000000..9a4eb73
--- /dev/null
+++ b/src/common/str-utils.h
@@ -0,0 +1,63 @@
+/*
+ * Copyright (c) 2019-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef RNP_STR_UTILS_H_
+#define RNP_STR_UTILS_H_
+
+#include <string>
+
+namespace rnp {
+char *strip_eol(char *s);
+/**
+ * @brief Strip EOL characters from the string's end.
+ *
+ * @param s string to check
+ * @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);
+
+bool is_hex(const std::string &s);
+std::string strip_hex(const std::string &s);
+
+/**
+ * @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 <string>
+std::wstring wstr_from_utf8(const char *s);
+std::wstring wstr_from_utf8(const char *first, const char *last);
+std::wstring wstr_from_utf8(const std::string &s);
+std::string wstr_to_utf8(const wchar_t *ws);
+std::string wstr_to_utf8(const std::wstring &ws);
+#endif // _WIN32
+#endif
diff --git a/src/common/time-utils.cpp b/src/common/time-utils.cpp
new file mode 100644
index 0000000..83d934a
--- /dev/null
+++ b/src/common/time-utils.cpp
@@ -0,0 +1,96 @@
+/*
+ * Copyright (c) 2021 [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.
+ */
+/** Time utilities
+ * @file
+ */
+
+#include <stdint.h>
+#include "time-utils.h"
+
+static inline time_t
+adjust_time32(time_t t)
+{
+ return (sizeof(t) == 4 && t < 0) ? INT32_MAX : t;
+}
+
+bool
+rnp_y2k38_warning(time_t t)
+{
+ return (sizeof(t) == 4 && (t < 0 || t == INT32_MAX));
+}
+
+time_t
+rnp_mktime(struct tm *tm)
+{
+ return adjust_time32(mktime(tm));
+}
+
+void
+rnp_gmtime(time_t t, struct tm &tm)
+{
+ time_t adjusted = adjust_time32(t);
+#ifndef _WIN32
+ gmtime_r(&adjusted, &tm);
+#else
+ (void) gmtime_s(&tm, &adjusted);
+#endif
+}
+
+void
+rnp_localtime(time_t t, struct tm &tm)
+{
+ time_t adjusted = adjust_time32(t);
+#ifndef _WIN32
+ localtime_r(&adjusted, &tm);
+#else
+ (void) localtime_s(&tm, &adjusted);
+#endif
+}
+
+std::string
+rnp_ctime(time_t t)
+{
+ char time_buf[26];
+ time_t adjusted = adjust_time32(t);
+#ifndef _WIN32
+ (void) ctime_r(&adjusted, time_buf);
+#else
+ (void) ctime_s(time_buf, sizeof(time_buf), &adjusted);
+#endif
+ return std::string(time_buf);
+}
+
+time_t
+rnp_timeadd(time_t t1, time_t t2)
+{
+ if (sizeof(time_t) == 4) {
+ if (t1 < 0 || t2 < 0) {
+ return INT32_MAX;
+ }
+ return adjust_time32(t1 + t2);
+ }
+ return t1 + t2;
+}
diff --git a/src/common/time-utils.h b/src/common/time-utils.h
new file mode 100644
index 0000000..525309e
--- /dev/null
+++ b/src/common/time-utils.h
@@ -0,0 +1,38 @@
+/*
+ * Copyright (c) 2021 [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_TIME_UTILS_H_
+#define RNP_TIME_UTILS_H_
+
+#include <time.h>
+#include <string>
+time_t rnp_mktime(struct tm *tm);
+void rnp_gmtime(time_t t, struct tm &tm);
+void rnp_localtime(time_t t, struct tm &tm);
+bool rnp_y2k38_warning(time_t t);
+std::string rnp_ctime(time_t t);
+time_t rnp_timeadd(time_t t1, time_t t2);
+#endif
diff --git a/src/common/uniwin.h b/src/common/uniwin.h
new file mode 100644
index 0000000..095c325
--- /dev/null
+++ b/src/common/uniwin.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+#if !defined(_UNISTD_H) && defined(_MSC_VER)
+#define _UNISTD_H 1
+
+/* Taken partially from
+ * https://stackoverflow.com/a/826027/1202830
+ */
+
+#include <io.h>
+#include "getoptwin.h"
+#include <direct.h> /* for _getcwd() and _chdir() */
+
+#ifdef _WIN64
+#define ssize_t __int64
+#else
+#define ssize_t long
+#endif
+
+#define STDIN_FILENO 0
+#define STDOUT_FILENO 1
+#define STDERR_FILENO 2
+
+#define NOMINMAX 1 /* to retain std::min and std::max */
+#include <windows.h>
+#include <dirent.h> /* for S_ISREG and S_ISDIR */
+
+#define strncasecmp strnicmp
+#define strcasecmp stricmp
+
+#ifndef MAXPATHLEN
+#define MAXPATHLEN _MAX_PATH
+#endif
+
+typedef unsigned short mode_t;
+
+#endif \ No newline at end of file
diff --git a/src/examples/CMakeLists.txt b/src/examples/CMakeLists.txt
new file mode 100644
index 0000000..8cf1e72
--- /dev/null
+++ b/src/examples/CMakeLists.txt
@@ -0,0 +1,140 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+if(MSVC)
+ # remove extra ${Configuration} subfolder
+ set(ArchiveOutputDir ${CMAKE_BINARY_DIR}\\src\\examples)
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ArchiveOutputDir})
+
+ set(RuntimeOutputDir ${CMAKE_BINARY_DIR}\\src\\examples)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${RuntimeOutputDir})
+
+ find_path(GETOPT_INCLUDE_DIR
+ NAMES getopt.h
+ )
+ find_library(GETOPT_LIBRARY
+ NAMES getopt
+ )
+endif()
+
+add_executable(generate generate.c)
+
+target_include_directories(generate
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(generate
+ PRIVATE
+ librnp
+)
+
+add_executable(encrypt encrypt.c)
+
+target_include_directories(encrypt
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(encrypt
+ PRIVATE
+ librnp
+)
+
+add_executable(decrypt decrypt.c)
+
+target_include_directories(decrypt
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(decrypt
+ PRIVATE
+ librnp
+)
+
+add_executable(sign sign.c)
+
+target_include_directories(sign
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(sign
+ PRIVATE
+ librnp
+)
+
+add_executable(verify verify.c)
+
+target_include_directories(verify
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(verify
+ PRIVATE
+ librnp
+)
+
+add_executable(dump dump.c)
+
+target_include_directories(dump
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(dump
+ PRIVATE
+ librnp
+)
+
+if(MSVC)
+ target_include_directories(dump
+ PRIVATE
+ "${GETOPT_INCLUDE_DIR}"
+ )
+ target_link_libraries(dump
+ PRIVATE
+ "${GETOPT_LIBRARY}"
+ )
+endif()
+
+if (ENABLE_SANITIZERS)
+ foreach(tgt generate encrypt decrypt sign verify dump)
+ set_target_properties(${tgt} PROPERTIES LINKER_LANGUAGE CXX)
+ endforeach()
+endif()
diff --git a/src/examples/README.md b/src/examples/README.md
new file mode 100644
index 0000000..84bdc2e
--- /dev/null
+++ b/src/examples/README.md
@@ -0,0 +1,5 @@
+# RNP C API usage samples
+
+This folder includes examples of RNP library usage for developers.
+
+See [Using the RNP C API](/docs/c-usage.adoc) for more details.
diff --git a/src/examples/decrypt.c b/src/examples/decrypt.c
new file mode 100644
index 0000000..5454a4b
--- /dev/null
+++ b/src/examples/decrypt.c
@@ -0,0 +1,138 @@
+/*
+ * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include <string.h>
+
+#define RNP_SUCCESS 0
+
+/* sample pass provider implementation, which always return 'password' for key decryption and
+ * 'encpassword' when password is needed for file decryption. You may ask for password via
+ * stdin, or choose password based on key properties, whatever else */
+static bool
+example_pass_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char buf[],
+ size_t buf_len)
+{
+ if (!strcmp(pgp_context, "decrypt (symmetric)")) {
+ strncpy(buf, "encpassword", buf_len);
+ return true;
+ }
+ if (!strcmp(pgp_context, "decrypt")) {
+ strncpy(buf, "password", buf_len);
+ return true;
+ }
+
+ return false;
+}
+
+static int
+ffi_decrypt(bool usekeys)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t keyfile = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ uint8_t * buf = NULL;
+ size_t buf_len = 0;
+ int result = 1;
+
+ /* initialize FFI object */
+ if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) {
+ return result;
+ }
+
+ /* check whether we want to use key or password for decryption */
+ if (usekeys) {
+ /* load secret keyring, as it is required for public-key decryption. However, you may
+ * need to load public keyring as well to validate key's signatures. */
+ if (rnp_input_from_path(&keyfile, "secring.pgp") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to open secring.pgp. Did you run ./generate sample?\n");
+ goto finish;
+ }
+
+ /* we may use RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS as well*/
+ if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to read secring.pgp\n");
+ goto finish;
+ }
+ rnp_input_destroy(keyfile);
+ keyfile = NULL;
+ }
+
+ /* set the password provider */
+ rnp_ffi_set_pass_provider(ffi, example_pass_provider, NULL);
+
+ /* create file input and memory output objects for the encrypted message and decrypted
+ * message */
+ if (rnp_input_from_path(&input, "encrypted.asc") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create input object\n");
+ goto finish;
+ }
+
+ if (rnp_output_to_memory(&output, 0) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create output object\n");
+ goto finish;
+ }
+
+ if (rnp_decrypt(ffi, input, output) != RNP_SUCCESS) {
+ fprintf(stdout, "public-key decryption failed\n");
+ goto finish;
+ }
+
+ /* get the decrypted message from the output structure */
+ if (rnp_output_memory_get_buf(output, &buf, &buf_len, false) != RNP_SUCCESS) {
+ goto finish;
+ }
+ fprintf(stdout,
+ "Decrypted message (%s):\n%.*s\n",
+ usekeys ? "with key" : "with password",
+ (int) buf_len,
+ buf);
+
+ result = 0;
+finish:
+ rnp_input_destroy(keyfile);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_ffi_destroy(ffi);
+ return result;
+}
+
+int
+main(int argc, char **argv)
+{
+ int res;
+ res = ffi_decrypt(true);
+ if (res) {
+ return res;
+ }
+ res = ffi_decrypt(false);
+ return res;
+} \ No newline at end of file
diff --git a/src/examples/dump.c b/src/examples/dump.c
new file mode 100644
index 0000000..dc24644
--- /dev/null
+++ b/src/examples/dump.c
@@ -0,0 +1,163 @@
+/*
+ * Copyright (c) 2019, [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 <stdio.h>
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <unistd.h> /* getopt() */
+#include <libgen.h> /* basename() */
+#endif
+#include <rnp/rnp.h>
+
+#define PFX "dump: "
+
+static void
+print_usage(char *program_name)
+{
+ const char *program_basename;
+#ifdef _MSC_VER
+ char fname[_MAX_FNAME];
+ program_basename = fname;
+ _splitpath_s(program_name, NULL, 0, NULL, 0, fname, _MAX_FNAME, NULL, 0);
+#else
+ program_basename = basename(program_name);
+#endif
+
+ fprintf(stderr,
+ "dump: " PFX "Program dumps PGP packets. \n\nUsage:\n"
+ "\t%s [-d|-h] [input.pgp]\n"
+ "\t -d : indicates whether to print packet content. Data is represented as hex\n"
+ "\t -m : dump mpi values\n"
+ "\t -g : dump key fingerprints and grips\n"
+ "\t -j : JSON output\n"
+ "\t -h : prints help and exists\n",
+ program_basename);
+}
+
+static bool
+stdin_reader(void *app_ctx, void *buf, size_t len, size_t *readres)
+{
+ ssize_t res = read(STDIN_FILENO, buf, len);
+ if (res < 0) {
+ return false;
+ }
+ *readres = res;
+ return true;
+}
+
+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;
+}
+
+int
+main(int argc, char *const argv[])
+{
+ char * input_file = NULL;
+ uint32_t flags = 0;
+ uint32_t jflags = 0;
+ bool json = false;
+
+ /* Parse command line options:
+ -i input_file [mandatory]: specifies name of the file with PGP packets
+ -d : indicates whether to dump whole packet content
+ -m : dump mpi contents
+ -g : dump key grips and fingerprints
+ -j : JSON output
+ -h : prints help and exists
+ */
+ int opt = 0;
+ while ((opt = getopt(argc, argv, "dmgjh")) != -1) {
+ switch (opt) {
+ case 'd':
+ flags |= RNP_DUMP_RAW;
+ jflags |= RNP_JSON_DUMP_RAW;
+ break;
+ case 'm':
+ flags |= RNP_DUMP_MPI;
+ jflags |= RNP_JSON_DUMP_MPI;
+ break;
+ case 'g':
+ flags |= RNP_DUMP_GRIP;
+ jflags |= RNP_JSON_DUMP_GRIP;
+ break;
+ case 'j':
+ json = true;
+ break;
+ default:
+ print_usage(argv[0]);
+ return 1;
+ }
+ }
+
+ /* Check whether we have input file */
+ if (optind < argc) {
+ input_file = argv[optind];
+ }
+
+ rnp_input_t input = NULL;
+ rnp_result_t ret = 0;
+ if (input_file) {
+ ret = rnp_input_from_path(&input, input_file);
+ } else {
+ ret = rnp_input_from_callback(&input, stdin_reader, NULL, NULL);
+ }
+ if (ret) {
+ fprintf(stderr, "failed to open source: error 0x%x\n", (int) ret);
+ return 1;
+ }
+
+ if (!json) {
+ rnp_output_t output = NULL;
+ ret = rnp_output_to_callback(&output, stdout_writer, NULL, NULL);
+ if (ret) {
+ fprintf(stderr, "failed to open stdout: error 0x%x\n", (int) ret);
+ rnp_input_destroy(input);
+ return 1;
+ }
+ ret = rnp_dump_packets_to_output(input, output, flags);
+ rnp_output_destroy(output);
+ } else {
+ char *json = NULL;
+ ret = rnp_dump_packets_to_json(input, jflags, &json);
+ if (!ret) {
+ fprintf(stdout, "%s\n", json);
+ }
+ rnp_buffer_destroy(json);
+ }
+ rnp_input_destroy(input);
+
+ /* Inform in case of error occurred during parsing */
+ if (ret) {
+ fprintf(stderr, "Operation failed [error code: 0x%X]\n", (int) ret);
+ return 1;
+ }
+
+ return 0;
+}
diff --git a/src/examples/encrypt.c b/src/examples/encrypt.c
new file mode 100644
index 0000000..0b07fd4
--- /dev/null
+++ b/src/examples/encrypt.c
@@ -0,0 +1,133 @@
+/*
+ * Copyright (c) 2018, [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 <string.h>
+#include <time.h>
+#include <rnp/rnp.h>
+
+#define RNP_SUCCESS 0
+
+static int
+ffi_encrypt()
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_op_encrypt_t encrypt = NULL;
+ rnp_key_handle_t key = NULL;
+ rnp_input_t keyfile = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ const char * message = "RNP encryption sample message";
+ int result = 1;
+
+ /* initialize FFI object */
+ if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) {
+ return result;
+ }
+
+ /* load public keyring - we do not need secret for encryption */
+ if (rnp_input_from_path(&keyfile, "pubring.pgp") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to open pubring.pgp. Did you run ./generate sample?\n");
+ goto finish;
+ }
+
+ /* we may use RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS as well */
+ if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_PUBLIC_KEYS) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to read pubring.pgp\n");
+ goto finish;
+ }
+ rnp_input_destroy(keyfile);
+ keyfile = NULL;
+
+ /* create memory input and file output objects for the message and encrypted message */
+ if (rnp_input_from_memory(&input, (uint8_t *) message, strlen(message), false) !=
+ RNP_SUCCESS) {
+ fprintf(stdout, "failed to create input object\n");
+ goto finish;
+ }
+
+ if (rnp_output_to_path(&output, "encrypted.asc") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create output object\n");
+ goto finish;
+ }
+
+ /* create encryption operation */
+ if (rnp_op_encrypt_create(&encrypt, ffi, input, output) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create encrypt operation\n");
+ goto finish;
+ }
+
+ /* setup encryption parameters */
+ rnp_op_encrypt_set_armor(encrypt, true);
+ rnp_op_encrypt_set_file_name(encrypt, "message.txt");
+ rnp_op_encrypt_set_file_mtime(encrypt, (uint32_t) time(NULL));
+ rnp_op_encrypt_set_compression(encrypt, "ZIP", 6);
+ rnp_op_encrypt_set_cipher(encrypt, RNP_ALGNAME_AES_256);
+ rnp_op_encrypt_set_aead(encrypt, "None");
+
+ /* locate recipient's key and add it to the operation context. While we search by userid
+ * (which is easier), you can search by keyid, fingerprint or grip. */
+ if (rnp_locate_key(ffi, "userid", "rsa@key", &key) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to locate recipient key rsa@key.\n");
+ goto finish;
+ }
+
+ if (rnp_op_encrypt_add_recipient(encrypt, key) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to add recipient\n");
+ goto finish;
+ }
+ rnp_key_handle_destroy(key);
+ key = NULL;
+
+ /* add encryption password as well */
+ if (rnp_op_encrypt_add_password(
+ encrypt, "encpassword", RNP_ALGNAME_SHA256, 0, RNP_ALGNAME_AES_256) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to add encryption password\n");
+ goto finish;
+ }
+
+ /* execute encryption operation */
+ if (rnp_op_encrypt_execute(encrypt) != RNP_SUCCESS) {
+ fprintf(stdout, "encryption failed\n");
+ goto finish;
+ }
+
+ fprintf(stdout, "Encryption succeeded. Encrypted message written to file encrypted.asc\n");
+ result = 0;
+finish:
+ rnp_op_encrypt_destroy(encrypt);
+ rnp_input_destroy(keyfile);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+ return result;
+}
+
+int
+main(int argc, char **argv)
+{
+ return ffi_encrypt();
+}
diff --git a/src/examples/generate.c b/src/examples/generate.c
new file mode 100644
index 0000000..979e63e
--- /dev/null
+++ b/src/examples/generate.c
@@ -0,0 +1,330 @@
+/*
+ * Copyright (c) 2018, [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 <string.h>
+#include <rnp/rnp.h>
+
+#define RNP_SUCCESS 0
+
+/* RSA key JSON description. 31536000 = 1 year expiration, 15768000 = half year */
+const char *RSA_KEY_DESC = "{\
+ 'primary': {\
+ 'type': 'RSA',\
+ 'length': 2048,\
+ 'userid': 'rsa@key',\
+ 'expiration': 31536000,\
+ 'usage': ['sign'],\
+ 'protection': {\
+ 'cipher': 'AES256',\
+ 'hash': 'SHA256'\
+ }\
+ },\
+ 'sub': {\
+ 'type': 'RSA',\
+ 'length': 2048,\
+ 'expiration': 15768000,\
+ 'usage': ['encrypt'],\
+ 'protection': {\
+ 'cipher': 'AES256',\
+ 'hash': 'SHA256'\
+ }\
+ }\
+}";
+
+const char *CURVE_25519_KEY_DESC = "{\
+ 'primary': {\
+ 'type': 'EDDSA',\
+ 'userid': '25519@key',\
+ 'expiration': 0,\
+ 'usage': ['sign'],\
+ 'protection': {\
+ 'cipher': 'AES256',\
+ 'hash': 'SHA256'\
+ }\
+ },\
+ 'sub': {\
+ 'type': 'ECDH',\
+ 'curve': 'Curve25519',\
+ 'expiration': 15768000,\
+ 'usage': ['encrypt'],\
+ 'protection': {\
+ 'cipher': 'AES256',\
+ 'hash': 'SHA256'\
+ }\
+ }\
+}";
+
+/* basic pass provider implementation, which always return 'password' for key protection.
+You may ask for password via stdin, or choose password based on key properties, whatever else
+*/
+static bool
+example_pass_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char buf[],
+ size_t buf_len)
+{
+ if (strcmp(pgp_context, "protect")) {
+ return false;
+ }
+
+ strncpy(buf, "password", buf_len);
+ return true;
+}
+
+/* this simple helper function just prints armored key, searched by userid, to stdout */
+static bool
+ffi_print_key(rnp_ffi_t ffi, const char *uid, bool secret)
+{
+ rnp_output_t keydata = NULL;
+ rnp_key_handle_t key = NULL;
+ uint32_t flags = RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS;
+ uint8_t * buf = NULL;
+ size_t buf_len = 0;
+ bool result = false;
+
+ /* you may search for the key via userid, keyid, fingerprint, grip */
+ if (rnp_locate_key(ffi, "userid", uid, &key) != RNP_SUCCESS) {
+ return false;
+ }
+
+ if (!key) {
+ return false;
+ }
+
+ /* create in-memory output structure to later use buffer */
+ if (rnp_output_to_memory(&keydata, 0) != RNP_SUCCESS) {
+ goto finish;
+ }
+
+ flags = flags | (secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC);
+ if (rnp_key_export(key, keydata, flags) != RNP_SUCCESS) {
+ goto finish;
+ }
+
+ /* get key's contents from the output structure */
+ if (rnp_output_memory_get_buf(keydata, &buf, &buf_len, false) != RNP_SUCCESS) {
+ goto finish;
+ }
+ fprintf(stdout, "%.*s", (int) buf_len, buf);
+
+ result = true;
+finish:
+ rnp_key_handle_destroy(key);
+ rnp_output_destroy(keydata);
+ return result;
+}
+
+static bool
+ffi_export_key(rnp_ffi_t ffi, const char *uid, bool secret)
+{
+ rnp_output_t keyfile = NULL;
+ rnp_key_handle_t key = NULL;
+ uint32_t flags = RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS;
+ char filename[32] = {0};
+ char * keyid = NULL;
+ bool result = false;
+
+ /* you may search for the key via userid, keyid, fingerprint, grip */
+ if (rnp_locate_key(ffi, "userid", uid, &key) != RNP_SUCCESS) {
+ return false;
+ }
+
+ if (!key) {
+ return false;
+ }
+
+ /* get key's id and build filename */
+ if (rnp_key_get_keyid(key, &keyid) != RNP_SUCCESS) {
+ goto finish;
+ }
+ snprintf(filename, sizeof(filename), "key-%s-%s.asc", keyid, secret ? "sec" : "pub");
+ rnp_buffer_destroy(keyid);
+
+ /* create file output structure */
+ if (rnp_output_to_path(&keyfile, filename) != RNP_SUCCESS) {
+ goto finish;
+ }
+
+ flags = flags | (secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC);
+ if (rnp_key_export(key, keyfile, flags) != RNP_SUCCESS) {
+ goto finish;
+ }
+
+ result = true;
+finish:
+ rnp_key_handle_destroy(key);
+ rnp_output_destroy(keyfile);
+ return result;
+}
+
+/* this example function generates RSA/RSA and Eddsa/X25519 keypairs */
+static int
+ffi_generate_keys()
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_output_t keyfile = NULL;
+ char * key_grips = NULL;
+ int result = 1;
+
+ /* initialize FFI object */
+ if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) {
+ return result;
+ }
+
+ /* set password provider */
+ if (rnp_ffi_set_pass_provider(ffi, example_pass_provider, NULL)) {
+ goto finish;
+ }
+
+ /* generate EDDSA/X25519 keypair */
+ if (rnp_generate_key_json(ffi, CURVE_25519_KEY_DESC, &key_grips) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to generate eddsa key\n");
+ goto finish;
+ }
+
+ fprintf(stdout, "Generated 25519 key/subkey:\n%s\n", key_grips);
+ /* destroying key_grips buffer is our obligation */
+ rnp_buffer_destroy(key_grips);
+ key_grips = NULL;
+
+ /* generate RSA keypair */
+ if (rnp_generate_key_json(ffi, RSA_KEY_DESC, &key_grips) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to generate rsa key\n");
+ goto finish;
+ }
+
+ fprintf(stdout, "Generated RSA key/subkey:\n%s\n", key_grips);
+ rnp_buffer_destroy(key_grips);
+ key_grips = NULL;
+
+ /* create file output object and save public keyring with generated keys, overwriting
+ * previous file if any. You may use rnp_output_to_memory() here as well. */
+ if (rnp_output_to_path(&keyfile, "pubring.pgp") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to initialize pubring.pgp writing\n");
+ goto finish;
+ }
+
+ if (rnp_save_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_PUBLIC_KEYS) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to save pubring\n");
+ goto finish;
+ }
+
+ rnp_output_destroy(keyfile);
+ keyfile = NULL;
+
+ /* create file output object and save secret keyring with generated keys */
+ if (rnp_output_to_path(&keyfile, "secring.pgp") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to initialize secring.pgp writing\n");
+ goto finish;
+ }
+
+ if (rnp_save_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to save secring\n");
+ goto finish;
+ }
+
+ rnp_output_destroy(keyfile);
+ keyfile = NULL;
+
+ result = 0;
+finish:
+ rnp_buffer_destroy(key_grips);
+ rnp_output_destroy(keyfile);
+ rnp_ffi_destroy(ffi);
+ return result;
+}
+
+static int
+ffi_output_keys()
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t keyfile = NULL;
+ int result = 2;
+
+ /* initialize FFI object */
+ if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) {
+ return result;
+ }
+
+ /* load keyrings */
+ if (rnp_input_from_path(&keyfile, "pubring.pgp") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to open pubring.pgp\n");
+ goto finish;
+ }
+
+ /* actually, we may use 0 instead of RNP_LOAD_SAVE_PUBLIC_KEYS, to not check key types */
+ if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_PUBLIC_KEYS) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to read pubring.pgp\n");
+ goto finish;
+ }
+ rnp_input_destroy(keyfile);
+ keyfile = NULL;
+
+ if (rnp_input_from_path(&keyfile, "secring.pgp") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to open secring.pgp\n");
+ goto finish;
+ }
+
+ if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to read secring.pgp\n");
+ goto finish;
+ }
+ rnp_input_destroy(keyfile);
+ keyfile = NULL;
+
+ /* print armored keys to the stdout */
+ if (!ffi_print_key(ffi, "rsa@key", false) || !ffi_print_key(ffi, "rsa@key", true) ||
+ !ffi_print_key(ffi, "25519@key", false) || !ffi_print_key(ffi, "25519@key", true)) {
+ fprintf(stdout, "failed to print armored key(s)\n");
+ goto finish;
+ }
+
+ /* write armored keys to the files, named key-<keyid>-pub.asc/named key-<keyid>-sec.asc */
+ if (!ffi_export_key(ffi, "rsa@key", false) || !ffi_export_key(ffi, "rsa@key", true) ||
+ !ffi_export_key(ffi, "25519@key", false) || !ffi_export_key(ffi, "25519@key", true)) {
+ fprintf(stdout, "failed to write armored key(s) to file\n");
+ goto finish;
+ }
+
+ result = 0;
+finish:
+ rnp_input_destroy(keyfile);
+ rnp_ffi_destroy(ffi);
+ return result;
+}
+
+int
+main(int argc, char **argv)
+{
+ int res = ffi_generate_keys();
+ if (res) {
+ return res;
+ }
+ res = ffi_output_keys();
+ return res;
+}
diff --git a/src/examples/sign.c b/src/examples/sign.c
new file mode 100644
index 0000000..678dd91
--- /dev/null
+++ b/src/examples/sign.c
@@ -0,0 +1,168 @@
+/*
+ * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include <string.h>
+#include <time.h>
+
+#define RNP_SUCCESS 0
+
+/* sample pass provider implementation, which always return 'password' */
+static bool
+example_pass_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char buf[],
+ size_t buf_len)
+{
+ strncpy(buf, "password", buf_len);
+ return true;
+}
+
+static int
+ffi_sign()
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t keyfile = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t sign = NULL;
+ rnp_key_handle_t key = NULL;
+ const char * message = "RNP signing sample message";
+ int result = 1;
+
+ /* initialize FFI object */
+ if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) {
+ return result;
+ }
+
+ /* load secret keyring, as it is required for signing. However, you may need to load public
+ * keyring as well to validate key's signatures. */
+ if (rnp_input_from_path(&keyfile, "secring.pgp") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to open secring.pgp. Did you run ./generate sample?\n");
+ goto finish;
+ }
+
+ /* we may use RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS as well */
+ if (rnp_load_keys(ffi, "GPG", keyfile, RNP_LOAD_SAVE_SECRET_KEYS) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to read secring.pgp\n");
+ goto finish;
+ }
+ rnp_input_destroy(keyfile);
+ keyfile = NULL;
+
+ /* set the password provider - we'll need password to unlock secret keys */
+ rnp_ffi_set_pass_provider(ffi, example_pass_provider, NULL);
+
+ /* create file input and memory output objects for the encrypted message and decrypted
+ * message */
+ if (rnp_input_from_memory(&input, (uint8_t *) message, strlen(message), false) !=
+ RNP_SUCCESS) {
+ fprintf(stdout, "failed to create input object\n");
+ goto finish;
+ }
+
+ if (rnp_output_to_path(&output, "signed.asc") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create output object\n");
+ goto finish;
+ }
+
+ /* initialize and configure sign operation, use
+ * rnp_op_sign_create_cleartext/rnp_op_sign_create_detached for cleartext or detached
+ * signature. */
+ if (rnp_op_sign_create(&sign, ffi, input, output) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create sign operation\n");
+ goto finish;
+ }
+
+ /* armor, file name, compression */
+ rnp_op_sign_set_armor(sign, true);
+ rnp_op_sign_set_file_name(sign, "message.txt");
+ rnp_op_sign_set_file_mtime(sign, (uint32_t) time(NULL));
+ rnp_op_sign_set_compression(sign, "ZIP", 6);
+ /* signatures creation time - by default will be set to the current time as well */
+ rnp_op_sign_set_creation_time(sign, (uint32_t) time(NULL));
+ /* signatures expiration time - by default will be 0, i.e. never expire */
+ rnp_op_sign_set_expiration_time(sign, 365 * 24 * 60 * 60);
+ /* set hash algorithm - should be compatible for all signatures */
+ rnp_op_sign_set_hash(sign, RNP_ALGNAME_SHA256);
+
+ /* now add signatures. First locate the signing key, then add and setup signature */
+ /* RSA signature */
+ if (rnp_locate_key(ffi, "userid", "rsa@key", &key) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to locate signing key rsa@key.\n");
+ goto finish;
+ }
+
+ /* we do not need pointer to the signature so passing NULL as the last parameter */
+ if (rnp_op_sign_add_signature(sign, key, NULL) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to add signature for key rsa@key.\n");
+ goto finish;
+ }
+
+ /* do not forget to destroy key handle */
+ rnp_key_handle_destroy(key);
+ key = NULL;
+
+ /* EdDSA signature */
+ if (rnp_locate_key(ffi, "userid", "25519@key", &key) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to locate signing key 25519@key.\n");
+ goto finish;
+ }
+
+ if (rnp_op_sign_add_signature(sign, key, NULL) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to add signature for key 25519@key.\n");
+ goto finish;
+ }
+
+ rnp_key_handle_destroy(key);
+ key = NULL;
+
+ /* finally do signing */
+ if (rnp_op_sign_execute(sign) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to sign\n");
+ goto finish;
+ }
+
+ fprintf(stdout, "Signing succeeded. See file signed.asc.\n");
+
+ result = 0;
+finish:
+ rnp_input_destroy(keyfile);
+ rnp_key_handle_destroy(key);
+ rnp_op_sign_destroy(sign);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_ffi_destroy(ffi);
+ return result;
+}
+
+int
+main(int argc, char **argv)
+{
+ return ffi_sign();
+}
diff --git a/src/examples/verify.c b/src/examples/verify.c
new file mode 100644
index 0000000..17f42a8
--- /dev/null
+++ b/src/examples/verify.c
@@ -0,0 +1,165 @@
+/*
+ * Copyright (c) 2018, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include <string.h>
+
+#define RNP_SUCCESS 0
+
+/* example key provider which loads key from file based on its keyid */
+static void
+example_key_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ const char *identifier_type,
+ const char *identifier,
+ bool secret)
+{
+ rnp_input_t input = NULL;
+ char filename[32] = {0};
+ if (strcmp(identifier_type, "keyid")) {
+ if (strcmp(identifier_type, "fingerprint")) {
+ fprintf(stdout, "Unsupported key search: %s = %s\n", identifier_type, identifier);
+ return;
+ }
+ /* if we search by fp then keyid is last 16 chars */
+ if (strlen(identifier) < 40) {
+ fprintf(stdout, "Invalid fingerprint: %s\n", identifier);
+ return;
+ }
+ identifier += 24;
+ }
+
+ snprintf(filename, sizeof(filename), "key-%s-%s.asc", identifier, secret ? "sec" : "pub");
+
+ if (rnp_input_from_path(&input, filename) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to open key file %s\n", filename);
+ return;
+ }
+
+ if (rnp_load_keys(
+ ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PUBLIC_KEYS) !=
+ RNP_SUCCESS) {
+ fprintf(stdout, "failed to load key from file %s\n", filename);
+ }
+ rnp_input_destroy(input);
+}
+
+static int
+ffi_verify()
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_op_verify_t verify = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ uint8_t * buf = NULL;
+ size_t buf_len = 0;
+ size_t sigcount = 0;
+ int result = 1;
+
+ /* initialize FFI object */
+ if (rnp_ffi_create(&ffi, "GPG", "GPG") != RNP_SUCCESS) {
+ return result;
+ }
+
+ /* we do not load any keys here since we'll use key provider */
+ rnp_ffi_set_key_provider(ffi, example_key_provider, NULL);
+
+ /* create file input and memory output objects for the signed message and verified
+ * message */
+ if (rnp_input_from_path(&input, "signed.asc") != RNP_SUCCESS) {
+ fprintf(stdout, "failed to open file 'signed.asc'. Did you run the sign example?\n");
+ goto finish;
+ }
+
+ if (rnp_output_to_memory(&output, 0) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create output object\n");
+ goto finish;
+ }
+
+ if (rnp_op_verify_create(&verify, ffi, input, output) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to create verification context\n");
+ goto finish;
+ }
+
+ if (rnp_op_verify_execute(verify) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to execute verification operation\n");
+ goto finish;
+ }
+
+ /* now check signatures and get some info about them */
+ if (rnp_op_verify_get_signature_count(verify, &sigcount) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to get signature count\n");
+ goto finish;
+ }
+
+ for (size_t i = 0; i < sigcount; i++) {
+ rnp_op_verify_signature_t sig = NULL;
+ rnp_result_t sigstatus = RNP_SUCCESS;
+ rnp_key_handle_t key = NULL;
+ char * keyid = NULL;
+
+ if (rnp_op_verify_get_signature_at(verify, i, &sig) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to get signature %d\n", (int) i);
+ goto finish;
+ }
+
+ if (rnp_op_verify_signature_get_key(sig, &key) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to get signature's %d key\n", (int) i);
+ goto finish;
+ }
+
+ if (rnp_key_get_keyid(key, &keyid) != RNP_SUCCESS) {
+ fprintf(stdout, "failed to get key id %d\n", (int) i);
+ rnp_key_handle_destroy(key);
+ goto finish;
+ }
+
+ sigstatus = rnp_op_verify_signature_get_status(sig);
+ fprintf(stdout, "Status for signature from key %s : %d\n", keyid, (int) sigstatus);
+ rnp_buffer_destroy(keyid);
+ rnp_key_handle_destroy(key);
+ }
+
+ /* get the verified message from the output structure */
+ if (rnp_output_memory_get_buf(output, &buf, &buf_len, false) != RNP_SUCCESS) {
+ goto finish;
+ }
+ fprintf(stdout, "Verified message:\n%.*s\n", (int) buf_len, buf);
+
+ result = 0;
+finish:
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_ffi_destroy(ffi);
+ return result;
+}
+
+int
+main(int argc, char **argv)
+{
+ return ffi_verify();
+}
diff --git a/src/fuzzing/CMakeLists.txt b/src/fuzzing/CMakeLists.txt
new file mode 100644
index 0000000..c177035
--- /dev/null
+++ b/src/fuzzing/CMakeLists.txt
@@ -0,0 +1,145 @@
+# Copyright (c) 2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+
+if(NOT DEFINED ENV{LIB_FUZZING_ENGINE})
+ add_compile_options(-fsanitize=fuzzer-no-link)
+ add_link_options(-fsanitize=fuzzer)
+else()
+ # This section is used by OSS-Fuzz
+ add_link_options($ENV{LIB_FUZZING_ENGINE})
+ if($ENV{FUZZING_ENGINE} STREQUAL "afl")
+ link_libraries(-stdlib=libc++)
+ endif()
+endif()
+
+add_executable(fuzz_dump dump.c)
+
+target_include_directories(fuzz_dump
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_dump
+ PRIVATE
+ librnp
+)
+
+add_executable(fuzz_keyring keyring.c)
+
+target_include_directories(fuzz_keyring
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_keyring
+ PRIVATE
+ librnp
+)
+
+add_executable(fuzz_keyimport keyimport.c)
+
+target_include_directories(fuzz_keyimport
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_keyimport
+ PRIVATE
+ librnp
+)
+
+add_executable(fuzz_sigimport sigimport.c)
+
+target_include_directories(fuzz_sigimport
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_sigimport
+ PRIVATE
+ librnp
+)
+
+add_executable(fuzz_verify verify.c)
+
+target_include_directories(fuzz_verify
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_verify
+ PRIVATE
+ librnp
+)
+
+add_executable(fuzz_verify_detached verify_detached.c)
+
+target_include_directories(fuzz_verify_detached
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_verify_detached
+ PRIVATE
+ librnp
+)
+
+add_executable(fuzz_keyring_kbx keyring_kbx.c)
+
+target_include_directories(fuzz_keyring_kbx
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_keyring_kbx
+ PRIVATE
+ librnp
+)
+
+add_executable(fuzz_keyring_g10 keyring_g10.cpp)
+
+target_include_directories(fuzz_keyring_g10
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(fuzz_keyring_g10
+ PRIVATE
+ librnp-static
+)
+
+if (ENABLE_SANITIZERS)
+ foreach(tgt fuzz_dump fuzz_keyring fuzz_keyimport fuzz_sigimport fuzz_verify fuzz_verify_detached fuzz_keyring_kbx fuzz_keyring_g10)
+ set_target_properties(${tgt} PROPERTIES LINKER_LANGUAGE CXX)
+ endforeach()
+endif()
diff --git a/src/fuzzing/dump.c b/src/fuzzing/dump.c
new file mode 100644
index 0000000..026bfc2
--- /dev/null
+++ b/src/fuzzing/dump.c
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+
+#ifdef RNP_RUN_TESTS
+int dump_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+dump_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp_input_t input = NULL;
+ (void) rnp_input_from_memory(&input, data, size, false);
+ rnp_output_t output = NULL;
+ (void) rnp_output_to_null(&output);
+
+ (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/src/fuzzing/keyimport.c b/src/fuzzing/keyimport.c
new file mode 100644
index 0000000..16e1272
--- /dev/null
+++ b/src/fuzzing/keyimport.c
@@ -0,0 +1,97 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include <rnp/rnp_err.h>
+
+#ifdef RNP_RUN_TESTS
+int keyimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+keyimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp_input_t input = NULL;
+ rnp_result_t ret = 0;
+ rnp_ffi_t ffi = NULL;
+
+ /* try non-permissive import */
+ ret = rnp_input_from_memory(&input, data, size, false);
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ char *results = NULL;
+ ret = rnp_import_keys(
+ ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, &results);
+ rnp_buffer_destroy(results);
+ rnp_input_destroy(input);
+ rnp_ffi_destroy(ffi);
+
+ /* try permissive import */
+ ret = rnp_input_from_memory(&input, data, size, false);
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ results = NULL;
+ ret = rnp_import_keys(ffi,
+ input,
+ RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS |
+ RNP_LOAD_SAVE_PERMISSIVE,
+ &results);
+ rnp_buffer_destroy(results);
+ rnp_input_destroy(input);
+ rnp_ffi_destroy(ffi);
+
+ /* try non-permissive iterative import */
+ ret = rnp_input_from_memory(&input, data, size, false);
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ do {
+ results = NULL;
+ ret = rnp_import_keys(ffi,
+ input,
+ RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS |
+ RNP_LOAD_SAVE_SINGLE,
+ &results);
+ rnp_buffer_destroy(results);
+ } while (!ret);
+ rnp_input_destroy(input);
+ rnp_ffi_destroy(ffi);
+
+ /* try permissive iterative import */
+ ret = rnp_input_from_memory(&input, data, size, false);
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ do {
+ results = NULL;
+ ret = rnp_import_keys(ffi,
+ input,
+ RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS |
+ RNP_LOAD_SAVE_PERMISSIVE | RNP_LOAD_SAVE_SINGLE,
+ &results);
+ rnp_buffer_destroy(results);
+ } while (!ret);
+ rnp_input_destroy(input);
+ rnp_ffi_destroy(ffi);
+
+ return 0;
+}
diff --git a/src/fuzzing/keyring.c b/src/fuzzing/keyring.c
new file mode 100644
index 0000000..bac4e13
--- /dev/null
+++ b/src/fuzzing/keyring.c
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+
+#ifdef RNP_RUN_TESTS
+int keyring_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+keyring_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp_input_t input = NULL;
+ rnp_result_t ret = 0;
+ rnp_ffi_t ffi = NULL;
+
+ ret = rnp_input_from_memory(&input, data, size, false);
+
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ ret =
+ rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS);
+
+ rnp_input_destroy(input);
+ rnp_ffi_destroy(ffi);
+
+ return 0;
+}
diff --git a/src/fuzzing/keyring_g10.cpp b/src/fuzzing/keyring_g10.cpp
new file mode 100644
index 0000000..f2495a5
--- /dev/null
+++ b/src/fuzzing/keyring_g10.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "../lib/pgp-key.h"
+#include "../librekey/key_store_g10.h"
+#include "../librepgp/stream-common.h"
+#include "../lib/sec_profile.hpp"
+
+#ifdef RNP_RUN_TESTS
+int keyring_g10_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+keyring_g10_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+extern "C" RNP_API int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp::SecurityContext ctx;
+ rnp_key_store_t ks(ctx);
+ pgp_source_t memsrc = {};
+
+ init_mem_src(&memsrc, data, size, false);
+ rnp_key_store_g10_from_src(&ks, &memsrc, NULL);
+ src_close(&memsrc);
+
+ return 0;
+}
diff --git a/src/fuzzing/keyring_kbx.c b/src/fuzzing/keyring_kbx.c
new file mode 100644
index 0000000..768e669
--- /dev/null
+++ b/src/fuzzing/keyring_kbx.c
@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+
+#ifdef RNP_RUN_TESTS
+int keyring_kbx_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+keyring_kbx_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp_input_t input = NULL;
+ rnp_result_t ret = 0;
+ rnp_ffi_t ffi = NULL;
+
+ ret = rnp_input_from_memory(&input, data, size, false);
+
+ ret = rnp_ffi_create(&ffi, "KBX", "G10");
+ ret = rnp_load_keys(ffi, "KBX", input, RNP_LOAD_SAVE_PUBLIC_KEYS);
+
+ rnp_input_destroy(input);
+ rnp_ffi_destroy(ffi);
+ return 0;
+}
diff --git a/src/fuzzing/sigimport.c b/src/fuzzing/sigimport.c
new file mode 100644
index 0000000..35adeb7
--- /dev/null
+++ b/src/fuzzing/sigimport.c
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+
+#ifdef RNP_RUN_TESTS
+int sigimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+sigimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp_input_t input = NULL;
+ rnp_result_t ret = 0;
+ rnp_ffi_t ffi = NULL;
+
+ ret = rnp_input_from_memory(&input, data, size, false);
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ char *results = NULL;
+ ret = rnp_import_signatures(ffi, input, 0, &results);
+ rnp_buffer_destroy(results);
+ rnp_input_destroy(input);
+ rnp_ffi_destroy(ffi);
+
+ return 0;
+}
diff --git a/src/fuzzing/verify.c b/src/fuzzing/verify.c
new file mode 100644
index 0000000..cd6c849
--- /dev/null
+++ b/src/fuzzing/verify.c
@@ -0,0 +1,58 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "stdio.h"
+
+#ifdef RNP_RUN_TESTS
+int verify_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+verify_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_result_t ret;
+
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ ret = rnp_input_from_memory(&input, data, size, false);
+ ret = rnp_output_to_null(&output);
+
+ rnp_op_verify_t op = NULL;
+ ret = rnp_op_verify_create(&op, ffi, input, output);
+ ret = rnp_op_verify_execute(op);
+ ret = rnp_op_verify_destroy(op);
+
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_ffi_destroy(ffi);
+
+ return 0;
+}
diff --git a/src/fuzzing/verify_detached.c b/src/fuzzing/verify_detached.c
new file mode 100644
index 0000000..2afb59a
--- /dev/null
+++ b/src/fuzzing/verify_detached.c
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "string.h"
+
+#ifdef RNP_RUN_TESTS
+int verify_detached_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+int
+verify_detached_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#else
+int
+LLVMFuzzerTestOneInput(const uint8_t *data, size_t size)
+#endif
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_input_t msg_input = NULL;
+ rnp_result_t ret;
+
+ ret = rnp_ffi_create(&ffi, "GPG", "GPG");
+ ret = rnp_input_from_memory(&input, data, size, false);
+ const char *msg = "message";
+ ret = rnp_input_from_memory(&msg_input, (const uint8_t *) msg, strlen(msg), true);
+
+ rnp_op_verify_t verify = NULL;
+ ret = rnp_op_verify_detached_create(&verify, ffi, msg_input, input);
+ ret = rnp_op_verify_execute(verify);
+ ret = rnp_op_verify_destroy(verify);
+
+ rnp_input_destroy(input);
+ rnp_input_destroy(msg_input);
+ rnp_ffi_destroy(ffi);
+
+ return 0;
+}
diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt
new file mode 100755
index 0000000..086ac57
--- /dev/null
+++ b/src/lib/CMakeLists.txt
@@ -0,0 +1,594 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+include(GNUInstallDirs)
+include(GenerateExportHeader)
+
+# these could probably be optional but are currently not
+find_package(BZip2 REQUIRED)
+find_package(ZLIB REQUIRED)
+
+# required packages
+find_package(JSON-C 0.11 REQUIRED)
+if (CRYPTO_BACKEND_BOTAN)
+ find_package(Botan2 2.14.0 REQUIRED)
+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)
+check_include_file_cxx(fcntl.h HAVE_FCNTL_H)
+check_include_file_cxx(inttypes.h HAVE_INTTYPES_H)
+check_include_file_cxx(limits.h HAVE_LIMITS_H)
+check_include_file_cxx(stdint.h HAVE_STDINT_H)
+check_include_file_cxx(string.h HAVE_STRING_H)
+check_include_file_cxx(sys/cdefs.h HAVE_SYS_CDEFS_H)
+check_include_file_cxx(sys/cdefs.h HAVE_SYS_MMAN_H)
+check_include_file_cxx(sys/resource.h HAVE_SYS_RESOURCE_H)
+check_include_file_cxx(sys/stat.h HAVE_SYS_STAT_H)
+check_include_file_cxx(sys/types.h HAVE_SYS_TYPES_H)
+check_include_file_cxx(sys/param.h HAVE_SYS_PARAM_H)
+check_include_file_cxx(unistd.h HAVE_UNISTD_H)
+check_include_file_cxx(sys/wait.h HAVE_SYS_WAIT_H)
+check_cxx_symbol_exists(mkdtemp "stdlib.h;unistd.h" HAVE_MKDTEMP)
+check_cxx_symbol_exists(mkstemp "stdlib.h;unistd.h" HAVE_MKSTEMP)
+check_cxx_symbol_exists(realpath stdlib.h HAVE_REALPATH)
+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}")
+# 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_LOWERCASE 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 CAMELLIA DES
+ # cipher modes
+ MODE_CBC MODE_CFB
+ # RNG
+ AUTO_RNG AUTO_SEEDING_RNG HMAC HMAC_DRBG
+ # hash
+ CRC24 HASH MD5 SHA1 SHA2_32 SHA2_64 SHA3
+ # public-key core
+ DL_GROUP DL_PUBLIC_KEY_FAMILY ECC_GROUP ECC_PUBLIC_KEY_CRYPTO PUBLIC_KEY_CRYPTO
+ # 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
+ )
+ 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
+ resolve_feature_state(ENABLE_BLOWFISH "BLOWFISH")
+ resolve_feature_state(ENABLE_CAST5 "CAST_128")
+ resolve_feature_state(ENABLE_RIPEMD160 "RIPEMD_160")
+ 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
+ AES-128-OCB AES-192-OCB AES-256-OCB
+ CAMELLIA-128-ECB CAMELLIA-192-ECB CAMELLIA-256-ECB
+ DES-EDE3
+ # hashes
+ MD5 SHA1 SHA224 SHA256 SHA384 SHA512 SHA3-256 SHA3-512
+ # curves
+ PRIME256V1 SECP384R1 SECP521R1 SECP256K1
+ # public key
+ RSAENCRYPTION DSAENCRYPTION DHKEYAGREEMENT ID-ECPUBLICKEY X25519 ED25519
+ )
+ 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")
+ resolve_feature_state(ENABLE_BLOWFISH "BF-ECB")
+ resolve_feature_state(ENABLE_CAST5 "CAST5-ECB")
+ resolve_feature_state(ENABLE_RIPEMD160 "RIPEMD160")
+ resolve_feature_state(ENABLE_AEAD "AES-128-OCB;AES-192-OCB;AES-256-OCB")
+ 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")
+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
+ crypto/ec_ossl.cpp
+ crypto/ecdh_utils.cpp
+ crypto/ecdh_ossl.cpp
+ crypto/ecdsa_ossl.cpp
+ crypto/eddsa_ossl.cpp
+ crypto/dl_ossl.cpp
+ crypto/elgamal_ossl.cpp
+ crypto/hash_common.cpp
+ crypto/hash_ossl.cpp
+ crypto/hash_crc24.cpp
+ crypto/mpi.cpp
+ crypto/rng_ossl.cpp
+ crypto/rsa_ossl.cpp
+ crypto/s2k.cpp
+ crypto/s2k_ossl.cpp
+ crypto/symmetric_ossl.cpp
+ crypto/signatures.cpp
+ crypto/mem_ossl.cpp
+ crypto/cipher.cpp
+ crypto/cipher_ossl.cpp
+ )
+ if(ENABLE_SM2)
+ list(APPEND CRYPTO_SOURCES crypto/sm2_ossl.cpp)
+ endif()
+elseif(CRYPTO_BACKEND_BOTAN)
+ set(CRYPTO_SOURCES
+ crypto/bn.cpp
+ crypto/dsa.cpp
+ crypto/ec_curves.cpp
+ crypto/ec.cpp
+ crypto/ecdh_utils.cpp
+ crypto/ecdh.cpp
+ crypto/ecdsa.cpp
+ crypto/eddsa.cpp
+ crypto/elgamal.cpp
+ crypto/hash_common.cpp
+ crypto/hash.cpp
+ crypto/mpi.cpp
+ crypto/rng.cpp
+ crypto/rsa.cpp
+ crypto/s2k.cpp
+ crypto/symmetric.cpp
+ crypto/signatures.cpp
+ crypto/mem.cpp
+ crypto/cipher.cpp
+ crypto/cipher_botan.cpp
+ )
+ if(ENABLE_SM2)
+ list(APPEND CRYPTO_SOURCES crypto/sm2.cpp)
+ endif()
+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/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
+ ../librepgp/stream-ctx.cpp
+ ../librepgp/stream-dump.cpp
+ ../librepgp/stream-key.cpp
+ ../librepgp/stream-packet.cpp
+ ../librepgp/stream-parse.cpp
+ ../librepgp/stream-sig.cpp
+ ../librepgp/stream-write.cpp
+
+ # librekey
+ ../librekey/key_store_g10.cpp
+ ../librekey/key_store_kbx.cpp
+ ../librekey/key_store_pgp.cpp
+ ../librekey/rnp_key_store.cpp
+
+ # cryptography
+ ${CRYPTO_SOURCES}
+
+ # other sources
+ sec_profile.cpp
+ crypto.cpp
+ fingerprint.cpp
+ generate-key.cpp
+ key-provider.cpp
+ logging.cpp
+ json-utils.cpp
+ utils.cpp
+ pass-provider.cpp
+ pgp-key.cpp
+ rnp.cpp
+)
+
+get_target_property(_comp_options librnp-obj COMPILE_OPTIONS)
+string(REGEX MATCH "\\-fsanitize=[a-z,]*undefined" _comp_sanitizers "${_comp_options}" "${CMAKE_C_FLAGS}")
+if (ENABLE_SANITIZERS OR _comp_sanitizers)
+ # sha1cd attempts to use unaligned access for optimisations on intel CPUs
+ # CFLAGS is checked as sanitizers may be enabled without CMake var
+ set_source_files_properties(crypto/sha1cd/sha1.c
+ PROPERTIES COMPILE_DEFINITIONS "SHA1DC_FORCE_ALIGNED_ACCESS"
+ )
+endif()
+
+set_target_properties(librnp-obj PROPERTIES POSITION_INDEPENDENT_CODE ON)
+target_include_directories(librnp-obj
+ PUBLIC
+ "$<BUILD_INTERFACE:${PROJECT_BINARY_DIR}/src/lib>"
+ "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/src/common>"
+ "$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>"
+ "$<INSTALL_INTERFACE:include>"
+ PRIVATE
+ "${CMAKE_CURRENT_SOURCE_DIR}"
+ "${PROJECT_SOURCE_DIR}/src"
+)
+target_link_libraries(librnp-obj PRIVATE JSON-C::JSON-C)
+if (CRYPTO_BACKEND_BOTAN)
+ target_link_libraries(librnp-obj PRIVATE Botan2::Botan2)
+elseif (CRYPTO_BACKEND_OPENSSL)
+ target_link_libraries(librnp-obj PRIVATE OpenSSL::Crypto)
+endif()
+
+target_link_libraries(librnp-obj PRIVATE sexp)
+
+set_target_properties(librnp-obj PROPERTIES CXX_VISIBILITY_PRESET hidden)
+if (TARGET BZip2::BZip2)
+ target_link_libraries(librnp-obj PRIVATE BZip2::BZip2)
+endif()
+if (TARGET ZLIB::ZLIB)
+ target_link_libraries(librnp-obj PRIVATE ZLIB::ZLIB)
+endif()
+if (BUILD_SHARED_LIBS)
+ target_compile_definitions(librnp-obj PRIVATE librnp_EXPORTS)
+else()
+ target_compile_definitions(librnp-obj PRIVATE RNP_STATIC)
+endif()
+
+add_library(librnp $<TARGET_OBJECTS:librnp-obj> $<TARGET_OBJECTS:rnp-common>)
+if (OpenSSL::applink)
+ target_link_libraries(librnp PRIVATE OpenSSL::applink)
+endif(OpenSSL::applink)
+
+set_target_properties(librnp
+ PROPERTIES
+ VERSION "${RNP_VERSION}"
+ SOVERSION "${RNP_VERSION_MAJOR}"
+ OUTPUT_NAME "rnp"
+)
+
+if (BUILD_SHARED_LIBS)
+ add_library(librnp-static STATIC $<TARGET_OBJECTS:librnp-obj> $<TARGET_OBJECTS:rnp-common>)
+ if (OpenSSL::applink)
+ target_link_libraries(librnp-static PRIVATE OpenSSL::applink)
+ endif(OpenSSL::applink)
+ if (WIN32)
+ set_target_properties(librnp-static PROPERTIES OUTPUT_NAME "rnp-static")
+ else (WIN32)
+# On Unix like systems we will build/install/pack shared and static libraries librnp.so and librnp.a
+# On Windows we will build/install/pack dynamic, import and static libraries rnp.dll, rnp.lib and rnp-static.lib
+ set_target_properties(librnp-static PROPERTIES OUTPUT_NAME "rnp")
+ endif (WIN32)
+ # Limit symbols export only to rnp_* functions.
+ if (APPLE)
+ # use -export_symbols_list on Apple OSs
+ target_link_options(librnp PRIVATE -Wl,-exported_symbols_list "${CMAKE_CURRENT_SOURCE_DIR}/librnp.symbols")
+ set_target_properties(librnp PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/librnp.symbols")
+ elseif(NOT WIN32)
+ target_link_options(librnp PRIVATE "-Wl,--version-script=${CMAKE_CURRENT_SOURCE_DIR}/librnp.vsc")
+ set_target_properties(librnp PROPERTIES LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/librnp.vsc")
+ endif()
+else()
+ add_library(librnp-static ALIAS librnp)
+endif()
+
+foreach (prop LINK_LIBRARIES INTERFACE_LINK_LIBRARIES INCLUDE_DIRECTORIES INTERFACE_INCLUDE_DIRECTORIES)
+ get_target_property(val librnp-obj ${prop})
+ if (BUILD_SHARED_LIBS)
+ set_property(TARGET librnp-static PROPERTY ${prop} ${val})
+ list(REMOVE_ITEM val "$<LINK_ONLY:sexp>")
+ set_property(TARGET librnp PROPERTY ${prop} ${val})
+ else()
+ set_property(TARGET librnp PROPERTY ${prop} ${val})
+ endif()
+
+endforeach()
+
+generate_export_header(librnp
+ BASE_NAME rnp
+ EXPORT_MACRO_NAME RNP_API
+ EXPORT_FILE_NAME rnp/rnp_export.h
+ STATIC_DEFINE RNP_STATIC
+ INCLUDE_GUARD_NAME RNP_EXPORT
+)
+
+# This has precedence and can cause some confusion when the binary
+# dir one isn't actually being used. To be improved.
+if (NOT CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_CURRENT_BINARY_DIR)
+ file(REMOVE "${CMAKE_CURRENT_SOURCE_DIR}/config.h")
+ file(REMOVE "${CMAKE_CURRENT_SOURCE_DIR}/version.h")
+endif()
+
+if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.12.0")
+ set(namelink_component NAMELINK_COMPONENT development)
+else()
+ set(namelink_component)
+endif()
+
+# add these to the rnp-targets export
+# On Unix like systems we will build/install/pack shared and static libraries librnp.so and librnp.a
+# On Windows we will build/install/pack dynamic, import and static libraries rnp.dll, rnp.lib and rnp-static.lib
+
+# If a client application uses shared rnp library, sexp is statically linked to librnp.so
+# If a client application uses static rnp library, it still needs libsexp.a
+
+if (BUILD_SHARED_LIBS)
+# both static and shared libraries
+install(TARGETS librnp
+ EXPORT rnp-targets
+ LIBRARY
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ COMPONENT runtime
+ ${namelink_component}
+ ARCHIVE
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ COMPONENT development
+ )
+
+ install(TARGETS librnp-static sexp
+ EXPORT rnp-targets
+ ARCHIVE
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ COMPONENT development
+ )
+else(BUILD_SHARED_LIBS)
+# static libraries only
+install(TARGETS librnp sexp
+ EXPORT rnp-targets
+ ARCHIVE
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}"
+ COMPONENT development
+)
+endif(BUILD_SHARED_LIBS)
+
+# install dll only for windows
+if (WIN32)
+ install(TARGETS librnp
+ RUNTIME
+ DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ COMPONENT runtime
+ )
+endif(WIN32)
+
+# install headers
+install(
+ FILES
+ "${PROJECT_SOURCE_DIR}/include/rnp/rnp.h"
+ COMPONENT
+ development
+ DESTINATION
+ "${CMAKE_INSTALL_INCLUDEDIR}/rnp"
+ RENAME
+ rnp.h
+)
+install(
+ FILES
+ "${PROJECT_SOURCE_DIR}/include/rnp/rnp_err.h"
+ COMPONENT
+ development
+ DESTINATION
+ "${CMAKE_INSTALL_INCLUDEDIR}/rnp"
+ RENAME
+ rnp_err.h
+)
+install(
+ FILES
+ "${PROJECT_BINARY_DIR}/src/lib/rnp/rnp_export.h"
+ COMPONENT
+ development
+ DESTINATION
+ "${CMAKE_INSTALL_INCLUDEDIR}/rnp"
+ RENAME
+ rnp_export.h
+)
+
+# .cmake installs
+set(INSTALL_CMAKEDIR "${CMAKE_INSTALL_LIBDIR}/cmake/rnp")
+
+install(EXPORT rnp-targets
+ FILE rnp-targets.cmake
+ NAMESPACE rnp::
+ DESTINATION "${INSTALL_CMAKEDIR}"
+ COMPONENT development
+)
+
+include(CMakePackageConfigHelpers)
+configure_package_config_file(
+ "${PROJECT_SOURCE_DIR}/cmake/rnp-config.cmake.in"
+ "${CMAKE_CURRENT_BINARY_DIR}/rnp-config.cmake"
+ INSTALL_DESTINATION "${INSTALL_CMAKEDIR}"
+)
+write_basic_package_version_file(rnp-config-version.cmake
+ VERSION "${PROJECT_VERSION}"
+ COMPATIBILITY SameMajorVersion
+)
+install (
+ FILES
+ "${CMAKE_CURRENT_BINARY_DIR}/rnp-config.cmake"
+ "${CMAKE_CURRENT_BINARY_DIR}/rnp-config-version.cmake"
+ DESTINATION "${INSTALL_CMAKEDIR}"
+ COMPONENT development
+)
+
+function(get_linked_libs libsvar dirsvar tgt)
+ get_target_property(imported ${tgt} IMPORTED)
+ list(APPEND visited_targets ${tgt})
+ if (imported)
+ get_target_property(linkedlibs ${tgt} INTERFACE_LINK_LIBRARIES)
+ endif()
+ set(libs)
+ foreach (lib ${linkedlibs})
+ if (TARGET ${lib})
+ list(FIND visited_targets ${lib} visited)
+ if ((${visited} EQUAL -1) AND (${CMAKE_SHARED_LIBRARY_PREFIX}))
+ # library
+ get_target_property(liblocation ${lib} LOCATION)
+ get_filename_component(linkedlib ${liblocation} NAME_WE)
+ string(REGEX REPLACE "^${CMAKE_SHARED_LIBRARY_PREFIX}" "" linkedlib ${linkedlib})
+ get_linked_libs(linkedlibs libdirs ${lib})
+ list(APPEND libs ${linkedlib} ${linkedlibs})
+ # directory
+ get_filename_component(libdir ${liblocation} DIRECTORY)
+ list(FIND ${dirsvar} ${libdir} seendir)
+ if (${seendir} EQUAL -1)
+ list(APPEND ${dirsvar} ${libdir} ${libdirs})
+ endif()
+ endif()
+ endif()
+ endforeach()
+ set(visited_targets ${visited_targets} PARENT_SCOPE)
+ set(${libsvar} ${libs} PARENT_SCOPE)
+ set(${dirsvar} ${${dirsvar}} PARENT_SCOPE)
+endfunction()
+
+get_linked_libs(libs dirs librnp)
+set(linkercmd)
+foreach (dir ${dirs})
+ string(APPEND linkercmd "-L${dir} ")
+endforeach()
+foreach (lib ${libs})
+ string(APPEND linkercmd "-l${lib} ")
+endforeach()
+string(STRIP "${linkercmd}" linkercmd)
+set(LIBRNP_PRIVATE_LIBS ${linkercmd})
+
+# create a pkgconfig .pc too
+find_package(PkgConfig)
+if (PKG_CONFIG_FOUND)
+ get_target_property(LIBRNP_OUTPUT_NAME librnp OUTPUT_NAME)
+
+ 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
+ )
+ install(
+ FILES "${PROJECT_BINARY_DIR}/librnp.pc"
+ DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig"
+ COMPONENT development
+ )
+endif()
+
+# Build and install man page
+if (ENABLE_DOC)
+ add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/librnp.3.adoc" ${RNP_VERSION})
+endif()
diff --git a/src/lib/config.h.in b/src/lib/config.h.in
new file mode 100644
index 0000000..f8880d5
--- /dev/null
+++ b/src/lib/config.h.in
@@ -0,0 +1,72 @@
+/*-
+ * Copyright (c) 2018-2020 Ribose Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#define PACKAGE_STRING "rnp @RNP_VERSION_FULL@"
+#define PACKAGE_BUGREPORT "@BUGREPORT_EMAIL@"
+
+#cmakedefine HAVE_BZLIB_H
+#cmakedefine HAVE_ZLIB_H
+
+#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
+
+#cmakedefine CRYPTO_BACKEND_BOTAN
+#cmakedefine CRYPTO_BACKEND_OPENSSL
+#cmakedefine CRYPTO_BACKEND_OPENSSL3
+
+#cmakedefine ENABLE_SM2
+#cmakedefine ENABLE_AEAD
+#cmakedefine ENABLE_TWOFISH
+#cmakedefine ENABLE_BRAINPOOL
+#cmakedefine ENABLE_IDEA
+#cmakedefine ENABLE_BLOWFISH
+#cmakedefine ENABLE_CAST5
+#cmakedefine ENABLE_RIPEMD160
+
+/* 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(WIN32) && !defined(MSYS)) || \
+ ((defined(__clang__) && (__clang_major__ >= 4)) )
+#define RNP_USE_STD_REGEX 1
+#endif
diff --git a/src/lib/crypto.cpp b/src/lib/crypto.cpp
new file mode 100644
index 0000000..2634604
--- /dev/null
+++ b/src/lib/crypto.cpp
@@ -0,0 +1,244 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "config.h"
+
+#ifdef HAVE_SYS_CDEFS_H
+#include <sys/cdefs.h>
+#endif
+
+#if defined(__NetBSD__)
+__COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved.");
+__RCSID("$NetBSD: crypto.c,v 1.36 2014/02/17 07:39:19 agc Exp $");
+#endif
+
+#include <sys/types.h>
+#include <sys/stat.h>
+
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#endif
+
+#include <string.h>
+#include <time.h>
+#include <rnp/rnp_def.h>
+
+#include <librepgp/stream-packet.h>
+#include <librepgp/stream-key.h>
+
+#include "types.h"
+#include "crypto/common.h"
+#include "crypto.h"
+#include "fingerprint.h"
+#include "pgp-key.h"
+#include "utils.h"
+
+bool
+pgp_generate_seckey(const rnp_keygen_crypto_params_t &crypto,
+ pgp_key_pkt_t & seckey,
+ bool primary)
+{
+ /* populate pgp key structure */
+ seckey = {};
+ seckey.version = PGP_V4;
+ 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) {
+ case PGP_PKA_RSA:
+ if (rsa_generate(&crypto.ctx->rng, &seckey.material.rsa, crypto.rsa.modulus_bit_len)) {
+ RNP_LOG("failed to generate RSA key");
+ return false;
+ }
+ break;
+ case PGP_PKA_DSA:
+ if (dsa_generate(&crypto.ctx->rng,
+ &seckey.material.dsa,
+ crypto.dsa.p_bitlen,
+ crypto.dsa.q_bitlen)) {
+ RNP_LOG("failed to generate DSA key");
+ return false;
+ }
+ break;
+ case PGP_PKA_EDDSA:
+ if (eddsa_generate(&crypto.ctx->rng, &seckey.material.ec)) {
+ RNP_LOG("failed to generate EDDSA key");
+ return false;
+ }
+ break;
+ case PGP_PKA_ECDH:
+ if (!ecdh_set_params(&seckey.material.ec, crypto.ecc.curve)) {
+ RNP_LOG("Unsupported curve [ID=%d]", crypto.ecc.curve);
+ return false;
+ }
+ if (crypto.ecc.curve == PGP_CURVE_25519) {
+ if (x25519_generate(&crypto.ctx->rng, &seckey.material.ec)) {
+ RNP_LOG("failed to generate x25519 key");
+ return false;
+ }
+ seckey.material.ec.curve = crypto.ecc.curve;
+ break;
+ }
+ [[fallthrough]];
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ if (!curve_supported(crypto.ecc.curve)) {
+ RNP_LOG("EC generate: curve %d is not supported.", (int) crypto.ecc.curve);
+ return false;
+ }
+ if (ec_generate(&crypto.ctx->rng, &seckey.material.ec, seckey.alg, crypto.ecc.curve)) {
+ RNP_LOG("failed to generate EC key");
+ return false;
+ }
+ seckey.material.ec.curve = crypto.ecc.curve;
+ break;
+ case PGP_PKA_ELGAMAL:
+ if (elgamal_generate(
+ &crypto.ctx->rng, &seckey.material.eg, crypto.elgamal.key_bitlen)) {
+ RNP_LOG("failed to generate ElGamal key");
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("key generation not implemented for PK alg: %d", seckey.alg);
+ return false;
+ }
+ seckey.sec_protection.s2k.usage = PGP_S2KU_NONE;
+ seckey.material.secret = true;
+ seckey.material.validity.mark_valid();
+ /* fill the sec_data/sec_len */
+ if (encrypt_secret_key(&seckey, NULL, crypto.ctx->rng)) {
+ RNP_LOG("failed to fill sec_data");
+ return false;
+ }
+ return true;
+}
+
+bool
+key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key2)
+{
+ if (key1->alg != key2->alg) {
+ return false;
+ }
+
+ switch (key1->alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return mpi_equal(&key1->rsa.n, &key2->rsa.n) && mpi_equal(&key1->rsa.e, &key2->rsa.e);
+ case PGP_PKA_DSA:
+ return mpi_equal(&key1->dsa.p, &key2->dsa.p) &&
+ mpi_equal(&key1->dsa.q, &key2->dsa.q) &&
+ mpi_equal(&key1->dsa.g, &key2->dsa.g) && mpi_equal(&key1->dsa.y, &key2->dsa.y);
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return mpi_equal(&key1->eg.p, &key2->eg.p) && mpi_equal(&key1->eg.g, &key2->eg.g) &&
+ mpi_equal(&key1->eg.y, &key2->eg.y);
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ return (key1->ec.curve == key2->ec.curve) && mpi_equal(&key1->ec.p, &key2->ec.p);
+ default:
+ RNP_LOG("unknown public key algorithm: %d", (int) key1->alg);
+ return false;
+ }
+}
+
+rnp_result_t
+validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng)
+{
+#ifdef FUZZERS_ENABLED
+ /* do not timeout on large keys during fuzzing */
+ return RNP_SUCCESS;
+#else
+ switch (material->alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return rsa_validate_key(rng, &material->rsa, material->secret);
+ case PGP_PKA_DSA:
+ return dsa_validate_key(rng, &material->dsa, material->secret);
+ case PGP_PKA_EDDSA:
+ return eddsa_validate_key(rng, &material->ec, material->secret);
+ case PGP_PKA_ECDH:
+ if (!curve_supported(material->ec.curve)) {
+ /* allow to import key if curve is not supported */
+ RNP_LOG("ECDH validate: curve %d is not supported.", (int) material->ec.curve);
+ return RNP_SUCCESS;
+ }
+ return ecdh_validate_key(rng, &material->ec, material->secret);
+ case PGP_PKA_ECDSA:
+ if (!curve_supported(material->ec.curve)) {
+ /* allow to import key if curve is not supported */
+ RNP_LOG("ECDH validate: curve %d is not supported.", (int) material->ec.curve);
+ return RNP_SUCCESS;
+ }
+ return ecdsa_validate_key(rng, &material->ec, material->secret);
+ case PGP_PKA_SM2:
+#if defined(ENABLE_SM2)
+ return sm2_validate_key(rng, &material->ec, material->secret);
+#else
+ RNP_LOG("SM2 key validation is not available.");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+#endif
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return elgamal_validate_key(&material->eg, material->secret) ? RNP_SUCCESS :
+ RNP_ERROR_GENERIC;
+ default:
+ RNP_LOG("unknown public key algorithm: %d", (int) material->alg);
+ }
+
+ return RNP_ERROR_BAD_PARAMETERS;
+#endif
+}
diff --git a/src/lib/crypto.h b/src/lib/crypto.h
new file mode 100644
index 0000000..320daf8
--- /dev/null
+++ b/src/lib/crypto.h
@@ -0,0 +1,118 @@
+/*
+ * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** \file
+ */
+
+#ifndef CRYPTO_H_
+#define CRYPTO_H_
+
+#include <limits.h>
+#include "crypto/common.h"
+#include <rekey/rnp_key_store.h>
+
+/* raw key generation */
+bool pgp_generate_seckey(const rnp_keygen_crypto_params_t &params,
+ pgp_key_pkt_t & seckey,
+ bool primary);
+
+/** generate a new primary key
+ *
+ * @param desc 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
+ * @return true if successful, false otherwise.
+ **/
+bool pgp_generate_primary_key(rnp_keygen_primary_desc_t &desc,
+ bool merge_defaults,
+ pgp_key_t & primary_sec,
+ pgp_key_t & primary_pub,
+ pgp_key_store_format_t secformat);
+
+/** generate a new subkey
+ *
+ * @param desc keygen description
+ * @param merge_defaults true if you want defaults to be set for unset
+ * keygen description parameters.
+ * @param primary_sec pointer to the primary secret key that will own this
+ * subkey, must not be NULL
+ * @param primary_pub pointer to the primary public key that will own this
+ * subkey, 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
+ * @param password_provider the password provider that will be used to
+ * decrypt the primary key, may be NULL if primary key is unlocked
+ * @return true if successful, false otherwise.
+ **/
+bool pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc,
+ bool merge_defaults,
+ pgp_key_t & primary_sec,
+ pgp_key_t & primary_pub,
+ pgp_key_t & subkey_sec,
+ pgp_key_t & subkey_pub,
+ const pgp_password_provider_t &password_provider,
+ 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
+ * @param key2 second key material
+ * @return true if both key materials are equal or false otherwise
+ */
+bool key_material_equal(const pgp_key_material_t *key1, const pgp_key_material_t *key2);
+
+rnp_result_t validate_pgp_key_material(const pgp_key_material_t *material, rnp::RNG *rng);
+
+#endif /* CRYPTO_H_ */
diff --git a/src/lib/crypto/backend_version.cpp b/src/lib/crypto/backend_version.cpp
new file mode 100644
index 0000000..859b048
--- /dev/null
+++ b/src/lib/crypto/backend_version.cpp
@@ -0,0 +1,184 @@
+/*-
+ * 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 "backend_version.h"
+#include "logging.h"
+#if defined(CRYPTO_BACKEND_BOTAN)
+#include <botan/version.h>
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+#include <openssl/opensslv.h>
+#include <openssl/crypto.h>
+#include "ossl_common.h"
+#if defined(CRYPTO_BACKEND_OPENSSL3)
+#include <openssl/provider.h>
+#endif
+#include <string.h>
+#include "config.h"
+#ifndef RNP_USE_STD_REGEX
+#include <regex.h>
+#else
+#include <regex>
+#endif
+#endif
+#include <cassert>
+
+namespace rnp {
+
+const char *
+backend_string()
+{
+#if defined(CRYPTO_BACKEND_BOTAN)
+ return "Botan";
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ return "OpenSSL";
+#else
+#error "Unknown backend"
+#endif
+}
+
+const char *
+backend_version()
+{
+#if defined(CRYPTO_BACKEND_BOTAN)
+ return Botan::short_version_cstr();
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ /* Use regexp to retrieve version (second word) from version string
+ * like "OpenSSL 1.1.1l 24 Aug 2021"
+ * */
+ static char version[32] = {};
+ if (version[0]) {
+ return version;
+ }
+ const char *reg = "OpenSSL (([0-9]\\.[0-9]\\.[0-9])[a-z]*(-beta[0-9])*(-dev)*) ";
+#ifndef RNP_USE_STD_REGEX
+ static regex_t r;
+ regmatch_t matches[5];
+ const char * ver = OpenSSL_version(OPENSSL_VERSION);
+
+ if (!strlen(version)) {
+ if (regcomp(&r, reg, REG_EXTENDED) != 0) {
+ RNP_LOG("failed to compile regexp");
+ return "unknown";
+ }
+ }
+ if (regexec(&r, ver, 5, matches, 0) != 0) {
+ return "unknown";
+ }
+ assert(sizeof(version) > matches[1].rm_eo - matches[1].rm_so);
+ memcpy(version, ver + matches[1].rm_so, matches[1].rm_eo - matches[1].rm_so);
+ version[matches[1].rm_eo - matches[1].rm_so] = '\0';
+#else
+ static std::regex re(reg, std::regex_constants::extended);
+ std::smatch result;
+ std::string ver = OpenSSL_version(OPENSSL_VERSION);
+ if (!std::regex_search(ver, result, re)) {
+ return "unknown";
+ }
+ assert(sizeof(version) > result[1].str().size());
+ strncpy(version, result[1].str().c_str(), sizeof(version) - 1);
+#endif
+ return version;
+#else
+#error "Unknown backend"
+#endif
+}
+
+#if defined(CRYPTO_BACKEND_OPENSSL3)
+
+#if defined(ENABLE_IDEA) || defined(ENABLE_CAST5) || defined(ENABLE_BLOWFISH) || \
+ defined(ENABLE_RIPEMD160)
+#define OPENSSL_LOAD_LEGACY
+#endif
+
+typedef struct openssl3_state {
+#if defined(OPENSSL_LOAD_LEGACY)
+ OSSL_PROVIDER *legacy;
+#endif
+ 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 if needed */
+#if defined(OPENSSL_LOAD_LEGACY)
+ 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;
+ }
+#endif
+ *param = state;
+ return true;
+}
+
+void
+backend_finish(void *param)
+{
+ if (!param) {
+ return;
+ }
+ openssl3_state *state = (openssl3_state *) param;
+ OSSL_PROVIDER_unload(state->def);
+#if defined(OPENSSL_LOAD_LEGACY)
+ OSSL_PROVIDER_unload(state->legacy);
+#endif
+ free(state);
+}
+#else
+bool
+backend_init(void **param)
+{
+ return true;
+}
+
+void
+backend_finish(void *param)
+{
+ // Do nothing
+}
+#endif
+
+} // namespace rnp
diff --git a/src/lib/crypto/backend_version.h b/src/lib/crypto/backend_version.h
new file mode 100644
index 0000000..13c5269
--- /dev/null
+++ b/src/lib/crypto/backend_version.h
@@ -0,0 +1,44 @@
+/*-
+ * 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_BACKEND_VERSION_H_
+#define CRYPTO_BACKEND_VERSION_H_
+
+#include "config.h"
+
+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/src/lib/crypto/bn.cpp b/src/lib/crypto/bn.cpp
new file mode 100644
index 0000000..d5ae6b4
--- /dev/null
+++ b/src/lib/crypto/bn.cpp
@@ -0,0 +1,107 @@
+/*
+ * Copyright (c) 2017-2021 Ribose Inc.
+ * Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org>
+ * 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 AUTHOR ``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 AUTHOR 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 "bn.h"
+#include <botan/ffi.h>
+#include <stdlib.h>
+#include <assert.h>
+#include "utils.h"
+
+/* essentiually, these are just wrappers around the botan functions */
+/* usually the order of args changes */
+/* the bignum_t API tends to have more const poisoning */
+/* these wrappers also check the arguments passed for sanity */
+
+/* store in unsigned [big endian] format */
+int
+bn_bn2bin(const bignum_t *a, unsigned char *b)
+{
+ if (!a || !b) {
+ return -1;
+ }
+ return botan_mp_to_bin(a->mp, b);
+}
+
+bignum_t *
+mpi2bn(const pgp_mpi_t *val)
+{
+ assert(val);
+ if (!val) {
+ RNP_LOG("NULL val.");
+ return NULL;
+ }
+ bignum_t *res = bn_new();
+ if (!res) {
+ return NULL;
+ }
+ if (botan_mp_from_bin(res->mp, val->mpi, val->len)) {
+ bn_free(res);
+ res = NULL;
+ }
+ return res;
+}
+
+bool
+bn2mpi(const bignum_t *bn, pgp_mpi_t *val)
+{
+ val->len = bn_num_bytes(*bn);
+ if (val->len > PGP_MPINT_SIZE) {
+ RNP_LOG("Too large MPI.");
+ val->len = 0;
+ return false;
+ }
+ return bn_bn2bin(bn, val->mpi) == 0;
+}
+
+bignum_t *
+bn_new(void)
+{
+ bignum_t *a = (bignum_t *) calloc(1, sizeof(*a));
+ if (!a) {
+ return NULL;
+ }
+ botan_mp_init(&a->mp);
+ return a;
+}
+
+void
+bn_free(bignum_t *a)
+{
+ if (a) {
+ botan_mp_destroy(a->mp);
+ free(a);
+ }
+}
+
+size_t
+bn_num_bytes(const bignum_t &a)
+{
+ size_t bytes = 0;
+ if (botan_mp_num_bits(a.mp, &bytes)) {
+ RNP_LOG("botan_mp_num_bits failed.");
+ }
+ return BITS_TO_BYTES(bytes);
+}
diff --git a/src/lib/crypto/bn.h b/src/lib/crypto/bn.h
new file mode 100644
index 0000000..26cc547
--- /dev/null
+++ b/src/lib/crypto/bn.h
@@ -0,0 +1,64 @@
+/*-
+ * Copyright (c) 2017-2021 Ribose Inc.
+ * Copyright (c) 2012 Alistair Crooks <agc@NetBSD.org>
+ * 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 AUTHOR ``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 AUTHOR 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_BN_H_
+#define RNP_BN_H_
+
+#include <stdio.h>
+#include <stdint.h>
+#include "config.h"
+#include "mpi.h"
+
+#if defined(CRYPTO_BACKEND_OPENSSL)
+#include <openssl/bn.h>
+
+#define bignum_t BIGNUM
+#elif defined(CRYPTO_BACKEND_BOTAN)
+typedef struct botan_mp_struct *botan_mp_t;
+typedef struct bignum_t_st {
+ botan_mp_t mp;
+} bignum_t;
+
+#define BN_HANDLE(x) ((x).mp)
+#define BN_HANDLE_PTR(x) ((x)->mp)
+#else
+#error "Unknown crypto backend."
+#endif
+
+/*********************************/
+
+bignum_t *bn_new(void);
+void bn_free(bignum_t * /*a*/);
+
+int bn_bn2bin(const bignum_t * /*a*/, unsigned char * /*b*/);
+
+bignum_t *mpi2bn(const pgp_mpi_t *val);
+
+bool bn2mpi(const bignum_t *bn, pgp_mpi_t *val);
+
+size_t bn_num_bytes(const bignum_t &a);
+
+#endif
diff --git a/src/lib/crypto/bn_ossl.cpp b/src/lib/crypto/bn_ossl.cpp
new file mode 100644
index 0000000..34e1a3e
--- /dev/null
+++ b/src/lib/crypto/bn_ossl.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2021, [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 <stdlib.h>
+#include <assert.h>
+#include "bn.h"
+#include "logging.h"
+
+/* store in unsigned [big endian] format */
+int
+bn_bn2bin(const bignum_t *a, unsigned char *b)
+{
+ if (!a || !b) {
+ return -1;
+ }
+ return BN_bn2bin(a, b) >= 0 ? 0 : -1;
+}
+
+bignum_t *
+mpi2bn(const pgp_mpi_t *val)
+{
+ assert(val);
+ if (!val) {
+ RNP_LOG("NULL val.");
+ return NULL;
+ }
+ bignum_t *res = bn_new();
+ if (!res) {
+ return NULL;
+ }
+ if (!BN_bin2bn(val->mpi, val->len, res)) {
+ bn_free(res);
+ res = NULL;
+ }
+ return res;
+}
+
+bool
+bn2mpi(const bignum_t *bn, pgp_mpi_t *val)
+{
+ val->len = bn_num_bytes(*bn);
+ return bn_bn2bin(bn, val->mpi) == 0;
+}
+
+bignum_t *
+bn_new(void)
+{
+ return BN_new();
+}
+
+void
+bn_free(bignum_t *a)
+{
+ BN_clear_free(a);
+}
+
+size_t
+bn_num_bytes(const bignum_t &a)
+{
+ return (BN_num_bits(&a) + 7) / 8;
+}
diff --git a/src/lib/crypto/cipher.cpp b/src/lib/crypto/cipher.cpp
new file mode 100644
index 0000000..a6c6fce
--- /dev/null
+++ b/src/lib/crypto/cipher.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2021, [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 "config.h"
+#include "symmetric.h"
+#include "cipher.hpp"
+
+#if defined(CRYPTO_BACKEND_OPENSSL)
+#include "cipher_ossl.hpp"
+#elif defined(CRYPTO_BACKEND_BOTAN)
+#include "cipher_botan.hpp"
+#endif
+
+std::unique_ptr<Cipher>
+Cipher::encryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding)
+{
+#if defined(CRYPTO_BACKEND_OPENSSL)
+ return Cipher_OpenSSL::encryption(cipher, mode, tag_size, disable_padding);
+#elif defined(CRYPTO_BACKEND_BOTAN)
+ return Cipher_Botan::encryption(cipher, mode, tag_size, disable_padding);
+#else
+#error "Crypto backend not specified"
+#endif
+}
+
+std::unique_ptr<Cipher>
+Cipher::decryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding)
+{
+#if defined(CRYPTO_BACKEND_OPENSSL)
+ return Cipher_OpenSSL::decryption(cipher, mode, tag_size, disable_padding);
+#elif defined(CRYPTO_BACKEND_BOTAN)
+ return Cipher_Botan::decryption(cipher, mode, tag_size, disable_padding);
+#else
+#error "Crypto backend not specified"
+#endif
+}
+
+Cipher::Cipher(pgp_symm_alg_t alg) : m_alg(alg)
+{
+}
+
+Cipher::~Cipher()
+{
+}
+
+size_t
+Cipher::block_size() const
+{
+ return pgp_block_size(m_alg);
+}
diff --git a/src/lib/crypto/cipher.hpp b/src/lib/crypto/cipher.hpp
new file mode 100644
index 0000000..c9edf15
--- /dev/null
+++ b/src/lib/crypto/cipher.hpp
@@ -0,0 +1,77 @@
+/*
+ * Copyright (c) 2021, [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_CIPHER_HPP
+#define RNP_CIPHER_HPP
+
+#include <memory>
+#include <string>
+#include <repgp/repgp_def.h>
+
+// Note: for AEAD modes we append the authentication tag to the ciphertext as in RFC 5116
+class Cipher {
+ public:
+ // the tag size should be 0 for non-AEAD and must be non-zero for AEAD modes (no default)
+ static std::unique_ptr<Cipher> encryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size = 0,
+ bool disable_padding = false);
+ static std::unique_ptr<Cipher> decryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size = 0,
+ bool disable_padding = false);
+
+ virtual bool set_key(const uint8_t *key, size_t key_length) = 0;
+ virtual bool set_iv(const uint8_t *iv, size_t iv_length) = 0;
+ // only valid for AEAD modes
+ virtual bool set_ad(const uint8_t *ad, size_t ad_length) = 0;
+
+ virtual size_t block_size() const;
+ virtual size_t update_granularity() const = 0;
+
+ // input_length must be a multiple of update_granularity
+ virtual bool update(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed) = 0;
+ // process final block and perform any padding
+ virtual bool finish(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed) = 0;
+
+ virtual ~Cipher();
+
+ protected:
+ Cipher(pgp_symm_alg_t alg);
+
+ pgp_symm_alg_t m_alg;
+};
+
+#endif
diff --git a/src/lib/crypto/cipher_botan.cpp b/src/lib/crypto/cipher_botan.cpp
new file mode 100644
index 0000000..c2c4ab3
--- /dev/null
+++ b/src/lib/crypto/cipher_botan.cpp
@@ -0,0 +1,245 @@
+/*
+ * Copyright (c) 2021, [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 <sstream>
+#include <cassert>
+#include <botan/aead.h>
+#include "cipher_botan.hpp"
+#include "utils.h"
+#include "types.h"
+
+static const id_str_pair cipher_mode_map[] = {
+ {PGP_CIPHER_MODE_CBC, "CBC"},
+ {PGP_CIPHER_MODE_OCB, "OCB"},
+ {0, NULL},
+};
+
+static const id_str_pair cipher_map[] = {
+ {PGP_SA_AES_128, "AES-128"},
+ {PGP_SA_AES_256, "AES-256"},
+ {PGP_SA_IDEA, "IDEA"},
+ {0, NULL},
+};
+
+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
+#if !defined(ENABLE_BLOWFISH)
+ if (alg == PGP_SA_BLOWFISH) {
+ RNP_LOG("Blowfish support has been disabled");
+ return nullptr;
+ }
+#endif
+#if !defined(ENABLE_CAST5)
+ if (alg == PGP_SA_CAST5) {
+ RNP_LOG("CAST5 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;
+ }
+ return new (std::nothrow) Cipher_Botan(alg, std::move(cipher));
+}
+
+static std::string
+make_name(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode, size_t tag_size, bool disable_padding)
+{
+ const char *cipher_string = id_str_pair::lookup(cipher_map, cipher, NULL);
+ const char *mode_string = id_str_pair::lookup(cipher_mode_map, mode, NULL);
+ if (!cipher_string || !mode_string) {
+ return "";
+ }
+ try {
+ std::stringstream ss;
+ ss << cipher_string << "/" << mode_string;
+ if (tag_size) {
+ ss << "(" << tag_size << ")";
+ }
+ if (mode == PGP_CIPHER_MODE_CBC && disable_padding) {
+ ss << "/NoPadding";
+ }
+ return ss.str();
+ } catch (const std::exception &e) {
+ return "";
+ }
+}
+
+std::unique_ptr<Cipher_Botan>
+Cipher_Botan::encryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding)
+{
+ return std::unique_ptr<Cipher_Botan>(
+ create(cipher, make_name(cipher, mode, tag_size, disable_padding), true));
+}
+
+std::unique_ptr<Cipher_Botan>
+Cipher_Botan::decryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding)
+{
+ return std::unique_ptr<Cipher_Botan>(
+ create(cipher, make_name(cipher, mode, tag_size, disable_padding), false));
+}
+
+size_t
+Cipher_Botan::update_granularity() const
+{
+ return m_cipher->update_granularity();
+}
+
+bool
+Cipher_Botan::set_key(const uint8_t *key, size_t key_length)
+{
+ try {
+ m_cipher->set_key(key, key_length);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to set key: %s", e.what());
+ return false;
+ }
+ return true;
+}
+
+bool
+Cipher_Botan::set_iv(const uint8_t *iv, size_t iv_length)
+{
+ try {
+ m_cipher->start(iv, iv_length);
+ m_buf.reserve(this->update_granularity());
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to set IV: %s", e.what());
+ return false;
+ }
+ return true;
+}
+
+bool
+Cipher_Botan::set_ad(const uint8_t *ad, size_t ad_length)
+{
+ assert(m_cipher->authenticated());
+ try {
+ dynamic_cast<Botan::AEAD_Mode &>(*m_cipher).set_associated_data(ad, ad_length);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to set AAD: %s", e.what());
+ return false;
+ }
+ return true;
+}
+
+bool
+Cipher_Botan::update(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed)
+{
+ try {
+ size_t ud = this->update_granularity();
+ m_buf.resize(ud);
+
+ *input_consumed = 0;
+ *output_written = 0;
+ while (input_length >= ud && output_length >= ud) {
+ m_buf.assign(input, input + ud);
+ size_t written = m_cipher->process(m_buf.data(), ud);
+ std::copy(m_buf.data(), m_buf.data() + written, output);
+ input += ud;
+ output += written;
+ input_length -= ud;
+ output_length -= written;
+
+ *output_written += written;
+ *input_consumed += ud;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ return true;
+}
+
+bool
+Cipher_Botan::finish(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed)
+{
+ try {
+ *input_consumed = 0;
+ *output_written = 0;
+ size_t ud = this->update_granularity();
+ if (input_length > ud) {
+ if (!update(output,
+ output_length,
+ output_written,
+ input,
+ input_length - ud,
+ input_consumed)) {
+ return false;
+ }
+ input += *input_consumed;
+ input_length = input_length - *input_consumed;
+ output += *output_written;
+ output_length -= *output_written;
+ }
+ Botan::secure_vector<uint8_t> final_block(input, input + input_length);
+ m_cipher->finish(final_block);
+ if (final_block.size() > output_length) {
+ RNP_LOG("Insufficient buffer");
+ return false;
+ }
+ std::copy(final_block.begin(), final_block.end(), output);
+ *output_written += final_block.size();
+ *input_consumed += input_length;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ return true;
+}
+
+Cipher_Botan::Cipher_Botan(pgp_symm_alg_t alg, std::unique_ptr<Botan::Cipher_Mode> cipher)
+ : Cipher(alg), m_cipher(std::move(cipher))
+{
+}
+
+Cipher_Botan::~Cipher_Botan()
+{
+}
diff --git a/src/lib/crypto/cipher_botan.hpp b/src/lib/crypto/cipher_botan.hpp
new file mode 100644
index 0000000..517d38b
--- /dev/null
+++ b/src/lib/crypto/cipher_botan.hpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2021, [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_CIPHER_BOTAN_HPP
+#define RNP_CIPHER_BOTAN_HPP
+
+#include "cipher.hpp"
+#include <botan/cipher_mode.h>
+#include <repgp/repgp_def.h>
+
+class Cipher_Botan : public Cipher {
+ public:
+ static std::unique_ptr<Cipher_Botan> encryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding);
+ static std::unique_ptr<Cipher_Botan> decryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding);
+
+ bool set_key(const uint8_t *key, size_t key_length) override;
+ bool set_iv(const uint8_t *iv, size_t iv_length) override;
+ bool set_ad(const uint8_t *ad, size_t ad_length) override;
+
+ size_t update_granularity() const override;
+
+ bool update(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed) override;
+ bool finish(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed) override;
+ virtual ~Cipher_Botan();
+
+ private:
+ Cipher_Botan(pgp_symm_alg_t alg, std::unique_ptr<Botan::Cipher_Mode> cipher);
+
+ std::unique_ptr<Botan::Cipher_Mode> m_cipher;
+ std::vector<uint8_t> m_buf;
+
+ static Cipher_Botan *create(pgp_symm_alg_t alg, const std::string &name, bool encrypt);
+};
+
+#endif
diff --git a/src/lib/crypto/cipher_ossl.cpp b/src/lib/crypto/cipher_ossl.cpp
new file mode 100644
index 0000000..bb81d48
--- /dev/null
+++ b/src/lib/crypto/cipher_ossl.cpp
@@ -0,0 +1,284 @@
+/*
+ * Copyright (c) 2021, [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 <cassert>
+#include <algorithm>
+
+#include "cipher_ossl.hpp"
+#include "utils.h"
+#include "types.h"
+#include <openssl/err.h>
+
+static const id_str_pair cipher_mode_map[] = {
+ {PGP_CIPHER_MODE_CBC, "CBC"},
+ {PGP_CIPHER_MODE_OCB, "OCB"},
+ {0, NULL},
+};
+
+static const id_str_pair cipher_map[] = {
+ {PGP_SA_AES_128, "AES-128"},
+ {PGP_SA_AES_256, "AES-256"},
+ {PGP_SA_IDEA, "IDEA"},
+ {0, NULL},
+};
+
+EVP_CIPHER_CTX *
+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
+#if !defined(ENABLE_BLOWFISH)
+ if (alg == PGP_SA_BLOWFISH) {
+ RNP_LOG("Blowfish support has been disabled");
+ return nullptr;
+ }
+#endif
+#if !defined(ENABLE_CAST5)
+ if (alg == PGP_SA_CAST5) {
+ RNP_LOG("CAST5 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;
+ }
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if (!ctx) {
+ RNP_LOG("Failed to create cipher context: %lu", ERR_peek_last_error());
+ return nullptr;
+ }
+ if (EVP_CipherInit_ex(ctx, cipher, NULL, NULL, NULL, encrypt ? 1 : 0) != 1) {
+ RNP_LOG("Failed to initialize cipher: %lu", ERR_peek_last_error());
+ EVP_CIPHER_CTX_free(ctx);
+ return nullptr;
+ }
+ // set tag size
+ if (encrypt && tag_size) {
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, tag_size, NULL) != 1) {
+ RNP_LOG("Failed to set AEAD tag length: %lu", ERR_peek_last_error());
+ EVP_CIPHER_CTX_free(ctx);
+ return nullptr;
+ }
+ }
+ if (disable_padding) {
+ EVP_CIPHER_CTX_set_padding(ctx, 0);
+ }
+ return ctx;
+}
+
+static std::string
+make_name(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode)
+{
+ const char *cipher_string = id_str_pair::lookup(cipher_map, cipher, NULL);
+ const char *mode_string = id_str_pair::lookup(cipher_mode_map, mode, NULL);
+ if (!cipher_string || !mode_string) {
+ return "";
+ }
+ return std::string(cipher_string) + "-" + mode_string;
+}
+
+std::unique_ptr<Cipher_OpenSSL>
+Cipher_OpenSSL::encryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding)
+{
+ 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<Cipher_OpenSSL>(new (std::nothrow)
+ Cipher_OpenSSL(cipher, ossl_ctx, tag_size, true));
+}
+
+std::unique_ptr<Cipher_OpenSSL>
+Cipher_OpenSSL::decryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding)
+{
+ 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<Cipher_OpenSSL>(
+ new (std::nothrow) Cipher_OpenSSL(cipher, ossl_ctx, tag_size, false));
+}
+
+bool
+Cipher_OpenSSL::set_key(const uint8_t *key, size_t key_length)
+{
+ assert(key_length <= INT_MAX);
+ return EVP_CIPHER_CTX_set_key_length(m_ctx, (int) key_length) == 1 &&
+ EVP_CipherInit_ex(m_ctx, NULL, NULL, key, NULL, -1) == 1;
+}
+
+bool
+Cipher_OpenSSL::set_iv(const uint8_t *iv, size_t iv_length)
+{
+ assert(iv_length <= INT_MAX);
+ // set IV len for AEAD modes
+ if (m_tag_size &&
+ EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_SET_IVLEN, (int) iv_length, NULL) != 1) {
+ RNP_LOG("Failed to set AEAD IV length: %lu", ERR_peek_last_error());
+ return false;
+ }
+ if (EVP_CIPHER_CTX_iv_length(m_ctx) != (int) iv_length) {
+ RNP_LOG("IV length mismatch");
+ return false;
+ }
+ if (EVP_CipherInit_ex(m_ctx, NULL, NULL, NULL, iv, -1) != 1) {
+ RNP_LOG("Failed to set IV: %lu", ERR_peek_last_error());
+ }
+ return true;
+}
+
+bool
+Cipher_OpenSSL::set_ad(const uint8_t *ad, size_t ad_length)
+{
+ assert(m_tag_size);
+ int outlen = 0;
+ if (EVP_CipherUpdate(m_ctx, NULL, &outlen, ad, ad_length) != 1) {
+ RNP_LOG("Failed to set AD: %lu", ERR_peek_last_error());
+ return false;
+ }
+ return true;
+}
+
+size_t
+Cipher_OpenSSL::update_granularity() const
+{
+ return (size_t) EVP_CIPHER_CTX_block_size(m_ctx);
+}
+
+bool
+Cipher_OpenSSL::update(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed)
+{
+ if (input_length > INT_MAX) {
+ return false;
+ }
+ *input_consumed = 0;
+ *output_written = 0;
+ if (input_length == 0) {
+ return true;
+ }
+ int outl = 0;
+ if (EVP_CipherUpdate(m_ctx, output, &outl, input, (int) input_length) != 1) {
+ RNP_LOG("EVP_CipherUpdate failed: %lu", ERR_peek_last_error());
+ return false;
+ }
+ assert((size_t) outl < output_length);
+ *input_consumed = input_length;
+ *output_written = (size_t) outl;
+ return true;
+}
+
+bool
+Cipher_OpenSSL::finish(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed)
+{
+ if (input_length > INT_MAX) {
+ return false;
+ }
+ if (!m_encrypt && input_length < m_tag_size) {
+ RNP_LOG("Insufficient input for final block (missing tag)");
+ return false;
+ }
+ *input_consumed = 0;
+ *output_written = 0;
+ if (!m_encrypt && m_tag_size) {
+ // set the tag from the end of the ciphertext
+ if (EVP_CIPHER_CTX_ctrl(m_ctx,
+ EVP_CTRL_AEAD_SET_TAG,
+ m_tag_size,
+ const_cast<uint8_t *>(input) + input_length - m_tag_size) !=
+ 1) {
+ RNP_LOG("Failed to set expected AEAD tag: %lu", ERR_peek_last_error());
+ return false;
+ }
+ size_t ats = std::min(m_tag_size, input_length);
+ input_length -= ats; // m_tag_size;
+ *input_consumed += ats; // m_tag_size;
+ }
+ int outl = 0;
+ if (EVP_CipherUpdate(m_ctx, output, &outl, input, (int) input_length) != 1) {
+ RNP_LOG("EVP_CipherUpdate failed: %lu", ERR_peek_last_error());
+ return false;
+ }
+ input += input_length;
+ *input_consumed += input_length;
+ output += outl;
+ *output_written += (size_t) outl;
+ if (EVP_CipherFinal_ex(m_ctx, output, &outl) != 1) {
+ RNP_LOG("EVP_CipherFinal_ex failed: %lu", ERR_peek_last_error());
+ return false;
+ }
+ *output_written += (size_t) outl;
+ output += (size_t) outl;
+ if (m_encrypt && m_tag_size) {
+ // append the tag
+ if (EVP_CIPHER_CTX_ctrl(m_ctx, EVP_CTRL_AEAD_GET_TAG, m_tag_size, output) != 1) {
+ RNP_LOG("Failed to append AEAD tag: %lu", ERR_peek_last_error());
+ return false;
+ }
+ *output_written += m_tag_size;
+ }
+ return true;
+}
+
+Cipher_OpenSSL::Cipher_OpenSSL(pgp_symm_alg_t alg,
+ EVP_CIPHER_CTX *ctx,
+ size_t tag_size,
+ bool encrypt)
+ : Cipher(alg), m_ctx(ctx), m_tag_size(tag_size), m_encrypt(encrypt)
+{
+ m_block_size = EVP_CIPHER_CTX_block_size(m_ctx);
+}
+
+Cipher_OpenSSL::~Cipher_OpenSSL()
+{
+ EVP_CIPHER_CTX_free(m_ctx);
+}
diff --git a/src/lib/crypto/cipher_ossl.hpp b/src/lib/crypto/cipher_ossl.hpp
new file mode 100644
index 0000000..da2bb4e
--- /dev/null
+++ b/src/lib/crypto/cipher_ossl.hpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2021, [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_CIPHER_OSSL_HPP
+#define RNP_CIPHER_OSSL_HPP
+
+#include "cipher.hpp"
+#include <openssl/evp.h>
+#include <repgp/repgp_def.h>
+
+class Cipher_OpenSSL : public Cipher {
+ public:
+ static std::unique_ptr<Cipher_OpenSSL> encryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding);
+ static std::unique_ptr<Cipher_OpenSSL> decryption(pgp_symm_alg_t cipher,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding);
+
+ bool set_key(const uint8_t *key, size_t key_length) override;
+ bool set_iv(const uint8_t *iv, size_t iv_length) override;
+ bool set_ad(const uint8_t *ad, size_t ad_length) override;
+
+ size_t update_granularity() const override;
+
+ // input_length should not exceed INT_MAX
+ bool update(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed) override;
+ bool finish(uint8_t * output,
+ size_t output_length,
+ size_t * output_written,
+ const uint8_t *input,
+ size_t input_length,
+ size_t * input_consumed) override;
+ virtual ~Cipher_OpenSSL();
+
+ private:
+ Cipher_OpenSSL(pgp_symm_alg_t alg, EVP_CIPHER_CTX *ctx, size_t tag_size, bool encrypt);
+
+ EVP_CIPHER_CTX *m_ctx;
+ size_t m_block_size;
+ size_t m_tag_size;
+ bool m_encrypt;
+
+ static EVP_CIPHER_CTX *create(pgp_symm_alg_t alg,
+ const std::string &name,
+ bool encrypt,
+ size_t tag_size,
+ bool disable_padding);
+};
+
+#endif
diff --git a/src/lib/crypto/common.h b/src/lib/crypto/common.h
new file mode 100644
index 0000000..3d9b837
--- /dev/null
+++ b/src/lib/crypto/common.h
@@ -0,0 +1,51 @@
+/*-
+ * Copyright (c) 2018 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 RNP_CRYPTO_COMMON_H_
+#define RNP_CRYPTO_COMMON_H_
+
+/* base */
+#include "mpi.h"
+#include "rng.h"
+/* asymmetric crypto */
+#include "rsa.h"
+#include "dsa.h"
+#include "elgamal.h"
+#include "ec.h"
+#include "ecdh.h"
+#include "ecdsa.h"
+#include "sm2.h"
+#include "eddsa.h"
+/* symmetric crypto */
+#include "symmetric.h"
+/* hash */
+#include "hash.hpp"
+/* s2k */
+#include "s2k.h"
+/* backend name and version */
+#include "backend_version.h"
+
+#endif // RNP_CRYPTO_COMMON_H_
diff --git a/src/lib/crypto/dl_ossl.cpp b/src/lib/crypto/dl_ossl.cpp
new file mode 100644
index 0000000..1e96218
--- /dev/null
+++ b/src/lib/crypto/dl_ossl.cpp
@@ -0,0 +1,200 @@
+/*
+ * Copyright (c) 2021, [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 <cstdlib>
+#include <string>
+#include <cassert>
+#include "bn.h"
+#include "dl_ossl.h"
+#include "utils.h"
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+
+EVP_PKEY *
+dl_load_key(const pgp_mpi_t &mp,
+ const pgp_mpi_t *mq,
+ const pgp_mpi_t &mg,
+ const pgp_mpi_t &my,
+ const pgp_mpi_t *mx)
+{
+ DH * dh = NULL;
+ EVP_PKEY *evpkey = NULL;
+ bignum_t *p = mpi2bn(&mp);
+ bignum_t *q = mq ? mpi2bn(mq) : NULL;
+ bignum_t *g = mpi2bn(&mg);
+ bignum_t *y = mpi2bn(&my);
+ bignum_t *x = mx ? mpi2bn(mx) : NULL;
+
+ if (!p || (mq && !q) || !g || !y || (mx && !x)) {
+ RNP_LOG("out of memory");
+ goto done;
+ }
+
+ dh = DH_new();
+ if (!dh) {
+ RNP_LOG("out of memory");
+ goto done;
+ }
+ int res;
+ /* line below must not fail */
+ res = DH_set0_pqg(dh, p, q, g);
+ assert(res == 1);
+ if (res < 1) {
+ goto done;
+ }
+ p = NULL;
+ q = NULL;
+ g = NULL;
+ /* line below must not fail */
+ res = DH_set0_key(dh, y, x);
+ assert(res == 1);
+ if (res < 1) {
+ goto done;
+ }
+ y = NULL;
+ x = NULL;
+
+ evpkey = EVP_PKEY_new();
+ if (!evpkey) {
+ RNP_LOG("allocation failed");
+ goto done;
+ }
+ if (EVP_PKEY_set1_DH(evpkey, dh) <= 0) {
+ RNP_LOG("Failed to set key: %lu", ERR_peek_last_error());
+ EVP_PKEY_free(evpkey);
+ evpkey = NULL;
+ }
+done:
+ DH_free(dh);
+ bn_free(p);
+ bn_free(q);
+ bn_free(g);
+ bn_free(y);
+ bn_free(x);
+ return evpkey;
+}
+
+static rnp_result_t
+dl_validate_secret_key(EVP_PKEY *dlkey, const pgp_mpi_t &mx)
+{
+ 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);
+ assert(p && g && y);
+ bignum_t *p1 = NULL;
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ BN_CTX * ctx = BN_CTX_new();
+ bignum_t *x = mpi2bn(&mx);
+ bignum_t *cy = bn_new();
+
+ if (!x || !cy || !ctx) {
+ RNP_LOG("Allocation failed");
+ goto done;
+ }
+ if (!q) {
+ /* if q is NULL then group order is (p - 1) / 2 */
+ p1 = BN_dup(p);
+ if (!p1) {
+ RNP_LOG("Allocation failed");
+ goto done;
+ }
+ int res;
+ res = BN_rshift(p1, p1, 1);
+ assert(res == 1);
+ if (res < 1) {
+ RNP_LOG("BN_rshift failed.");
+ goto done;
+ }
+ q = p1;
+ }
+ if (BN_cmp(x, q) != -1) {
+ RNP_LOG("x is too large.");
+ goto done;
+ }
+ if (BN_mod_exp_mont_consttime(cy, g, x, p, ctx, NULL) < 1) {
+ RNP_LOG("Exponentiation failed");
+ goto done;
+ }
+ if (BN_cmp(cy, y) == 0) {
+ ret = RNP_SUCCESS;
+ }
+done:
+ BN_CTX_free(ctx);
+ bn_free(x);
+ bn_free(cy);
+ bn_free(p1);
+ return ret;
+}
+
+rnp_result_t
+dl_validate_key(EVP_PKEY *pkey, const pgp_mpi_t *x)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(pkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ int res;
+ res = EVP_PKEY_param_check(ctx);
+ if (res < 0) {
+ RNP_LOG("Param validation error: %lu (%s)",
+ ERR_peek_last_error(),
+ ERR_reason_error_string(ERR_peek_last_error()));
+ }
+ 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_REASON(err) == DH_R_CHECK_P_NOT_SAFE_PRIME)) {
+ RNP_LOG("Warning! P is not a safe prime.");
+ } else {
+ goto done;
+ }
+ }
+ res = EVP_PKEY_public_check(ctx);
+ if (res < 0) {
+ RNP_LOG("Key validation error: %lu", ERR_peek_last_error());
+ }
+ if (res < 1) {
+ goto done;
+ }
+ /* There is no private key check in OpenSSL yet, so need to check x vs y manually */
+ if (!x) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+ ret = dl_validate_secret_key(pkey, *x);
+done:
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
diff --git a/src/lib/crypto/dl_ossl.h b/src/lib/crypto/dl_ossl.h
new file mode 100644
index 0000000..fcafc0a
--- /dev/null
+++ b/src/lib/crypto/dl_ossl.h
@@ -0,0 +1,44 @@
+/*
+ * Copyright (c) 2021, [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 DL_OSSL_H_
+#define DL_OSSL_H_
+
+#include "types.h"
+#include "config.h"
+#include <rnp/rnp_def.h>
+#include "mpi.h"
+#include <openssl/evp.h>
+
+EVP_PKEY *dl_load_key(const pgp_mpi_t &mp,
+ const pgp_mpi_t *mq,
+ const pgp_mpi_t &mg,
+ const pgp_mpi_t &my,
+ const pgp_mpi_t *mx);
+
+rnp_result_t dl_validate_key(EVP_PKEY *pkey, const pgp_mpi_t *mx);
+
+#endif
diff --git a/src/lib/crypto/dsa.cpp b/src/lib/crypto/dsa.cpp
new file mode 100644
index 0000000..8763f00
--- /dev/null
+++ b/src/lib/crypto/dsa.cpp
@@ -0,0 +1,382 @@
+/*-
+ * Copyright (c) 2017-2018 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.
+ */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Alistair Crooks (agc@NetBSD.org)
+ *
+ * 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.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** \file
+ */
+#include <stdlib.h>
+#include <string.h>
+#include <botan/ffi.h>
+#include <rnp/rnp_def.h>
+#include "dsa.h"
+#include "bn.h"
+#include "utils.h"
+
+#define DSA_MAX_Q_BITLEN 256
+
+rnp_result_t
+dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret)
+{
+ bignum_t * p = NULL;
+ bignum_t * q = NULL;
+ bignum_t * g = NULL;
+ bignum_t * y = NULL;
+ bignum_t * x = NULL;
+ botan_pubkey_t bpkey = NULL;
+ botan_privkey_t bskey = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ /* load and check public key part */
+ p = mpi2bn(&key->p);
+ q = mpi2bn(&key->q);
+ g = mpi2bn(&key->g);
+ y = mpi2bn(&key->y);
+
+ if (!p || !q || !g || !y) {
+ RNP_LOG("out of memory");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ if (botan_pubkey_load_dsa(
+ &bpkey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y))) {
+ goto done;
+ }
+
+ if (botan_pubkey_check_key(bpkey, rng->handle(), 0)) {
+ goto done;
+ }
+
+ if (!secret) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ /* load and check secret key part */
+ if (!(x = mpi2bn(&key->x))) {
+ RNP_LOG("out of memory");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ if (botan_privkey_load_dsa(
+ &bskey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x))) {
+ goto done;
+ }
+
+ if (botan_privkey_check_key(bskey, rng->handle(), 0)) {
+ goto done;
+ }
+
+ ret = RNP_SUCCESS;
+done:
+ bn_free(p);
+ bn_free(q);
+ bn_free(g);
+ bn_free(y);
+ bn_free(x);
+ botan_privkey_destroy(bskey);
+ botan_pubkey_destroy(bpkey);
+ return ret;
+}
+
+rnp_result_t
+dsa_sign(rnp::RNG * rng,
+ pgp_dsa_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_dsa_key_t *key)
+{
+ botan_privkey_t dsa_key = NULL;
+ botan_pk_op_sign_t sign_op = NULL;
+ size_t q_order = 0;
+ uint8_t sign_buf[2 * BITS_TO_BYTES(DSA_MAX_Q_BITLEN)] = {0};
+ bignum_t * p = NULL, *q = NULL, *g = NULL, *x = NULL;
+ rnp_result_t ret = RNP_ERROR_SIGNING_FAILED;
+ size_t sigbuf_size = sizeof(sign_buf);
+
+ size_t z_len = 0;
+
+ memset(sig, 0, sizeof(*sig));
+ q_order = mpi_bytes(&key->q);
+ if ((2 * q_order) > sizeof(sign_buf)) {
+ RNP_LOG("wrong q order");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // As 'Raw' is used we need to reduce hash size (as per FIPS-186-4, 4.6)
+ z_len = hash_len < q_order ? hash_len : q_order;
+
+ p = mpi2bn(&key->p);
+ q = mpi2bn(&key->q);
+ g = mpi2bn(&key->g);
+ x = mpi2bn(&key->x);
+
+ if (!p || !q || !g || !x) {
+ RNP_LOG("out of memory");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto end;
+ }
+
+ if (botan_privkey_load_dsa(
+ &dsa_key, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x))) {
+ RNP_LOG("Can't load key");
+ goto end;
+ }
+
+ if (botan_pk_op_sign_create(&sign_op, dsa_key, "Raw", 0)) {
+ goto end;
+ }
+
+ if (botan_pk_op_sign_update(sign_op, hash, z_len)) {
+ goto end;
+ }
+
+ if (botan_pk_op_sign_finish(sign_op, rng->handle(), sign_buf, &sigbuf_size)) {
+ RNP_LOG("Signing has failed");
+ goto end;
+ }
+
+ // Now load the DSA (r,s) values from the signature.
+ if (!mem2mpi(&sig->r, sign_buf, q_order) ||
+ !mem2mpi(&sig->s, sign_buf + q_order, q_order)) {
+ goto end;
+ }
+ ret = RNP_SUCCESS;
+
+end:
+ bn_free(p);
+ bn_free(q);
+ bn_free(g);
+ bn_free(x);
+ botan_pk_op_sign_destroy(sign_op);
+ botan_privkey_destroy(dsa_key);
+ return ret;
+}
+
+rnp_result_t
+dsa_verify(const pgp_dsa_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_dsa_key_t * key)
+{
+ botan_pubkey_t dsa_key = NULL;
+ botan_pk_op_verify_t verify_op = NULL;
+ uint8_t sign_buf[2 * BITS_TO_BYTES(DSA_MAX_Q_BITLEN)] = {0};
+ size_t q_order = 0;
+ size_t r_blen, s_blen;
+ bignum_t * p = NULL, *q = NULL, *g = NULL, *y = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ size_t z_len = 0;
+
+ q_order = mpi_bytes(&key->q);
+ if ((2 * q_order) > sizeof(sign_buf)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ z_len = hash_len < q_order ? hash_len : q_order;
+
+ r_blen = mpi_bytes(&sig->r);
+ s_blen = mpi_bytes(&sig->s);
+ if ((r_blen > q_order) || (s_blen > q_order)) {
+ RNP_LOG("Wrong signature");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ p = mpi2bn(&key->p);
+ q = mpi2bn(&key->q);
+ g = mpi2bn(&key->g);
+ y = mpi2bn(&key->y);
+
+ if (!p || !q || !g || !y) {
+ RNP_LOG("out of memory");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto end;
+ }
+
+ if (botan_pubkey_load_dsa(
+ &dsa_key, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y))) {
+ RNP_LOG("Wrong key");
+ goto end;
+ }
+
+ mpi2mem(&sig->r, sign_buf + q_order - r_blen);
+ mpi2mem(&sig->s, sign_buf + 2 * q_order - s_blen);
+
+ if (botan_pk_op_verify_create(&verify_op, dsa_key, "Raw", 0)) {
+ RNP_LOG("Can't create verifier");
+ goto end;
+ }
+
+ if (botan_pk_op_verify_update(verify_op, hash, z_len)) {
+ goto end;
+ }
+
+ ret = (botan_pk_op_verify_finish(verify_op, sign_buf, 2 * q_order) == BOTAN_FFI_SUCCESS) ?
+ RNP_SUCCESS :
+ RNP_ERROR_SIGNATURE_INVALID;
+
+end:
+ bn_free(p);
+ bn_free(q);
+ bn_free(g);
+ bn_free(y);
+ botan_pk_op_verify_destroy(verify_op);
+ botan_pubkey_destroy(dsa_key);
+ return ret;
+}
+
+rnp_result_t
+dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits)
+{
+ if ((keylen < 1024) || (keylen > 3072) || (qbits < 160) || (qbits > 256)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ botan_privkey_t key_priv = NULL;
+ botan_pubkey_t key_pub = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ bignum_t * p = bn_new();
+ bignum_t * q = bn_new();
+ bignum_t * g = bn_new();
+ bignum_t * y = bn_new();
+ bignum_t * x = bn_new();
+
+ if (!p || !q || !g || !y || !x) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto end;
+ }
+
+ if (botan_privkey_create_dsa(&key_priv, rng->handle(), keylen, qbits) ||
+ botan_privkey_check_key(key_priv, rng->handle(), 1) ||
+ botan_privkey_export_pubkey(&key_pub, key_priv)) {
+ RNP_LOG("Wrong parameters");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto end;
+ }
+
+ if (botan_pubkey_get_field(BN_HANDLE_PTR(p), key_pub, "p") ||
+ botan_pubkey_get_field(BN_HANDLE_PTR(q), key_pub, "q") ||
+ botan_pubkey_get_field(BN_HANDLE_PTR(g), key_pub, "g") ||
+ botan_pubkey_get_field(BN_HANDLE_PTR(y), key_pub, "y") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(x), key_priv, "x")) {
+ RNP_LOG("Botan FFI call failed");
+ ret = RNP_ERROR_GENERIC;
+ goto end;
+ }
+
+ if (!bn2mpi(p, &key->p) || !bn2mpi(q, &key->q) || !bn2mpi(g, &key->g) ||
+ !bn2mpi(y, &key->y) || !bn2mpi(x, &key->x)) {
+ RNP_LOG("failed to copy mpi");
+ goto end;
+ }
+ ret = RNP_SUCCESS;
+end:
+ bn_free(p);
+ bn_free(q);
+ bn_free(g);
+ bn_free(y);
+ bn_free(x);
+ botan_privkey_destroy(key_priv);
+ botan_pubkey_destroy(key_pub);
+ return ret;
+}
+
+pgp_hash_alg_t
+dsa_get_min_hash(size_t qsize)
+{
+ /*
+ * I'm using _broken_ SHA1 here only because
+ * some old implementations may not understand keys created
+ * with other hashes. If you're sure we don't have to support
+ * such implementations, please be my guest and remove it.
+ */
+ return (qsize < 160) ? PGP_HASH_UNKNOWN :
+ (qsize == 160) ? PGP_HASH_SHA1 :
+ (qsize <= 224) ? PGP_HASH_SHA224 :
+ (qsize <= 256) ? PGP_HASH_SHA256 :
+ (qsize <= 384) ? PGP_HASH_SHA384 :
+ (qsize <= 512) ? PGP_HASH_SHA512
+ /*(qsize>512)*/ :
+ PGP_HASH_UNKNOWN;
+}
+
+size_t
+dsa_choose_qsize_by_psize(size_t psize)
+{
+ return (psize == 1024) ? 160 :
+ (psize <= 2047) ? 224 :
+ (psize <= 3072) ? DSA_MAX_Q_BITLEN :
+ 0;
+}
diff --git a/src/lib/crypto/dsa.h b/src/lib/crypto/dsa.h
new file mode 100644
index 0000000..52a186a
--- /dev/null
+++ b/src/lib/crypto/dsa.h
@@ -0,0 +1,141 @@
+/*
+ * Copyright (c) 2017-2018, [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).
+ *
+ * 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 RNP_DSA_H_
+#define RNP_DSA_H_
+
+#include <rnp/rnp_def.h>
+#include <repgp/repgp_def.h>
+#include "crypto/rng.h"
+#include "crypto/mpi.h"
+
+typedef struct pgp_dsa_key_t {
+ pgp_mpi_t p;
+ pgp_mpi_t q;
+ pgp_mpi_t g;
+ pgp_mpi_t y;
+ /* secret mpi */
+ pgp_mpi_t x;
+} pgp_dsa_key_t;
+
+typedef struct pgp_dsa_signature_t {
+ pgp_mpi_t r;
+ pgp_mpi_t s;
+} pgp_dsa_signature_t;
+
+/**
+ * @brief Checks DSA key fields for validity
+ *
+ * @param rng initialized PRNG
+ * @param key initialized DSA key structure
+ * @param secret flag which tells whether key has populated secret fields
+ *
+ * @return RNP_SUCCESS if key is valid or error code otherwise
+ */
+rnp_result_t dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret);
+
+/*
+ * @brief Performs DSA signing
+ *
+ * @param rng initialized PRNG
+ * @param sig[out] created signature
+ * @param hash hash to sign
+ * @param hash_len length of `hash`
+ * @param key DSA key (must include secret mpi)
+ *
+ * @returns RNP_SUCCESS
+ * RNP_ERROR_BAD_PARAMETERS wrong input provided
+ * RNP_ERROR_SIGNING_FAILED internal error
+ */
+rnp_result_t dsa_sign(rnp::RNG * rng,
+ pgp_dsa_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_dsa_key_t *key);
+
+/*
+ * @brief Performs DSA verification
+ *
+ * @param hash hash to verify
+ * @param hash_len length of `hash`
+ * @param sig signature to be verified
+ * @param key DSA key (secret mpi is not needed)
+ *
+ * @returns RNP_SUCCESS
+ * RNP_ERROR_BAD_PARAMETERS wrong input provided
+ * RNP_ERROR_GENERIC internal error
+ * RNP_ERROR_SIGNATURE_INVALID signature is invalid
+ */
+rnp_result_t dsa_verify(const pgp_dsa_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_dsa_key_t * key);
+
+/*
+ * @brief Performs DSA key generation
+ *
+ * @param rng initialized PRNG
+ * @param key[out] generated key data will be stored here
+ * @param keylen length of the key, in bits
+ * @param qbits subgroup size in bits
+ *
+ * @returns RNP_SUCCESS
+ * RNP_ERROR_BAD_PARAMETERS wrong input provided
+ * RNP_ERROR_OUT_OF_MEMORY memory allocation failed
+ * RNP_ERROR_GENERIC internal error
+ * RNP_ERROR_SIGNATURE_INVALID signature is invalid
+ */
+rnp_result_t dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits);
+
+/*
+ * @brief Returns minimally sized hash which will work
+ * with the DSA subgroup.
+ *
+ * @param qsize subgroup order
+ *
+ * @returns Either ID of the hash algorithm, or PGP_HASH_UNKNOWN
+ * if not found
+ */
+pgp_hash_alg_t dsa_get_min_hash(size_t qsize);
+
+/*
+ * @brief Helps to determine subgroup size by size of p
+ * In order not to confuse users, we use less complicated
+ * approach than suggested by FIPS-186, which is:
+ * p=1024 => q=160
+ * p<2048 => q=224
+ * p<=3072 => q=256
+ * So we don't generate (2048, 224) pair
+ *
+ * @return Size of `q' or 0 in case `psize' is not in <1024,3072> range
+ */
+size_t dsa_choose_qsize_by_psize(size_t psize);
+
+#endif
diff --git a/src/lib/crypto/dsa_ossl.cpp b/src/lib/crypto/dsa_ossl.cpp
new file mode 100644
index 0000000..1fb75b5
--- /dev/null
+++ b/src/lib/crypto/dsa_ossl.cpp
@@ -0,0 +1,355 @@
+/*
+ * Copyright (c) 2021, [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 <stdlib.h>
+#include <string.h>
+#include <rnp/rnp_def.h>
+#include "bn.h"
+#include "dsa.h"
+#include "dl_ossl.h"
+#include "utils.h"
+#include <openssl/dsa.h>
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+
+#define DSA_MAX_Q_BITLEN 256
+
+static bool
+dsa_decode_sig(const uint8_t *data, size_t len, pgp_dsa_signature_t &sig)
+{
+ DSA_SIG *dsig = d2i_DSA_SIG(NULL, &data, len);
+ if (!dsig) {
+ RNP_LOG("Failed to parse DSA sig: %lu", ERR_peek_last_error());
+ return false;
+ }
+ const BIGNUM *r, *s;
+ DSA_SIG_get0(dsig, &r, &s);
+ bn2mpi(r, &sig.r);
+ bn2mpi(s, &sig.s);
+ DSA_SIG_free(dsig);
+ return true;
+}
+
+static bool
+dsa_encode_sig(uint8_t *data, size_t *len, const pgp_dsa_signature_t &sig)
+{
+ bool res = false;
+ DSA_SIG *dsig = DSA_SIG_new();
+ BIGNUM * r = mpi2bn(&sig.r);
+ BIGNUM * s = mpi2bn(&sig.s);
+ if (!dsig || !r || !s) {
+ RNP_LOG("Allocation failed.");
+ goto done;
+ }
+ DSA_SIG_set0(dsig, r, s);
+ r = NULL;
+ s = NULL;
+ int outlen;
+ outlen = i2d_DSA_SIG(dsig, &data);
+ if (outlen < 0) {
+ RNP_LOG("Failed to encode signature.");
+ goto done;
+ }
+ *len = outlen;
+ res = true;
+done:
+ DSA_SIG_free(dsig);
+ BN_free(r);
+ BN_free(s);
+ return res;
+}
+
+static EVP_PKEY *
+dsa_load_key(const pgp_dsa_key_t *key, bool secret = false)
+{
+ DSA * dsa = NULL;
+ EVP_PKEY *evpkey = NULL;
+ bignum_t *p = mpi2bn(&key->p);
+ bignum_t *q = mpi2bn(&key->q);
+ bignum_t *g = mpi2bn(&key->g);
+ bignum_t *y = mpi2bn(&key->y);
+ bignum_t *x = secret ? mpi2bn(&key->x) : NULL;
+
+ if (!p || !q || !g || !y || (secret && !x)) {
+ RNP_LOG("out of memory");
+ goto done;
+ }
+
+ dsa = DSA_new();
+ if (!dsa) {
+ RNP_LOG("Out of memory");
+ goto done;
+ }
+ if (DSA_set0_pqg(dsa, p, q, g) != 1) {
+ RNP_LOG("Failed to set pqg. Error: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ p = NULL;
+ q = NULL;
+ g = NULL;
+ if (DSA_set0_key(dsa, y, x) != 1) {
+ RNP_LOG("Secret key load error: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ y = NULL;
+ x = NULL;
+
+ evpkey = EVP_PKEY_new();
+ if (!evpkey) {
+ RNP_LOG("allocation failed");
+ goto done;
+ }
+ if (EVP_PKEY_set1_DSA(evpkey, dsa) <= 0) {
+ RNP_LOG("Failed to set key: %lu", ERR_peek_last_error());
+ EVP_PKEY_free(evpkey);
+ evpkey = NULL;
+ }
+done:
+ DSA_free(dsa);
+ bn_free(p);
+ bn_free(q);
+ bn_free(g);
+ bn_free(y);
+ bn_free(x);
+ return evpkey;
+}
+
+rnp_result_t
+dsa_validate_key(rnp::RNG *rng, const pgp_dsa_key_t *key, bool secret)
+{
+ /* OpenSSL doesn't implement key checks for the DSA, however we may use DL via DH */
+ EVP_PKEY *pkey = dl_load_key(key->p, &key->q, key->g, key->y, NULL);
+ if (!pkey) {
+ RNP_LOG("Failed to load key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_result_t ret = dl_validate_key(pkey, secret ? &key->x : NULL);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+rnp_result_t
+dsa_sign(rnp::RNG * rng,
+ pgp_dsa_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_dsa_key_t *key)
+{
+ if (mpi_bytes(&key->x) == 0) {
+ RNP_LOG("private key not set");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* Load secret key to DSA structure*/
+ EVP_PKEY *evpkey = dsa_load_key(key, true);
+ if (!evpkey) {
+ RNP_LOG("Failed to load key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ /* init context and sign */
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_sign_init(ctx) <= 0) {
+ RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ sig->s.len = PGP_MPINT_SIZE;
+ if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) {
+ RNP_LOG("Signing failed: %lu", ERR_peek_last_error());
+ sig->s.len = 0;
+ goto done;
+ }
+ if (!dsa_decode_sig(&sig->s.mpi[0], sig->s.len, *sig)) {
+ RNP_LOG("Failed to parse DSA sig: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(evpkey);
+ return ret;
+}
+
+rnp_result_t
+dsa_verify(const pgp_dsa_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_dsa_key_t * key)
+{
+ /* Load secret key to DSA structure*/
+ EVP_PKEY *evpkey = dsa_load_key(key, false);
+ if (!evpkey) {
+ RNP_LOG("Failed to load key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ /* init context and sign */
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_verify_init(ctx) <= 0) {
+ RNP_LOG("Failed to initialize verify: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ pgp_mpi_t sigbuf;
+ if (!dsa_encode_sig(sigbuf.mpi, &sigbuf.len, *sig)) {
+ goto done;
+ }
+ if (EVP_PKEY_verify(ctx, sigbuf.mpi, sigbuf.len, hash, hash_len) <= 0) {
+ ret = RNP_ERROR_SIGNATURE_INVALID;
+ } else {
+ ret = RNP_SUCCESS;
+ }
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(evpkey);
+ return ret;
+}
+
+rnp_result_t
+dsa_generate(rnp::RNG *rng, pgp_dsa_key_t *key, size_t keylen, size_t qbits)
+{
+ if ((keylen < 1024) || (keylen > 3072) || (qbits < 160) || (qbits > 256)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ const DSA * dsa = NULL;
+ EVP_PKEY * pkey = NULL;
+ EVP_PKEY * parmkey = NULL;
+ EVP_PKEY_CTX *ctx = NULL;
+
+ /* Generate DSA params */
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DSA, NULL);
+ if (!ctx) {
+ RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error());
+ return ret;
+ }
+ if (EVP_PKEY_paramgen_init(ctx) <= 0) {
+ RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_CTX_set_dsa_paramgen_bits(ctx, keylen) <= 0) {
+ RNP_LOG("Failed to set key bits: %lu", ERR_peek_last_error());
+ goto done;
+ }
+#if OPENSSL_VERSION_NUMBER < 0x1010105fL
+ EVP_PKEY_CTX_ctrl(
+ ctx, EVP_PKEY_DSA, EVP_PKEY_OP_PARAMGEN, EVP_PKEY_CTRL_DSA_PARAMGEN_Q_BITS, qbits, NULL);
+#else
+ if (EVP_PKEY_CTX_set_dsa_paramgen_q_bits(ctx, qbits) <= 0) {
+ RNP_LOG("Failed to set key qbits: %lu", ERR_peek_last_error());
+ goto done;
+ }
+#endif
+ if (EVP_PKEY_paramgen(ctx, &parmkey) <= 0) {
+ RNP_LOG("Failed to generate parameters: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ EVP_PKEY_CTX_free(ctx);
+ /* Generate DSA key */
+ ctx = EVP_PKEY_CTX_new(parmkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_keygen_init(ctx) <= 0) {
+ RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
+ RNP_LOG("DSA keygen failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ dsa = EVP_PKEY_get0_DSA(pkey);
+ if (!dsa) {
+ RNP_LOG("Failed to retrieve DSA key: %lu", ERR_peek_last_error());
+ goto done;
+ }
+
+ const bignum_t *p;
+ const bignum_t *q;
+ const bignum_t *g;
+ const bignum_t *y;
+ const bignum_t *x;
+ p = DSA_get0_p(dsa);
+ q = DSA_get0_q(dsa);
+ g = DSA_get0_g(dsa);
+ y = DSA_get0_pub_key(dsa);
+ x = DSA_get0_priv_key(dsa);
+ if (!p || !q || !g || !y || !x) {
+ ret = RNP_ERROR_BAD_STATE;
+ goto done;
+ }
+ bn2mpi(p, &key->p);
+ bn2mpi(q, &key->q);
+ bn2mpi(g, &key->g);
+ bn2mpi(y, &key->y);
+ bn2mpi(x, &key->x);
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(parmkey);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+pgp_hash_alg_t
+dsa_get_min_hash(size_t qsize)
+{
+ /*
+ * I'm using _broken_ SHA1 here only because
+ * some old implementations may not understand keys created
+ * with other hashes. If you're sure we don't have to support
+ * such implementations, please be my guest and remove it.
+ */
+ return (qsize < 160) ? PGP_HASH_UNKNOWN :
+ (qsize == 160) ? PGP_HASH_SHA1 :
+ (qsize <= 224) ? PGP_HASH_SHA224 :
+ (qsize <= 256) ? PGP_HASH_SHA256 :
+ (qsize <= 384) ? PGP_HASH_SHA384 :
+ (qsize <= 512) ? PGP_HASH_SHA512
+ /*(qsize>512)*/ :
+ PGP_HASH_UNKNOWN;
+}
+
+size_t
+dsa_choose_qsize_by_psize(size_t psize)
+{
+ return (psize == 1024) ? 160 :
+ (psize <= 2047) ? 224 :
+ (psize <= 3072) ? DSA_MAX_Q_BITLEN :
+ 0;
+}
diff --git a/src/lib/crypto/ec.cpp b/src/lib/crypto/ec.cpp
new file mode 100644
index 0000000..144c362
--- /dev/null
+++ b/src/lib/crypto/ec.cpp
@@ -0,0 +1,187 @@
+/*
+ * Copyright (c) 2017, [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 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 <botan/ffi.h>
+#include <string.h>
+#include <cassert>
+#include "ec.h"
+#include "types.h"
+#include "utils.h"
+#include "mem.h"
+#include "bn.h"
+
+static id_str_pair ec_algo_to_botan[] = {
+ {PGP_PKA_ECDH, "ECDH"},
+ {PGP_PKA_ECDSA, "ECDSA"},
+ {PGP_PKA_SM2, "SM2_Sig"},
+ {0, NULL},
+};
+
+rnp_result_t
+x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key)
+{
+ botan_privkey_t pr_key = NULL;
+ botan_pubkey_t pu_key = NULL;
+ rnp_result_t ret = RNP_ERROR_KEY_GENERATION;
+
+ rnp::secure_array<uint8_t, 32> keyle;
+
+ if (botan_privkey_create(&pr_key, "Curve25519", "", rng->handle())) {
+ goto end;
+ }
+
+ if (botan_privkey_export_pubkey(&pu_key, pr_key)) {
+ goto end;
+ }
+
+ /* botan returns key in little-endian, while mpi is big-endian */
+ if (botan_privkey_x25519_get_privkey(pr_key, keyle.data())) {
+ goto end;
+ }
+ for (int i = 0; i < 32; i++) {
+ key->x.mpi[31 - i] = keyle[i];
+ }
+ key->x.len = 32;
+ /* botan doesn't tweak secret key bits, so we should do that here */
+ if (!x25519_tweak_bits(*key)) {
+ goto end;
+ }
+
+ if (botan_pubkey_x25519_get_pubkey(pu_key, &key->p.mpi[1])) {
+ goto end;
+ }
+ key->p.len = 33;
+ key->p.mpi[0] = 0x40;
+
+ ret = RNP_SUCCESS;
+end:
+ botan_privkey_destroy(pr_key);
+ botan_pubkey_destroy(pu_key);
+ return ret;
+}
+
+rnp_result_t
+ec_generate(rnp::RNG * rng,
+ pgp_ec_key_t * key,
+ const pgp_pubkey_alg_t alg_id,
+ const pgp_curve_t curve)
+{
+ /**
+ * Keeps "0x04 || x || y"
+ * \see 13.2. ECDSA, ECDH, SM2 Conversion Primitives
+ *
+ * P-521 is biggest supported curve
+ */
+ botan_privkey_t pr_key = NULL;
+ botan_pubkey_t pu_key = NULL;
+ bignum_t * px = NULL;
+ bignum_t * py = NULL;
+ bignum_t * x = NULL;
+ rnp_result_t ret = RNP_ERROR_KEY_GENERATION;
+ size_t filed_byte_size = 0;
+
+ if (!alg_allows_curve(alg_id, curve)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ const char *ec_algo = id_str_pair::lookup(ec_algo_to_botan, alg_id, NULL);
+ assert(ec_algo);
+ const ec_curve_desc_t *ec_desc = get_curve_desc(curve);
+ if (!ec_desc) {
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto end;
+ }
+ filed_byte_size = BITS_TO_BYTES(ec_desc->bitlen);
+
+ // at this point it must succeed
+ if (botan_privkey_create(&pr_key, ec_algo, ec_desc->botan_name, rng->handle())) {
+ goto end;
+ }
+
+ if (botan_privkey_export_pubkey(&pu_key, pr_key)) {
+ goto end;
+ }
+
+ // Crash if seckey is null. It's clean and easy to debug design
+ px = bn_new();
+ py = bn_new();
+ x = bn_new();
+
+ if (!px || !py || !x) {
+ RNP_LOG("Allocation failed");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto end;
+ }
+
+ if (botan_pubkey_get_field(BN_HANDLE_PTR(px), pu_key, "public_x")) {
+ goto end;
+ }
+
+ if (botan_pubkey_get_field(BN_HANDLE_PTR(py), pu_key, "public_y")) {
+ goto end;
+ }
+
+ if (botan_privkey_get_field(BN_HANDLE_PTR(x), pr_key, "x")) {
+ goto end;
+ }
+
+ size_t x_bytes;
+ size_t y_bytes;
+ x_bytes = bn_num_bytes(*px);
+ y_bytes = bn_num_bytes(*py);
+
+ // Safety check
+ if ((x_bytes > filed_byte_size) || (y_bytes > filed_byte_size)) {
+ RNP_LOG("Key generation failed");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto end;
+ }
+
+ /*
+ * Convert coordinates to MPI stored as
+ * "0x04 || x || y"
+ *
+ * \see 13.2. ECDSA and ECDH Conversion Primitives
+ *
+ * Note: Generated pk/sk may not always have exact number of bytes
+ * which is important when converting to octet-string
+ */
+ memset(key->p.mpi, 0, sizeof(key->p.mpi));
+ key->p.mpi[0] = 0x04;
+ bn_bn2bin(px, &key->p.mpi[1 + filed_byte_size - x_bytes]);
+ bn_bn2bin(py, &key->p.mpi[1 + filed_byte_size + (filed_byte_size - y_bytes)]);
+ key->p.len = 2 * filed_byte_size + 1;
+ /* secret key value */
+ bn2mpi(x, &key->x);
+ ret = RNP_SUCCESS;
+end:
+ botan_privkey_destroy(pr_key);
+ botan_pubkey_destroy(pu_key);
+ bn_free(px);
+ bn_free(py);
+ bn_free(x);
+ return ret;
+}
diff --git a/src/lib/crypto/ec.h b/src/lib/crypto/ec.h
new file mode 100644
index 0000000..07cb8e8
--- /dev/null
+++ b/src/lib/crypto/ec.h
@@ -0,0 +1,173 @@
+/*
+ * Copyright (c) 2017-2020 [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).
+ *
+ * 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 EC_H_
+#define EC_H_
+
+#include "config.h"
+#include <rnp/rnp_def.h>
+#include <repgp/repgp_def.h>
+#include "crypto/rng.h"
+#include "crypto/mpi.h"
+
+#define MAX_CURVE_BIT_SIZE 521 // secp521r1
+/* Maximal byte size of elliptic curve order (NIST P-521) */
+#define MAX_CURVE_BYTELEN ((MAX_CURVE_BIT_SIZE + 7) / 8)
+
+/**
+ * Maximal length of the OID in hex representation.
+ *
+ * \see RFC4880 bis01 - 9.2 ECC Curve OID
+ */
+#define MAX_CURVE_OID_HEX_LEN 10U
+
+/**
+ * Structure holds description of elliptic curve
+ */
+typedef struct ec_curve_desc_t {
+ const pgp_curve_t rnp_curve_id;
+ const size_t bitlen;
+ const uint8_t OIDhex[MAX_CURVE_OID_HEX_LEN];
+ const size_t OIDhex_len;
+#if defined(CRYPTO_BACKEND_BOTAN)
+ const char *botan_name;
+#endif
+#if defined(CRYPTO_BACKEND_OPENSSL)
+ const char *openssl_name;
+#endif
+ const char *pgp_name;
+ /* Curve is supported for keygen/sign/encrypt operations */
+ bool supported;
+ /* Curve parameters below. Needed for grip calculation */
+ const char *p;
+ const char *a;
+ const char *b;
+ const char *n;
+ const char *gx;
+ const char *gy;
+ const char *h;
+} ec_curve_desc_t;
+
+typedef struct pgp_ec_key_t {
+ pgp_curve_t curve;
+ pgp_mpi_t p;
+ /* secret mpi */
+ pgp_mpi_t x;
+ /* ecdh params */
+ pgp_hash_alg_t kdf_hash_alg; /* Hash used by kdf */
+ pgp_symm_alg_t key_wrap_alg; /* Symmetric algorithm used to wrap KEK*/
+} pgp_ec_key_t;
+
+typedef struct pgp_ec_signature_t {
+ pgp_mpi_t r;
+ pgp_mpi_t s;
+} pgp_ec_signature_t;
+
+/*
+ * @brief Finds curve ID by hex representation of OID
+ *
+ * @param oid buffer with OID in hex
+ * @param oid_len length of oid buffer
+ *
+ * @returns success curve ID
+ * failure PGP_CURVE_MAX is returned
+ *
+ * @remarks see RFC 4880 bis 01 - 9.2 ECC Curve OID
+ */
+pgp_curve_t find_curve_by_OID(const uint8_t *oid, size_t oid_len);
+
+pgp_curve_t find_curve_by_name(const char *name);
+
+/*
+ * @brief Returns pointer to the curve descriptor
+ *
+ * @param Valid curve ID
+ *
+ * @returns NULL if wrong ID provided, otherwise descriptor
+ *
+ */
+const ec_curve_desc_t *get_curve_desc(const pgp_curve_t curve_id);
+
+bool alg_allows_curve(pgp_pubkey_alg_t alg, pgp_curve_t curve);
+
+/**
+ * @brief Check whether curve is supported for operations.
+ * All available curves are supported for reading/parsing key data, however some of them
+ * may be disabled for use, i.e. for key generation/signing/encryption.
+ */
+bool curve_supported(pgp_curve_t curve);
+
+/*
+ * @brief Generates EC key in uncompressed format
+ *
+ * @param rng initialized rnp::RNG context*
+ * @param key key data to be generated
+ * @param alg_id ID of EC algorithm
+ * @param curve underlying ECC curve ID
+ *
+ * @pre alg_id MUST be supported algorithm
+ *
+ * @returns RNP_ERROR_BAD_PARAMETERS unknown curve_id
+ * @returns RNP_ERROR_OUT_OF_MEMORY memory allocation failed
+ * @returns RNP_ERROR_KEY_GENERATION implementation error
+ */
+rnp_result_t ec_generate(rnp::RNG * rng,
+ pgp_ec_key_t * key,
+ const pgp_pubkey_alg_t alg_id,
+ const pgp_curve_t curve);
+
+/*
+ * @brief Generates x25519 ECDH key in x25519-specific format
+ *
+ * @param rng initialized rnp::RNG context*
+ * @param key key data to be generated
+ *
+ * @returns RNP_ERROR_KEY_GENERATION implementation error
+ */
+rnp_result_t x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key);
+
+/**
+ * @brief Set least significant/most significant bits of the 25519 secret key as per
+ * specification.
+ *
+ * @param key secret key.
+ * @return true on success or false otherwise.
+ */
+bool x25519_tweak_bits(pgp_ec_key_t &key);
+
+/**
+ * @brief Check whether least significant/most significant bits of 25519 secret key are
+ * correctly tweaked.
+ *
+ * @param key secret key.
+ * @return true if bits are set correctly, and false otherwise.
+ */
+bool x25519_bits_tweaked(const pgp_ec_key_t &key);
+
+#endif
diff --git a/src/lib/crypto/ec_curves.cpp b/src/lib/crypto/ec_curves.cpp
new file mode 100644
index 0000000..db5cf09
--- /dev/null
+++ b/src/lib/crypto/ec_curves.cpp
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2021, [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 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 <string.h>
+#include "ec.h"
+#include "types.h"
+#include "utils.h"
+#include "str-utils.h"
+
+/**
+ * EC Curves definition used by implementation
+ *
+ * \see RFC4880 bis01 - 9.2. ECC Curve OID
+ *
+ * Order of the elements in this array corresponds to
+ * values in pgp_curve_t enum.
+ */
+static const ec_curve_desc_t ec_curves[] = {
+ {PGP_CURVE_UNKNOWN, 0, {0}, 0, NULL, NULL},
+
+ {PGP_CURVE_NIST_P_256,
+ 256,
+ {0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07},
+ 8,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "secp256r1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "prime256v1",
+#endif
+ "NIST P-256",
+ true,
+ "0xffffffff00000001000000000000000000000000ffffffffffffffffffffffff",
+ "0xffffffff00000001000000000000000000000000fffffffffffffffffffffffc",
+ "0x5ac635d8aa3a93e7b3ebbd55769886bc651d06b0cc53b0f63bce3c3e27d2604b",
+ "0xffffffff00000000ffffffffffffffffbce6faada7179e84f3b9cac2fc632551",
+ "0x6b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c296",
+ "0x4fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5",
+ "0x01"},
+ {PGP_CURVE_NIST_P_384,
+ 384,
+ {0x2B, 0x81, 0x04, 0x00, 0x22},
+ 5,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "secp384r1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "secp384r1",
+#endif
+ "NIST P-384",
+ true,
+ "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000"
+ "ffffffff",
+ "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffeffffffff0000000000000000"
+ "fffffffc",
+ "0xb3312fa7e23ee7e4988e056be3f82d19181d9c6efe8141120314088f5013875ac656398d8a2ed19d2a85c8ed"
+ "d3ec2aef",
+ "0xffffffffffffffffffffffffffffffffffffffffffffffffc7634d81f4372ddf581a0db248b0a77aecec196a"
+ "ccc52973",
+ "0xaa87ca22be8b05378eb1c71ef320ad746e1d3b628ba79b9859f741e082542a385502f25dbf55296c3a545e38"
+ "72760ab7",
+ "0x3617de4a96262c6f5d9e98bf9292dc29f8f41dbd289a147ce9da3113b5f0b8c00a60b1ce1d7e819d7a431d7c"
+ "90ea0e5f",
+ "0x01"},
+ {PGP_CURVE_NIST_P_521,
+ 521,
+ {0x2B, 0x81, 0x04, 0x00, 0x23},
+ 5,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "secp521r1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "secp521r1",
+#endif
+ "NIST P-521",
+ true,
+ "0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "ffffffffffffffffffffffffffffffffffffffffffff",
+ "0x01ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"
+ "fffffffffffffffffffffffffffffffffffffffffffc",
+ "0x0051953eb9618e1c9a1f929a21a0b68540eea2da725b99b315f3b8b489918ef109e156193951ec7e937b1652"
+ "c0bd3bb1bf073573df883d2c34f1ef451fd46b503f00",
+ "0x01fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa51868783bf2f966b7fcc"
+ "0148f709a5d03bb5c9b8899c47aebb6fb71e91386409",
+ "0x00c6858e06b70404e9cd9e3ecb662395b4429c648139053fb521f828af606b4d3dbaa14b5e77efe75928fe1d"
+ "c127a2ffa8de3348b3c1856a429bf97e7e31c2e5bd66",
+ "0x011839296a789a3bc0045c8a5fb42c7d1bd998f54449579b446817afbd17273e662c97ee72995ef42640c550"
+ "b9013fad0761353c7086a272c24088be94769fd16650",
+ "0x01"},
+ {PGP_CURVE_ED25519,
+ 255,
+ {0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01},
+ 9,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "Ed25519",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "ED25519",
+#endif
+ "Ed25519",
+ true,
+ "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed",
+ /* two below are actually negative */
+ "0x01",
+ "0x2dfc9311d490018c7338bf8688861767ff8ff5b2bebe27548a14b235eca6874a",
+ "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed",
+ "0x216936d3cd6e53fec0a4e231fdd6dc5c692cc7609525a7b2c9562d608f25d51a",
+ "0x6666666666666666666666666666666666666666666666666666666666666658",
+ "0x08"},
+ {PGP_CURVE_25519,
+ 255,
+ {0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01},
+ 10,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "curve25519",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "X25519",
+#endif
+ "Curve25519",
+ true,
+ "0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffed",
+ "0x01db41",
+ "0x01",
+ "0x1000000000000000000000000000000014def9dea2f79cd65812631a5cf5d3ed",
+ "0x0000000000000000000000000000000000000000000000000000000000000009",
+ "0x20ae19a1b8a086b4e01edd2c7748d14c923d4d7e6d7c61b229e9c5a27eced3d9",
+ "0x08"},
+ {PGP_CURVE_BP256,
+ 256,
+ {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x07},
+ 9,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "brainpool256r1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "brainpoolP256r1",
+#endif
+ "brainpoolP256r1",
+#if defined(ENABLE_BRAINPOOL)
+ true,
+#else
+ false,
+#endif
+ "0xa9fb57dba1eea9bc3e660a909d838d726e3bf623d52620282013481d1f6e5377",
+ "0x7d5a0975fc2c3057eef67530417affe7fb8055c126dc5c6ce94a4b44f330b5d9",
+ "0x26dc5c6ce94a4b44f330b5d9bbd77cbf958416295cf7e1ce6bccdc18ff8c07b6",
+ "0xa9fb57dba1eea9bc3e660a909d838d718c397aa3b561a6f7901e0e82974856a7",
+ "0x8bd2aeb9cb7e57cb2c4b482ffc81b7afb9de27e1e3bd23c23a4453bd9ace3262",
+ "0x547ef835c3dac4fd97f8461a14611dc9c27745132ded8e545c1d54c72f046997",
+ "0x01"},
+ {PGP_CURVE_BP384,
+ 384,
+ {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B},
+ 9,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "brainpool384r1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "brainpoolP384r1",
+#endif
+ "brainpoolP384r1",
+#if defined(ENABLE_BRAINPOOL)
+ true,
+#else
+ false,
+#endif
+ "0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b412b1da197fb71123acd3a729901d1a7187470013"
+ "3107ec53",
+ "0x7bc382c63d8c150c3c72080ace05afa0c2bea28e4fb22787139165efba91f90f8aa5814a503ad4eb04a8c7dd"
+ "22ce2826",
+ "0x04a8c7dd22ce28268b39b55416f0447c2fb77de107dcd2a62e880ea53eeb62d57cb4390295dbc9943ab78696"
+ "fa504c11",
+ "0x8cb91e82a3386d280f5d6f7e50e641df152f7109ed5456b31f166e6cac0425a7cf3ab6af6b7fc3103b883202"
+ "e9046565",
+ "0x1d1c64f068cf45ffa2a63a81b7c13f6b8847a3e77ef14fe3db7fcafe0cbd10e8e826e03436d646aaef87b2e2"
+ "47d4af1e",
+ "0x8abe1d7520f9c2a45cb1eb8e95cfd55262b70b29feec5864e19c054ff99129280e4646217791811142820341"
+ "263c5315",
+ "0x01"},
+ {PGP_CURVE_BP512,
+ 512,
+ {0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0D},
+ 9,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "brainpool512r1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "brainpoolP512r1",
+#endif
+ "brainpoolP512r1",
+#if defined(ENABLE_BRAINPOOL)
+ true,
+#else
+ false,
+#endif
+ "0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca703308717d4d9b009bc66842aecda12a"
+ "e6a380e62881ff2f2d82c68528aa6056583a48f3",
+ "0x7830a3318b603b89e2327145ac234cc594cbdd8d3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c9"
+ "8b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca",
+ "0x3df91610a83441caea9863bc2ded5d5aa8253aa10a2ef1c98b9ac8b57f1117a72bf2c7b9e7c1ac4d77fc94ca"
+ "dc083e67984050b75ebae5dd2809bd638016f723",
+ "0xaadd9db8dbe9c48b3fd4e6ae33c9fc07cb308db3b3c9d20ed6639cca70330870553e5c414ca9261941866119"
+ "7fac10471db1d381085ddaddb58796829ca90069",
+ "0x81aee4bdd82ed9645a21322e9c4c6a9385ed9f70b5d916c1b43b62eef4d0098eff3b1f78e2d0d48d50d1687b"
+ "93b97d5f7c6d5047406a5e688b352209bcb9f822",
+ "0x7dde385d566332ecc0eabfa9cf7822fdf209f70024a57b1aa000c55b881f8111b2dcde494a5f485e5bca4bd8"
+ "8a2763aed1ca2b2fa8f0540678cd1e0f3ad80892",
+ "0x01"},
+ {PGP_CURVE_P256K1,
+ 256,
+ {0x2B, 0x81, 0x04, 0x00, 0x0A},
+ 5,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "secp256k1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "secp256k1",
+#endif
+ "secp256k1",
+ true,
+ "0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffefffffc2f",
+ "0x0000000000000000000000000000000000000000000000000000000000000000",
+ "0x0000000000000000000000000000000000000000000000000000000000000007",
+ "0xfffffffffffffffffffffffffffffffebaaedce6af48a03bbfd25e8cd0364141",
+ "0x79be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798",
+ "0x483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8",
+ "0x01"},
+ {
+ PGP_CURVE_SM2_P_256,
+ 256,
+ {0x2A, 0x81, 0x1C, 0xCF, 0x55, 0x01, 0x82, 0x2D},
+ 8,
+#if defined(CRYPTO_BACKEND_BOTAN)
+ "sm2p256v1",
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ "sm2",
+#endif
+ "SM2 P-256",
+#if defined(ENABLE_SM2)
+ true,
+#else
+ false,
+#endif
+ "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFF",
+ "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00000000FFFFFFFFFFFFFFFC",
+ "0x28E9FA9E9D9F5E344D5A9E4BCF6509A7F39789F515AB8F92DDBCBD414D940E93",
+ "0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123",
+ "0x32C4AE2C1F1981195F9904466A39C9948FE30BBFF2660BE1715A4589334C74C7",
+ "0xBC3736A2F4F6779C59BDCEE36B692153D0A9877CC62A474002DF32E52139F0A0",
+ },
+};
+
+pgp_curve_t
+find_curve_by_OID(const uint8_t *oid, size_t oid_len)
+{
+ for (size_t i = 0; i < PGP_CURVE_MAX; i++) {
+ if ((oid_len == ec_curves[i].OIDhex_len) &&
+ (!memcmp(oid, ec_curves[i].OIDhex, oid_len))) {
+ return static_cast<pgp_curve_t>(i);
+ }
+ }
+
+ return PGP_CURVE_MAX;
+}
+
+pgp_curve_t
+find_curve_by_name(const char *name)
+{
+ for (size_t i = 1; i < PGP_CURVE_MAX; i++) {
+ if (rnp::str_case_eq(ec_curves[i].pgp_name, name)) {
+ return ec_curves[i].rnp_curve_id;
+ }
+ }
+
+ return PGP_CURVE_MAX;
+}
+
+const ec_curve_desc_t *
+get_curve_desc(const pgp_curve_t curve_id)
+{
+ return (curve_id < PGP_CURVE_MAX && curve_id > 0) ? &ec_curves[curve_id] : NULL;
+}
+
+bool
+alg_allows_curve(pgp_pubkey_alg_t alg, pgp_curve_t curve)
+{
+ /* SM2 curve is only for SM2 algo */
+ if ((alg == PGP_PKA_SM2) || (curve == PGP_CURVE_SM2_P_256)) {
+ return (alg == PGP_PKA_SM2) && (curve == PGP_CURVE_SM2_P_256);
+ }
+ /* EDDSA and PGP_CURVE_ED25519 */
+ if ((alg == PGP_PKA_EDDSA) || (curve == PGP_CURVE_ED25519)) {
+ return (alg == PGP_PKA_EDDSA) && (curve == PGP_CURVE_ED25519);
+ }
+ /* Curve x25519 is only for ECDH */
+ if (curve == PGP_CURVE_25519) {
+ return alg == PGP_PKA_ECDH;
+ }
+ /* Other curves are good for both ECDH and ECDSA */
+ return true;
+}
+
+bool
+curve_supported(pgp_curve_t curve)
+{
+ const ec_curve_desc_t *info = get_curve_desc(curve);
+ return info && info->supported;
+}
diff --git a/src/lib/crypto/ec_ossl.cpp b/src/lib/crypto/ec_ossl.cpp
new file mode 100644
index 0000000..6974b4c
--- /dev/null
+++ b/src/lib/crypto/ec_ossl.cpp
@@ -0,0 +1,349 @@
+/*
+ * Copyright (c) 2021, [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 <string>
+#include <cassert>
+#include "ec.h"
+#include "ec_ossl.h"
+#include "bn.h"
+#include "types.h"
+#include "mem.h"
+#include "utils.h"
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/err.h>
+#include <openssl/ec.h>
+
+static bool
+ec_is_raw_key(const pgp_curve_t curve)
+{
+ return (curve == PGP_CURVE_ED25519) || (curve == PGP_CURVE_25519);
+}
+
+rnp_result_t
+x25519_generate(rnp::RNG *rng, pgp_ec_key_t *key)
+{
+ return ec_generate(rng, key, PGP_PKA_ECDH, PGP_CURVE_25519);
+}
+
+EVP_PKEY *
+ec_generate_pkey(const pgp_pubkey_alg_t alg_id, const pgp_curve_t curve)
+{
+ if (!alg_allows_curve(alg_id, curve)) {
+ return NULL;
+ }
+ const ec_curve_desc_t *ec_desc = get_curve_desc(curve);
+ if (!ec_desc) {
+ return NULL;
+ }
+ int nid = OBJ_sn2nid(ec_desc->openssl_name);
+ if (nid == NID_undef) {
+ RNP_LOG("Unknown SN: %s", ec_desc->openssl_name);
+ return NULL;
+ }
+ bool raw = ec_is_raw_key(curve);
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(raw ? nid : EVP_PKEY_EC, NULL);
+ if (!ctx) {
+ RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error());
+ return NULL;
+ }
+ EVP_PKEY *pkey = NULL;
+ if (EVP_PKEY_keygen_init(ctx) <= 0) {
+ RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (!raw && (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, nid) <= 0)) {
+ RNP_LOG("Failed to set curve nid: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
+ RNP_LOG("EC keygen failed: %lu", ERR_peek_last_error());
+ }
+done:
+ EVP_PKEY_CTX_free(ctx);
+ return pkey;
+}
+
+static bool
+ec_write_raw_seckey(EVP_PKEY *pkey, pgp_ec_key_t *key)
+{
+ /* EdDSA and X25519 keys are saved in a different way */
+ static_assert(sizeof(key->x.mpi) > 32, "mpi is too small.");
+ key->x.len = sizeof(key->x.mpi);
+ if (EVP_PKEY_get_raw_private_key(pkey, key->x.mpi, &key->x.len) <= 0) {
+ RNP_LOG("Failed get raw private key: %lu", ERR_peek_last_error());
+ return false;
+ }
+ assert(key->x.len == 32);
+ if (EVP_PKEY_id(pkey) == EVP_PKEY_X25519) {
+ /* in OpenSSL private key is exported as little-endian, while MPI is big-endian */
+ for (size_t i = 0; i < 16; i++) {
+ std::swap(key->x.mpi[i], key->x.mpi[31 - i]);
+ }
+ }
+ return true;
+}
+
+rnp_result_t
+ec_generate(rnp::RNG * rng,
+ pgp_ec_key_t * key,
+ const pgp_pubkey_alg_t alg_id,
+ const pgp_curve_t curve)
+{
+ EVP_PKEY *pkey = ec_generate_pkey(alg_id, curve);
+ if (!pkey) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ if (ec_is_raw_key(curve)) {
+ if (ec_write_pubkey(pkey, key->p, curve) && ec_write_raw_seckey(pkey, key)) {
+ ret = RNP_SUCCESS;
+ }
+ EVP_PKEY_free(pkey);
+ return ret;
+ }
+ 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)) {
+ RNP_LOG("Failed to write pubkey.");
+ goto done;
+ }
+ const bignum_t *x;
+ x = EC_KEY_get0_private_key(ec);
+ if (!x) {
+ ret = RNP_ERROR_BAD_STATE;
+ goto done;
+ }
+ if (bn2mpi(x, &key->x)) {
+ ret = RNP_SUCCESS;
+ }
+done:
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+static EVP_PKEY *
+ec_load_raw_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, int nid)
+{
+ if (!keyx) {
+ /* as per RFC, EdDSA & 25519 keys must use 0x40 byte for encoding */
+ if ((mpi_bytes(&keyp) != 33) || (keyp.mpi[0] != 0x40)) {
+ RNP_LOG("Invalid 25519 public key.");
+ return NULL;
+ }
+
+ EVP_PKEY *evpkey =
+ EVP_PKEY_new_raw_public_key(nid, NULL, &keyp.mpi[1], mpi_bytes(&keyp) - 1);
+ if (!evpkey) {
+ RNP_LOG("Failed to load public key: %lu", ERR_peek_last_error());
+ }
+ return evpkey;
+ }
+
+ EVP_PKEY *evpkey = NULL;
+ if (nid == EVP_PKEY_X25519) {
+ if (keyx->len != 32) {
+ RNP_LOG("Invalid 25519 secret key");
+ return NULL;
+ }
+ /* need to reverse byte order since in mpi we have big-endian */
+ rnp::secure_array<uint8_t, 32> prkey;
+ for (int i = 0; i < 32; i++) {
+ prkey[i] = keyx->mpi[31 - i];
+ }
+ evpkey = EVP_PKEY_new_raw_private_key(nid, NULL, prkey.data(), keyx->len);
+ } else {
+ if (keyx->len > 32) {
+ RNP_LOG("Invalid Ed25519 secret key");
+ return NULL;
+ }
+ /* keyx->len may be smaller then 32 as high byte is random and could become 0 */
+ rnp::secure_array<uint8_t, 32> prkey{};
+ memcpy(prkey.data() + 32 - keyx->len, keyx->mpi, keyx->len);
+ evpkey = EVP_PKEY_new_raw_private_key(nid, NULL, prkey.data(), 32);
+ }
+ if (!evpkey) {
+ RNP_LOG("Failed to load private key: %lu", ERR_peek_last_error());
+ }
+ return evpkey;
+}
+
+EVP_PKEY *
+ec_load_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, pgp_curve_t curve)
+{
+ const ec_curve_desc_t *curv_desc = get_curve_desc(curve);
+ if (!curv_desc) {
+ RNP_LOG("unknown curve");
+ return NULL;
+ }
+ if (!curve_supported(curve)) {
+ RNP_LOG("Curve %s is not supported.", curv_desc->pgp_name);
+ return NULL;
+ }
+ int nid = OBJ_sn2nid(curv_desc->openssl_name);
+ if (nid == NID_undef) {
+ RNP_LOG("Unknown SN: %s", curv_desc->openssl_name);
+ return NULL;
+ }
+ /* EdDSA and X25519 keys are loaded in a different way */
+ if (ec_is_raw_key(curve)) {
+ return ec_load_raw_key(keyp, keyx, nid);
+ }
+ EC_KEY *ec = EC_KEY_new_by_curve_name(nid);
+ if (!ec) {
+ RNP_LOG("Failed to create EC key with group %d (%s): %s",
+ nid,
+ curv_desc->openssl_name,
+ ERR_reason_error_string(ERR_peek_last_error()));
+ return NULL;
+ }
+
+ bool res = false;
+ bignum_t *x = NULL;
+ EVP_PKEY *pkey = NULL;
+ EC_POINT *p = EC_POINT_new(EC_KEY_get0_group(ec));
+ if (!p) {
+ RNP_LOG("Failed to allocate point: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EC_POINT_oct2point(EC_KEY_get0_group(ec), p, keyp.mpi, keyp.len, NULL) <= 0) {
+ RNP_LOG("Failed to decode point: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EC_KEY_set_public_key(ec, p) <= 0) {
+ RNP_LOG("Failed to set public key: %lu", ERR_peek_last_error());
+ goto done;
+ }
+
+ pkey = EVP_PKEY_new();
+ if (!pkey) {
+ RNP_LOG("EVP_PKEY allocation failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (!keyx) {
+ res = true;
+ goto done;
+ }
+
+ x = mpi2bn(keyx);
+ if (!x) {
+ RNP_LOG("allocation failed");
+ goto done;
+ }
+ if (EC_KEY_set_private_key(ec, x) <= 0) {
+ RNP_LOG("Failed to set secret key: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ res = true;
+done:
+ if (res) {
+ res = EVP_PKEY_set1_EC_KEY(pkey, ec) > 0;
+ }
+ EC_POINT_free(p);
+ BN_free(x);
+ EC_KEY_free(ec);
+ if (!res) {
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ }
+ return pkey;
+}
+
+rnp_result_t
+ec_validate_key(const pgp_ec_key_t &key, bool secret)
+{
+ if (key.curve == PGP_CURVE_25519) {
+ /* No key check implementation for x25519 in the OpenSSL yet, so just basic size checks
+ */
+ if ((mpi_bytes(&key.p) != 33) || (key.p.mpi[0] != 0x40)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (secret && mpi_bytes(&key.x) != 32) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+ }
+ EVP_PKEY *evpkey = ec_load_key(key.p, secret ? &key.x : NULL, key.curve);
+ if (!evpkey) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ int res;
+ res = secret ? EVP_PKEY_check(ctx) : EVP_PKEY_public_check(ctx);
+ if (res < 0) {
+ auto err = ERR_peek_last_error();
+ RNP_LOG("EC key check failed: %lu (%s)", err, ERR_reason_error_string(err));
+ }
+ if (res > 0) {
+ ret = RNP_SUCCESS;
+ }
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(evpkey);
+ return ret;
+}
+
+bool
+ec_write_pubkey(EVP_PKEY *pkey, pgp_mpi_t &mpi, pgp_curve_t curve)
+{
+ if (ec_is_raw_key(curve)) {
+ /* EdDSA and X25519 keys are saved in a different way */
+ mpi.len = sizeof(mpi.mpi) - 1;
+ if (EVP_PKEY_get_raw_public_key(pkey, &mpi.mpi[1], &mpi.len) <= 0) {
+ RNP_LOG("Failed get raw public key: %lu", ERR_peek_last_error());
+ return false;
+ }
+ assert(mpi.len == 32);
+ mpi.mpi[0] = 0x40;
+ mpi.len++;
+ return true;
+ }
+ 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);
+ if (!p) {
+ RNP_LOG("Null point: %lu", ERR_peek_last_error());
+ return false;
+ }
+ /* call below adds leading zeroes if needed */
+ mpi.len = EC_POINT_point2oct(
+ EC_KEY_get0_group(ec), p, POINT_CONVERSION_UNCOMPRESSED, mpi.mpi, sizeof(mpi.mpi), NULL);
+ if (!mpi.len) {
+ RNP_LOG("Failed to encode public key: %lu", ERR_peek_last_error());
+ }
+ return mpi.len;
+}
diff --git a/src/lib/crypto/ec_ossl.h b/src/lib/crypto/ec_ossl.h
new file mode 100644
index 0000000..f16afe8
--- /dev/null
+++ b/src/lib/crypto/ec_ossl.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2021, [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 EC_OSSL_H_
+#define EC_OSSL_H_
+
+#include "types.h"
+#include "ec.h"
+#include <openssl/evp.h>
+
+EVP_PKEY *ec_load_key(const pgp_mpi_t &keyp, const pgp_mpi_t *keyx, pgp_curve_t curve);
+
+rnp_result_t ec_validate_key(const pgp_ec_key_t &key, bool secret);
+
+EVP_PKEY *ec_generate_pkey(const pgp_pubkey_alg_t alg_id, const pgp_curve_t curve);
+
+bool ec_write_pubkey(EVP_PKEY *key, pgp_mpi_t &mpi, pgp_curve_t curve);
+
+#endif
diff --git a/src/lib/crypto/ecdh.cpp b/src/lib/crypto/ecdh.cpp
new file mode 100644
index 0000000..d4411c3
--- /dev/null
+++ b/src/lib/crypto/ecdh.cpp
@@ -0,0 +1,377 @@
+/*-
+ * 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.
+ */
+
+#include <string.h>
+#include <botan/ffi.h>
+#include "hash_botan.hpp"
+#include "ecdh.h"
+#include "ecdh_utils.h"
+#include "symmetric.h"
+#include "types.h"
+#include "utils.h"
+#include "mem.h"
+#include "bn.h"
+
+// Produces kek of size kek_len which corresponds to length of wrapping key
+static bool
+compute_kek(uint8_t * kek,
+ size_t kek_len,
+ const uint8_t * other_info,
+ size_t other_info_size,
+ const ec_curve_desc_t *curve_desc,
+ const pgp_mpi_t * ec_pubkey,
+ const botan_privkey_t ec_prvkey,
+ const pgp_hash_alg_t hash_alg)
+{
+ const uint8_t *p = ec_pubkey->mpi;
+ uint8_t p_len = ec_pubkey->len;
+
+ if (curve_desc->rnp_curve_id == PGP_CURVE_25519) {
+ if ((p_len != 33) || (p[0] != 0x40)) {
+ return false;
+ }
+ p++;
+ p_len--;
+ }
+
+ rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN * 2 + 1> s;
+
+ botan_pk_op_ka_t op_key_agreement = NULL;
+ bool ret = false;
+ char kdf_name[32] = {0};
+ size_t s_len = s.size();
+
+ 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_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);
+}
+
+static bool
+ecdh_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *key)
+{
+ bool res = false;
+
+ const ec_curve_desc_t *curve = get_curve_desc(key->curve);
+ if (!curve) {
+ RNP_LOG("unknown curve");
+ return false;
+ }
+
+ if (curve->rnp_curve_id == PGP_CURVE_25519) {
+ if ((key->p.len != 33) || (key->p.mpi[0] != 0x40)) {
+ return false;
+ }
+ rnp::secure_array<uint8_t, 32> pkey;
+ memcpy(pkey.data(), key->p.mpi + 1, 32);
+ return !botan_pubkey_load_x25519(pubkey, pkey.data());
+ }
+
+ if (!mpi_bytes(&key->p) || (key->p.mpi[0] != 0x04)) {
+ RNP_LOG("Failed to load public key");
+ return false;
+ }
+
+ botan_mp_t px = NULL;
+ botan_mp_t py = NULL;
+ const size_t curve_order = BITS_TO_BYTES(curve->bitlen);
+
+ if (botan_mp_init(&px) || botan_mp_init(&py) ||
+ botan_mp_from_bin(px, &key->p.mpi[1], curve_order) ||
+ botan_mp_from_bin(py, &key->p.mpi[1 + curve_order], curve_order)) {
+ goto end;
+ }
+
+ if (!(res = !botan_pubkey_load_ecdh(pubkey, px, py, curve->botan_name))) {
+ RNP_LOG("failed to load ecdh public key");
+ }
+end:
+ botan_mp_destroy(px);
+ botan_mp_destroy(py);
+ return res;
+}
+
+static bool
+ecdh_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *key)
+{
+ const ec_curve_desc_t *curve = get_curve_desc(key->curve);
+
+ if (!curve) {
+ return false;
+ }
+
+ if (curve->rnp_curve_id == PGP_CURVE_25519) {
+ if (key->x.len != 32) {
+ RNP_LOG("wrong x25519 key");
+ return false;
+ }
+ /* need to reverse byte order since in mpi we have big-endian */
+ rnp::secure_array<uint8_t, 32> prkey;
+ for (int i = 0; i < 32; i++) {
+ prkey[i] = key->x.mpi[31 - i];
+ }
+ return !botan_privkey_load_x25519(seckey, prkey.data());
+ }
+
+ bignum_t *x = NULL;
+ if (!(x = mpi2bn(&key->x))) {
+ return false;
+ }
+ bool res = !botan_privkey_load_ecdh(seckey, BN_HANDLE_PTR(x), curve->botan_name);
+ bn_free(x);
+ return res;
+}
+
+rnp_result_t
+ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ botan_pubkey_t bpkey = NULL;
+ botan_privkey_t bskey = NULL;
+ rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS;
+
+ const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve);
+ if (!curve_desc) {
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+
+ if (!ecdh_load_public_key(&bpkey, key) ||
+ botan_pubkey_check_key(bpkey, rng->handle(), 0)) {
+ goto done;
+ }
+ if (!secret) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ if (!ecdh_load_secret_key(&bskey, key) ||
+ botan_privkey_check_key(bskey, rng->handle(), 0)) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ botan_privkey_destroy(bskey);
+ botan_pubkey_destroy(bpkey);
+ return ret;
+}
+
+rnp_result_t
+ecdh_encrypt_pkcs5(rnp::RNG * rng,
+ pgp_ecdh_encrypted_t * out,
+ const uint8_t *const in,
+ size_t in_len,
+ const pgp_ec_key_t * key,
+ const pgp_fingerprint_t &fingerprint)
+{
+ botan_privkey_t eph_prv_key = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ uint8_t other_info[MAX_SP800_56A_OTHER_INFO];
+ uint8_t kek[32] = {0}; // Size of SHA-256 or smaller
+ // 'm' is padded to the 8-byte granularity
+ uint8_t m[MAX_SESSION_KEY_SIZE];
+ const size_t m_padded_len = ((in_len / 8) + 1) * 8;
+
+ if (!key || !out || !in || (in_len > sizeof(m))) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+#if !defined(ENABLE_SM2)
+ if (key->curve == PGP_CURVE_SM2_P_256) {
+ RNP_LOG("SM2 curve support is disabled.");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+ }
+#endif
+ const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve);
+ if (!curve_desc) {
+ RNP_LOG("unsupported curve");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+
+ // +8 because of AES-wrap adds 8 bytes
+ if (ECDH_WRAPPED_KEY_SIZE < (m_padded_len + 8)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // See 13.5 of RFC 4880 for definition of other_info_size
+ const size_t other_info_size = curve_desc->OIDhex_len + 46;
+ const size_t kek_len = pgp_key_size(key->key_wrap_alg);
+ size_t tmp_len = kdf_other_info_serialize(
+ other_info, curve_desc, fingerprint, key->kdf_hash_alg, key->key_wrap_alg);
+
+ if (tmp_len != other_info_size) {
+ RNP_LOG("Serialization of other info failed");
+ return RNP_ERROR_GENERIC;
+ }
+
+ if (!strcmp(curve_desc->botan_name, "curve25519")) {
+ if (botan_privkey_create(&eph_prv_key, "Curve25519", "", rng->handle())) {
+ goto end;
+ }
+ } else {
+ if (botan_privkey_create(
+ &eph_prv_key, "ECDH", curve_desc->botan_name, rng->handle())) {
+ goto end;
+ }
+ }
+
+ if (!compute_kek(kek,
+ kek_len,
+ other_info,
+ other_info_size,
+ curve_desc,
+ &key->p,
+ eph_prv_key,
+ key->kdf_hash_alg)) {
+ RNP_LOG("KEK computation failed");
+ goto end;
+ }
+
+ memcpy(m, in, in_len);
+ if (!pad_pkcs7(m, m_padded_len, in_len)) {
+ // Should never happen
+ goto end;
+ }
+
+ out->mlen = sizeof(out->m);
+ if (botan_key_wrap3394(m, m_padded_len, kek, kek_len, out->m, &out->mlen)) {
+ goto end;
+ }
+
+ /* we need to prepend 0x40 for the x25519 */
+ if (key->curve == PGP_CURVE_25519) {
+ out->p.len = sizeof(out->p.mpi) - 1;
+ if (botan_pk_op_key_agreement_export_public(
+ eph_prv_key, out->p.mpi + 1, &out->p.len)) {
+ goto end;
+ }
+ out->p.mpi[0] = 0x40;
+ out->p.len++;
+ } else {
+ out->p.len = sizeof(out->p.mpi);
+ if (botan_pk_op_key_agreement_export_public(eph_prv_key, out->p.mpi, &out->p.len)) {
+ goto end;
+ }
+ }
+
+ // All OK
+ ret = RNP_SUCCESS;
+end:
+ botan_privkey_destroy(eph_prv_key);
+ return ret;
+}
+
+rnp_result_t
+ecdh_decrypt_pkcs5(uint8_t * out,
+ size_t * out_len,
+ const pgp_ecdh_encrypted_t *in,
+ const pgp_ec_key_t * key,
+ const pgp_fingerprint_t & fingerprint)
+{
+ if (!out_len || !in || !key || !mpi_bytes(&key->x)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ const ec_curve_desc_t *curve_desc = get_curve_desc(key->curve);
+ if (!curve_desc) {
+ RNP_LOG("unknown curve");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+
+ const pgp_symm_alg_t wrap_alg = key->key_wrap_alg;
+ const pgp_hash_alg_t kdf_hash = key->kdf_hash_alg;
+ /* Ensure that AES is used for wrapping */
+ if ((wrap_alg != PGP_SA_AES_128) && (wrap_alg != PGP_SA_AES_192) &&
+ (wrap_alg != PGP_SA_AES_256)) {
+ RNP_LOG("non-aes wrap algorithm");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+
+ // See 13.5 of RFC 4880 for definition of other_info_size
+ uint8_t other_info[MAX_SP800_56A_OTHER_INFO];
+ const size_t other_info_size = curve_desc->OIDhex_len + 46;
+ const size_t tmp_len =
+ kdf_other_info_serialize(other_info, curve_desc, fingerprint, kdf_hash, wrap_alg);
+
+ if (other_info_size != tmp_len) {
+ RNP_LOG("Serialization of other info failed");
+ return RNP_ERROR_GENERIC;
+ }
+
+ botan_privkey_t prv_key = NULL;
+ if (!ecdh_load_secret_key(&prv_key, key)) {
+ RNP_LOG("failed to load ecdh secret key");
+ return RNP_ERROR_GENERIC;
+ }
+
+ // Size of SHA-256 or smaller
+ rnp::secure_array<uint8_t, MAX_SYMM_KEY_SIZE> kek;
+ rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> deckey;
+
+ size_t deckey_len = deckey.size();
+ size_t offset = 0;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ /* Security: Always return same error code in case compute_kek,
+ * botan_key_unwrap3394 or unpad_pkcs7 fails
+ */
+ size_t kek_len = pgp_key_size(wrap_alg);
+ if (!compute_kek(kek.data(),
+ kek_len,
+ other_info,
+ other_info_size,
+ curve_desc,
+ &in->p,
+ prv_key,
+ kdf_hash)) {
+ goto end;
+ }
+
+ if (botan_key_unwrap3394(
+ in->m, in->mlen, kek.data(), kek_len, deckey.data(), &deckey_len)) {
+ goto end;
+ }
+
+ if (!unpad_pkcs7(deckey.data(), deckey_len, &offset)) {
+ goto end;
+ }
+
+ if (*out_len < offset) {
+ ret = RNP_ERROR_SHORT_BUFFER;
+ goto end;
+ }
+
+ *out_len = offset;
+ memcpy(out, deckey.data(), *out_len);
+ ret = RNP_SUCCESS;
+end:
+ botan_privkey_destroy(prv_key);
+ return ret;
+}
diff --git a/src/lib/crypto/ecdh.h b/src/lib/crypto/ecdh.h
new file mode 100644
index 0000000..017e1e6
--- /dev/null
+++ b/src/lib/crypto/ecdh.h
@@ -0,0 +1,117 @@
+/*-
+ * Copyright (c) 2017 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 ECDH_H_
+#define ECDH_H_
+
+#include "crypto/ec.h"
+
+/* Max size of wrapped and obfuscated key size
+ *
+ * RNP pads a key with PKCS-5 always to 8 byte granularity,
+ * then 8 bytes is added by AES-wrap (RFC3394).
+ */
+#define ECDH_WRAPPED_KEY_SIZE 48
+
+/* Forward declarations */
+typedef struct pgp_fingerprint_t pgp_fingerprint_t;
+
+typedef struct pgp_ecdh_encrypted_t {
+ pgp_mpi_t p;
+ uint8_t m[ECDH_WRAPPED_KEY_SIZE];
+ size_t mlen;
+} pgp_ecdh_encrypted_t;
+
+rnp_result_t ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret);
+
+/*
+ * @brief Sets hash algorithm and key wrapping algo
+ * based on curve_id
+ *
+ * @param key ec key to set parameters for
+ * @param curve underlying ECC curve ID
+ *
+ * @returns false if curve is not supported, otherwise true
+ */
+bool ecdh_set_params(pgp_ec_key_t *key, pgp_curve_t curve_id);
+
+/*
+ * Encrypts session key with a KEK agreed during ECDH as specified in
+ * RFC 4880 bis 01, 13.5
+ *
+ * @param rng initialized rnp::RNG object
+ * @param session_key key to be encrypted
+ * @param session_key_len length of the key buffer
+ * @param wrapped_key [out] resulting key wrapped in by some AES
+ * as specified in RFC 3394
+ * @param wrapped_key_len [out] length of the `wrapped_key' buffer
+ * Current implementation always produces 48 bytes as key
+ * is padded with PKCS-5/7
+ * @param ephemeral_key [out] public ephemeral ECDH key used for key
+ * agreement (private part). Must be initialized
+ * @param pubkey public key to be used for encryption
+ * @param fingerprint fingerprint of the pubkey
+ *
+ * @return RNP_SUCCESS on success and output parameters are populated
+ * @return RNP_ERROR_NOT_SUPPORTED unknown curve
+ * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided
+ * @return RNP_ERROR_SHORT_BUFFER `wrapped_key_len' to small to store result
+ * @return RNP_ERROR_GENERIC implementation error
+ */
+rnp_result_t ecdh_encrypt_pkcs5(rnp::RNG * rng,
+ pgp_ecdh_encrypted_t * out,
+ const uint8_t *const in,
+ size_t in_len,
+ const pgp_ec_key_t * key,
+ const pgp_fingerprint_t &fingerprint);
+
+/*
+ * Decrypts session key with a KEK agreed during ECDH as specified in
+ * RFC 4880 bis 01, 13.5
+ *
+ * @param session_key [out] resulting session key
+ * @param session_key_len [out] length of the resulting session key
+ * @param wrapped_key session key wrapped with some AES as specified
+ * in RFC 3394
+ * @param wrapped_key_len length of the `wrapped_key' buffer
+ * @param ephemeral_key public ephemeral ECDH key coming from
+ * encrypted packet.
+ * @param seckey secret key to be used for decryption
+ * @param fingerprint fingerprint of the key
+ *
+ * @return RNP_SUCCESS on success and output parameters are populated
+ * @return RNP_ERROR_NOT_SUPPORTED unknown curve
+ * @return RNP_ERROR_BAD_PARAMETERS unexpected input provided
+ * @return RNP_ERROR_SHORT_BUFFER `session_key_len' to small to store result
+ * @return RNP_ERROR_GENERIC decryption failed or implementation error
+ */
+rnp_result_t ecdh_decrypt_pkcs5(uint8_t * out,
+ size_t * out_len,
+ const pgp_ecdh_encrypted_t *in,
+ const pgp_ec_key_t * key,
+ const pgp_fingerprint_t & fingerprint);
+
+#endif // ECDH_H_
diff --git a/src/lib/crypto/ecdh_ossl.cpp b/src/lib/crypto/ecdh_ossl.cpp
new file mode 100644
index 0000000..60b7260
--- /dev/null
+++ b/src/lib/crypto/ecdh_ossl.cpp
@@ -0,0 +1,389 @@
+/*
+ * 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:
+ *
+ * 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 <string>
+#include <cassert>
+#include "ecdh.h"
+#include "ecdh_utils.h"
+#include "ec_ossl.h"
+#include "hash.hpp"
+#include "symmetric.h"
+#include "types.h"
+#include "utils.h"
+#include "logging.h"
+#include "mem.h"
+#include <openssl/evp.h>
+#include <openssl/err.h>
+
+static const struct ecdh_wrap_alg_map_t {
+ pgp_symm_alg_t alg;
+ const char * name;
+} ecdh_wrap_alg_map[] = {{PGP_SA_AES_128, "aes128-wrap"},
+ {PGP_SA_AES_192, "aes192-wrap"},
+ {PGP_SA_AES_256, "aes256-wrap"}};
+
+rnp_result_t
+ecdh_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ return ec_validate_key(*key, secret);
+}
+
+static rnp_result_t
+ecdh_derive_kek(uint8_t * x,
+ size_t xlen,
+ const pgp_ec_key_t & key,
+ const pgp_fingerprint_t &fingerprint,
+ uint8_t * kek,
+ const size_t kek_len)
+{
+ const ec_curve_desc_t *curve_desc = get_curve_desc(key.curve);
+ if (!curve_desc) {
+ RNP_LOG("unsupported curve");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+
+ // Serialize other info, see 13.5 of RFC 4880 bis
+ uint8_t other_info[MAX_SP800_56A_OTHER_INFO];
+ const size_t hash_len = rnp::Hash::size(key.kdf_hash_alg);
+ if (!hash_len) {
+ // must not assert here as kdf/hash algs are not checked during key parsing
+ RNP_LOG("Unsupported key wrap hash algorithm.");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ size_t other_len = kdf_other_info_serialize(
+ other_info, curve_desc, fingerprint, key.kdf_hash_alg, key.key_wrap_alg);
+ // Self-check
+ assert(other_len == curve_desc->OIDhex_len + 46);
+ // Derive KEK, using the KDF from SP800-56A
+ rnp::secure_array<uint8_t, PGP_MAX_HASH_SIZE> dgst;
+ assert(hash_len <= PGP_MAX_HASH_SIZE);
+ size_t reps = (kek_len + hash_len - 1) / hash_len;
+ // As we use AES & SHA2 we should not get more then 2 iterations
+ if (reps > 2) {
+ RNP_LOG("Invalid key wrap/hash alg combination.");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ size_t have = 0;
+ try {
+ for (size_t i = 1; i <= reps; i++) {
+ 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;
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to derive kek: %s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+}
+
+static rnp_result_t
+ecdh_rfc3394_wrap_ctx(EVP_CIPHER_CTX **ctx,
+ pgp_symm_alg_t wrap_alg,
+ const uint8_t * key,
+ bool decrypt)
+{
+ /* get OpenSSL EVP cipher for key wrap */
+ const char *cipher_name = NULL;
+ ARRAY_LOOKUP_BY_ID(ecdh_wrap_alg_map, alg, name, wrap_alg, cipher_name);
+ if (!cipher_name) {
+ RNP_LOG("Unsupported key wrap algorithm: %d", (int) wrap_alg);
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_name);
+ if (!cipher) {
+ RNP_LOG("Cipher %s is not supported by OpenSSL.", cipher_name);
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ *ctx = EVP_CIPHER_CTX_new();
+ if (!ctx) {
+ RNP_LOG("Context allocation failed : %lu", ERR_peek_last_error());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ EVP_CIPHER_CTX_set_flags(*ctx, EVP_CIPHER_CTX_FLAG_WRAP_ALLOW);
+ int res = decrypt ? EVP_DecryptInit_ex(*ctx, cipher, NULL, key, NULL) :
+ EVP_EncryptInit_ex(*ctx, cipher, NULL, key, NULL);
+ if (res <= 0) {
+ RNP_LOG("Failed to initialize cipher : %lu", ERR_peek_last_error());
+ EVP_CIPHER_CTX_free(*ctx);
+ *ctx = NULL;
+ return RNP_ERROR_GENERIC;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+ecdh_rfc3394_wrap(uint8_t * out,
+ size_t * out_len,
+ const uint8_t *const in,
+ size_t in_len,
+ const uint8_t * key,
+ pgp_symm_alg_t wrap_alg)
+{
+ EVP_CIPHER_CTX *ctx = NULL;
+ rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, false);
+ if (ret) {
+ RNP_LOG("Wrap context initialization failed.");
+ return ret;
+ }
+ int intlen = *out_len;
+ /* encrypts in one pass, no final is needed */
+ int res = EVP_EncryptUpdate(ctx, out, &intlen, in, in_len);
+ if (res <= 0) {
+ RNP_LOG("Failed to encrypt data : %lu", ERR_peek_last_error());
+ } else {
+ *out_len = intlen;
+ }
+ EVP_CIPHER_CTX_free(ctx);
+ return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC;
+}
+
+static rnp_result_t
+ecdh_rfc3394_unwrap(uint8_t * out,
+ size_t * out_len,
+ const uint8_t *const in,
+ size_t in_len,
+ const uint8_t * key,
+ pgp_symm_alg_t wrap_alg)
+{
+ if ((in_len < 16) || (in_len % 8)) {
+ RNP_LOG("Invalid wrapped key size.");
+ return RNP_ERROR_GENERIC;
+ }
+ EVP_CIPHER_CTX *ctx = NULL;
+ rnp_result_t ret = ecdh_rfc3394_wrap_ctx(&ctx, wrap_alg, key, true);
+ if (ret) {
+ RNP_LOG("Unwrap context initialization failed.");
+ return ret;
+ }
+ int intlen = *out_len;
+ /* decrypts in one pass, no final is needed */
+ int res = EVP_DecryptUpdate(ctx, out, &intlen, in, in_len);
+ if (res <= 0) {
+ RNP_LOG("Failed to decrypt data : %lu", ERR_peek_last_error());
+ } else {
+ *out_len = intlen;
+ }
+ EVP_CIPHER_CTX_free(ctx);
+ return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC;
+}
+
+static bool
+ecdh_derive_secret(EVP_PKEY *sec, EVP_PKEY *peer, uint8_t *x, size_t *xlen)
+{
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(sec, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ return false;
+ }
+ bool res = false;
+ if (EVP_PKEY_derive_init(ctx) <= 0) {
+ RNP_LOG("Key derivation init failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_derive_set_peer(ctx, peer) <= 0) {
+ RNP_LOG("Peer setting failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_derive(ctx, x, xlen) <= 0) {
+ RNP_LOG("Failed to obtain shared secret size: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ res = true;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ return res;
+}
+
+static size_t
+ecdh_kek_len(pgp_symm_alg_t wrap_alg)
+{
+ switch (wrap_alg) {
+ case PGP_SA_AES_128:
+ case PGP_SA_AES_192:
+ case PGP_SA_AES_256:
+ return pgp_key_size(wrap_alg);
+ default:
+ return 0;
+ }
+}
+
+rnp_result_t
+ecdh_encrypt_pkcs5(rnp::RNG * rng,
+ pgp_ecdh_encrypted_t * out,
+ const uint8_t *const in,
+ size_t in_len,
+ const pgp_ec_key_t * key,
+ const pgp_fingerprint_t &fingerprint)
+{
+ if (!key || !out || !in || (in_len > MAX_SESSION_KEY_SIZE)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+#if !defined(ENABLE_SM2)
+ if (key->curve == PGP_CURVE_SM2_P_256) {
+ RNP_LOG("SM2 curve support is disabled.");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+ }
+#endif
+ /* check whether we have valid wrap_alg before doing heavy operations */
+ size_t keklen = ecdh_kek_len(key->key_wrap_alg);
+ if (!keklen) {
+ RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg);
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ /* load our public key */
+ EVP_PKEY *pkey = ec_load_key(key->p, NULL, key->curve);
+ if (!pkey) {
+ RNP_LOG("Failed to load public key.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec;
+ rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek;
+ rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad;
+
+ size_t seclen = sec.size();
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ /* generate ephemeral key */
+ EVP_PKEY *ephkey = ec_generate_pkey(PGP_PKA_ECDH, key->curve);
+ if (!ephkey) {
+ RNP_LOG("Failed to generate ephemeral key.");
+ ret = RNP_ERROR_KEY_GENERATION;
+ goto done;
+ }
+ /* do ECDH derivation */
+ if (!ecdh_derive_secret(ephkey, pkey, sec.data(), &seclen)) {
+ RNP_LOG("ECDH derivation failed.");
+ goto done;
+ }
+ /* here we got x value in sec, deriving kek */
+ ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen);
+ if (ret) {
+ RNP_LOG("Failed to derive KEK.");
+ goto done;
+ }
+ /* add PKCS#7 padding */
+ size_t m_padded_len;
+ m_padded_len = ((in_len / 8) + 1) * 8;
+ memcpy(mpad.data(), in, in_len);
+ if (!pad_pkcs7(mpad.data(), m_padded_len, in_len)) {
+ RNP_LOG("Failed to add PKCS #7 padding.");
+ goto done;
+ }
+ /* do RFC 3394 AES key wrap */
+ static_assert(sizeof(out->m) == ECDH_WRAPPED_KEY_SIZE, "Wrong ECDH wrapped key size.");
+ out->mlen = ECDH_WRAPPED_KEY_SIZE;
+ ret = ecdh_rfc3394_wrap(
+ out->m, &out->mlen, mpad.data(), m_padded_len, kek.data(), key->key_wrap_alg);
+ if (ret) {
+ RNP_LOG("Failed to wrap key.");
+ goto done;
+ }
+ /* write ephemeral public key */
+ if (!ec_write_pubkey(ephkey, out->p, key->curve)) {
+ RNP_LOG("Failed to write ec key.");
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_free(ephkey);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
+
+rnp_result_t
+ecdh_decrypt_pkcs5(uint8_t * out,
+ size_t * out_len,
+ const pgp_ecdh_encrypted_t *in,
+ const pgp_ec_key_t * key,
+ const pgp_fingerprint_t & fingerprint)
+{
+ if (!out || !out_len || !in || !key || !mpi_bytes(&key->x)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* check whether we have valid wrap_alg before doing heavy operations */
+ size_t keklen = ecdh_kek_len(key->key_wrap_alg);
+ if (!keklen) {
+ RNP_LOG("Unsupported key wrap algorithm: %d", (int) key->key_wrap_alg);
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ /* load ephemeral public key */
+ EVP_PKEY *ephkey = ec_load_key(in->p, NULL, key->curve);
+ if (!ephkey) {
+ RNP_LOG("Failed to load ephemeral public key.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* load our secret key */
+ rnp::secure_array<uint8_t, MAX_CURVE_BYTELEN + 1> sec;
+ rnp::secure_array<uint8_t, MAX_AES_KEY_SIZE> kek;
+ rnp::secure_array<uint8_t, MAX_SESSION_KEY_SIZE> mpad;
+
+ size_t seclen = sec.size();
+ size_t mpadlen = mpad.size();
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ EVP_PKEY * pkey = ec_load_key(key->p, &key->x, key->curve);
+ if (!pkey) {
+ RNP_LOG("Failed to load secret key.");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto done;
+ }
+ /* do ECDH derivation */
+ if (!ecdh_derive_secret(pkey, ephkey, sec.data(), &seclen)) {
+ RNP_LOG("ECDH derivation failed.");
+ goto done;
+ }
+ /* here we got x value in sec, deriving kek */
+ ret = ecdh_derive_kek(sec.data(), seclen, *key, fingerprint, kek.data(), keklen);
+ if (ret) {
+ RNP_LOG("Failed to derive KEK.");
+ goto done;
+ }
+ /* do RFC 3394 AES key unwrap */
+ ret = ecdh_rfc3394_unwrap(
+ mpad.data(), &mpadlen, in->m, in->mlen, kek.data(), key->key_wrap_alg);
+ if (ret) {
+ RNP_LOG("Failed to unwrap key.");
+ goto done;
+ }
+ /* remove PKCS#7 padding */
+ if (!unpad_pkcs7(mpad.data(), mpadlen, &mpadlen)) {
+ RNP_LOG("Failed to unpad key.");
+ goto done;
+ }
+ assert(mpadlen <= *out_len);
+ *out_len = mpadlen;
+ memcpy(out, mpad.data(), mpadlen);
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_free(ephkey);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
diff --git a/src/lib/crypto/ecdh_utils.cpp b/src/lib/crypto/ecdh_utils.cpp
new file mode 100644
index 0000000..3ceb153
--- /dev/null
+++ b/src/lib/crypto/ecdh_utils.cpp
@@ -0,0 +1,166 @@
+/*
+ * Copyright (c) 2021, [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 "ecdh_utils.h"
+#include "types.h"
+#include "utils.h"
+#include <cassert>
+
+/* Used by ECDH keys. Specifies which hash and wrapping algorithm
+ * to be used (see point 15. of RFC 4880).
+ *
+ * Note: sync with ec_curves.
+ */
+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[] = {
+ {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,
+ 0x65, 0x72, 0x20, 0x20, 0x20, 0x20};
+
+// returns size of data written to other_info
+size_t
+kdf_other_info_serialize(uint8_t other_info[MAX_SP800_56A_OTHER_INFO],
+ const ec_curve_desc_t * ec_curve,
+ const pgp_fingerprint_t &fingerprint,
+ const pgp_hash_alg_t kdf_hash,
+ const pgp_symm_alg_t wrap_alg)
+{
+ assert(fingerprint.length >= 20);
+ uint8_t *buf_ptr = &other_info[0];
+
+ /* KDF-OtherInfo: AlgorithmID
+ * Current implementation will always use SHA-512 and AES-256 for KEK wrapping
+ */
+ *(buf_ptr++) = ec_curve->OIDhex_len;
+ memcpy(buf_ptr, ec_curve->OIDhex, ec_curve->OIDhex_len);
+ buf_ptr += ec_curve->OIDhex_len;
+ *(buf_ptr++) = PGP_PKA_ECDH;
+ // size of following 3 params (each 1 byte)
+ *(buf_ptr++) = 0x03;
+ // Value reserved for future use
+ *(buf_ptr++) = 0x01;
+ // Hash used with KDF
+ *(buf_ptr++) = kdf_hash;
+ // Algorithm ID used for key wrapping
+ *(buf_ptr++) = wrap_alg;
+
+ /* KDF-OtherInfo: PartyUInfo
+ * 20 bytes representing "Anonymous Sender "
+ */
+ memcpy(buf_ptr, ANONYMOUS_SENDER, sizeof(ANONYMOUS_SENDER));
+ buf_ptr += sizeof(ANONYMOUS_SENDER);
+
+ // keep 20, as per spec
+ memcpy(buf_ptr, fingerprint.fingerprint, 20);
+ return (buf_ptr - other_info) + 20 /*anonymous_sender*/;
+}
+
+bool
+pad_pkcs7(uint8_t *buf, size_t buf_len, size_t offset)
+{
+ if (buf_len <= offset) {
+ // Must have at least 1 byte of padding
+ return false;
+ }
+
+ const uint8_t pad_byte = buf_len - offset;
+ memset(buf + offset, pad_byte, pad_byte);
+ return true;
+}
+
+bool
+unpad_pkcs7(uint8_t *buf, size_t buf_len, size_t *offset)
+{
+ if (!buf || !offset || !buf_len) {
+ return false;
+ }
+
+ uint8_t err = 0;
+ const uint8_t pad_byte = buf[buf_len - 1];
+ const uint32_t pad_begin = buf_len - pad_byte;
+
+ // TODO: Still >, <, and <=,== are not constant time (maybe?)
+ err |= (pad_byte > buf_len);
+ err |= (pad_byte == 0);
+
+ /* Check if padding is OK */
+ for (size_t c = 0; c < buf_len; c++) {
+ err |= (buf[c] ^ pad_byte) * (pad_begin <= c);
+ }
+
+ *offset = pad_begin;
+ return (err == 0);
+}
+
+bool
+ecdh_set_params(pgp_ec_key_t *key, pgp_curve_t curve_id)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(ecdh_params); i++) {
+ if (ecdh_params[i].curve == curve_id) {
+ key->kdf_hash_alg = ecdh_params[i].hash;
+ key->key_wrap_alg = ecdh_params[i].wrap_alg;
+ return true;
+ }
+ }
+
+ return false;
+}
+
+bool
+x25519_tweak_bits(pgp_ec_key_t &key)
+{
+ if (key.x.len != 32) {
+ return false;
+ }
+ /* MPI is big-endian, while raw x25519 key is little-endian */
+ key.x.mpi[31] &= 248; // zero 3 low bits
+ key.x.mpi[0] &= 127; // zero high bit
+ key.x.mpi[0] |= 64; // set high - 1 bit
+ return true;
+}
+
+bool
+x25519_bits_tweaked(const pgp_ec_key_t &key)
+{
+ if (key.x.len != 32) {
+ return false;
+ }
+ return !(key.x.mpi[31] & 7) && (key.x.mpi[0] < 128) && (key.x.mpi[0] >= 64);
+}
diff --git a/src/lib/crypto/ecdh_utils.h b/src/lib/crypto/ecdh_utils.h
new file mode 100644
index 0000000..2d37a71
--- /dev/null
+++ b/src/lib/crypto/ecdh_utils.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2021, [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 ECDH_UTILS_H_
+#define ECDH_UTILS_H_
+
+#include "ecdh.h"
+
+#define MAX_SP800_56A_OTHER_INFO 56
+// Keys up to 312 bits (+1 bytes of PKCS5 padding)
+#define MAX_SESSION_KEY_SIZE 40
+#define MAX_AES_KEY_SIZE 32
+
+size_t kdf_other_info_serialize(uint8_t other_info[MAX_SP800_56A_OTHER_INFO],
+ const ec_curve_desc_t * ec_curve,
+ const pgp_fingerprint_t &fingerprint,
+ const pgp_hash_alg_t kdf_hash,
+ const pgp_symm_alg_t wrap_alg);
+
+bool pad_pkcs7(uint8_t *buf, size_t buf_len, size_t offset);
+
+bool unpad_pkcs7(uint8_t *buf, size_t buf_len, size_t *offset);
+
+#endif // ECDH_UTILS_H_
diff --git a/src/lib/crypto/ecdsa.cpp b/src/lib/crypto/ecdsa.cpp
new file mode 100644
index 0000000..cffce12
--- /dev/null
+++ b/src/lib/crypto/ecdsa.cpp
@@ -0,0 +1,267 @@
+/*
+ * Copyright (c) 2017, [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 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 "ecdsa.h"
+#include "utils.h"
+#include <botan/ffi.h>
+#include <string.h>
+#include "bn.h"
+
+static bool
+ecdsa_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata)
+{
+ botan_mp_t px = NULL;
+ botan_mp_t py = NULL;
+ bool res = false;
+
+ const ec_curve_desc_t *curve = get_curve_desc(keydata->curve);
+ if (!curve) {
+ RNP_LOG("unknown curve");
+ return false;
+ }
+ const size_t curve_order = BITS_TO_BYTES(curve->bitlen);
+
+ if (!mpi_bytes(&keydata->p) || (keydata->p.mpi[0] != 0x04)) {
+ RNP_LOG(
+ "Failed to load public key: %zu, %02x", mpi_bytes(&keydata->p), keydata->p.mpi[0]);
+ return false;
+ }
+
+ if (botan_mp_init(&px) || botan_mp_init(&py) ||
+ botan_mp_from_bin(px, &keydata->p.mpi[1], curve_order) ||
+ botan_mp_from_bin(py, &keydata->p.mpi[1 + curve_order], curve_order)) {
+ goto end;
+ }
+
+ if (!(res = !botan_pubkey_load_ecdsa(pubkey, px, py, curve->botan_name))) {
+ RNP_LOG("failed to load ecdsa public key");
+ }
+end:
+ botan_mp_destroy(px);
+ botan_mp_destroy(py);
+ return res;
+}
+
+static bool
+ecdsa_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata)
+{
+ const ec_curve_desc_t *curve;
+ bignum_t * x = NULL;
+ bool res = false;
+
+ if (!(curve = get_curve_desc(keydata->curve))) {
+ return false;
+ }
+ if (!(x = mpi2bn(&keydata->x))) {
+ return false;
+ }
+ if (!(res = !botan_privkey_load_ecdsa(seckey, BN_HANDLE_PTR(x), curve->botan_name))) {
+ RNP_LOG("Can't load private key");
+ }
+ bn_free(x);
+ return res;
+}
+
+rnp_result_t
+ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ botan_pubkey_t bpkey = NULL;
+ botan_privkey_t bskey = NULL;
+ rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS;
+
+ if (!ecdsa_load_public_key(&bpkey, key) ||
+ botan_pubkey_check_key(bpkey, rng->handle(), 0)) {
+ goto done;
+ }
+ if (!secret) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ if (!ecdsa_load_secret_key(&bskey, key) ||
+ botan_privkey_check_key(bskey, rng->handle(), 0)) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ botan_privkey_destroy(bskey);
+ botan_pubkey_destroy(bpkey);
+ return ret;
+}
+
+static const char *
+ecdsa_padding_str_for(pgp_hash_alg_t hash_alg)
+{
+ switch (hash_alg) {
+ case PGP_HASH_MD5:
+ return "Raw(MD5)";
+ case PGP_HASH_SHA1:
+ return "Raw(SHA-1)";
+ case PGP_HASH_RIPEMD:
+ return "Raw(RIPEMD-160)";
+
+ case PGP_HASH_SHA256:
+ return "Raw(SHA-256)";
+ case PGP_HASH_SHA384:
+ return "Raw(SHA-384)";
+ case PGP_HASH_SHA512:
+ return "Raw(SHA-512)";
+ case PGP_HASH_SHA224:
+ return "Raw(SHA-224)";
+ case PGP_HASH_SHA3_256:
+ return "Raw(SHA3(256))";
+ case PGP_HASH_SHA3_512:
+ return "Raw(SHA3(512))";
+
+ case PGP_HASH_SM3:
+ return "Raw(SM3)";
+ default:
+ return "Raw";
+ }
+}
+
+rnp_result_t
+ecdsa_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key)
+{
+ botan_pk_op_sign_t signer = NULL;
+ botan_privkey_t b_key = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ uint8_t out_buf[2 * MAX_CURVE_BYTELEN] = {0};
+ const ec_curve_desc_t *curve = get_curve_desc(key->curve);
+ const char * padding_str = ecdsa_padding_str_for(hash_alg);
+
+ if (!curve) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ const size_t curve_order = BITS_TO_BYTES(curve->bitlen);
+ size_t sig_len = 2 * curve_order;
+
+ if (!ecdsa_load_secret_key(&b_key, key)) {
+ RNP_LOG("Can't load private key");
+ goto end;
+ }
+
+ if (botan_pk_op_sign_create(&signer, b_key, padding_str, 0)) {
+ goto end;
+ }
+
+ if (botan_pk_op_sign_update(signer, hash, hash_len)) {
+ goto end;
+ }
+
+ if (botan_pk_op_sign_finish(signer, rng->handle(), out_buf, &sig_len)) {
+ RNP_LOG("Signing failed");
+ goto end;
+ }
+
+ // Allocate memory and copy results
+ if (mem2mpi(&sig->r, out_buf, curve_order) &&
+ mem2mpi(&sig->s, out_buf + curve_order, curve_order)) {
+ ret = RNP_SUCCESS;
+ }
+end:
+ botan_privkey_destroy(b_key);
+ botan_pk_op_sign_destroy(signer);
+ return ret;
+}
+
+rnp_result_t
+ecdsa_verify(const pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key)
+{
+ botan_pubkey_t pub = NULL;
+ botan_pk_op_verify_t verifier = NULL;
+ rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID;
+ uint8_t sign_buf[2 * MAX_CURVE_BYTELEN] = {0};
+ size_t r_blen, s_blen;
+ const char * padding_str = ecdsa_padding_str_for(hash_alg);
+
+ const ec_curve_desc_t *curve = get_curve_desc(key->curve);
+ if (!curve) {
+ RNP_LOG("unknown curve");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ const size_t curve_order = BITS_TO_BYTES(curve->bitlen);
+
+ if (!ecdsa_load_public_key(&pub, key)) {
+ goto end;
+ }
+
+ if (botan_pk_op_verify_create(&verifier, pub, padding_str, 0)) {
+ goto end;
+ }
+
+ if (botan_pk_op_verify_update(verifier, hash, hash_len)) {
+ goto end;
+ }
+
+ r_blen = mpi_bytes(&sig->r);
+ s_blen = mpi_bytes(&sig->s);
+ if ((r_blen > curve_order) || (s_blen > curve_order) ||
+ (curve_order > MAX_CURVE_BYTELEN)) {
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto end;
+ }
+
+ // Both can't fail
+ mpi2mem(&sig->r, &sign_buf[curve_order - r_blen]);
+ mpi2mem(&sig->s, &sign_buf[curve_order + curve_order - s_blen]);
+
+ if (!botan_pk_op_verify_finish(verifier, sign_buf, curve_order * 2)) {
+ ret = RNP_SUCCESS;
+ }
+end:
+ botan_pubkey_destroy(pub);
+ botan_pk_op_verify_destroy(verifier);
+ return ret;
+}
+
+pgp_hash_alg_t
+ecdsa_get_min_hash(pgp_curve_t curve)
+{
+ switch (curve) {
+ case PGP_CURVE_NIST_P_256:
+ case PGP_CURVE_BP256:
+ case PGP_CURVE_P256K1:
+ return PGP_HASH_SHA256;
+ case PGP_CURVE_NIST_P_384:
+ case PGP_CURVE_BP384:
+ return PGP_HASH_SHA384;
+ case PGP_CURVE_NIST_P_521:
+ case PGP_CURVE_BP512:
+ return PGP_HASH_SHA512;
+ default:
+ return PGP_HASH_UNKNOWN;
+ }
+}
diff --git a/src/lib/crypto/ecdsa.h b/src/lib/crypto/ecdsa.h
new file mode 100644
index 0000000..aebfe6a
--- /dev/null
+++ b/src/lib/crypto/ecdsa.h
@@ -0,0 +1,57 @@
+/*-
+ * Copyright (c) 2017 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 ECDSA_H_
+#define ECDSA_H_
+
+#include "crypto/ec.h"
+
+rnp_result_t ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret);
+
+rnp_result_t ecdsa_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key);
+
+rnp_result_t ecdsa_verify(const pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key);
+
+/*
+ * @brief Returns hash which should be used with the curve
+ *
+ * @param curve Curve ID
+ *
+ * @returns Either ID of the hash algorithm, or PGP_HASH_UNKNOWN
+ * if not found
+ */
+pgp_hash_alg_t ecdsa_get_min_hash(pgp_curve_t curve);
+
+#endif // ECDSA_H_
diff --git a/src/lib/crypto/ecdsa_ossl.cpp b/src/lib/crypto/ecdsa_ossl.cpp
new file mode 100644
index 0000000..534811a
--- /dev/null
+++ b/src/lib/crypto/ecdsa_ossl.cpp
@@ -0,0 +1,190 @@
+/*
+ * Copyright (c) 2021, [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 "ecdsa.h"
+#include "utils.h"
+#include <string.h>
+#include "bn.h"
+#include "ec_ossl.h"
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include <openssl/ec.h>
+
+static bool
+ecdsa_decode_sig(const uint8_t *data, size_t len, pgp_ec_signature_t &sig)
+{
+ ECDSA_SIG *esig = d2i_ECDSA_SIG(NULL, &data, len);
+ if (!esig) {
+ RNP_LOG("Failed to parse ECDSA sig: %lu", ERR_peek_last_error());
+ return false;
+ }
+ const BIGNUM *r, *s;
+ ECDSA_SIG_get0(esig, &r, &s);
+ bn2mpi(r, &sig.r);
+ bn2mpi(s, &sig.s);
+ ECDSA_SIG_free(esig);
+ return true;
+}
+
+static bool
+ecdsa_encode_sig(uint8_t *data, size_t *len, const pgp_ec_signature_t &sig)
+{
+ bool res = false;
+ ECDSA_SIG *dsig = ECDSA_SIG_new();
+ BIGNUM * r = mpi2bn(&sig.r);
+ BIGNUM * s = mpi2bn(&sig.s);
+ if (!dsig || !r || !s) {
+ RNP_LOG("Allocation failed.");
+ goto done;
+ }
+ ECDSA_SIG_set0(dsig, r, s);
+ r = NULL;
+ s = NULL;
+ int outlen;
+ outlen = i2d_ECDSA_SIG(dsig, &data);
+ if (outlen < 0) {
+ RNP_LOG("Failed to encode signature.");
+ goto done;
+ }
+ *len = outlen;
+ res = true;
+done:
+ ECDSA_SIG_free(dsig);
+ BN_free(r);
+ BN_free(s);
+ return res;
+}
+
+rnp_result_t
+ecdsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ return ec_validate_key(*key, secret);
+}
+
+rnp_result_t
+ecdsa_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key)
+{
+ if (mpi_bytes(&key->x) == 0) {
+ RNP_LOG("private key not set");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* Load secret key to DSA structure*/
+ EVP_PKEY *evpkey = ec_load_key(key->p, &key->x, key->curve);
+ if (!evpkey) {
+ RNP_LOG("Failed to load key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ /* init context and sign */
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_sign_init(ctx) <= 0) {
+ RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ sig->s.len = PGP_MPINT_SIZE;
+ if (EVP_PKEY_sign(ctx, sig->s.mpi, &sig->s.len, hash, hash_len) <= 0) {
+ RNP_LOG("Signing failed: %lu", ERR_peek_last_error());
+ sig->s.len = 0;
+ goto done;
+ }
+ if (!ecdsa_decode_sig(&sig->s.mpi[0], sig->s.len, *sig)) {
+ RNP_LOG("Failed to parse ECDSA sig: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(evpkey);
+ return ret;
+}
+
+rnp_result_t
+ecdsa_verify(const pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key)
+{
+ /* Load secret key to DSA structure*/
+ EVP_PKEY *evpkey = ec_load_key(key->p, NULL, key->curve);
+ if (!evpkey) {
+ RNP_LOG("Failed to load key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID;
+ /* init context and sign */
+ EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new(evpkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_verify_init(ctx) <= 0) {
+ RNP_LOG("Failed to initialize verify: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ pgp_mpi_t sigbuf;
+ if (!ecdsa_encode_sig(sigbuf.mpi, &sigbuf.len, *sig)) {
+ goto done;
+ }
+ if (EVP_PKEY_verify(ctx, sigbuf.mpi, sigbuf.len, hash, hash_len) > 0) {
+ ret = RNP_SUCCESS;
+ }
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(evpkey);
+ return ret;
+}
+
+pgp_hash_alg_t
+ecdsa_get_min_hash(pgp_curve_t curve)
+{
+ switch (curve) {
+ case PGP_CURVE_NIST_P_256:
+ case PGP_CURVE_BP256:
+ case PGP_CURVE_P256K1:
+ return PGP_HASH_SHA256;
+ case PGP_CURVE_NIST_P_384:
+ case PGP_CURVE_BP384:
+ return PGP_HASH_SHA384;
+ case PGP_CURVE_NIST_P_521:
+ case PGP_CURVE_BP512:
+ return PGP_HASH_SHA512;
+ default:
+ return PGP_HASH_UNKNOWN;
+ }
+}
diff --git a/src/lib/crypto/eddsa.cpp b/src/lib/crypto/eddsa.cpp
new file mode 100644
index 0000000..8669180
--- /dev/null
+++ b/src/lib/crypto/eddsa.cpp
@@ -0,0 +1,212 @@
+/*
+ * Copyright (c) 2017, [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 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 <string.h>
+#include <botan/ffi.h>
+#include "eddsa.h"
+#include "utils.h"
+
+static bool
+eddsa_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata)
+{
+ if (keydata->curve != PGP_CURVE_ED25519) {
+ return false;
+ }
+ /*
+ * See draft-ietf-openpgp-rfc4880bis-01 section 13.3
+ */
+ if ((mpi_bytes(&keydata->p) != 33) || (keydata->p.mpi[0] != 0x40)) {
+ return false;
+ }
+ if (botan_pubkey_load_ed25519(pubkey, keydata->p.mpi + 1)) {
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+eddsa_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata)
+{
+ uint8_t keybuf[32] = {0};
+ size_t sz;
+
+ if (keydata->curve != PGP_CURVE_ED25519) {
+ return false;
+ }
+ sz = mpi_bytes(&keydata->x);
+ if (!sz || (sz > 32)) {
+ return false;
+ }
+ mpi2mem(&keydata->x, keybuf + 32 - sz);
+ if (botan_privkey_load_ed25519(seckey, keybuf)) {
+ return false;
+ }
+
+ return true;
+}
+
+rnp_result_t
+eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ botan_pubkey_t bpkey = NULL;
+ botan_privkey_t bskey = NULL;
+ rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS;
+
+ if (!eddsa_load_public_key(&bpkey, key) ||
+ botan_pubkey_check_key(bpkey, rng->handle(), 0)) {
+ goto done;
+ }
+
+ if (!secret) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ if (!eddsa_load_secret_key(&bskey, key) ||
+ botan_privkey_check_key(bskey, rng->handle(), 0)) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ botan_privkey_destroy(bskey);
+ botan_pubkey_destroy(bpkey);
+ return ret;
+}
+
+rnp_result_t
+eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key)
+{
+ botan_privkey_t eddsa = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ uint8_t key_bits[64];
+
+ if (botan_privkey_create(&eddsa, "Ed25519", NULL, rng->handle()) != 0) {
+ goto end;
+ }
+
+ if (botan_privkey_ed25519_get_privkey(eddsa, key_bits)) {
+ goto end;
+ }
+
+ // First 32 bytes of key_bits are the EdDSA seed (private key)
+ // Second 32 bytes are the EdDSA public key
+
+ mem2mpi(&key->x, key_bits, 32);
+ // insert the required 0x40 prefix on the public key
+ key_bits[31] = 0x40;
+ mem2mpi(&key->p, key_bits + 31, 33);
+ key->curve = PGP_CURVE_ED25519;
+
+ ret = RNP_SUCCESS;
+end:
+ botan_privkey_destroy(eddsa);
+ return ret;
+}
+
+rnp_result_t
+eddsa_verify(const pgp_ec_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key)
+{
+ botan_pubkey_t eddsa = NULL;
+ botan_pk_op_verify_t verify_op = NULL;
+ rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID;
+ uint8_t bn_buf[64] = {0};
+
+ if (!eddsa_load_public_key(&eddsa, key)) {
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto done;
+ }
+
+ if (botan_pk_op_verify_create(&verify_op, eddsa, "Pure", 0) != 0) {
+ goto done;
+ }
+
+ if (botan_pk_op_verify_update(verify_op, hash, hash_len) != 0) {
+ goto done;
+ }
+
+ // Unexpected size for Ed25519 signature
+ if ((mpi_bytes(&sig->r) > 32) || (mpi_bytes(&sig->s) > 32)) {
+ goto done;
+ }
+ mpi2mem(&sig->r, &bn_buf[32 - mpi_bytes(&sig->r)]);
+ mpi2mem(&sig->s, &bn_buf[64 - mpi_bytes(&sig->s)]);
+
+ if (botan_pk_op_verify_finish(verify_op, bn_buf, 64) == 0) {
+ ret = RNP_SUCCESS;
+ }
+done:
+ botan_pk_op_verify_destroy(verify_op);
+ botan_pubkey_destroy(eddsa);
+ return ret;
+}
+
+rnp_result_t
+eddsa_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key)
+{
+ botan_privkey_t eddsa = NULL;
+ botan_pk_op_sign_t sign_op = NULL;
+ rnp_result_t ret = RNP_ERROR_SIGNING_FAILED;
+ uint8_t bn_buf[64] = {0};
+ size_t sig_size = sizeof(bn_buf);
+
+ if (!eddsa_load_secret_key(&eddsa, key)) {
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto done;
+ }
+
+ if (botan_pk_op_sign_create(&sign_op, eddsa, "Pure", 0) != 0) {
+ goto done;
+ }
+
+ if (botan_pk_op_sign_update(sign_op, hash, hash_len) != 0) {
+ goto done;
+ }
+
+ if (botan_pk_op_sign_finish(sign_op, rng->handle(), bn_buf, &sig_size) != 0) {
+ goto done;
+ }
+
+ // Unexpected size...
+ if (sig_size != 64) {
+ goto done;
+ }
+
+ mem2mpi(&sig->r, bn_buf, 32);
+ mem2mpi(&sig->s, bn_buf + 32, 32);
+ ret = RNP_SUCCESS;
+done:
+ botan_pk_op_sign_destroy(sign_op);
+ botan_privkey_destroy(eddsa);
+ return ret;
+}
diff --git a/src/lib/crypto/eddsa.h b/src/lib/crypto/eddsa.h
new file mode 100644
index 0000000..7410a28
--- /dev/null
+++ b/src/lib/crypto/eddsa.h
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017, [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).
+ *
+ * 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 RNP_ED25519_H_
+#define RNP_ED25519_H_
+
+#include "ec.h"
+
+rnp_result_t eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret);
+/*
+ * curve_len must be 255 currently (for Ed25519)
+ * If Ed448 was supported in the future curve_len=448 would also be allowed.
+ */
+rnp_result_t eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key);
+
+rnp_result_t eddsa_verify(const pgp_ec_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key);
+
+rnp_result_t eddsa_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key);
+
+#endif
diff --git a/src/lib/crypto/eddsa_ossl.cpp b/src/lib/crypto/eddsa_ossl.cpp
new file mode 100644
index 0000000..16d8fad
--- /dev/null
+++ b/src/lib/crypto/eddsa_ossl.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2021, [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 <string>
+#include <cassert>
+#include "eddsa.h"
+#include "ec.h"
+#include "ec_ossl.h"
+#include "utils.h"
+#include "bn.h"
+#include <openssl/evp.h>
+#include <openssl/objects.h>
+#include <openssl/err.h>
+#include <openssl/ec.h>
+
+rnp_result_t
+eddsa_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ /* Not implemented in the OpenSSL, so just do basic size checks. */
+ if ((mpi_bytes(&key->p) != 33) || (key->p.mpi[0] != 0x40)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (secret && mpi_bytes(&key->x) > 32) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+eddsa_generate(rnp::RNG *rng, pgp_ec_key_t *key)
+{
+ rnp_result_t ret = ec_generate(rng, key, PGP_PKA_EDDSA, PGP_CURVE_ED25519);
+ if (!ret) {
+ key->curve = PGP_CURVE_ED25519;
+ }
+ return ret;
+}
+
+rnp_result_t
+eddsa_verify(const pgp_ec_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key)
+{
+ if ((mpi_bytes(&sig->r) > 32) || (mpi_bytes(&sig->s) > 32)) {
+ RNP_LOG("Invalid EdDSA signature.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if ((mpi_bytes(&key->p) != 33) || (key->p.mpi[0] != 0x40)) {
+ RNP_LOG("Invalid EdDSA public key.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ EVP_PKEY *evpkey = ec_load_key(key->p, NULL, PGP_CURVE_ED25519);
+ if (!evpkey) {
+ RNP_LOG("Failed to load key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID;
+ uint8_t sigbuf[64] = {0};
+ /* init context and sign */
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_MD_CTX * md = EVP_MD_CTX_new();
+ if (!md) {
+ RNP_LOG("Failed to allocate MD ctx: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_DigestVerifyInit(md, &ctx, NULL, NULL, evpkey) <= 0) {
+ RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ mpi2mem(&sig->r, &sigbuf[32 - mpi_bytes(&sig->r)]);
+ mpi2mem(&sig->s, &sigbuf[64 - mpi_bytes(&sig->s)]);
+
+ if (EVP_DigestVerify(md, sigbuf, 64, hash, hash_len) > 0) {
+ ret = RNP_SUCCESS;
+ }
+done:
+ /* line below will also free ctx */
+ EVP_MD_CTX_free(md);
+ EVP_PKEY_free(evpkey);
+ return ret;
+}
+
+rnp_result_t
+eddsa_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key)
+{
+ if (!mpi_bytes(&key->x)) {
+ RNP_LOG("private key not set");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ EVP_PKEY *evpkey = ec_load_key(key->p, &key->x, PGP_CURVE_ED25519);
+ if (!evpkey) {
+ RNP_LOG("Failed to load private key: %lu", ERR_peek_last_error());
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ /* init context and sign */
+ EVP_PKEY_CTX *ctx = NULL;
+ EVP_MD_CTX * md = EVP_MD_CTX_new();
+ if (!md) {
+ RNP_LOG("Failed to allocate MD ctx: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_DigestSignInit(md, &ctx, NULL, NULL, evpkey) <= 0) {
+ RNP_LOG("Failed to initialize signing: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ static_assert((sizeof(sig->r.mpi) == PGP_MPINT_SIZE) && (PGP_MPINT_SIZE >= 64),
+ "invalid mpi type/size");
+ sig->r.len = PGP_MPINT_SIZE;
+ if (EVP_DigestSign(md, sig->r.mpi, &sig->r.len, hash, hash_len) <= 0) {
+ RNP_LOG("Signing failed: %lu", ERR_peek_last_error());
+ sig->r.len = 0;
+ goto done;
+ }
+ assert(sig->r.len == 64);
+ sig->r.len = 32;
+ sig->s.len = 32;
+ memcpy(sig->s.mpi, &sig->r.mpi[32], 32);
+ ret = RNP_SUCCESS;
+done:
+ /* line below will also free ctx */
+ EVP_MD_CTX_free(md);
+ EVP_PKEY_free(evpkey);
+ return ret;
+}
diff --git a/src/lib/crypto/elgamal.cpp b/src/lib/crypto/elgamal.cpp
new file mode 100644
index 0000000..acebf4d
--- /dev/null
+++ b/src/lib/crypto/elgamal.cpp
@@ -0,0 +1,302 @@
+/*-
+ * 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.
+ */
+
+#include <stdlib.h>
+#include <string.h>
+#include <botan/ffi.h>
+#include <botan/bigint.h>
+#include <botan/numthry.h>
+#include <botan/reducer.h>
+#include <rnp/rnp_def.h>
+#include "elgamal.h"
+#include "utils.h"
+#include "bn.h"
+
+// Max supported key byte size
+#define ELGAMAL_MAX_P_BYTELEN BITS_TO_BYTES(PGP_MPINT_BITS)
+
+static bool
+elgamal_load_public_key(botan_pubkey_t *pubkey, const pgp_eg_key_t *keydata)
+{
+ bignum_t *p = NULL;
+ bignum_t *g = NULL;
+ bignum_t *y = NULL;
+ bool res = false;
+
+ // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN.
+ if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) {
+ goto done;
+ }
+
+ if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) ||
+ !(y = mpi2bn(&keydata->y))) {
+ goto done;
+ }
+
+ res =
+ !botan_pubkey_load_elgamal(pubkey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(y));
+done:
+ bn_free(p);
+ bn_free(g);
+ bn_free(y);
+ return res;
+}
+
+static bool
+elgamal_load_secret_key(botan_privkey_t *seckey, const pgp_eg_key_t *keydata)
+{
+ bignum_t *p = NULL;
+ bignum_t *g = NULL;
+ bignum_t *x = NULL;
+ bool res = false;
+
+ // Check if provided secret key byte size is not greater than ELGAMAL_MAX_P_BYTELEN.
+ if (mpi_bytes(&keydata->p) > ELGAMAL_MAX_P_BYTELEN) {
+ goto done;
+ }
+
+ if (!(p = mpi2bn(&keydata->p)) || !(g = mpi2bn(&keydata->g)) ||
+ !(x = mpi2bn(&keydata->x))) {
+ goto done;
+ }
+
+ res = !botan_privkey_load_elgamal(
+ seckey, BN_HANDLE_PTR(p), BN_HANDLE_PTR(g), BN_HANDLE_PTR(x));
+done:
+ bn_free(p);
+ bn_free(g);
+ bn_free(x);
+ return res;
+}
+
+bool
+elgamal_validate_key(const pgp_eg_key_t *key, bool secret)
+{
+ // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN.
+ if (mpi_bytes(&key->p) > ELGAMAL_MAX_P_BYTELEN) {
+ return false;
+ }
+
+ /* Use custom validation since we added some custom validation, and Botan has slow test for
+ * prime for p */
+ try {
+ Botan::BigInt p(key->p.mpi, key->p.len);
+ Botan::BigInt g(key->g.mpi, key->g.len);
+
+ /* 1 < g < p */
+ if ((g.cmp_word(1) != 1) || (g.cmp(p) != -1)) {
+ return false;
+ }
+ /* g ^ (p - 1) = 1 mod p */
+ if (Botan::power_mod(g, p - 1, p).cmp_word(1)) {
+ return false;
+ }
+ /* check for small order subgroups */
+ Botan::Modular_Reducer reducer(p);
+ Botan::BigInt v = g;
+ for (size_t i = 2; i < (1 << 17); i++) {
+ v = reducer.multiply(v, g);
+ if (!v.cmp_word(1)) {
+ RNP_LOG("Small subgroup detected. Order %zu", i);
+ return false;
+ }
+ }
+ if (!secret) {
+ return true;
+ }
+ /* check that g ^ x = y (mod p) */
+ Botan::BigInt y(key->y.mpi, key->y.len);
+ Botan::BigInt x(key->x.mpi, key->x.len);
+ return Botan::power_mod(g, x, p) == y;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+}
+
+rnp_result_t
+elgamal_encrypt_pkcs1(rnp::RNG * rng,
+ pgp_eg_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ const pgp_eg_key_t *key)
+{
+ botan_pubkey_t b_key = NULL;
+ botan_pk_op_encrypt_t op_ctx = NULL;
+ rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS;
+ /* Max size of an output len is twice an order of underlying group (p length) */
+ uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0};
+ size_t p_len;
+
+ if (!elgamal_load_public_key(&b_key, key)) {
+ RNP_LOG("Failed to load public key");
+ goto end;
+ }
+
+ /* Size of output buffer must be equal to twice the size of key byte len.
+ * as ElGamal encryption outputs concatenation of two components, both
+ * of size equal to size of public key byte len.
+ * Successful call to botan's ElGamal encryption will return output that's
+ * always 2*pubkey size.
+ */
+ p_len = mpi_bytes(&key->p) * 2;
+
+ if (botan_pk_op_encrypt_create(&op_ctx, b_key, "PKCS1v15", 0) ||
+ botan_pk_op_encrypt(op_ctx, rng->handle(), enc_buf, &p_len, in, in_len)) {
+ RNP_LOG("Failed to create operation context");
+ goto end;
+ }
+
+ /*
+ * Botan's ElGamal formats the g^k and msg*(y^k) together into a single byte string.
+ * We have to parse out the two values after encryption, as rnp stores those values
+ * separatelly.
+ *
+ * We don't trim zeros from octet string as it is done before final marshalling
+ * (add_packet_body_mpi)
+ *
+ * We must assume that botan copies even number of bytes to output buffer (to avoid
+ * memory corruption)
+ */
+ p_len /= 2;
+ if (mem2mpi(&out->g, enc_buf, p_len) && mem2mpi(&out->m, enc_buf + p_len, p_len)) {
+ ret = RNP_SUCCESS;
+ }
+end:
+ botan_pk_op_encrypt_destroy(op_ctx);
+ botan_pubkey_destroy(b_key);
+ return ret;
+}
+
+rnp_result_t
+elgamal_decrypt_pkcs1(rnp::RNG * rng,
+ uint8_t * out,
+ size_t * out_len,
+ const pgp_eg_encrypted_t *in,
+ const pgp_eg_key_t * key)
+{
+ botan_privkey_t b_key = NULL;
+ botan_pk_op_decrypt_t op_ctx = NULL;
+ rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS;
+ uint8_t enc_buf[ELGAMAL_MAX_P_BYTELEN * 2] = {0};
+ size_t p_len;
+ size_t g_len;
+ size_t m_len;
+
+ if (!mpi_bytes(&key->x)) {
+ RNP_LOG("empty secret key");
+ goto end;
+ }
+
+ // Check if provided public key byte size is not greater than ELGAMAL_MAX_P_BYTELEN.
+ p_len = mpi_bytes(&key->p);
+ g_len = mpi_bytes(&in->g);
+ m_len = mpi_bytes(&in->m);
+
+ if ((2 * p_len > sizeof(enc_buf)) || (g_len > p_len) || (m_len > p_len)) {
+ RNP_LOG("Unsupported/wrong public key or encrypted data");
+ goto end;
+ }
+
+ if (!elgamal_load_secret_key(&b_key, key)) {
+ RNP_LOG("Failed to load private key");
+ goto end;
+ }
+
+ /* Botan expects ciphertext to be concatenated (g^k | encrypted m). Size must
+ * be equal to twice the byte size of public key, potentially prepended with zeros.
+ */
+ memcpy(&enc_buf[p_len - g_len], in->g.mpi, g_len);
+ memcpy(&enc_buf[2 * p_len - m_len], in->m.mpi, m_len);
+
+ *out_len = p_len;
+ if (botan_pk_op_decrypt_create(&op_ctx, b_key, "PKCS1v15", 0) ||
+ botan_pk_op_decrypt(op_ctx, out, out_len, enc_buf, 2 * p_len)) {
+ RNP_LOG("Decryption failed");
+ goto end;
+ }
+ ret = RNP_SUCCESS;
+end:
+ botan_pk_op_decrypt_destroy(op_ctx);
+ botan_privkey_destroy(b_key);
+ return ret;
+}
+
+rnp_result_t
+elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits)
+{
+ if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ botan_privkey_t key_priv = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ bignum_t * p = bn_new();
+ bignum_t * g = bn_new();
+ bignum_t * y = bn_new();
+ bignum_t * x = bn_new();
+
+ if (!p || !g || !y || !x) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto end;
+ }
+
+start:
+ if (botan_privkey_create_elgamal(&key_priv, rng->handle(), keybits, keybits - 1)) {
+ RNP_LOG("Wrong parameters");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto end;
+ }
+
+ if (botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y")) {
+ RNP_LOG("Failed to obtain public key");
+ goto end;
+ }
+ if (bn_num_bytes(*y) < BITS_TO_BYTES(keybits)) {
+ botan_privkey_destroy(key_priv);
+ goto start;
+ }
+
+ if (botan_privkey_get_field(BN_HANDLE_PTR(p), key_priv, "p") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(g), key_priv, "g") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(y), key_priv, "y") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(x), key_priv, "x")) {
+ RNP_LOG("Botan FFI call failed");
+ ret = RNP_ERROR_GENERIC;
+ goto end;
+ }
+
+ if (bn2mpi(p, &key->p) && bn2mpi(g, &key->g) && bn2mpi(y, &key->y) && bn2mpi(x, &key->x)) {
+ ret = RNP_SUCCESS;
+ }
+end:
+ bn_free(p);
+ bn_free(g);
+ bn_free(y);
+ bn_free(x);
+ botan_privkey_destroy(key_priv);
+ return ret;
+}
diff --git a/src/lib/crypto/elgamal.h b/src/lib/crypto/elgamal.h
new file mode 100644
index 0000000..42d0555
--- /dev/null
+++ b/src/lib/crypto/elgamal.h
@@ -0,0 +1,116 @@
+/*-
+ * 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 RNP_ELG_H_
+#define RNP_ELG_H_
+
+#include <stdint.h>
+#include "crypto/rng.h"
+#include "crypto/mpi.h"
+
+typedef struct pgp_eg_key_t {
+ pgp_mpi_t p;
+ pgp_mpi_t g;
+ pgp_mpi_t y;
+ /* secret mpi */
+ pgp_mpi_t x;
+} pgp_eg_key_t;
+
+typedef struct pgp_eg_signature_t {
+ /* This is kept only for packet reading. Implementation MUST
+ * not create elgamal signatures */
+ pgp_mpi_t r;
+ pgp_mpi_t s;
+} pgp_eg_signature_t;
+
+typedef struct pgp_eg_encrypted_t {
+ pgp_mpi_t g;
+ pgp_mpi_t m;
+} pgp_eg_encrypted_t;
+
+bool elgamal_validate_key(const pgp_eg_key_t *key, bool secret);
+
+/*
+ * Performs ElGamal encryption
+ * Result of an encryption is composed of two parts - g2k and encm
+ *
+ * @param rng initialized rnp::RNG
+ * @param out encryption result
+ * @param in plaintext to be encrypted
+ * @param in_len length of the plaintext
+ * @param key public key to be used for encryption
+ *
+ * @pre out: must be valid pointer to corresponding structure
+ * @pre in_len: can't be bigger than byte size of `p'
+ *
+ * @return RNP_SUCCESS
+ * RNP_ERROR_OUT_OF_MEMORY allocation failure
+ * RNP_ERROR_BAD_PARAMETERS wrong input provided
+ */
+rnp_result_t elgamal_encrypt_pkcs1(rnp::RNG * rng,
+ pgp_eg_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ const pgp_eg_key_t *key);
+
+/*
+ * Performs ElGamal decryption
+ *
+ * @param rng initialized rnp::RNG
+ * @param out decrypted plaintext. Must be capable of storing at least as much bytes as p size
+ * @param out_len number of plaintext bytes written will be put here
+ * @param in encrypted data
+ * @param key private key
+ *
+ * @pre out, in: must be valid pointers
+ * @pre out: length must be long enough to store decrypted data. Max size of
+ * decrypted data is equal to bytes size of `p'
+ *
+ * @return RNP_SUCCESS
+ * RNP_ERROR_OUT_OF_MEMORY allocation failure
+ * RNP_ERROR_BAD_PARAMETERS wrong input provided
+ */
+rnp_result_t elgamal_decrypt_pkcs1(rnp::RNG * rng,
+ uint8_t * out,
+ size_t * out_len,
+ const pgp_eg_encrypted_t *in,
+ const pgp_eg_key_t * key);
+
+/*
+ * Generates ElGamal key
+ *
+ * @param rng pointer to PRNG
+ * @param key generated key
+ * @param keybits key bitlen
+ *
+ * @pre `keybits' > 1024
+ *
+ * @returns RNP_ERROR_BAD_PARAMETERS wrong parameters provided
+ * RNP_ERROR_GENERIC internal error
+ * RNP_SUCCESS key generated and copied to `seckey'
+ */
+rnp_result_t elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits);
+#endif
diff --git a/src/lib/crypto/elgamal_ossl.cpp b/src/lib/crypto/elgamal_ossl.cpp
new file mode 100644
index 0000000..f3fa381
--- /dev/null
+++ b/src/lib/crypto/elgamal_ossl.cpp
@@ -0,0 +1,418 @@
+/*
+ * Copyright (c) 2021, [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 <cstdlib>
+#include <string>
+#include <cassert>
+#include <rnp/rnp_def.h>
+#include "elgamal.h"
+#include "dl_ossl.h"
+#include "utils.h"
+#include "bn.h"
+#include "mem.h"
+#include <openssl/bn.h>
+#include <openssl/dh.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#include <openssl/rand.h>
+
+// Max supported key byte size
+#define ELGAMAL_MAX_P_BYTELEN BITS_TO_BYTES(PGP_MPINT_BITS)
+
+bool
+elgamal_validate_key(const pgp_eg_key_t *key, bool secret)
+{
+ BN_CTX *ctx = BN_CTX_new();
+ if (!ctx) {
+ RNP_LOG("Allocation failed.");
+ return false;
+ }
+ BN_CTX_start(ctx);
+ bool res = false;
+ bignum_t * p = mpi2bn(&key->p);
+ bignum_t * g = mpi2bn(&key->g);
+ bignum_t * p1 = BN_CTX_get(ctx);
+ bignum_t * r = BN_CTX_get(ctx);
+ bignum_t * y = NULL;
+ bignum_t * x = NULL;
+ BN_RECP_CTX *rctx = NULL;
+
+ if (!p || !g || !p1 || !r) {
+ goto done;
+ }
+
+ /* 1 < g < p */
+ if ((BN_cmp(g, BN_value_one()) != 1) || (BN_cmp(g, p) != -1)) {
+ RNP_LOG("Invalid g value.");
+ goto done;
+ }
+ /* g ^ (p - 1) = 1 mod p */
+ if (!BN_copy(p1, p) || !BN_sub_word(p1, 1) || !BN_mod_exp(r, g, p1, p, ctx)) {
+ RNP_LOG("g exp failed.");
+ goto done;
+ }
+ if (BN_cmp(r, BN_value_one()) != 0) {
+ RNP_LOG("Wrong g exp value.");
+ goto done;
+ }
+ /* check for small order subgroups */
+ rctx = BN_RECP_CTX_new();
+ if (!rctx || !BN_RECP_CTX_set(rctx, p, ctx) || !BN_copy(r, g)) {
+ RNP_LOG("Failed to init RECP context.");
+ goto done;
+ }
+ for (size_t i = 2; i < (1 << 17); i++) {
+ if (!BN_mod_mul_reciprocal(r, r, g, rctx, ctx)) {
+ RNP_LOG("Multiplication failed.");
+ goto done;
+ }
+ if (BN_cmp(r, BN_value_one()) == 0) {
+ RNP_LOG("Small subgroup detected. Order %zu", i);
+ goto done;
+ }
+ }
+ if (!secret) {
+ res = true;
+ goto done;
+ }
+ /* check that g ^ x = y (mod p) */
+ x = mpi2bn(&key->x);
+ y = mpi2bn(&key->y);
+ if (!x || !y) {
+ goto done;
+ }
+ res = BN_mod_exp(r, g, x, p, ctx) && !BN_cmp(r, y);
+done:
+ BN_CTX_free(ctx);
+ BN_RECP_CTX_free(rctx);
+ bn_free(p);
+ bn_free(g);
+ bn_free(y);
+ bn_free(x);
+ return res;
+}
+
+static bool
+pkcs1v15_pad(uint8_t *out, size_t out_len, const uint8_t *in, size_t in_len)
+{
+ assert(out && in);
+ if (out_len < in_len + 11) {
+ return false;
+ }
+ out[0] = 0x00;
+ out[1] = 0x02;
+ size_t rnd = out_len - in_len - 3;
+ out[2 + rnd] = 0x00;
+ if (RAND_bytes(&out[2], rnd) != 1) {
+ return false;
+ }
+ for (size_t i = 2; i < 2 + rnd; i++) {
+ /* we need non-zero bytes */
+ size_t cntr = 16;
+ while (!out[i] && (cntr--) && (RAND_bytes(&out[i], 1) == 1)) {
+ }
+ if (!out[i]) {
+ RNP_LOG("Something is wrong with RNG.");
+ return false;
+ }
+ }
+ memcpy(out + rnd + 3, in, in_len);
+ return true;
+}
+
+static bool
+pkcs1v15_unpad(size_t *padlen, const uint8_t *in, size_t in_len, bool skip0)
+{
+ if (in_len <= (size_t)(11 - skip0)) {
+ return false;
+ }
+ if (!skip0 && in[0]) {
+ return false;
+ }
+ if (in[1 - skip0] != 0x02) {
+ return false;
+ }
+ size_t pad = 2 - skip0;
+ while ((pad < in_len) && in[pad]) {
+ pad++;
+ }
+ if (pad >= in_len) {
+ return false;
+ }
+ *padlen = pad + 1;
+ return true;
+}
+
+rnp_result_t
+elgamal_encrypt_pkcs1(rnp::RNG * rng,
+ pgp_eg_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ const pgp_eg_key_t *key)
+{
+ pgp_mpi_t mm = {};
+ mm.len = key->p.len;
+ if (!pkcs1v15_pad(mm.mpi, mm.len, in, in_len)) {
+ RNP_LOG("Failed to add PKCS1 v1.5 padding.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ BN_CTX * ctx = BN_CTX_new();
+ if (!ctx) {
+ RNP_LOG("Allocation failed.");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ BN_CTX_start(ctx);
+ BN_MONT_CTX *mctx = BN_MONT_CTX_new();
+ bignum_t * m = mpi2bn(&mm);
+ bignum_t * p = mpi2bn(&key->p);
+ bignum_t * g = mpi2bn(&key->g);
+ bignum_t * y = mpi2bn(&key->y);
+ bignum_t * c1 = BN_CTX_get(ctx);
+ bignum_t * c2 = BN_CTX_get(ctx);
+ bignum_t * k = BN_secure_new();
+ if (!mctx || !m || !p || !g || !y || !c1 || !c2 || !k) {
+ RNP_LOG("Allocation failed.");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ /* initialize Montgomery context */
+ if (BN_MONT_CTX_set(mctx, p, ctx) < 1) {
+ RNP_LOG("Failed to setup Montgomery context.");
+ goto done;
+ }
+ int res;
+ /* must not fail */
+ res = BN_rshift1(c1, p);
+ assert(res == 1);
+ if (res < 1) {
+ RNP_LOG("BN_rshift1 failed.");
+ goto done;
+ }
+ /* generate k */
+ if (BN_rand_range(k, c1) < 1) {
+ RNP_LOG("Failed to generate k.");
+ goto done;
+ }
+ /* calculate c1 = g ^ k (mod p) */
+ if (BN_mod_exp_mont_consttime(c1, g, k, p, ctx, mctx) < 1) {
+ RNP_LOG("Exponentiation 1 failed");
+ goto done;
+ }
+ /* calculate c2 = m * y ^ k (mod p)*/
+ if (BN_mod_exp_mont_consttime(c2, y, k, p, ctx, mctx) < 1) {
+ RNP_LOG("Exponentiation 2 failed");
+ goto done;
+ }
+ if (BN_mod_mul(c2, c2, m, p, ctx) < 1) {
+ RNP_LOG("Multiplication failed");
+ goto done;
+ }
+ res = bn2mpi(c1, &out->g) && bn2mpi(c2, &out->m);
+ assert(res == 1);
+ ret = RNP_SUCCESS;
+done:
+ BN_MONT_CTX_free(mctx);
+ BN_CTX_free(ctx);
+ bn_free(m);
+ bn_free(p);
+ bn_free(g);
+ bn_free(y);
+ bn_free(k);
+ return ret;
+}
+
+rnp_result_t
+elgamal_decrypt_pkcs1(rnp::RNG * rng,
+ uint8_t * out,
+ size_t * out_len,
+ const pgp_eg_encrypted_t *in,
+ const pgp_eg_key_t * key)
+{
+ if (!mpi_bytes(&key->x)) {
+ RNP_LOG("Secret key not set.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ BN_CTX *ctx = BN_CTX_new();
+ if (!ctx) {
+ RNP_LOG("Allocation failed.");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ pgp_mpi_t mm = {};
+ size_t padlen = 0;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ BN_CTX_start(ctx);
+ BN_MONT_CTX *mctx = BN_MONT_CTX_new();
+ bignum_t * p = mpi2bn(&key->p);
+ bignum_t * g = mpi2bn(&key->g);
+ bignum_t * x = mpi2bn(&key->x);
+ bignum_t * c1 = mpi2bn(&in->g);
+ bignum_t * c2 = mpi2bn(&in->m);
+ bignum_t * s = BN_CTX_get(ctx);
+ bignum_t * m = BN_secure_new();
+ if (!mctx || !p || !g || !x || !c1 || !c2 || !m) {
+ RNP_LOG("Allocation failed.");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ /* initialize Montgomery context */
+ if (BN_MONT_CTX_set(mctx, p, ctx) < 1) {
+ RNP_LOG("Failed to setup Montgomery context.");
+ goto done;
+ }
+ /* calculate s = c1 ^ x (mod p) */
+ if (BN_mod_exp_mont_consttime(s, c1, x, p, ctx, mctx) < 1) {
+ RNP_LOG("Exponentiation 1 failed");
+ goto done;
+ }
+ /* calculate s^-1 (mod p) */
+ BN_set_flags(s, BN_FLG_CONSTTIME);
+ if (!BN_mod_inverse(s, s, p, ctx)) {
+ RNP_LOG("Failed to calculate inverse.");
+ goto done;
+ }
+ /* calculate m = c2 * s ^ -1 (mod p)*/
+ if (BN_mod_mul(m, c2, s, p, ctx) < 1) {
+ RNP_LOG("Multiplication failed");
+ goto done;
+ }
+ bool res;
+ res = bn2mpi(m, &mm);
+ assert(res);
+ if (!res) {
+ RNP_LOG("bn2mpi failed.");
+ goto done;
+ }
+ /* unpad, handling skipped leftmost 0 case */
+ if (!pkcs1v15_unpad(&padlen, mm.mpi, mm.len, mm.len == key->p.len - 1)) {
+ RNP_LOG("Unpad failed.");
+ goto done;
+ }
+ *out_len = mm.len - padlen;
+ memcpy(out, &mm.mpi[padlen], *out_len);
+ ret = RNP_SUCCESS;
+done:
+ secure_clear(mm.mpi, PGP_MPINT_SIZE);
+ BN_MONT_CTX_free(mctx);
+ BN_CTX_free(ctx);
+ bn_free(p);
+ bn_free(g);
+ bn_free(x);
+ bn_free(c1);
+ bn_free(c2);
+ bn_free(m);
+ return ret;
+}
+
+rnp_result_t
+elgamal_generate(rnp::RNG *rng, pgp_eg_key_t *key, size_t keybits)
+{
+ if ((keybits < 1024) || (keybits > PGP_MPINT_BITS)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ 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 */
+ ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_DH, NULL);
+ if (!ctx) {
+ RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error());
+ return ret;
+ }
+ if (EVP_PKEY_paramgen_init(ctx) <= 0) {
+ RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_CTX_set_dh_paramgen_prime_len(ctx, keybits) <= 0) {
+ RNP_LOG("Failed to set key bits: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ /* OpenSSL correctly handles case with g = 5, making sure that g is primitive root of
+ * q-group */
+ if (EVP_PKEY_CTX_set_dh_paramgen_generator(ctx, DH_GENERATOR_5) <= 0) {
+ RNP_LOG("Failed to set key generator: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_paramgen(ctx, &parmkey) <= 0) {
+ RNP_LOG("Failed to generate parameters: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ EVP_PKEY_CTX_free(ctx);
+ /* Generate DH (ElGamal) key */
+start:
+ ctx = EVP_PKEY_CTX_new(parmkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Failed to create ctx: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_keygen_init(ctx) <= 0) {
+ RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
+ RNP_LOG("ElGamal keygen failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ dh = EVP_PKEY_get0_DH(pkey);
+ if (!dh) {
+ RNP_LOG("Failed to retrieve DH key: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (BITS_TO_BYTES(BN_num_bits(DH_get0_pub_key(dh))) != BITS_TO_BYTES(keybits)) {
+ EVP_PKEY_CTX_free(ctx);
+ ctx = NULL;
+ EVP_PKEY_free(pkey);
+ pkey = NULL;
+ goto start;
+ }
+
+ const bignum_t *p;
+ const bignum_t *g;
+ const bignum_t *y;
+ const bignum_t *x;
+ p = DH_get0_p(dh);
+ g = DH_get0_g(dh);
+ y = DH_get0_pub_key(dh);
+ x = DH_get0_priv_key(dh);
+ if (!p || !g || !y || !x) {
+ ret = RNP_ERROR_BAD_STATE;
+ goto done;
+ }
+ bn2mpi(p, &key->p);
+ bn2mpi(g, &key->g);
+ bn2mpi(y, &key->y);
+ bn2mpi(x, &key->x);
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(parmkey);
+ EVP_PKEY_free(pkey);
+ return ret;
+}
diff --git a/src/lib/crypto/hash.cpp b/src/lib/crypto/hash.cpp
new file mode 100644
index 0000000..250deec
--- /dev/null
+++ b/src/lib/crypto/hash.cpp
@@ -0,0 +1,160 @@
+/*
+ * 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.
+ */
+
+#include "hash_botan.hpp"
+#include "logging.h"
+#include <cassert>
+
+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_Botan::Hash_Botan(pgp_hash_alg_t alg) : Hash(alg)
+{
+ auto name = Hash_Botan::name_backend(alg);
+ if (!name) {
+ throw rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ fn_ = Botan::HashFunction::create(name);
+ if (!fn_) {
+ RNP_LOG("Error creating hash object for '%s'", name);
+ throw rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ assert(size_ == fn_->output_length());
+}
+
+Hash_Botan::Hash_Botan(const Hash_Botan &src) : Hash(src.alg_)
+{
+ if (!src.fn_) {
+ throw rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ fn_ = src.fn_->copy_state();
+}
+
+Hash_Botan::~Hash_Botan()
+{
+}
+
+std::unique_ptr<Hash_Botan>
+Hash_Botan::create(pgp_hash_alg_t alg)
+{
+ return std::unique_ptr<Hash_Botan>(new Hash_Botan(alg));
+}
+
+std::unique_ptr<Hash>
+Hash_Botan::clone() const
+{
+ return std::unique_ptr<Hash>(new Hash_Botan(*this));
+}
+
+void
+Hash_Botan::add(const void *buf, size_t len)
+{
+ if (!fn_) {
+ throw rnp_exception(RNP_ERROR_NULL_POINTER);
+ }
+ fn_->update(static_cast<const uint8_t *>(buf), len);
+}
+
+size_t
+Hash_Botan::finish(uint8_t *digest)
+{
+ if (!fn_) {
+ return 0;
+ }
+ size_t outlen = size_;
+ if (digest) {
+ fn_->final(digest);
+ }
+ fn_ = nullptr;
+ size_ = 0;
+ return outlen;
+}
+
+const char *
+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>
+CRC24_Botan::create()
+{
+ return std::unique_ptr<CRC24_Botan>(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<const uint8_t *>(buf), len);
+}
+
+std::array<uint8_t, 3>
+CRC24_Botan::finish()
+{
+ if (!fn_) {
+ throw rnp_exception(RNP_ERROR_NULL_POINTER);
+ }
+ std::array<uint8_t, 3> crc{};
+ fn_->final(crc.data());
+ fn_ = nullptr;
+ return crc;
+}
+
+} // namespace rnp
diff --git a/src/lib/crypto/hash.hpp b/src/lib/crypto/hash.hpp
new file mode 100644
index 0000000..7fcb817
--- /dev/null
+++ b/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 <repgp/repgp_def.h>
+#include "types.h"
+#include "config.h"
+#include <memory>
+#include <vector>
+#include <array>
+
+/**
+ * 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<Hash> create(pgp_hash_alg_t alg);
+ virtual std::unique_ptr<Hash> 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<CRC24> create();
+
+ virtual void add(const void *buf, size_t len) = 0;
+ virtual std::array<uint8_t, 3> finish() = 0;
+
+ virtual ~CRC24(){};
+};
+
+class HashList {
+ public:
+ std::vector<std::unique_ptr<Hash>> 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/src/lib/crypto/hash_botan.hpp b/src/lib/crypto/hash_botan.hpp
new file mode 100644
index 0000000..942e3a8
--- /dev/null
+++ b/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 <botan/hash.h>
+
+namespace rnp {
+class Hash_Botan : public Hash {
+ private:
+ std::unique_ptr<Botan::HashFunction> fn_;
+
+ Hash_Botan(pgp_hash_alg_t alg);
+ Hash_Botan(const Hash_Botan &src);
+
+ public:
+ virtual ~Hash_Botan();
+
+ static std::unique_ptr<Hash_Botan> create(pgp_hash_alg_t alg);
+ std::unique_ptr<Hash> 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<Botan::HashFunction> fn_;
+ CRC24_Botan();
+
+ public:
+ virtual ~CRC24_Botan();
+
+ static std::unique_ptr<CRC24_Botan> create();
+
+ void add(const void *buf, size_t len) override;
+ std::array<uint8_t, 3> finish() override;
+};
+
+} // namespace rnp
+
+#endif \ No newline at end of file
diff --git a/src/lib/crypto/hash_common.cpp b/src/lib/crypto/hash_common.cpp
new file mode 100644
index 0000000..69b400b
--- /dev/null
+++ b/src/lib/crypto/hash_common.cpp
@@ -0,0 +1,194 @@
+/*
+ * 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 "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;
+} hash_alg_map[] = {{PGP_HASH_MD5, "MD5", 16},
+ {PGP_HASH_SHA1, "SHA1", 20},
+ {PGP_HASH_RIPEMD, "RIPEMD160", 20},
+ {PGP_HASH_SHA256, "SHA256", 32},
+ {PGP_HASH_SHA384, "SHA384", 48},
+ {PGP_HASH_SHA512, "SHA512", 64},
+ {PGP_HASH_SHA224, "SHA224", 28},
+ {PGP_HASH_SM3, "SM3", 32},
+ {PGP_HASH_SHA3_256, "SHA3-256", 32},
+ {PGP_HASH_SHA3_512, "SHA3-512", 64}};
+
+namespace rnp {
+
+pgp_hash_alg_t
+Hash::alg() const
+{
+ return alg_;
+}
+
+size_t
+Hash::size() const
+{
+ return Hash::size(alg_);
+}
+
+std::unique_ptr<Hash>
+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>
+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)
+{
+ uint8_t ibuf[4];
+ STORE32BE(ibuf, val);
+ add(ibuf, sizeof(ibuf));
+}
+
+void
+Hash::add(const pgp_mpi_t &val)
+{
+ size_t len = mpi_bytes(&val);
+ size_t idx = 0;
+ while ((idx < len) && (!val.mpi[idx])) {
+ idx++;
+ }
+
+ if (idx >= len) {
+ add(0);
+ return;
+ }
+
+ add(len - idx);
+ if (val.mpi[idx] & 0x80) {
+ uint8_t padbyte = 0;
+ 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;
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(hash_alg_map); i++) {
+ if (rnp::str_case_eq(name, hash_alg_map[i].name)) {
+ return hash_alg_map[i].type;
+ }
+ }
+ return PGP_HASH_UNKNOWN;
+}
+
+const char *
+Hash::name(pgp_hash_alg_t alg)
+{
+ const char *ret = NULL;
+ ARRAY_LOOKUP_BY_ID(hash_alg_map, type, name, alg, ret);
+ return ret;
+}
+
+size_t
+Hash::size(pgp_hash_alg_t alg)
+{
+ size_t val = 0;
+ ARRAY_LOOKUP_BY_ID(hash_alg_map, type, len, alg, val);
+ return val;
+}
+
+void
+HashList::add_alg(pgp_hash_alg_t alg)
+{
+ if (!get(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.get();
+ }
+ }
+ return NULL;
+}
+
+void
+HashList::add(const void *buf, size_t len)
+{
+ for (auto &hash : hashes) {
+ hash->add(buf, len);
+ }
+}
+
+} // namespace rnp
diff --git a/src/lib/crypto/hash_crc24.cpp b/src/lib/crypto/hash_crc24.cpp
new file mode 100644
index 0000000..54f4d96
--- /dev/null
+++ b/src/lib/crypto/hash_crc24.cpp
@@ -0,0 +1,288 @@
+/*
+ * Copyright (c) 2017-2021, [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 <stdint.h>
+#include <stddef.h>
+#include "utils.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,
+ 0x0093E4D0, 0x00657DDC, 0x009E315A, 0x00B0CF64, 0x004B83E2, 0x00BD1AEE, 0x00465668,
+ 0x005129F7, 0x00AA6571, 0x005CFC7D, 0x00A7B0FB, 0x00E9D10C, 0x00129D8A, 0x00E40486,
+ 0x001F4800, 0x0008379F, 0x00F37B19, 0x0005E215, 0x00FEAE93, 0x00D050AD, 0x002B1C2B,
+ 0x00DD8527, 0x0026C9A1, 0x0031B63E, 0x00CAFAB8, 0x003C63B4, 0x00C72F32, 0x00609FC9,
+ 0x009BD34F, 0x006D4A43, 0x009606C5, 0x0081795A, 0x007A35DC, 0x008CACD0, 0x0077E056,
+ 0x00591E68, 0x00A252EE, 0x0054CBE2, 0x00AF8764, 0x00B8F8FB, 0x0043B47D, 0x00B52D71,
+ 0x004E61F7, 0x00D2A319, 0x0029EF9F, 0x00DF7693, 0x00243A15, 0x0033458A, 0x00C8090C,
+ 0x003E9000, 0x00C5DC86, 0x00EB22B8, 0x00106E3E, 0x00E6F732, 0x001DBBB4, 0x000AC42B,
+ 0x00F188AD, 0x000711A1, 0x00FC5D27, 0x005BEDDC, 0x00A0A15A, 0x00563856, 0x00AD74D0,
+ 0x00BA0B4F, 0x004147C9, 0x00B7DEC5, 0x004C9243, 0x00626C7D, 0x009920FB, 0x006FB9F7,
+ 0x0094F571, 0x00838AEE, 0x0078C668, 0x008E5F64, 0x007513E2, 0x003B7215, 0x00C03E93,
+ 0x0036A79F, 0x00CDEB19, 0x00DA9486, 0x0021D800, 0x00D7410C, 0x002C0D8A, 0x0002F3B4,
+ 0x00F9BF32, 0x000F263E, 0x00F46AB8, 0x00E31527, 0x001859A1, 0x00EEC0AD, 0x00158C2B,
+ 0x00B23CD0, 0x00497056, 0x00BFE95A, 0x0044A5DC, 0x0053DA43, 0x00A896C5, 0x005E0FC9,
+ 0x00A5434F, 0x008BBD71, 0x0070F1F7, 0x008668FB, 0x007D247D, 0x006A5BE2, 0x00911764,
+ 0x00678E68, 0x009CC2EE, 0x00A44733, 0x005F0BB5, 0x00A992B9, 0x0052DE3F, 0x0045A1A0,
+ 0x00BEED26, 0x0048742A, 0x00B338AC, 0x009DC692, 0x00668A14, 0x00901318, 0x006B5F9E,
+ 0x007C2001, 0x00876C87, 0x0071F58B, 0x008AB90D, 0x002D09F6, 0x00D64570, 0x0020DC7C,
+ 0x00DB90FA, 0x00CCEF65, 0x0037A3E3, 0x00C13AEF, 0x003A7669, 0x00148857, 0x00EFC4D1,
+ 0x00195DDD, 0x00E2115B, 0x00F56EC4, 0x000E2242, 0x00F8BB4E, 0x0003F7C8, 0x004D963F,
+ 0x00B6DAB9, 0x004043B5, 0x00BB0F33, 0x00AC70AC, 0x00573C2A, 0x00A1A526, 0x005AE9A0,
+ 0x0074179E, 0x008F5B18, 0x0079C214, 0x00828E92, 0x0095F10D, 0x006EBD8B, 0x00982487,
+ 0x00636801, 0x00C4D8FA, 0x003F947C, 0x00C90D70, 0x003241F6, 0x00253E69, 0x00DE72EF,
+ 0x0028EBE3, 0x00D3A765, 0x00FD595B, 0x000615DD, 0x00F08CD1, 0x000BC057, 0x001CBFC8,
+ 0x00E7F34E, 0x00116A42, 0x00EA26C4, 0x0076E42A, 0x008DA8AC, 0x007B31A0, 0x00807D26,
+ 0x009702B9, 0x006C4E3F, 0x009AD733, 0x00619BB5, 0x004F658B, 0x00B4290D, 0x0042B001,
+ 0x00B9FC87, 0x00AE8318, 0x0055CF9E, 0x00A35692, 0x00581A14, 0x00FFAAEF, 0x0004E669,
+ 0x00F27F65, 0x000933E3, 0x001E4C7C, 0x00E500FA, 0x001399F6, 0x00E8D570, 0x00C62B4E,
+ 0x003D67C8, 0x00CBFEC4, 0x0030B242, 0x0027CDDD, 0x00DC815B, 0x002A1857, 0x00D154D1,
+ 0x009F3526, 0x006479A0, 0x0092E0AC, 0x0069AC2A, 0x007ED3B5, 0x00859F33, 0x0073063F,
+ 0x00884AB9, 0x00A6B487, 0x005DF801, 0x00AB610D, 0x00502D8B, 0x00475214, 0x00BC1E92,
+ 0x004A879E, 0x00B1CB18, 0x00167BE3, 0x00ED3765, 0x001BAE69, 0x00E0E2EF, 0x00F79D70,
+ 0x000CD1F6, 0x00FA48FA, 0x0001047C, 0x002FFA42, 0x00D4B6C4, 0x00222FC8, 0x00D9634E,
+ 0x00CE1CD1, 0x00355057, 0x00C3C95B, 0x003885DD,
+};
+
+static const uint32_t T1[256] = {
+ 0x00000000, 0x00488F66, 0x00901ECD, 0x00D891AB, 0x00DB711C, 0x0093FE7A, 0x004B6FD1,
+ 0x0003E0B7, 0x00B6E338, 0x00FE6C5E, 0x0026FDF5, 0x006E7293, 0x006D9224, 0x00251D42,
+ 0x00FD8CE9, 0x00B5038F, 0x006CC771, 0x00244817, 0x00FCD9BC, 0x00B456DA, 0x00B7B66D,
+ 0x00FF390B, 0x0027A8A0, 0x006F27C6, 0x00DA2449, 0x0092AB2F, 0x004A3A84, 0x0002B5E2,
+ 0x00015555, 0x0049DA33, 0x00914B98, 0x00D9C4FE, 0x00D88EE3, 0x00900185, 0x0048902E,
+ 0x00001F48, 0x0003FFFF, 0x004B7099, 0x0093E132, 0x00DB6E54, 0x006E6DDB, 0x0026E2BD,
+ 0x00FE7316, 0x00B6FC70, 0x00B51CC7, 0x00FD93A1, 0x0025020A, 0x006D8D6C, 0x00B44992,
+ 0x00FCC6F4, 0x0024575F, 0x006CD839, 0x006F388E, 0x0027B7E8, 0x00FF2643, 0x00B7A925,
+ 0x0002AAAA, 0x004A25CC, 0x0092B467, 0x00DA3B01, 0x00D9DBB6, 0x009154D0, 0x0049C57B,
+ 0x00014A1D, 0x004B5141, 0x0003DE27, 0x00DB4F8C, 0x0093C0EA, 0x0090205D, 0x00D8AF3B,
+ 0x00003E90, 0x0048B1F6, 0x00FDB279, 0x00B53D1F, 0x006DACB4, 0x002523D2, 0x0026C365,
+ 0x006E4C03, 0x00B6DDA8, 0x00FE52CE, 0x00279630, 0x006F1956, 0x00B788FD, 0x00FF079B,
+ 0x00FCE72C, 0x00B4684A, 0x006CF9E1, 0x00247687, 0x00917508, 0x00D9FA6E, 0x00016BC5,
+ 0x0049E4A3, 0x004A0414, 0x00028B72, 0x00DA1AD9, 0x009295BF, 0x0093DFA2, 0x00DB50C4,
+ 0x0003C16F, 0x004B4E09, 0x0048AEBE, 0x000021D8, 0x00D8B073, 0x00903F15, 0x00253C9A,
+ 0x006DB3FC, 0x00B52257, 0x00FDAD31, 0x00FE4D86, 0x00B6C2E0, 0x006E534B, 0x0026DC2D,
+ 0x00FF18D3, 0x00B797B5, 0x006F061E, 0x00278978, 0x002469CF, 0x006CE6A9, 0x00B47702,
+ 0x00FCF864, 0x0049FBEB, 0x0001748D, 0x00D9E526, 0x00916A40, 0x00928AF7, 0x00DA0591,
+ 0x0002943A, 0x004A1B5C, 0x0096A282, 0x00DE2DE4, 0x0006BC4F, 0x004E3329, 0x004DD39E,
+ 0x00055CF8, 0x00DDCD53, 0x00954235, 0x002041BA, 0x0068CEDC, 0x00B05F77, 0x00F8D011,
+ 0x00FB30A6, 0x00B3BFC0, 0x006B2E6B, 0x0023A10D, 0x00FA65F3, 0x00B2EA95, 0x006A7B3E,
+ 0x0022F458, 0x002114EF, 0x00699B89, 0x00B10A22, 0x00F98544, 0x004C86CB, 0x000409AD,
+ 0x00DC9806, 0x00941760, 0x0097F7D7, 0x00DF78B1, 0x0007E91A, 0x004F667C, 0x004E2C61,
+ 0x0006A307, 0x00DE32AC, 0x0096BDCA, 0x00955D7D, 0x00DDD21B, 0x000543B0, 0x004DCCD6,
+ 0x00F8CF59, 0x00B0403F, 0x0068D194, 0x00205EF2, 0x0023BE45, 0x006B3123, 0x00B3A088,
+ 0x00FB2FEE, 0x0022EB10, 0x006A6476, 0x00B2F5DD, 0x00FA7ABB, 0x00F99A0C, 0x00B1156A,
+ 0x006984C1, 0x00210BA7, 0x00940828, 0x00DC874E, 0x000416E5, 0x004C9983, 0x004F7934,
+ 0x0007F652, 0x00DF67F9, 0x0097E89F, 0x00DDF3C3, 0x00957CA5, 0x004DED0E, 0x00056268,
+ 0x000682DF, 0x004E0DB9, 0x00969C12, 0x00DE1374, 0x006B10FB, 0x00239F9D, 0x00FB0E36,
+ 0x00B38150, 0x00B061E7, 0x00F8EE81, 0x00207F2A, 0x0068F04C, 0x00B134B2, 0x00F9BBD4,
+ 0x00212A7F, 0x0069A519, 0x006A45AE, 0x0022CAC8, 0x00FA5B63, 0x00B2D405, 0x0007D78A,
+ 0x004F58EC, 0x0097C947, 0x00DF4621, 0x00DCA696, 0x009429F0, 0x004CB85B, 0x0004373D,
+ 0x00057D20, 0x004DF246, 0x009563ED, 0x00DDEC8B, 0x00DE0C3C, 0x0096835A, 0x004E12F1,
+ 0x00069D97, 0x00B39E18, 0x00FB117E, 0x002380D5, 0x006B0FB3, 0x0068EF04, 0x00206062,
+ 0x00F8F1C9, 0x00B07EAF, 0x0069BA51, 0x00213537, 0x00F9A49C, 0x00B12BFA, 0x00B2CB4D,
+ 0x00FA442B, 0x0022D580, 0x006A5AE6, 0x00DF5969, 0x0097D60F, 0x004F47A4, 0x0007C8C2,
+ 0x00042875, 0x004CA713, 0x009436B8, 0x00DCB9DE,
+};
+
+static const uint32_t T2[256] = {
+ 0x00000000, 0x00D70983, 0x00555F80, 0x00825603, 0x0051F286, 0x0086FB05, 0x0004AD06,
+ 0x00D3A485, 0x0059A88B, 0x008EA108, 0x000CF70B, 0x00DBFE88, 0x00085A0D, 0x00DF538E,
+ 0x005D058D, 0x008A0C0E, 0x00491C91, 0x009E1512, 0x001C4311, 0x00CB4A92, 0x0018EE17,
+ 0x00CFE794, 0x004DB197, 0x009AB814, 0x0010B41A, 0x00C7BD99, 0x0045EB9A, 0x0092E219,
+ 0x0041469C, 0x00964F1F, 0x0014191C, 0x00C3109F, 0x006974A4, 0x00BE7D27, 0x003C2B24,
+ 0x00EB22A7, 0x00388622, 0x00EF8FA1, 0x006DD9A2, 0x00BAD021, 0x0030DC2F, 0x00E7D5AC,
+ 0x006583AF, 0x00B28A2C, 0x00612EA9, 0x00B6272A, 0x00347129, 0x00E378AA, 0x00206835,
+ 0x00F761B6, 0x007537B5, 0x00A23E36, 0x00719AB3, 0x00A69330, 0x0024C533, 0x00F3CCB0,
+ 0x0079C0BE, 0x00AEC93D, 0x002C9F3E, 0x00FB96BD, 0x00283238, 0x00FF3BBB, 0x007D6DB8,
+ 0x00AA643B, 0x0029A4CE, 0x00FEAD4D, 0x007CFB4E, 0x00ABF2CD, 0x00785648, 0x00AF5FCB,
+ 0x002D09C8, 0x00FA004B, 0x00700C45, 0x00A705C6, 0x002553C5, 0x00F25A46, 0x0021FEC3,
+ 0x00F6F740, 0x0074A143, 0x00A3A8C0, 0x0060B85F, 0x00B7B1DC, 0x0035E7DF, 0x00E2EE5C,
+ 0x00314AD9, 0x00E6435A, 0x00641559, 0x00B31CDA, 0x003910D4, 0x00EE1957, 0x006C4F54,
+ 0x00BB46D7, 0x0068E252, 0x00BFEBD1, 0x003DBDD2, 0x00EAB451, 0x0040D06A, 0x0097D9E9,
+ 0x00158FEA, 0x00C28669, 0x001122EC, 0x00C62B6F, 0x00447D6C, 0x009374EF, 0x001978E1,
+ 0x00CE7162, 0x004C2761, 0x009B2EE2, 0x00488A67, 0x009F83E4, 0x001DD5E7, 0x00CADC64,
+ 0x0009CCFB, 0x00DEC578, 0x005C937B, 0x008B9AF8, 0x00583E7D, 0x008F37FE, 0x000D61FD,
+ 0x00DA687E, 0x00506470, 0x00876DF3, 0x00053BF0, 0x00D23273, 0x000196F6, 0x00D69F75,
+ 0x0054C976, 0x0083C0F5, 0x00A9041B, 0x007E0D98, 0x00FC5B9B, 0x002B5218, 0x00F8F69D,
+ 0x002FFF1E, 0x00ADA91D, 0x007AA09E, 0x00F0AC90, 0x0027A513, 0x00A5F310, 0x0072FA93,
+ 0x00A15E16, 0x00765795, 0x00F40196, 0x00230815, 0x00E0188A, 0x00371109, 0x00B5470A,
+ 0x00624E89, 0x00B1EA0C, 0x0066E38F, 0x00E4B58C, 0x0033BC0F, 0x00B9B001, 0x006EB982,
+ 0x00ECEF81, 0x003BE602, 0x00E84287, 0x003F4B04, 0x00BD1D07, 0x006A1484, 0x00C070BF,
+ 0x0017793C, 0x00952F3F, 0x004226BC, 0x00918239, 0x00468BBA, 0x00C4DDB9, 0x0013D43A,
+ 0x0099D834, 0x004ED1B7, 0x00CC87B4, 0x001B8E37, 0x00C82AB2, 0x001F2331, 0x009D7532,
+ 0x004A7CB1, 0x00896C2E, 0x005E65AD, 0x00DC33AE, 0x000B3A2D, 0x00D89EA8, 0x000F972B,
+ 0x008DC128, 0x005AC8AB, 0x00D0C4A5, 0x0007CD26, 0x00859B25, 0x005292A6, 0x00813623,
+ 0x00563FA0, 0x00D469A3, 0x00036020, 0x0080A0D5, 0x0057A956, 0x00D5FF55, 0x0002F6D6,
+ 0x00D15253, 0x00065BD0, 0x00840DD3, 0x00530450, 0x00D9085E, 0x000E01DD, 0x008C57DE,
+ 0x005B5E5D, 0x0088FAD8, 0x005FF35B, 0x00DDA558, 0x000AACDB, 0x00C9BC44, 0x001EB5C7,
+ 0x009CE3C4, 0x004BEA47, 0x00984EC2, 0x004F4741, 0x00CD1142, 0x001A18C1, 0x009014CF,
+ 0x00471D4C, 0x00C54B4F, 0x001242CC, 0x00C1E649, 0x0016EFCA, 0x0094B9C9, 0x0043B04A,
+ 0x00E9D471, 0x003EDDF2, 0x00BC8BF1, 0x006B8272, 0x00B826F7, 0x006F2F74, 0x00ED7977,
+ 0x003A70F4, 0x00B07CFA, 0x00677579, 0x00E5237A, 0x00322AF9, 0x00E18E7C, 0x003687FF,
+ 0x00B4D1FC, 0x0063D87F, 0x00A0C8E0, 0x0077C163, 0x00F59760, 0x00229EE3, 0x00F13A66,
+ 0x002633E5, 0x00A465E6, 0x00736C65, 0x00F9606B, 0x002E69E8, 0x00AC3FEB, 0x007B3668,
+ 0x00A892ED, 0x007F9B6E, 0x00FDCD6D, 0x002AC4EE,
+};
+
+static const uint32_t T3[256] = {
+ 0x00000000, 0x00520936, 0x00A4126C, 0x00F61B5A, 0x004825D8, 0x001A2CEE, 0x00EC37B4,
+ 0x00BE3E82, 0x006B0636, 0x00390F00, 0x00CF145A, 0x009D1D6C, 0x002323EE, 0x00712AD8,
+ 0x00873182, 0x00D538B4, 0x00D60C6C, 0x0084055A, 0x00721E00, 0x00201736, 0x009E29B4,
+ 0x00CC2082, 0x003A3BD8, 0x006832EE, 0x00BD0A5A, 0x00EF036C, 0x00191836, 0x004B1100,
+ 0x00F52F82, 0x00A726B4, 0x00513DEE, 0x000334D8, 0x00AC19D8, 0x00FE10EE, 0x00080BB4,
+ 0x005A0282, 0x00E43C00, 0x00B63536, 0x00402E6C, 0x0012275A, 0x00C71FEE, 0x009516D8,
+ 0x00630D82, 0x003104B4, 0x008F3A36, 0x00DD3300, 0x002B285A, 0x0079216C, 0x007A15B4,
+ 0x00281C82, 0x00DE07D8, 0x008C0EEE, 0x0032306C, 0x0060395A, 0x00962200, 0x00C42B36,
+ 0x00111382, 0x00431AB4, 0x00B501EE, 0x00E708D8, 0x0059365A, 0x000B3F6C, 0x00FD2436,
+ 0x00AF2D00, 0x00A37F36, 0x00F17600, 0x00076D5A, 0x0055646C, 0x00EB5AEE, 0x00B953D8,
+ 0x004F4882, 0x001D41B4, 0x00C87900, 0x009A7036, 0x006C6B6C, 0x003E625A, 0x00805CD8,
+ 0x00D255EE, 0x00244EB4, 0x00764782, 0x0075735A, 0x00277A6C, 0x00D16136, 0x00836800,
+ 0x003D5682, 0x006F5FB4, 0x009944EE, 0x00CB4DD8, 0x001E756C, 0x004C7C5A, 0x00BA6700,
+ 0x00E86E36, 0x005650B4, 0x00045982, 0x00F242D8, 0x00A04BEE, 0x000F66EE, 0x005D6FD8,
+ 0x00AB7482, 0x00F97DB4, 0x00474336, 0x00154A00, 0x00E3515A, 0x00B1586C, 0x006460D8,
+ 0x003669EE, 0x00C072B4, 0x00927B82, 0x002C4500, 0x007E4C36, 0x0088576C, 0x00DA5E5A,
+ 0x00D96A82, 0x008B63B4, 0x007D78EE, 0x002F71D8, 0x00914F5A, 0x00C3466C, 0x00355D36,
+ 0x00675400, 0x00B26CB4, 0x00E06582, 0x00167ED8, 0x004477EE, 0x00FA496C, 0x00A8405A,
+ 0x005E5B00, 0x000C5236, 0x0046FF6C, 0x0014F65A, 0x00E2ED00, 0x00B0E436, 0x000EDAB4,
+ 0x005CD382, 0x00AAC8D8, 0x00F8C1EE, 0x002DF95A, 0x007FF06C, 0x0089EB36, 0x00DBE200,
+ 0x0065DC82, 0x0037D5B4, 0x00C1CEEE, 0x0093C7D8, 0x0090F300, 0x00C2FA36, 0x0034E16C,
+ 0x0066E85A, 0x00D8D6D8, 0x008ADFEE, 0x007CC4B4, 0x002ECD82, 0x00FBF536, 0x00A9FC00,
+ 0x005FE75A, 0x000DEE6C, 0x00B3D0EE, 0x00E1D9D8, 0x0017C282, 0x0045CBB4, 0x00EAE6B4,
+ 0x00B8EF82, 0x004EF4D8, 0x001CFDEE, 0x00A2C36C, 0x00F0CA5A, 0x0006D100, 0x0054D836,
+ 0x0081E082, 0x00D3E9B4, 0x0025F2EE, 0x0077FBD8, 0x00C9C55A, 0x009BCC6C, 0x006DD736,
+ 0x003FDE00, 0x003CEAD8, 0x006EE3EE, 0x0098F8B4, 0x00CAF182, 0x0074CF00, 0x0026C636,
+ 0x00D0DD6C, 0x0082D45A, 0x0057ECEE, 0x0005E5D8, 0x00F3FE82, 0x00A1F7B4, 0x001FC936,
+ 0x004DC000, 0x00BBDB5A, 0x00E9D26C, 0x00E5805A, 0x00B7896C, 0x00419236, 0x00139B00,
+ 0x00ADA582, 0x00FFACB4, 0x0009B7EE, 0x005BBED8, 0x008E866C, 0x00DC8F5A, 0x002A9400,
+ 0x00789D36, 0x00C6A3B4, 0x0094AA82, 0x0062B1D8, 0x0030B8EE, 0x00338C36, 0x00618500,
+ 0x00979E5A, 0x00C5976C, 0x007BA9EE, 0x0029A0D8, 0x00DFBB82, 0x008DB2B4, 0x00588A00,
+ 0x000A8336, 0x00FC986C, 0x00AE915A, 0x0010AFD8, 0x0042A6EE, 0x00B4BDB4, 0x00E6B482,
+ 0x00499982, 0x001B90B4, 0x00ED8BEE, 0x00BF82D8, 0x0001BC5A, 0x0053B56C, 0x00A5AE36,
+ 0x00F7A700, 0x00229FB4, 0x00709682, 0x00868DD8, 0x00D484EE, 0x006ABA6C, 0x0038B35A,
+ 0x00CEA800, 0x009CA136, 0x009F95EE, 0x00CD9CD8, 0x003B8782, 0x00698EB4, 0x00D7B036,
+ 0x0085B900, 0x0073A25A, 0x0021AB6C, 0x00F493D8, 0x00A69AEE, 0x005081B4, 0x00028882,
+ 0x00BCB600, 0x00EEBF36, 0x0018A46C, 0x004AAD5A};
+
+#define CRC24_FAST_INIT 0xce04b7L
+
+static inline uint32_t
+process8(uint32_t crc, uint8_t data)
+{
+ return (crc >> 8) ^ T0[(crc & 0xff) ^ data];
+}
+
+/*
+ * Process 4 bytes in one go
+ */
+static inline uint32_t
+process32(uint32_t crc, uint32_t word)
+{
+ crc ^= word;
+ crc = T3[crc & 0xff] ^ T2[((crc >> 8) & 0xff)] ^ T1[((crc >> 16) & 0xff)] ^
+ T0[(crc >> 24) & 0xff];
+ return crc;
+}
+
+static uint32_t
+crc24_update(uint32_t crc, const uint8_t *in, size_t length)
+{
+ uint32_t d0, d1, d2, d3;
+
+ while (length >= 16) {
+ LOAD32LE(d0, &in[0]);
+ LOAD32LE(d1, &in[4]);
+ LOAD32LE(d2, &in[8]);
+ LOAD32LE(d3, &in[12]);
+
+ crc = process32(crc, d0);
+ crc = process32(crc, d1);
+ crc = process32(crc, d2);
+ crc = process32(crc, d3);
+
+ in += 16;
+ length -= 16;
+ }
+
+ while (length--) {
+ crc = process8(crc, *in++);
+ }
+
+ return crc & 0xffffff;
+}
+
+/* Swap endianness of 32-bit value */
+#if defined(__GNUC__) || defined(__clang__)
+#define BSWAP32(x) __builtin_bswap32(x)
+#else
+#define BSWAP32(x) \
+ ((x & 0x000000FF) << 24 | (x & 0x0000FF00) << 8 | (x & 0x00FF0000) >> 8 | \
+ (x & 0xFF000000) >> 24)
+#endif
+
+static uint32_t
+crc24_final(uint32_t crc)
+{
+ return (BSWAP32(crc) >> 8);
+}
+
+namespace rnp {
+
+CRC24_RNP::CRC24_RNP()
+{
+ state_ = CRC24_FAST_INIT;
+}
+
+CRC24_RNP::~CRC24_RNP()
+{
+}
+
+std::unique_ptr<CRC24_RNP>
+CRC24_RNP::create()
+{
+ return std::unique_ptr<CRC24_RNP>(new CRC24_RNP());
+}
+
+void
+CRC24_RNP::add(const void *buf, size_t len)
+{
+ state_ = crc24_update(state_, static_cast<const uint8_t *>(buf), len);
+}
+
+std::array<uint8_t, 3>
+CRC24_RNP::finish()
+{
+ uint32_t crc_fin = crc24_final(state_);
+ state_ = 0;
+ std::array<uint8_t, 3> 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/src/lib/crypto/hash_crc24.hpp b/src/lib/crypto/hash_crc24.hpp
new file mode 100644
index 0000000..a73a466
--- /dev/null
+++ b/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<CRC24_RNP> create();
+
+ void add(const void *buf, size_t len) override;
+ std::array<uint8_t, 3> finish() override;
+};
+} // namespace rnp
+
+#endif \ No newline at end of file
diff --git a/src/lib/crypto/hash_ossl.cpp b/src/lib/crypto/hash_ossl.cpp
new file mode 100644
index 0000000..37b7546
--- /dev/null
+++ b/src/lib/crypto/hash_ossl.cpp
@@ -0,0 +1,152 @@
+/*
+ * 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 "hash_ossl.hpp"
+#include <stdio.h>
+#include <memory>
+#include <cassert>
+#include <openssl/err.h>
+#include "config.h"
+#include "types.h"
+#include "utils.h"
+#include "str-utils.h"
+#include "defaults.h"
+
+static const id_str_pair openssl_alg_map[] = {
+ {PGP_HASH_MD5, "md5"},
+ {PGP_HASH_SHA1, "sha1"},
+ {PGP_HASH_RIPEMD, "ripemd160"},
+ {PGP_HASH_SHA256, "sha256"},
+ {PGP_HASH_SHA384, "sha384"},
+ {PGP_HASH_SHA512, "sha512"},
+ {PGP_HASH_SHA224, "sha224"},
+ {PGP_HASH_SM3, "sm3"},
+ {PGP_HASH_SHA3_256, "sha3-256"},
+ {PGP_HASH_SHA3_512, "sha3-512"},
+ {0, NULL},
+};
+
+namespace rnp {
+Hash_OpenSSL::Hash_OpenSSL(pgp_hash_alg_t alg) : Hash(alg)
+{
+ 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);
+ }
+ 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_MD_CTX_copy(fn_, src.fn_);
+ if (res != 1) {
+ RNP_LOG("Digest copying error %d: %lu", res, ERR_peek_last_error());
+ EVP_MD_CTX_free(fn_);
+ throw rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+}
+
+std::unique_ptr<Hash_OpenSSL>
+Hash_OpenSSL::create(pgp_hash_alg_t alg)
+{
+ return std::unique_ptr<Hash_OpenSSL>(new Hash_OpenSSL(alg));
+}
+
+std::unique_ptr<Hash>
+Hash_OpenSSL::clone() const
+{
+ return std::unique_ptr<Hash>(new Hash_OpenSSL(*this));
+}
+
+void
+Hash_OpenSSL::add(const void *buf, size_t len)
+{
+ if (!fn_) {
+ throw rnp_exception(RNP_ERROR_NULL_POINTER);
+ }
+ 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_OpenSSL::finish(uint8_t *digest)
+{
+ if (!fn_) {
+ return 0;
+ }
+ 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;
+ return outsz;
+}
+
+Hash_OpenSSL::~Hash_OpenSSL()
+{
+ if (!fn_) {
+ return;
+ }
+ EVP_MD_CTX_free(fn_);
+}
+
+const char *
+Hash_OpenSSL::name_backend(pgp_hash_alg_t alg)
+{
+ return id_str_pair::lookup(openssl_alg_map, alg);
+}
+} // namespace rnp
diff --git a/src/lib/crypto/hash_ossl.hpp b/src/lib/crypto/hash_ossl.hpp
new file mode 100644
index 0000000..95b365b
--- /dev/null
+++ b/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 <openssl/evp.h>
+
+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<Hash_OpenSSL> create(pgp_hash_alg_t alg);
+ std::unique_ptr<Hash> 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/src/lib/crypto/hash_sha1cd.cpp b/src/lib/crypto/hash_sha1cd.cpp
new file mode 100644
index 0000000..863591e
--- /dev/null
+++ b/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 <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <cassert>
+#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>
+Hash_SHA1CD::create()
+{
+ return std::unique_ptr<Hash_SHA1CD>(new Hash_SHA1CD());
+}
+
+std::unique_ptr<Hash>
+Hash_SHA1CD::clone() const
+{
+ return std::unique_ptr<Hash>(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/src/lib/crypto/hash_sha1cd.hpp b/src/lib/crypto/hash_sha1cd.hpp
new file mode 100644
index 0000000..523bbe0
--- /dev/null
+++ b/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<Hash_SHA1CD> create();
+ std::unique_ptr<Hash> 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/src/lib/crypto/mem.cpp b/src/lib/crypto/mem.cpp
new file mode 100644
index 0000000..bf54aa6
--- /dev/null
+++ b/src/lib/crypto/mem.cpp
@@ -0,0 +1,68 @@
+/*-
+ * 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 <cstdio>
+#include "mem.h"
+#include "logging.h"
+#include <botan/ffi.h>
+
+void
+secure_clear(void *vp, size_t size)
+{
+ botan_scrub_mem(vp, size);
+}
+
+namespace rnp {
+
+bool
+hex_encode(const uint8_t *buf, size_t buf_len, char *hex, size_t hex_len, hex_format_t format)
+{
+ uint32_t flags = format == HEX_LOWERCASE ? BOTAN_FFI_HEX_LOWER_CASE : 0;
+
+ if (hex_len < (buf_len * 2 + 1)) {
+ return false;
+ }
+ hex[buf_len * 2] = '\0';
+ return botan_hex_encode(buf, buf_len, hex, flags) == 0;
+}
+
+size_t
+hex_decode(const char *hex, uint8_t *buf, size_t buf_len)
+{
+ size_t hexlen = strlen(hex);
+
+ /* check for 0x prefix */
+ if ((hexlen >= 2) && (hex[0] == '0') && ((hex[1] == 'x') || (hex[1] == 'X'))) {
+ hex += 2;
+ hexlen -= 2;
+ }
+ if (botan_hex_decode(hex, hexlen, buf, &buf_len) != 0) {
+ RNP_LOG("Hex decode failed on string: %s", hex);
+ return 0;
+ }
+ return buf_len;
+}
+} // namespace rnp \ No newline at end of file
diff --git a/src/lib/crypto/mem.h b/src/lib/crypto/mem.h
new file mode 100644
index 0000000..fe574da
--- /dev/null
+++ b/src/lib/crypto/mem.h
@@ -0,0 +1,158 @@
+/*-
+ * 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_MEM_H_
+#define CRYPTO_MEM_H_
+
+#include "config.h"
+#include <array>
+#include <vector>
+#if defined(CRYPTO_BACKEND_BOTAN)
+#include <botan/secmem.h>
+#include <botan/ffi.h>
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+#include <openssl/crypto.h>
+#endif
+
+namespace rnp {
+
+#if defined(CRYPTO_BACKEND_BOTAN)
+template <typename T> using secure_vector = Botan::secure_vector<T>;
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+template <typename T> class ossl_allocator {
+ public:
+ static_assert(std::is_integral<T>::value, "T must be integral type");
+
+ typedef T value_type;
+ typedef std::size_t size_type;
+
+ ossl_allocator() noexcept = default;
+ ossl_allocator(const ossl_allocator &) noexcept = default;
+ ossl_allocator &operator=(const ossl_allocator &) noexcept = default;
+ ~ossl_allocator() noexcept = default;
+
+ template <typename U> ossl_allocator(const ossl_allocator<U> &) noexcept
+ {
+ }
+
+ T *
+ allocate(std::size_t n)
+ {
+ if (!n) {
+ return nullptr;
+ }
+
+ /* attempt to use OpenSSL secure alloc */
+ T *ptr = static_cast<T *>(OPENSSL_secure_zalloc(n * sizeof(T)));
+ if (ptr) {
+ return ptr;
+ }
+ /* fallback to std::alloc if failed */
+ ptr = static_cast<T *>(std::calloc(n, sizeof(T)));
+ if (!ptr)
+ throw std::bad_alloc();
+ return ptr;
+ }
+
+ void
+ deallocate(T *p, std::size_t n)
+ {
+ if (!p) {
+ return;
+ }
+ if (CRYPTO_secure_allocated(p)) {
+ OPENSSL_secure_clear_free(p, n * sizeof(T));
+ return;
+ }
+ OPENSSL_cleanse(p, n * sizeof(T));
+ std::free(p);
+ }
+};
+
+template <typename T> using secure_vector = std::vector<T, ossl_allocator<T> >;
+#else
+#error Unsupported backend.
+#endif
+
+template <typename T, std::size_t N> struct secure_array {
+ private:
+ static_assert(std::is_integral<T>::value, "T must be integer type");
+ std::array<T, N> data_;
+
+ public:
+ secure_array() : data_({0})
+ {
+ }
+
+ T *
+ data()
+ {
+ return &data_[0];
+ }
+
+ std::size_t
+ size() const
+ {
+ return data_.size();
+ }
+
+ T
+ operator[](size_t idx) const
+ {
+ return data_[idx];
+ }
+
+ T &
+ operator[](size_t idx)
+ {
+ return data_[idx];
+ }
+
+ ~secure_array()
+ {
+#if defined(CRYPTO_BACKEND_BOTAN)
+ botan_scrub_mem(&data_[0], sizeof(data_));
+#elif defined(CRYPTO_BACKEND_OPENSSL)
+ OPENSSL_cleanse(&data_[0], sizeof(data_));
+#else
+#error "Unsupported crypto backend."
+#endif
+ }
+};
+
+typedef enum { HEX_LOWERCASE, HEX_UPPERCASE } hex_format_t;
+
+bool hex_encode(const uint8_t *buf,
+ size_t buf_len,
+ char * hex,
+ size_t hex_len,
+ hex_format_t format = HEX_UPPERCASE);
+size_t hex_decode(const char *hex, uint8_t *buf, size_t buf_len);
+} // namespace rnp
+
+void secure_clear(void *vp, size_t size);
+
+#endif // CRYPTO_MEM_H_
diff --git a/src/lib/crypto/mem_ossl.cpp b/src/lib/crypto/mem_ossl.cpp
new file mode 100644
index 0000000..e9d6a93
--- /dev/null
+++ b/src/lib/crypto/mem_ossl.cpp
@@ -0,0 +1,113 @@
+/*-
+ * 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 <cstdio>
+#include <cstring>
+#include "mem.h"
+#include "logging.h"
+#include <openssl/crypto.h>
+
+void
+secure_clear(void *vp, size_t size)
+{
+ OPENSSL_cleanse(vp, size);
+}
+
+namespace rnp {
+
+bool
+hex_encode(const uint8_t *buf, size_t buf_len, char *hex, size_t hex_len, hex_format_t format)
+{
+ if (hex_len < (buf_len * 2 + 1)) {
+ return false;
+ }
+ static const char *hex_low = "0123456789abcdef";
+ static const char *hex_up = "0123456789ABCDEF";
+ const char * hex_ch = (format == HEX_LOWERCASE) ? hex_low : hex_up;
+ hex[buf_len * 2] = '\0';
+ for (size_t i = 0; i < buf_len; i++) {
+ hex[i << 1] = hex_ch[buf[i] >> 4];
+ hex[(i << 1) + 1] = hex_ch[buf[i] & 0xF];
+ }
+ return true;
+}
+
+static bool
+hex_char_decode(const char hex, uint8_t &res)
+{
+ if ((hex >= '0') && (hex <= '9')) {
+ res = hex - '0';
+ return true;
+ }
+ if (hex >= 'a' && hex <= 'f') {
+ res = hex + 10 - 'a';
+ return true;
+ }
+ if (hex >= 'A' && hex <= 'F') {
+ res = hex + 10 - 'A';
+ return true;
+ }
+ return false;
+}
+
+size_t
+hex_decode(const char *hex, uint8_t *buf, size_t buf_len)
+{
+ size_t hexlen = strlen(hex);
+
+ /* check for 0x prefix */
+ if ((hexlen >= 2) && (hex[0] == '0') && ((hex[1] == 'x') || (hex[1] == 'X'))) {
+ hex += 2;
+ hexlen -= 2;
+ }
+ const char *end = hex + hexlen;
+ uint8_t * buf_st = buf;
+ uint8_t * buf_en = buf + buf_len;
+ while (hex < end) {
+ /* skip whitespaces */
+ if ((*hex < '0') &&
+ ((*hex == ' ') || (*hex == '\t') || (*hex == '\r') || (*hex == '\n'))) {
+ hex++;
+ continue;
+ }
+ if (hexlen < 2) {
+ RNP_LOG("Invalid hex string length.");
+ return 0;
+ }
+ uint8_t lo, hi;
+ if (!hex_char_decode(*hex++, hi) || !hex_char_decode(*hex++, lo)) {
+ RNP_LOG("Hex decode failed on string: %s", hex);
+ return 0;
+ }
+ if (buf == buf_en) {
+ return 0;
+ }
+ *buf++ = (hi << 4) | lo;
+ }
+ return buf - buf_st;
+}
+
+} // namespace rnp
diff --git a/src/lib/crypto/mpi.cpp b/src/lib/crypto/mpi.cpp
new file mode 100644
index 0000000..4df7eea
--- /dev/null
+++ b/src/lib/crypto/mpi.cpp
@@ -0,0 +1,119 @@
+/*-
+ * Copyright (c) 2018 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 <string.h>
+#include <stdlib.h>
+#include "mpi.h"
+#include "mem.h"
+#include "utils.h"
+
+size_t
+mpi_bits(const pgp_mpi_t *val)
+{
+ size_t bits = 0;
+ size_t idx = 0;
+ uint8_t bt;
+
+ for (idx = 0; (idx < val->len) && !val->mpi[idx]; idx++)
+ ;
+
+ if (idx < val->len) {
+ for (bits = (val->len - idx - 1) << 3, bt = val->mpi[idx]; bt; bits++, bt = bt >> 1)
+ ;
+ }
+
+ return bits;
+}
+
+size_t
+mpi_bytes(const pgp_mpi_t *val)
+{
+ return val->len;
+}
+
+bool
+mem2mpi(pgp_mpi_t *val, const void *mem, size_t len)
+{
+ if (len > sizeof(val->mpi)) {
+ return false;
+ }
+
+ memcpy(val->mpi, mem, len);
+ val->len = len;
+ return true;
+}
+
+void
+mpi2mem(const pgp_mpi_t *val, void *mem)
+{
+ memcpy(mem, val->mpi, val->len);
+}
+
+char *
+mpi2hex(const pgp_mpi_t *val)
+{
+ static const char *hexes = "0123456789abcdef";
+ char * out;
+ size_t len;
+ size_t idx = 0;
+
+ len = mpi_bytes(val);
+ out = (char *) malloc(len * 2 + 1);
+
+ if (!out) {
+ return out;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ out[idx++] = hexes[val->mpi[i] >> 4];
+ out[idx++] = hexes[val->mpi[i] & 0xf];
+ }
+ out[idx] = '\0';
+ return out;
+}
+
+bool
+mpi_equal(const pgp_mpi_t *val1, const pgp_mpi_t *val2)
+{
+ size_t idx1 = 0;
+ size_t idx2 = 0;
+
+ for (idx1 = 0; (idx1 < val1->len) && !val1->mpi[idx1]; idx1++)
+ ;
+
+ for (idx2 = 0; (idx2 < val2->len) && !val2->mpi[idx2]; idx2++)
+ ;
+
+ return ((val1->len - idx1) == (val2->len - idx2) &&
+ !memcmp(val1->mpi + idx1, val2->mpi + idx2, val1->len - idx1));
+}
+
+void
+mpi_forget(pgp_mpi_t *val)
+{
+ secure_clear(val, sizeof(*val));
+ val->len = 0;
+}
diff --git a/src/lib/crypto/mpi.h b/src/lib/crypto/mpi.h
new file mode 100644
index 0000000..f95aeea
--- /dev/null
+++ b/src/lib/crypto/mpi.h
@@ -0,0 +1,58 @@
+/*-
+ * Copyright (c) 2018 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 RNP_MPI_H_
+#define RNP_MPI_H_
+
+#include <cstdint>
+#include <cstdbool>
+#include <cstddef>
+
+/* 16384 bits should be pretty enough for now */
+#define PGP_MPINT_BITS (16384)
+#define PGP_MPINT_SIZE (PGP_MPINT_BITS >> 3)
+
+/** multi-precision integer, used in signatures and public/secret keys */
+typedef struct pgp_mpi_t {
+ uint8_t mpi[PGP_MPINT_SIZE];
+ size_t len;
+} pgp_mpi_t;
+
+bool mem2mpi(pgp_mpi_t *val, const void *mem, size_t len);
+
+void mpi2mem(const pgp_mpi_t *val, void *mem);
+
+char *mpi2hex(const pgp_mpi_t *val);
+
+size_t mpi_bits(const pgp_mpi_t *val);
+
+size_t mpi_bytes(const pgp_mpi_t *val);
+
+bool mpi_equal(const pgp_mpi_t *val1, const pgp_mpi_t *val2);
+
+void mpi_forget(pgp_mpi_t *val);
+
+#endif // MPI_H_
diff --git a/src/lib/crypto/ossl_common.h b/src/lib/crypto/ossl_common.h
new file mode 100644
index 0000000..b6b7067
--- /dev/null
+++ b/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 <string>
+#include "config.h"
+#include <openssl/err.h>
+
+inline const char *
+ossl_latest_err()
+{
+ return ERR_error_string(ERR_peek_last_error(), NULL);
+}
+
+#endif
diff --git a/src/lib/crypto/rng.cpp b/src/lib/crypto/rng.cpp
new file mode 100644
index 0000000..bf5bfad
--- /dev/null
+++ b/src/lib/crypto/rng.cpp
@@ -0,0 +1,59 @@
+/*
+ * Copyright (c) 2017-2021, [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 <assert.h>
+#include <botan/ffi.h>
+#include "rng.h"
+#include "types.h"
+
+namespace rnp {
+RNG::RNG(Type type)
+{
+ if (botan_rng_init(&botan_rng, type == Type::DRBG ? "user" : NULL)) {
+ throw rnp::rnp_exception(RNP_ERROR_RNG);
+ }
+}
+
+RNG::~RNG()
+{
+ (void) botan_rng_destroy(botan_rng);
+}
+
+void
+RNG::get(uint8_t *data, size_t len)
+{
+ if (botan_rng_get(botan_rng, data, len)) {
+ // This should never happen
+ throw rnp::rnp_exception(RNP_ERROR_RNG);
+ }
+}
+
+struct botan_rng_struct *
+RNG::handle()
+{
+ return botan_rng;
+}
+} // namespace rnp
diff --git a/src/lib/crypto/rng.h b/src/lib/crypto/rng.h
new file mode 100644
index 0000000..f452bd9
--- /dev/null
+++ b/src/lib/crypto/rng.h
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2017-2021, [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_RNG_H_
+#define RNP_RNG_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "config.h"
+
+#ifdef CRYPTO_BACKEND_BOTAN
+typedef struct botan_rng_struct *botan_rng_t;
+#endif
+
+namespace rnp {
+class RNG {
+ private:
+#ifdef CRYPTO_BACKEND_BOTAN
+ struct botan_rng_struct *botan_rng;
+#endif
+ public:
+ enum Type { DRBG, System };
+ /**
+ * @brief Construct a new RNG object.
+ * Note: OpenSSL uses own global RNG, so this class is not needed there and left
+ * only for code-level compatibility.
+ *
+ * @param type indicates which random generator to initialize.
+ * Possible values for Botan backend:
+ * - DRBG will initialize HMAC_DRBG, this generator is initialized on-demand
+ * (when used for the first time)
+ * - SYSTEM will initialize /dev/(u)random
+ */
+ RNG(Type type = Type::DRBG);
+ ~RNG();
+ /**
+ * @brief Get randoom bytes.
+ *
+ * @param data buffer where data should be stored. Cannot be NULL.
+ * @param len number of bytes required.
+ */
+ void get(uint8_t *data, size_t len);
+#ifdef CRYPTO_BACKEND_BOTAN
+ /**
+ * @brief Returns internal handle to botan rng. Returned
+ * handle is always initialized. In case of
+ * internal error NULL is returned
+ */
+ struct botan_rng_struct *handle();
+#endif
+};
+} // namespace rnp
+
+#endif // RNP_RNG_H_
diff --git a/src/lib/crypto/rng_ossl.cpp b/src/lib/crypto/rng_ossl.cpp
new file mode 100644
index 0000000..4ebcc95
--- /dev/null
+++ b/src/lib/crypto/rng_ossl.cpp
@@ -0,0 +1,48 @@
+/*-
+ * 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 <assert.h>
+#include <openssl/rand.h>
+#include "rng.h"
+#include "types.h"
+
+namespace rnp {
+RNG::RNG(Type type)
+{
+}
+
+RNG::~RNG()
+{
+}
+
+void
+RNG::get(uint8_t *data, size_t len)
+{
+ if (RAND_bytes(data, len) != 1) {
+ throw rnp::rnp_exception(RNP_ERROR_RNG);
+ }
+}
+} // namespace rnp
diff --git a/src/lib/crypto/rsa.cpp b/src/lib/crypto/rsa.cpp
new file mode 100644
index 0000000..f7ddefe
--- /dev/null
+++ b/src/lib/crypto/rsa.cpp
@@ -0,0 +1,419 @@
+/*-
+ * 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.
+ */
+
+/*-
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is derived from software contributed to The NetBSD Foundation
+ * by Alistair Crooks (agc@NetBSD.org)
+ *
+ * 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.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** \file
+ */
+#include <string>
+#include <cstring>
+#include <botan/ffi.h>
+#include "hash_botan.hpp"
+#include "crypto/rsa.h"
+#include "config.h"
+#include "utils.h"
+#include "bn.h"
+
+rnp_result_t
+rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret)
+{
+ bignum_t * n = NULL;
+ bignum_t * e = NULL;
+ bignum_t * p = NULL;
+ bignum_t * q = NULL;
+ botan_pubkey_t bpkey = NULL;
+ botan_privkey_t bskey = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ /* load and check public key part */
+ if (!(n = mpi2bn(&key->n)) || !(e = mpi2bn(&key->e))) {
+ RNP_LOG("out of memory");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ if (botan_pubkey_load_rsa(&bpkey, BN_HANDLE_PTR(n), BN_HANDLE_PTR(e)) != 0) {
+ goto done;
+ }
+
+ if (botan_pubkey_check_key(bpkey, rng->handle(), 0)) {
+ goto done;
+ }
+
+ if (!secret) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ /* load and check secret key part */
+ if (!(p = mpi2bn(&key->p)) || !(q = mpi2bn(&key->q))) {
+ RNP_LOG("out of memory");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ /* p and q are reversed from normal usage in PGP */
+ if (botan_privkey_load_rsa(&bskey, BN_HANDLE_PTR(q), BN_HANDLE_PTR(p), BN_HANDLE_PTR(e))) {
+ goto done;
+ }
+
+ if (botan_privkey_check_key(bskey, rng->handle(), 0)) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ botan_pubkey_destroy(bpkey);
+ botan_privkey_destroy(bskey);
+ bn_free(n);
+ bn_free(e);
+ bn_free(p);
+ bn_free(q);
+ return ret;
+}
+
+static bool
+rsa_load_public_key(botan_pubkey_t *bkey, const pgp_rsa_key_t *key)
+{
+ bignum_t *n = NULL;
+ bignum_t *e = NULL;
+ bool res = false;
+
+ *bkey = NULL;
+ n = mpi2bn(&key->n);
+ e = mpi2bn(&key->e);
+
+ if (!n || !e) {
+ RNP_LOG("out of memory");
+ goto done;
+ }
+
+ res = !botan_pubkey_load_rsa(bkey, BN_HANDLE_PTR(n), BN_HANDLE_PTR(e));
+done:
+ bn_free(n);
+ bn_free(e);
+ return res;
+}
+
+static bool
+rsa_load_secret_key(botan_privkey_t *bkey, const pgp_rsa_key_t *key)
+{
+ bignum_t *p = NULL;
+ bignum_t *q = NULL;
+ bignum_t *e = NULL;
+ bool res = false;
+
+ *bkey = NULL;
+ p = mpi2bn(&key->p);
+ q = mpi2bn(&key->q);
+ e = mpi2bn(&key->e);
+
+ if (!p || !q || !e) {
+ RNP_LOG("out of memory");
+ goto done;
+ }
+
+ /* p and q are reversed from normal usage in PGP */
+ res = !botan_privkey_load_rsa(bkey, BN_HANDLE_PTR(q), BN_HANDLE_PTR(p), BN_HANDLE_PTR(e));
+done:
+ bn_free(p);
+ bn_free(q);
+ bn_free(e);
+ return res;
+}
+
+rnp_result_t
+rsa_encrypt_pkcs1(rnp::RNG * rng,
+ pgp_rsa_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ const pgp_rsa_key_t *key)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ botan_pubkey_t rsa_key = NULL;
+ botan_pk_op_encrypt_t enc_op = NULL;
+
+ if (!rsa_load_public_key(&rsa_key, key)) {
+ RNP_LOG("failed to load key");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (botan_pk_op_encrypt_create(&enc_op, rsa_key, "PKCS1v15", 0) != 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)) {
+ out->m.len = 0;
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ botan_pk_op_encrypt_destroy(enc_op);
+ botan_pubkey_destroy(rsa_key);
+ return ret;
+}
+
+rnp_result_t
+rsa_verify_pkcs1(const pgp_rsa_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_rsa_key_t * key)
+{
+ char padding_name[64] = {0};
+ botan_pubkey_t rsa_key = NULL;
+ botan_pk_op_verify_t verify_op = NULL;
+ rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID;
+
+ if (!rsa_load_public_key(&rsa_key, key)) {
+ RNP_LOG("failed to load key");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ snprintf(padding_name,
+ sizeof(padding_name),
+ "EMSA-PKCS1-v1_5(Raw,%s)",
+ rnp::Hash_Botan::name_backend(hash_alg));
+
+ if (botan_pk_op_verify_create(&verify_op, rsa_key, padding_name, 0) != 0) {
+ goto done;
+ }
+
+ if (botan_pk_op_verify_update(verify_op, hash, hash_len) != 0) {
+ goto done;
+ }
+
+ if (botan_pk_op_verify_finish(verify_op, sig->s.mpi, sig->s.len) != 0) {
+ goto done;
+ }
+
+ ret = RNP_SUCCESS;
+done:
+ botan_pk_op_verify_destroy(verify_op);
+ botan_pubkey_destroy(rsa_key);
+ return ret;
+}
+
+rnp_result_t
+rsa_sign_pkcs1(rnp::RNG * rng,
+ pgp_rsa_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_rsa_key_t *key)
+{
+ char padding_name[64] = {0};
+ botan_privkey_t rsa_key;
+ botan_pk_op_sign_t sign_op;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ if (mpi_bytes(&key->q) == 0) {
+ RNP_LOG("private key not set");
+ return ret;
+ }
+
+ if (!rsa_load_secret_key(&rsa_key, key)) {
+ RNP_LOG("failed to load key");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ snprintf(padding_name,
+ sizeof(padding_name),
+ "EMSA-PKCS1-v1_5(Raw,%s)",
+ rnp::Hash_Botan::name_backend(hash_alg));
+
+ if (botan_pk_op_sign_create(&sign_op, rsa_key, padding_name, 0) != 0) {
+ goto done;
+ }
+
+ if (botan_pk_op_sign_update(sign_op, hash, hash_len)) {
+ goto done;
+ }
+
+ sig->s.len = sizeof(sig->s.mpi);
+ if (botan_pk_op_sign_finish(sign_op, rng->handle(), sig->s.mpi, &sig->s.len)) {
+ goto done;
+ }
+
+ ret = RNP_SUCCESS;
+done:
+ botan_pk_op_sign_destroy(sign_op);
+ botan_privkey_destroy(rsa_key);
+ return ret;
+}
+
+rnp_result_t
+rsa_decrypt_pkcs1(rnp::RNG * rng,
+ uint8_t * out,
+ size_t * out_len,
+ const pgp_rsa_encrypted_t *in,
+ const pgp_rsa_key_t * key)
+{
+ botan_privkey_t rsa_key = NULL;
+ botan_pk_op_decrypt_t decrypt_op = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ if (mpi_bytes(&key->q) == 0) {
+ RNP_LOG("private key not set");
+ return ret;
+ }
+
+ if (!rsa_load_secret_key(&rsa_key, key)) {
+ RNP_LOG("failed to load key");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (botan_pk_op_decrypt_create(&decrypt_op, rsa_key, "PKCS1v15", 0)) {
+ goto done;
+ }
+
+ *out_len = PGP_MPINT_SIZE;
+ if (botan_pk_op_decrypt(decrypt_op, out, out_len, in->m.mpi, in->m.len)) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ botan_privkey_destroy(rsa_key);
+ botan_pk_op_decrypt_destroy(decrypt_op);
+ return ret;
+}
+
+rnp_result_t
+rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits)
+{
+ if ((numbits < 1024) || (numbits > PGP_MPINT_BITS)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ botan_privkey_t rsa_key = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ int cmp;
+ bignum_t * n = bn_new();
+ bignum_t * e = bn_new();
+ bignum_t * p = bn_new();
+ bignum_t * q = bn_new();
+ bignum_t * d = bn_new();
+ bignum_t * u = bn_new();
+
+ if (!n || !e || !p || !q || !d || !u) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto end;
+ }
+
+ if (botan_privkey_create(
+ &rsa_key, "RSA", std::to_string(numbits).c_str(), rng->handle())) {
+ goto end;
+ }
+
+ if (botan_privkey_check_key(rsa_key, rng->handle(), 1) != 0) {
+ goto end;
+ }
+
+ if (botan_privkey_get_field(BN_HANDLE_PTR(n), rsa_key, "n") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(e), rsa_key, "e") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(d), rsa_key, "d") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(p), rsa_key, "p") ||
+ botan_privkey_get_field(BN_HANDLE_PTR(q), rsa_key, "q")) {
+ goto end;
+ }
+
+ /* RFC 4880, 5.5.3 tells that p < q. GnuPG relies on this. */
+ (void) botan_mp_cmp(&cmp, BN_HANDLE_PTR(p), BN_HANDLE_PTR(q));
+ if (cmp > 0) {
+ (void) botan_mp_swap(BN_HANDLE_PTR(p), BN_HANDLE_PTR(q));
+ }
+
+ if (botan_mp_mod_inverse(BN_HANDLE_PTR(u), BN_HANDLE_PTR(p), BN_HANDLE_PTR(q)) != 0) {
+ RNP_LOG("Error computing RSA u param");
+ ret = RNP_ERROR_BAD_STATE;
+ goto end;
+ }
+
+ bn2mpi(n, &key->n);
+ bn2mpi(e, &key->e);
+ bn2mpi(p, &key->p);
+ bn2mpi(q, &key->q);
+ bn2mpi(d, &key->d);
+ bn2mpi(u, &key->u);
+
+ ret = RNP_SUCCESS;
+end:
+ botan_privkey_destroy(rsa_key);
+ bn_free(n);
+ bn_free(e);
+ bn_free(p);
+ bn_free(q);
+ bn_free(d);
+ bn_free(u);
+ return ret;
+}
diff --git a/src/lib/crypto/rsa.h b/src/lib/crypto/rsa.h
new file mode 100644
index 0000000..6b1b615
--- /dev/null
+++ b/src/lib/crypto/rsa.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (c) 2017, [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).
+ *
+ * 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 RNP_RSA_H_
+#define RNP_RSA_H_
+
+#include <rnp/rnp_def.h>
+#include <repgp/repgp_def.h>
+#include "crypto/rng.h"
+#include "crypto/mpi.h"
+
+typedef struct pgp_rsa_key_t {
+ pgp_mpi_t n;
+ pgp_mpi_t e;
+ /* secret mpis */
+ pgp_mpi_t d;
+ pgp_mpi_t p;
+ pgp_mpi_t q;
+ pgp_mpi_t u;
+} pgp_rsa_key_t;
+
+typedef struct pgp_rsa_signature_t {
+ pgp_mpi_t s;
+} pgp_rsa_signature_t;
+
+typedef struct pgp_rsa_encrypted_t {
+ pgp_mpi_t m;
+} pgp_rsa_encrypted_t;
+
+/*
+ * RSA encrypt/decrypt
+ */
+
+rnp_result_t rsa_validate_key(rnp::RNG *rng, const pgp_rsa_key_t *key, bool secret);
+
+rnp_result_t rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits);
+
+rnp_result_t rsa_encrypt_pkcs1(rnp::RNG * rng,
+ pgp_rsa_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ const pgp_rsa_key_t *key);
+
+rnp_result_t rsa_decrypt_pkcs1(rnp::RNG * rng,
+ uint8_t * out,
+ size_t * out_len,
+ const pgp_rsa_encrypted_t *in,
+ const pgp_rsa_key_t * key);
+
+rnp_result_t rsa_verify_pkcs1(const pgp_rsa_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_rsa_key_t * key);
+
+rnp_result_t rsa_sign_pkcs1(rnp::RNG * rng,
+ pgp_rsa_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_rsa_key_t *key);
+
+#endif
diff --git a/src/lib/crypto/rsa_ossl.cpp b/src/lib/crypto/rsa_ossl.cpp
new file mode 100644
index 0000000..24cff29
--- /dev/null
+++ b/src/lib/crypto/rsa_ossl.cpp
@@ -0,0 +1,629 @@
+/*
+ * 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:
+ *
+ * 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 <string>
+#include <cstring>
+#include <cassert>
+#include "crypto/rsa.h"
+#include "config.h"
+#include "utils.h"
+#include "bn.h"
+#include "ossl_common.h"
+#include <openssl/rsa.h>
+#include <openssl/err.h>
+#include <openssl/evp.h>
+#ifdef CRYPTO_BACKEND_OPENSSL3
+#include <openssl/param_build.h>
+#include <openssl/core_names.h>
+#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);
+ bignum_t *e = mpi2bn(&key->e);
+
+ if (!n || !e) {
+ RNP_LOG("out of memory");
+ goto done;
+ }
+ rsa = RSA_new();
+ if (!rsa) {
+ RNP_LOG("Out of memory");
+ goto done;
+ }
+ if (RSA_set0_key(rsa, n, e, NULL) != 1) {
+ RNP_LOG("Public key load error: %lu", ERR_peek_last_error());
+ RSA_free(rsa);
+ rsa = NULL;
+ goto done;
+ }
+done:
+ /* OpenSSL set0 function transfers ownership of bignums */
+ if (!rsa) {
+ bn_free(n);
+ bn_free(e);
+ }
+ return rsa;
+}
+
+static RSA *
+rsa_load_secret_key(const pgp_rsa_key_t *key)
+{
+ RSA * rsa = NULL;
+ bignum_t *n = mpi2bn(&key->n);
+ bignum_t *e = mpi2bn(&key->e);
+ bignum_t *p = mpi2bn(&key->p);
+ bignum_t *q = mpi2bn(&key->q);
+ bignum_t *d = mpi2bn(&key->d);
+
+ if (!n || !p || !q || !e || !d) {
+ RNP_LOG("out of memory");
+ goto done;
+ }
+
+ rsa = RSA_new();
+ if (!rsa) {
+ RNP_LOG("Out of memory");
+ goto done;
+ }
+ if (RSA_set0_key(rsa, n, e, d) != 1) {
+ RNP_LOG("Secret key load error: %lu", ERR_peek_last_error());
+ RSA_free(rsa);
+ rsa = NULL;
+ goto done;
+ }
+ /* OpenSSL has p < q, as we do */
+ if (RSA_set0_factors(rsa, p, q) != 1) {
+ RNP_LOG("Factors load error: %lu", ERR_peek_last_error());
+ RSA_free(rsa);
+ rsa = NULL;
+ goto done;
+ }
+done:
+ /* OpenSSL set0 function transfers ownership of bignums */
+ if (!rsa) {
+ bn_free(n);
+ bn_free(p);
+ bn_free(q);
+ bn_free(e);
+ bn_free(d);
+ }
+ return rsa;
+}
+
+static EVP_PKEY_CTX *
+rsa_init_context(const pgp_rsa_key_t *key, bool secret)
+{
+ EVP_PKEY *evpkey = EVP_PKEY_new();
+ if (!evpkey) {
+ RNP_LOG("allocation failed");
+ return NULL;
+ }
+ EVP_PKEY_CTX *ctx = NULL;
+ RSA * rsakey = secret ? rsa_load_secret_key(key) : rsa_load_public_key(key);
+ if (!rsakey) {
+ goto done;
+ }
+ if (EVP_PKEY_set1_RSA(evpkey, rsakey) <= 0) {
+ RNP_LOG("Failed to set key: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ ctx = EVP_PKEY_CTX_new(evpkey, NULL);
+ if (!ctx) {
+ RNP_LOG("Context allocation failed: %lu", ERR_peek_last_error());
+ }
+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: %s", ossl_latest_err());
+ }
+ EVP_PKEY_CTX_free(ctx);
+ return res > 0 ? RNP_SUCCESS : RNP_ERROR_GENERIC;
+ }
+
+ /* OpenSSL 1.1.1 doesn't have RSA public key check function, so let's do some checks */
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ bignum_t * n = mpi2bn(&key->n);
+ bignum_t * e = mpi2bn(&key->e);
+ if (!n || !e) {
+ RNP_LOG("out of memory");
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ if ((BN_num_bits(n) < 512) || !BN_is_odd(n) || (BN_num_bits(e) < 2) || !BN_is_odd(e)) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ bn_free(n);
+ bn_free(e);
+ return ret;
+#endif
+}
+
+static bool
+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;
+ }
+ 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) {
+ 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
+rsa_encrypt_pkcs1(rnp::RNG * rng,
+ pgp_rsa_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ const pgp_rsa_key_t *key)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ EVP_PKEY_CTX *ctx = rsa_init_context(key, false);
+ if (!ctx) {
+ return ret;
+ }
+ if (EVP_PKEY_encrypt_init(ctx) <= 0) {
+ RNP_LOG("Failed to initialize encryption: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (!rsa_setup_context(ctx)) {
+ goto done;
+ }
+ out->m.len = sizeof(out->m.mpi);
+ if (EVP_PKEY_encrypt(ctx, out->m.mpi, &out->m.len, in, in_len) <= 0) {
+ RNP_LOG("Encryption failed: %lu", ERR_peek_last_error());
+ out->m.len = 0;
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+rnp_result_t
+rsa_verify_pkcs1(const pgp_rsa_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_rsa_key_t * key)
+{
+ 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) ||
+ !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;
+ size_t diff = key->n.len - sig->s.len;
+ memset(sn.mpi, 0, diff);
+ memcpy(&sn.mpi[diff], sig->s.mpi, sig->s.len);
+ res = EVP_PKEY_verify(ctx, sn.mpi, sn.len, hash, hash_len);
+ } else {
+ 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", ossl_latest_err());
+ }
+done:
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+rnp_result_t
+rsa_sign_pkcs1(rnp::RNG * rng,
+ pgp_rsa_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_rsa_key_t *key)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ if (mpi_bytes(&key->q) == 0) {
+ RNP_LOG("private key not set");
+ return ret;
+ }
+ 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) ||
+ !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;
+ }
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+rnp_result_t
+rsa_decrypt_pkcs1(rnp::RNG * rng,
+ uint8_t * out,
+ size_t * out_len,
+ const pgp_rsa_encrypted_t *in,
+ const pgp_rsa_key_t * key)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ if (mpi_bytes(&key->q) == 0) {
+ RNP_LOG("private key not set");
+ return ret;
+ }
+ EVP_PKEY_CTX *ctx = rsa_init_context(key, true);
+ if (!ctx) {
+ return ret;
+ }
+ if (EVP_PKEY_decrypt_init(ctx) <= 0) {
+ RNP_LOG("Failed to initialize encryption: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (!rsa_setup_context(ctx)) {
+ goto done;
+ }
+ *out_len = PGP_MPINT_SIZE;
+ if (EVP_PKEY_decrypt(ctx, out, out_len, in->m.mpi, in->m.len) <= 0) {
+ RNP_LOG("Encryption failed: %lu", ERR_peek_last_error());
+ *out_len = 0;
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ return ret;
+}
+
+rnp_result_t
+rsa_generate(rnp::RNG *rng, pgp_rsa_key_t *key, size_t numbits)
+{
+ if ((numbits < 1024) || (numbits > PGP_MPINT_BITS)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ 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;
+ }
+ if (EVP_PKEY_keygen_init(ctx) <= 0) {
+ RNP_LOG("Failed to init keygen: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, numbits) <= 0) {
+ RNP_LOG("Failed to set rsa bits: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (EVP_PKEY_keygen(ctx, &pkey) <= 0) {
+ RNP_LOG("RSA keygen failed: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ rsa = EVP_PKEY_get0_RSA(pkey);
+ if (!rsa) {
+ RNP_LOG("Failed to retrieve RSA key: %lu", ERR_peek_last_error());
+ goto done;
+ }
+ if (RSA_check_key(rsa) != 1) {
+ RNP_LOG("Key validation error: %lu", ERR_peek_last_error());
+ goto done;
+ }
+
+ const bignum_t *n;
+ const bignum_t *e;
+ const bignum_t *p;
+ const bignum_t *q;
+ const bignum_t *d;
+ n = RSA_get0_n(rsa);
+ e = RSA_get0_e(rsa);
+ d = RSA_get0_d(rsa);
+ p = RSA_get0_p(rsa);
+ q = RSA_get0_q(rsa);
+ if (!n || !e || !d || !p || !q) {
+ 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;
+ 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;
+ }
+ bn2mpi(n, &key->n);
+ bn2mpi(e, &key->e);
+ bn2mpi(p, &key->p);
+ bn2mpi(q, &key->q);
+ bn2mpi(d, &key->d);
+ bn2mpi(u, &key->u);
+ ret = RNP_SUCCESS;
+done:
+ EVP_PKEY_CTX_free(ctx);
+ EVP_PKEY_free(pkey);
+ BN_CTX_free(bnctx);
+ return ret;
+}
diff --git a/src/lib/crypto/s2k.cpp b/src/lib/crypto/s2k.cpp
new file mode 100644
index 0000000..ede7965
--- /dev/null
+++ b/src/lib/crypto/s2k.cpp
@@ -0,0 +1,203 @@
+/*
+ * 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).
+ *
+ * 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 <stdio.h>
+#include "config.h"
+#ifndef _MSC_VER
+#include <sys/time.h>
+#else
+#include "uniwin.h"
+#endif
+
+#include "crypto/s2k.h"
+#include "defaults.h"
+#include "rnp.h"
+#include "types.h"
+#include "utils.h"
+#ifdef CRYPTO_BACKEND_BOTAN
+#include <botan/ffi.h>
+#include "hash_botan.hpp"
+#endif
+
+bool
+pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize)
+{
+ uint8_t *saltptr = NULL;
+ unsigned iterations = 1;
+
+ switch (s2k->specifier) {
+ case PGP_S2KS_SIMPLE:
+ break;
+ case PGP_S2KS_SALTED:
+ saltptr = s2k->salt;
+ break;
+ case PGP_S2KS_ITERATED_AND_SALTED:
+ saltptr = s2k->salt;
+ if (s2k->iterations < 256) {
+ iterations = pgp_s2k_decode_iterations(s2k->iterations);
+ } else {
+ iterations = s2k->iterations;
+ }
+ break;
+ default:
+ return false;
+ }
+
+ if (pgp_s2k_iterated(s2k->hash_alg, key, keysize, password, saltptr, iterations)) {
+ RNP_LOG("s2k failed");
+ return false;
+ }
+
+ return true;
+}
+
+#ifdef CRYPTO_BACKEND_BOTAN
+int
+pgp_s2k_iterated(pgp_hash_alg_t alg,
+ uint8_t * out,
+ size_t output_len,
+ 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_Botan::name_backend(alg));
+
+ return botan_pwdhash(s2k_algo_str,
+ iterations,
+ 0,
+ 0,
+ out,
+ output_len,
+ password,
+ 0,
+ salt,
+ salt ? PGP_SALT_SIZE : 0);
+}
+#endif
+
+size_t
+pgp_s2k_decode_iterations(uint8_t c)
+{
+ // See RFC 4880 section 3.7.1.3
+ return (16 + (c & 0x0F)) << ((c >> 4) + 6);
+}
+
+size_t
+pgp_s2k_round_iterations(size_t iterations)
+{
+ return pgp_s2k_decode_iterations(pgp_s2k_encode_iterations(iterations));
+}
+
+uint8_t
+pgp_s2k_encode_iterations(size_t iterations)
+{
+ /* For compatibility, when an S2K specifier is used, the special value
+ * 254 or 255 is stored in the position where the hash algorithm octet
+ * would have been in the old data structure. This is then followed
+ * immediately by a one-octet algorithm identifier, and then by the S2K
+ * specifier as encoded above.
+ * 0: secret data is unencrypted (no password)
+ * 255 or 254: followed by algorithm octet and S2K specifier
+ * Cipher alg: use Simple S2K algorithm using MD5 hash
+ * For more info refer to rfc 4880 section 3.7.2.1.
+ */
+ for (uint16_t c = 0; c < 256; ++c) {
+ // This could be a binary search
+ if (pgp_s2k_decode_iterations(c) >= iterations) {
+ return c;
+ }
+ }
+ return 255;
+}
+
+/// Should this function be elsewhere?
+static uint64_t
+get_timestamp_usec()
+{
+#ifndef _MSC_VER
+ // TODO: Consider clock_gettime
+ struct timeval tv;
+ ::gettimeofday(&tv, NULL);
+ return (static_cast<uint64_t>(tv.tv_sec) * 1000000) + static_cast<uint64_t>(tv.tv_usec);
+#else
+ return GetTickCount64() * 1000;
+#endif
+}
+
+size_t
+pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec)
+{
+ if (desired_msec == 0) {
+ desired_msec = DEFAULT_S2K_MSEC;
+ }
+ if (trial_msec == 0) {
+ trial_msec = DEFAULT_S2K_TUNE_MSEC;
+ }
+
+ // number of iterations to estimate the number of iterations
+ // (sorry, cannot tell it better)
+ const uint8_t NUM_ITERATIONS = 16;
+ uint64_t duration = 0;
+ size_t bytes = 0;
+ try {
+ for (uint8_t i = 0; i < NUM_ITERATIONS; i++) {
+ uint64_t start = get_timestamp_usec();
+ uint64_t end = start;
+ auto hash = rnp::Hash::create(alg);
+ uint8_t buf[8192] = {0};
+ while (end - start < trial_msec * 1000ull) {
+ hash->add(buf, sizeof(buf));
+ bytes += sizeof(buf);
+ end = get_timestamp_usec();
+ }
+ hash->finish(buf);
+ duration += (end - start);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to hash data: %s", e.what());
+ return 0;
+ }
+
+ const uint8_t MIN_ITERS = 96;
+ if (duration == 0) {
+ return pgp_s2k_decode_iterations(MIN_ITERS);
+ }
+
+ const double bytes_per_usec = static_cast<double>(bytes) / duration;
+ const double desired_usec = desired_msec * 1000.0;
+ const double bytes_for_target = bytes_per_usec * desired_usec;
+ const uint8_t iters = pgp_s2k_encode_iterations(bytes_for_target);
+
+ return pgp_s2k_decode_iterations((iters > MIN_ITERS) ? iters : MIN_ITERS);
+}
diff --git a/src/lib/crypto/s2k.h b/src/lib/crypto/s2k.h
new file mode 100644
index 0000000..c67a773
--- /dev/null
+++ b/src/lib/crypto/s2k.h
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2017, [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).
+ *
+ * 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 RNP_S2K_H_
+#define RNP_S2K_H_
+
+#include <cstdint>
+#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,
+ size_t output_len,
+ const char * password,
+ const uint8_t *salt,
+ size_t iterations);
+
+size_t pgp_s2k_decode_iterations(uint8_t encoded_iter);
+
+uint8_t pgp_s2k_encode_iterations(size_t iterations);
+
+// Round iterations to nearest representable value
+size_t pgp_s2k_round_iterations(size_t iterations);
+
+size_t pgp_s2k_compute_iters(pgp_hash_alg_t alg, size_t desired_msec, size_t trial_msec);
+
+/** @brief Derive key from password using the information stored in s2k structure
+ * @param s2k pointer to s2k structure, filled according to RFC 4880.
+ * Iterations field may contain encoded ( < 256) or decoded ( > 256) value.
+ * @param password NULL-terminated password
+ * @param key buffer to store the derived key, must have at least keysize bytes
+ * @param keysize number of bytes in the key.
+ * @return true on success or false otherwise
+ */
+bool pgp_s2k_derive_key(pgp_s2k_t *s2k, const char *password, uint8_t *key, int keysize);
+
+#endif
diff --git a/src/lib/crypto/s2k_ossl.cpp b/src/lib/crypto/s2k_ossl.cpp
new file mode 100644
index 0000000..acf1ca9
--- /dev/null
+++ b/src/lib/crypto/s2k_ossl.cpp
@@ -0,0 +1,97 @@
+/*-
+ * 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 <cstdint>
+#include <vector>
+#include <algorithm>
+#include <openssl/evp.h>
+#include "hash.hpp"
+#include "s2k.h"
+#include "mem.h"
+#include "logging.h"
+
+int
+pgp_s2k_iterated(pgp_hash_alg_t alg,
+ uint8_t * out,
+ size_t output_len,
+ const char * password,
+ const uint8_t *salt,
+ size_t iterations)
+{
+ if ((iterations > 1) && !salt) {
+ RNP_LOG("Iterated S2K mus be salted as well.");
+ return 1;
+ }
+ size_t hash_len = rnp::Hash::size(alg);
+ if (!hash_len) {
+ RNP_LOG("Unknown digest: %d", (int) alg);
+ return 1;
+ }
+ try {
+ size_t pswd_len = strlen(password);
+ size_t salt_len = salt ? PGP_SALT_SIZE : 0;
+
+ rnp::secure_vector<uint8_t> data(salt_len + pswd_len);
+ if (salt_len) {
+ memcpy(data.data(), salt, PGP_SALT_SIZE);
+ }
+ memcpy(data.data() + salt_len, password, pswd_len);
+ size_t zeroes = 0;
+
+ while (output_len) {
+ /* create hash context */
+ 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);
+ }
+ 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);
+ left -= to_hash;
+ }
+ }
+ rnp::secure_vector<uint8_t> dgst(hash_len);
+ size_t out_cpy = std::min(dgst.size(), output_len);
+ if (hash->finish(dgst.data()) != dgst.size()) {
+ RNP_LOG("Unexpected digest size.");
+ return 1;
+ }
+ memcpy(out, dgst.data(), out_cpy);
+ output_len -= out_cpy;
+ out += out_cpy;
+ zeroes++;
+ }
+ return 0;
+ } catch (const std::exception &e) {
+ RNP_LOG("s2k failed: %s", e.what());
+ return 1;
+ }
+}
diff --git a/src/lib/crypto/sha1cd/sha1.c b/src/lib/crypto/sha1cd/sha1.c
new file mode 100644
index 0000000..d90bc41
--- /dev/null
+++ b/src/lib/crypto/sha1cd/sha1.c
@@ -0,0 +1,2162 @@
+/***
+ * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow (danshu@microsoft.com)
+ * Distributed under the MIT Software License.
+ * See accompanying file LICENSE.txt or copy at
+ * https://opensource.org/licenses/MIT
+ ***/
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <string.h>
+#include <memory.h>
+#include <stdio.h>
+#include <stdlib.h>
+#ifdef __unix__
+#include <sys/types.h> /* make sure macros like _BIG_ENDIAN visible */
+#endif
+#endif
+
+#ifdef SHA1DC_CUSTOM_INCLUDE_SHA1_C
+#include SHA1DC_CUSTOM_INCLUDE_SHA1_C
+#endif
+
+#ifndef SHA1DC_INIT_SAFE_HASH_DEFAULT
+#define SHA1DC_INIT_SAFE_HASH_DEFAULT 1
+#endif
+
+#include "sha1.h"
+#include "ubc_check.h"
+
+#if (defined(__amd64__) || defined(__amd64) || defined(__x86_64__) || defined(__x86_64) || \
+ defined(i386) || defined(__i386) || defined(__i386__) || defined(__i486__) || \
+ defined(__i586__) || defined(__i686__) || defined(_M_IX86) || defined(__X86__) || \
+ defined(_X86_) || defined(__THW_INTEL__) || defined(__I86__) || defined(__INTEL__) || \
+ defined(__386) || defined(_M_X64) || defined(_M_AMD64))
+#define SHA1DC_ON_INTEL_LIKE_PROCESSOR
+#endif
+
+/*
+ Because Little-Endian architectures are most common,
+ we only set SHA1DC_BIGENDIAN if one of these conditions is met.
+ Note that all MSFT platforms are little endian,
+ so none of these will be defined under the MSC compiler.
+ If you are compiling on a big endian platform and your compiler does not define one of
+ these, you will have to add whatever macros your tool chain defines to indicate
+ Big-Endianness.
+ */
+
+#if defined(__BYTE_ORDER__) && defined(__ORDER_BIG_ENDIAN__)
+/*
+ * Should detect Big Endian under GCC since at least 4.6.0 (gcc svn
+ * rev #165881). See
+ * https://gcc.gnu.org/onlinedocs/cpp/Common-Predefined-Macros.html
+ *
+ * This also works under clang since 3.2, it copied the GCC-ism. See
+ * clang.git's 3b198a97d2 ("Preprocessor: add __BYTE_ORDER__
+ * predefined macro", 2012-07-27)
+ */
+#if __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
+#define SHA1DC_BIGENDIAN
+#endif
+
+/* Not under GCC-alike */
+#elif defined(__BYTE_ORDER) && defined(__BIG_ENDIAN)
+/*
+ * Should detect Big Endian under glibc.git since 14245eb70e ("entered
+ * into RCS", 1992-11-25). Defined in <endian.h> which will have been
+ * brought in by standard headers. See glibc.git and
+ * https://sourceforge.net/p/predef/wiki/Endianness/
+ */
+#if __BYTE_ORDER == __BIG_ENDIAN
+#define SHA1DC_BIGENDIAN
+#endif
+
+/* Not under GCC-alike or glibc */
+#elif defined(_BYTE_ORDER) && defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN)
+/*
+ * *BSD and newlib (embedded linux, cygwin, etc).
+ * the defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) part prevents
+ * this condition from matching with Solaris/sparc.
+ * (Solaris defines only one endian macro)
+ */
+#if _BYTE_ORDER == _BIG_ENDIAN
+#define SHA1DC_BIGENDIAN
+#endif
+
+/* Not under GCC-alike or glibc or *BSD or newlib */
+#elif (defined(__ARMEB__) || defined(__THUMBEB__) || defined(__AARCH64EB__) || \
+ defined(__MIPSEB__) || defined(__MIPSEB) || defined(_MIPSEB) || defined(__sparc))
+/*
+ * Should define Big Endian for a whitelist of known processors. See
+ * https://sourceforge.net/p/predef/wiki/Endianness/ and
+ * http://www.oracle.com/technetwork/server-storage/solaris/portingtosolaris-138514.html
+ */
+#define SHA1DC_BIGENDIAN
+
+/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> */
+#elif (defined(_AIX) || defined(__hpux))
+
+/*
+ * Defines Big Endian on a whitelist of OSs that are known to be Big
+ * Endian-only. See
+ * https://public-inbox.org/git/93056823-2740-d072-1ebd-46b440b33d7e@felt.demon.nl/
+ */
+#define SHA1DC_BIGENDIAN
+
+/* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os whitelist> */
+#elif defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR)
+/*
+ * As a last resort before we do anything else we're not 100% sure
+ * about below, we blacklist specific processors here. We could add
+ * more, see e.g. https://wiki.debian.org/ArchitectureSpecificsMemo
+ */
+#else /* Not under GCC-alike or glibc or *BSD or newlib or <processor whitelist> or <os \
+ whitelist> or <processor blacklist> */
+
+/* We do nothing more here for now */
+/*#error "Uncomment this to see if you fall through all the detection"*/
+
+#endif /* Big Endian detection */
+
+#if (defined(SHA1DC_FORCE_LITTLEENDIAN) && defined(SHA1DC_BIGENDIAN))
+#undef SHA1DC_BIGENDIAN
+#endif
+#if (defined(SHA1DC_FORCE_BIGENDIAN) && !defined(SHA1DC_BIGENDIAN))
+#define SHA1DC_BIGENDIAN
+#endif
+/*ENDIANNESS SELECTION*/
+
+#ifndef SHA1DC_FORCE_ALIGNED_ACCESS
+#if defined(SHA1DC_FORCE_UNALIGNED_ACCESS) || defined(SHA1DC_ON_INTEL_LIKE_PROCESSOR)
+#define SHA1DC_ALLOW_UNALIGNED_ACCESS
+#endif /*UNALIGNED ACCESS DETECTION*/
+#endif /*FORCE ALIGNED ACCESS*/
+
+#define rotate_right(x, n) (((x) >> (n)) | ((x) << (32 - (n))))
+#define rotate_left(x, n) (((x) << (n)) | ((x) >> (32 - (n))))
+
+#define sha1_bswap32(x) \
+ { \
+ x = ((x << 8) & 0xFF00FF00) | ((x >> 8) & 0xFF00FF); \
+ x = (x << 16) | (x >> 16); \
+ }
+
+#define sha1_mix(W, t) (rotate_left(W[t - 3] ^ W[t - 8] ^ W[t - 14] ^ W[t - 16], 1))
+
+#ifdef SHA1DC_BIGENDIAN
+#define sha1_load(m, t, temp) \
+ { \
+ temp = m[t]; \
+ }
+#else
+#define sha1_load(m, t, temp) \
+ { \
+ temp = m[t]; \
+ sha1_bswap32(temp); \
+ }
+#endif
+
+#define sha1_store(W, t, x) *(volatile uint32_t *) &W[t] = x
+
+#define sha1_f1(b, c, d) ((d) ^ ((b) & ((c) ^ (d))))
+#define sha1_f2(b, c, d) ((b) ^ (c) ^ (d))
+#define sha1_f3(b, c, d) (((b) & (c)) + ((d) & ((b) ^ (c))))
+#define sha1_f4(b, c, d) ((b) ^ (c) ^ (d))
+
+#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, m, t) \
+ { \
+ e += rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999 + m[t]; \
+ b = rotate_left(b, 30); \
+ }
+#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, m, t) \
+ { \
+ e += rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1 + m[t]; \
+ b = rotate_left(b, 30); \
+ }
+#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, m, t) \
+ { \
+ e += rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC + m[t]; \
+ b = rotate_left(b, 30); \
+ }
+#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, m, t) \
+ { \
+ e += rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6 + m[t]; \
+ b = rotate_left(b, 30); \
+ }
+
+#define HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, m, t) \
+ { \
+ b = rotate_right(b, 30); \
+ e -= rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999 + m[t]; \
+ }
+#define HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, m, t) \
+ { \
+ b = rotate_right(b, 30); \
+ e -= rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1 + m[t]; \
+ }
+#define HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, m, t) \
+ { \
+ b = rotate_right(b, 30); \
+ e -= rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC + m[t]; \
+ }
+#define HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, m, t) \
+ { \
+ b = rotate_right(b, 30); \
+ e -= rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6 + m[t]; \
+ }
+
+#define SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, t, temp) \
+ { \
+ sha1_load(m, t, temp); \
+ sha1_store(W, t, temp); \
+ e += temp + rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999; \
+ b = rotate_left(b, 30); \
+ }
+
+#define SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(a, b, c, d, e, W, t, temp) \
+ { \
+ temp = sha1_mix(W, t); \
+ sha1_store(W, t, temp); \
+ e += temp + rotate_left(a, 5) + sha1_f1(b, c, d) + 0x5A827999; \
+ b = rotate_left(b, 30); \
+ }
+
+#define SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, t, temp) \
+ { \
+ temp = sha1_mix(W, t); \
+ sha1_store(W, t, temp); \
+ e += temp + rotate_left(a, 5) + sha1_f2(b, c, d) + 0x6ED9EBA1; \
+ b = rotate_left(b, 30); \
+ }
+
+#define SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, t, temp) \
+ { \
+ temp = sha1_mix(W, t); \
+ sha1_store(W, t, temp); \
+ e += temp + rotate_left(a, 5) + sha1_f3(b, c, d) + 0x8F1BBCDC; \
+ b = rotate_left(b, 30); \
+ }
+
+#define SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, t, temp) \
+ { \
+ temp = sha1_mix(W, t); \
+ sha1_store(W, t, temp); \
+ e += temp + rotate_left(a, 5) + sha1_f4(b, c, d) + 0xCA62C1D6; \
+ b = rotate_left(b, 30); \
+ }
+
+#define SHA1_STORE_STATE(i) \
+ states[i][0] = a; \
+ states[i][1] = b; \
+ states[i][2] = c; \
+ states[i][3] = d; \
+ states[i][4] = e;
+
+#ifdef BUILDNOCOLLDETECTSHA1COMPRESSION
+void
+sha1_compression(uint32_t ihv[5], const uint32_t m[16])
+{
+ uint32_t W[80];
+ uint32_t a, b, c, d, e;
+ unsigned i;
+
+ memcpy(W, m, 16 * 4);
+ for (i = 16; i < 80; ++i)
+ W[i] = sha1_mix(W, i);
+
+ a = ihv[0];
+ b = ihv[1];
+ c = ihv[2];
+ d = ihv[3];
+ e = ihv[4];
+
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19);
+
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39);
+
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59);
+
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79);
+
+ ihv[0] += a;
+ ihv[1] += b;
+ ihv[2] += c;
+ ihv[3] += d;
+ ihv[4] += e;
+}
+#endif /*BUILDNOCOLLDETECTSHA1COMPRESSION*/
+
+static void
+sha1_compression_W(uint32_t ihv[5], const uint32_t W[80])
+{
+ uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4];
+
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 0);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 1);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 2);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 3);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 4);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 5);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 6);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 7);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 8);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 9);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 10);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 11);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 12);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 13);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 14);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, W, 15);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, W, 16);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, W, 17);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, W, 18);
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, W, 19);
+
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 20);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 21);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 22);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 23);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 24);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 25);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 26);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 27);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 28);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 29);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 30);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 31);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 32);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 33);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 34);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, W, 35);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, W, 36);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, W, 37);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, W, 38);
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, W, 39);
+
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 40);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 41);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 42);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 43);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 44);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 45);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 46);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 47);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 48);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 49);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 50);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 51);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 52);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 53);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 54);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, W, 55);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, W, 56);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, W, 57);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, W, 58);
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, W, 59);
+
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 60);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 61);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 62);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 63);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 64);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 65);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 66);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 67);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 68);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 69);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 70);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 71);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 72);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 73);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 74);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, W, 75);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, W, 76);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, W, 77);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, W, 78);
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, W, 79);
+
+ ihv[0] += a;
+ ihv[1] += b;
+ ihv[2] += c;
+ ihv[3] += d;
+ ihv[4] += e;
+}
+
+void
+sha1_compression_states(uint32_t ihv[5],
+ const uint32_t m[16],
+ uint32_t W[80],
+ uint32_t states[80][5])
+{
+ uint32_t a = ihv[0], b = ihv[1], c = ihv[2], d = ihv[3], e = ihv[4];
+ uint32_t temp;
+
+#ifdef DOSTORESTATE00
+ SHA1_STORE_STATE(0)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 0, temp);
+
+#ifdef DOSTORESTATE01
+ SHA1_STORE_STATE(1)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 1, temp);
+
+#ifdef DOSTORESTATE02
+ SHA1_STORE_STATE(2)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 2, temp);
+
+#ifdef DOSTORESTATE03
+ SHA1_STORE_STATE(3)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 3, temp);
+
+#ifdef DOSTORESTATE04
+ SHA1_STORE_STATE(4)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 4, temp);
+
+#ifdef DOSTORESTATE05
+ SHA1_STORE_STATE(5)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 5, temp);
+
+#ifdef DOSTORESTATE06
+ SHA1_STORE_STATE(6)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 6, temp);
+
+#ifdef DOSTORESTATE07
+ SHA1_STORE_STATE(7)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 7, temp);
+
+#ifdef DOSTORESTATE08
+ SHA1_STORE_STATE(8)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 8, temp);
+
+#ifdef DOSTORESTATE09
+ SHA1_STORE_STATE(9)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 9, temp);
+
+#ifdef DOSTORESTATE10
+ SHA1_STORE_STATE(10)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 10, temp);
+
+#ifdef DOSTORESTATE11
+ SHA1_STORE_STATE(11)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(e, a, b, c, d, m, W, 11, temp);
+
+#ifdef DOSTORESTATE12
+ SHA1_STORE_STATE(12)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(d, e, a, b, c, m, W, 12, temp);
+
+#ifdef DOSTORESTATE13
+ SHA1_STORE_STATE(13)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(c, d, e, a, b, m, W, 13, temp);
+
+#ifdef DOSTORESTATE14
+ SHA1_STORE_STATE(14)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(b, c, d, e, a, m, W, 14, temp);
+
+#ifdef DOSTORESTATE15
+ SHA1_STORE_STATE(15)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_LOAD(a, b, c, d, e, m, W, 15, temp);
+
+#ifdef DOSTORESTATE16
+ SHA1_STORE_STATE(16)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(e, a, b, c, d, W, 16, temp);
+
+#ifdef DOSTORESTATE17
+ SHA1_STORE_STATE(17)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(d, e, a, b, c, W, 17, temp);
+
+#ifdef DOSTORESTATE18
+ SHA1_STORE_STATE(18)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(c, d, e, a, b, W, 18, temp);
+
+#ifdef DOSTORESTATE19
+ SHA1_STORE_STATE(19)
+#endif
+ SHA1COMPRESS_FULL_ROUND1_STEP_EXPAND(b, c, d, e, a, W, 19, temp);
+
+#ifdef DOSTORESTATE20
+ SHA1_STORE_STATE(20)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 20, temp);
+
+#ifdef DOSTORESTATE21
+ SHA1_STORE_STATE(21)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 21, temp);
+
+#ifdef DOSTORESTATE22
+ SHA1_STORE_STATE(22)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 22, temp);
+
+#ifdef DOSTORESTATE23
+ SHA1_STORE_STATE(23)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 23, temp);
+
+#ifdef DOSTORESTATE24
+ SHA1_STORE_STATE(24)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 24, temp);
+
+#ifdef DOSTORESTATE25
+ SHA1_STORE_STATE(25)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 25, temp);
+
+#ifdef DOSTORESTATE26
+ SHA1_STORE_STATE(26)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 26, temp);
+
+#ifdef DOSTORESTATE27
+ SHA1_STORE_STATE(27)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 27, temp);
+
+#ifdef DOSTORESTATE28
+ SHA1_STORE_STATE(28)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 28, temp);
+
+#ifdef DOSTORESTATE29
+ SHA1_STORE_STATE(29)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 29, temp);
+
+#ifdef DOSTORESTATE30
+ SHA1_STORE_STATE(30)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 30, temp);
+
+#ifdef DOSTORESTATE31
+ SHA1_STORE_STATE(31)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 31, temp);
+
+#ifdef DOSTORESTATE32
+ SHA1_STORE_STATE(32)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 32, temp);
+
+#ifdef DOSTORESTATE33
+ SHA1_STORE_STATE(33)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 33, temp);
+
+#ifdef DOSTORESTATE34
+ SHA1_STORE_STATE(34)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 34, temp);
+
+#ifdef DOSTORESTATE35
+ SHA1_STORE_STATE(35)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(a, b, c, d, e, W, 35, temp);
+
+#ifdef DOSTORESTATE36
+ SHA1_STORE_STATE(36)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(e, a, b, c, d, W, 36, temp);
+
+#ifdef DOSTORESTATE37
+ SHA1_STORE_STATE(37)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(d, e, a, b, c, W, 37, temp);
+
+#ifdef DOSTORESTATE38
+ SHA1_STORE_STATE(38)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(c, d, e, a, b, W, 38, temp);
+
+#ifdef DOSTORESTATE39
+ SHA1_STORE_STATE(39)
+#endif
+ SHA1COMPRESS_FULL_ROUND2_STEP(b, c, d, e, a, W, 39, temp);
+
+#ifdef DOSTORESTATE40
+ SHA1_STORE_STATE(40)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 40, temp);
+
+#ifdef DOSTORESTATE41
+ SHA1_STORE_STATE(41)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 41, temp);
+
+#ifdef DOSTORESTATE42
+ SHA1_STORE_STATE(42)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 42, temp);
+
+#ifdef DOSTORESTATE43
+ SHA1_STORE_STATE(43)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 43, temp);
+
+#ifdef DOSTORESTATE44
+ SHA1_STORE_STATE(44)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 44, temp);
+
+#ifdef DOSTORESTATE45
+ SHA1_STORE_STATE(45)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 45, temp);
+
+#ifdef DOSTORESTATE46
+ SHA1_STORE_STATE(46)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 46, temp);
+
+#ifdef DOSTORESTATE47
+ SHA1_STORE_STATE(47)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 47, temp);
+
+#ifdef DOSTORESTATE48
+ SHA1_STORE_STATE(48)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 48, temp);
+
+#ifdef DOSTORESTATE49
+ SHA1_STORE_STATE(49)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 49, temp);
+
+#ifdef DOSTORESTATE50
+ SHA1_STORE_STATE(50)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 50, temp);
+
+#ifdef DOSTORESTATE51
+ SHA1_STORE_STATE(51)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 51, temp);
+
+#ifdef DOSTORESTATE52
+ SHA1_STORE_STATE(52)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 52, temp);
+
+#ifdef DOSTORESTATE53
+ SHA1_STORE_STATE(53)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 53, temp);
+
+#ifdef DOSTORESTATE54
+ SHA1_STORE_STATE(54)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 54, temp);
+
+#ifdef DOSTORESTATE55
+ SHA1_STORE_STATE(55)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(a, b, c, d, e, W, 55, temp);
+
+#ifdef DOSTORESTATE56
+ SHA1_STORE_STATE(56)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(e, a, b, c, d, W, 56, temp);
+
+#ifdef DOSTORESTATE57
+ SHA1_STORE_STATE(57)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(d, e, a, b, c, W, 57, temp);
+
+#ifdef DOSTORESTATE58
+ SHA1_STORE_STATE(58)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(c, d, e, a, b, W, 58, temp);
+
+#ifdef DOSTORESTATE59
+ SHA1_STORE_STATE(59)
+#endif
+ SHA1COMPRESS_FULL_ROUND3_STEP(b, c, d, e, a, W, 59, temp);
+
+#ifdef DOSTORESTATE60
+ SHA1_STORE_STATE(60)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 60, temp);
+
+#ifdef DOSTORESTATE61
+ SHA1_STORE_STATE(61)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 61, temp);
+
+#ifdef DOSTORESTATE62
+ SHA1_STORE_STATE(62)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 62, temp);
+
+#ifdef DOSTORESTATE63
+ SHA1_STORE_STATE(63)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 63, temp);
+
+#ifdef DOSTORESTATE64
+ SHA1_STORE_STATE(64)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 64, temp);
+
+#ifdef DOSTORESTATE65
+ SHA1_STORE_STATE(65)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 65, temp);
+
+#ifdef DOSTORESTATE66
+ SHA1_STORE_STATE(66)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 66, temp);
+
+#ifdef DOSTORESTATE67
+ SHA1_STORE_STATE(67)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 67, temp);
+
+#ifdef DOSTORESTATE68
+ SHA1_STORE_STATE(68)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 68, temp);
+
+#ifdef DOSTORESTATE69
+ SHA1_STORE_STATE(69)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 69, temp);
+
+#ifdef DOSTORESTATE70
+ SHA1_STORE_STATE(70)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 70, temp);
+
+#ifdef DOSTORESTATE71
+ SHA1_STORE_STATE(71)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 71, temp);
+
+#ifdef DOSTORESTATE72
+ SHA1_STORE_STATE(72)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 72, temp);
+
+#ifdef DOSTORESTATE73
+ SHA1_STORE_STATE(73)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 73, temp);
+
+#ifdef DOSTORESTATE74
+ SHA1_STORE_STATE(74)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 74, temp);
+
+#ifdef DOSTORESTATE75
+ SHA1_STORE_STATE(75)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(a, b, c, d, e, W, 75, temp);
+
+#ifdef DOSTORESTATE76
+ SHA1_STORE_STATE(76)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(e, a, b, c, d, W, 76, temp);
+
+#ifdef DOSTORESTATE77
+ SHA1_STORE_STATE(77)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(d, e, a, b, c, W, 77, temp);
+
+#ifdef DOSTORESTATE78
+ SHA1_STORE_STATE(78)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(c, d, e, a, b, W, 78, temp);
+
+#ifdef DOSTORESTATE79
+ SHA1_STORE_STATE(79)
+#endif
+ SHA1COMPRESS_FULL_ROUND4_STEP(b, c, d, e, a, W, 79, temp);
+
+ ihv[0] += a;
+ ihv[1] += b;
+ ihv[2] += c;
+ ihv[3] += d;
+ ihv[4] += e;
+}
+
+#define SHA1_RECOMPRESS(t) \
+ static void sha1recompress_fast_##t( \
+ uint32_t ihvin[5], uint32_t ihvout[5], const uint32_t me2[80], const uint32_t state[5]) \
+ { \
+ uint32_t a = state[0], b = state[1], c = state[2], d = state[3], e = state[4]; \
+ if (t > 79) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 79); \
+ if (t > 78) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 78); \
+ if (t > 77) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 77); \
+ if (t > 76) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 76); \
+ if (t > 75) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 75); \
+ if (t > 74) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 74); \
+ if (t > 73) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 73); \
+ if (t > 72) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 72); \
+ if (t > 71) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 71); \
+ if (t > 70) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 70); \
+ if (t > 69) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 69); \
+ if (t > 68) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 68); \
+ if (t > 67) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 67); \
+ if (t > 66) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 66); \
+ if (t > 65) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 65); \
+ if (t > 64) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(b, c, d, e, a, me2, 64); \
+ if (t > 63) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(c, d, e, a, b, me2, 63); \
+ if (t > 62) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(d, e, a, b, c, me2, 62); \
+ if (t > 61) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(e, a, b, c, d, me2, 61); \
+ if (t > 60) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP_BW(a, b, c, d, e, me2, 60); \
+ if (t > 59) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 59); \
+ if (t > 58) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 58); \
+ if (t > 57) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 57); \
+ if (t > 56) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 56); \
+ if (t > 55) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 55); \
+ if (t > 54) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 54); \
+ if (t > 53) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 53); \
+ if (t > 52) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 52); \
+ if (t > 51) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 51); \
+ if (t > 50) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 50); \
+ if (t > 49) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 49); \
+ if (t > 48) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 48); \
+ if (t > 47) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 47); \
+ if (t > 46) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 46); \
+ if (t > 45) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 45); \
+ if (t > 44) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(b, c, d, e, a, me2, 44); \
+ if (t > 43) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(c, d, e, a, b, me2, 43); \
+ if (t > 42) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(d, e, a, b, c, me2, 42); \
+ if (t > 41) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(e, a, b, c, d, me2, 41); \
+ if (t > 40) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP_BW(a, b, c, d, e, me2, 40); \
+ if (t > 39) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 39); \
+ if (t > 38) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 38); \
+ if (t > 37) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 37); \
+ if (t > 36) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 36); \
+ if (t > 35) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 35); \
+ if (t > 34) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 34); \
+ if (t > 33) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 33); \
+ if (t > 32) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 32); \
+ if (t > 31) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 31); \
+ if (t > 30) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 30); \
+ if (t > 29) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 29); \
+ if (t > 28) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 28); \
+ if (t > 27) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 27); \
+ if (t > 26) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 26); \
+ if (t > 25) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 25); \
+ if (t > 24) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(b, c, d, e, a, me2, 24); \
+ if (t > 23) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(c, d, e, a, b, me2, 23); \
+ if (t > 22) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(d, e, a, b, c, me2, 22); \
+ if (t > 21) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(e, a, b, c, d, me2, 21); \
+ if (t > 20) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP_BW(a, b, c, d, e, me2, 20); \
+ if (t > 19) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 19); \
+ if (t > 18) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 18); \
+ if (t > 17) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 17); \
+ if (t > 16) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 16); \
+ if (t > 15) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 15); \
+ if (t > 14) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 14); \
+ if (t > 13) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 13); \
+ if (t > 12) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 12); \
+ if (t > 11) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 11); \
+ if (t > 10) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 10); \
+ if (t > 9) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 9); \
+ if (t > 8) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 8); \
+ if (t > 7) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 7); \
+ if (t > 6) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 6); \
+ if (t > 5) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 5); \
+ if (t > 4) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(b, c, d, e, a, me2, 4); \
+ if (t > 3) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(c, d, e, a, b, me2, 3); \
+ if (t > 2) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(d, e, a, b, c, me2, 2); \
+ if (t > 1) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(e, a, b, c, d, me2, 1); \
+ if (t > 0) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP_BW(a, b, c, d, e, me2, 0); \
+ ihvin[0] = a; \
+ ihvin[1] = b; \
+ ihvin[2] = c; \
+ ihvin[3] = d; \
+ ihvin[4] = e; \
+ a = state[0]; \
+ b = state[1]; \
+ c = state[2]; \
+ d = state[3]; \
+ e = state[4]; \
+ if (t <= 0) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 0); \
+ if (t <= 1) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 1); \
+ if (t <= 2) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 2); \
+ if (t <= 3) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 3); \
+ if (t <= 4) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 4); \
+ if (t <= 5) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 5); \
+ if (t <= 6) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 6); \
+ if (t <= 7) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 7); \
+ if (t <= 8) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 8); \
+ if (t <= 9) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 9); \
+ if (t <= 10) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 10); \
+ if (t <= 11) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 11); \
+ if (t <= 12) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 12); \
+ if (t <= 13) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 13); \
+ if (t <= 14) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 14); \
+ if (t <= 15) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(a, b, c, d, e, me2, 15); \
+ if (t <= 16) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(e, a, b, c, d, me2, 16); \
+ if (t <= 17) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(d, e, a, b, c, me2, 17); \
+ if (t <= 18) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(c, d, e, a, b, me2, 18); \
+ if (t <= 19) \
+ HASHCLASH_SHA1COMPRESS_ROUND1_STEP(b, c, d, e, a, me2, 19); \
+ if (t <= 20) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 20); \
+ if (t <= 21) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 21); \
+ if (t <= 22) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 22); \
+ if (t <= 23) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 23); \
+ if (t <= 24) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 24); \
+ if (t <= 25) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 25); \
+ if (t <= 26) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 26); \
+ if (t <= 27) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 27); \
+ if (t <= 28) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 28); \
+ if (t <= 29) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 29); \
+ if (t <= 30) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 30); \
+ if (t <= 31) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 31); \
+ if (t <= 32) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 32); \
+ if (t <= 33) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 33); \
+ if (t <= 34) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 34); \
+ if (t <= 35) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(a, b, c, d, e, me2, 35); \
+ if (t <= 36) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(e, a, b, c, d, me2, 36); \
+ if (t <= 37) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(d, e, a, b, c, me2, 37); \
+ if (t <= 38) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(c, d, e, a, b, me2, 38); \
+ if (t <= 39) \
+ HASHCLASH_SHA1COMPRESS_ROUND2_STEP(b, c, d, e, a, me2, 39); \
+ if (t <= 40) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 40); \
+ if (t <= 41) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 41); \
+ if (t <= 42) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 42); \
+ if (t <= 43) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 43); \
+ if (t <= 44) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 44); \
+ if (t <= 45) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 45); \
+ if (t <= 46) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 46); \
+ if (t <= 47) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 47); \
+ if (t <= 48) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 48); \
+ if (t <= 49) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 49); \
+ if (t <= 50) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 50); \
+ if (t <= 51) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 51); \
+ if (t <= 52) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 52); \
+ if (t <= 53) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 53); \
+ if (t <= 54) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 54); \
+ if (t <= 55) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(a, b, c, d, e, me2, 55); \
+ if (t <= 56) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(e, a, b, c, d, me2, 56); \
+ if (t <= 57) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(d, e, a, b, c, me2, 57); \
+ if (t <= 58) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(c, d, e, a, b, me2, 58); \
+ if (t <= 59) \
+ HASHCLASH_SHA1COMPRESS_ROUND3_STEP(b, c, d, e, a, me2, 59); \
+ if (t <= 60) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 60); \
+ if (t <= 61) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 61); \
+ if (t <= 62) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 62); \
+ if (t <= 63) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 63); \
+ if (t <= 64) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 64); \
+ if (t <= 65) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 65); \
+ if (t <= 66) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 66); \
+ if (t <= 67) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 67); \
+ if (t <= 68) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 68); \
+ if (t <= 69) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 69); \
+ if (t <= 70) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 70); \
+ if (t <= 71) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 71); \
+ if (t <= 72) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 72); \
+ if (t <= 73) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 73); \
+ if (t <= 74) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 74); \
+ if (t <= 75) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(a, b, c, d, e, me2, 75); \
+ if (t <= 76) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(e, a, b, c, d, me2, 76); \
+ if (t <= 77) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(d, e, a, b, c, me2, 77); \
+ if (t <= 78) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(c, d, e, a, b, me2, 78); \
+ if (t <= 79) \
+ HASHCLASH_SHA1COMPRESS_ROUND4_STEP(b, c, d, e, a, me2, 79); \
+ ihvout[0] = ihvin[0] + a; \
+ ihvout[1] = ihvin[1] + b; \
+ ihvout[2] = ihvin[2] + c; \
+ ihvout[3] = ihvin[3] + d; \
+ ihvout[4] = ihvin[4] + e; \
+ }
+
+#ifdef _MSC_VER
+#pragma warning(push)
+#pragma warning(disable : 4127) /* Compiler complains about the checks in the above macro \
+ being constant. */
+#endif
+
+#ifdef DOSTORESTATE0
+SHA1_RECOMPRESS(0)
+#endif
+
+#ifdef DOSTORESTATE1
+SHA1_RECOMPRESS(1)
+#endif
+
+#ifdef DOSTORESTATE2
+SHA1_RECOMPRESS(2)
+#endif
+
+#ifdef DOSTORESTATE3
+SHA1_RECOMPRESS(3)
+#endif
+
+#ifdef DOSTORESTATE4
+SHA1_RECOMPRESS(4)
+#endif
+
+#ifdef DOSTORESTATE5
+SHA1_RECOMPRESS(5)
+#endif
+
+#ifdef DOSTORESTATE6
+SHA1_RECOMPRESS(6)
+#endif
+
+#ifdef DOSTORESTATE7
+SHA1_RECOMPRESS(7)
+#endif
+
+#ifdef DOSTORESTATE8
+SHA1_RECOMPRESS(8)
+#endif
+
+#ifdef DOSTORESTATE9
+SHA1_RECOMPRESS(9)
+#endif
+
+#ifdef DOSTORESTATE10
+SHA1_RECOMPRESS(10)
+#endif
+
+#ifdef DOSTORESTATE11
+SHA1_RECOMPRESS(11)
+#endif
+
+#ifdef DOSTORESTATE12
+SHA1_RECOMPRESS(12)
+#endif
+
+#ifdef DOSTORESTATE13
+SHA1_RECOMPRESS(13)
+#endif
+
+#ifdef DOSTORESTATE14
+SHA1_RECOMPRESS(14)
+#endif
+
+#ifdef DOSTORESTATE15
+SHA1_RECOMPRESS(15)
+#endif
+
+#ifdef DOSTORESTATE16
+SHA1_RECOMPRESS(16)
+#endif
+
+#ifdef DOSTORESTATE17
+SHA1_RECOMPRESS(17)
+#endif
+
+#ifdef DOSTORESTATE18
+SHA1_RECOMPRESS(18)
+#endif
+
+#ifdef DOSTORESTATE19
+SHA1_RECOMPRESS(19)
+#endif
+
+#ifdef DOSTORESTATE20
+SHA1_RECOMPRESS(20)
+#endif
+
+#ifdef DOSTORESTATE21
+SHA1_RECOMPRESS(21)
+#endif
+
+#ifdef DOSTORESTATE22
+SHA1_RECOMPRESS(22)
+#endif
+
+#ifdef DOSTORESTATE23
+SHA1_RECOMPRESS(23)
+#endif
+
+#ifdef DOSTORESTATE24
+SHA1_RECOMPRESS(24)
+#endif
+
+#ifdef DOSTORESTATE25
+SHA1_RECOMPRESS(25)
+#endif
+
+#ifdef DOSTORESTATE26
+SHA1_RECOMPRESS(26)
+#endif
+
+#ifdef DOSTORESTATE27
+SHA1_RECOMPRESS(27)
+#endif
+
+#ifdef DOSTORESTATE28
+SHA1_RECOMPRESS(28)
+#endif
+
+#ifdef DOSTORESTATE29
+SHA1_RECOMPRESS(29)
+#endif
+
+#ifdef DOSTORESTATE30
+SHA1_RECOMPRESS(30)
+#endif
+
+#ifdef DOSTORESTATE31
+SHA1_RECOMPRESS(31)
+#endif
+
+#ifdef DOSTORESTATE32
+SHA1_RECOMPRESS(32)
+#endif
+
+#ifdef DOSTORESTATE33
+SHA1_RECOMPRESS(33)
+#endif
+
+#ifdef DOSTORESTATE34
+SHA1_RECOMPRESS(34)
+#endif
+
+#ifdef DOSTORESTATE35
+SHA1_RECOMPRESS(35)
+#endif
+
+#ifdef DOSTORESTATE36
+SHA1_RECOMPRESS(36)
+#endif
+
+#ifdef DOSTORESTATE37
+SHA1_RECOMPRESS(37)
+#endif
+
+#ifdef DOSTORESTATE38
+SHA1_RECOMPRESS(38)
+#endif
+
+#ifdef DOSTORESTATE39
+SHA1_RECOMPRESS(39)
+#endif
+
+#ifdef DOSTORESTATE40
+SHA1_RECOMPRESS(40)
+#endif
+
+#ifdef DOSTORESTATE41
+SHA1_RECOMPRESS(41)
+#endif
+
+#ifdef DOSTORESTATE42
+SHA1_RECOMPRESS(42)
+#endif
+
+#ifdef DOSTORESTATE43
+SHA1_RECOMPRESS(43)
+#endif
+
+#ifdef DOSTORESTATE44
+SHA1_RECOMPRESS(44)
+#endif
+
+#ifdef DOSTORESTATE45
+SHA1_RECOMPRESS(45)
+#endif
+
+#ifdef DOSTORESTATE46
+SHA1_RECOMPRESS(46)
+#endif
+
+#ifdef DOSTORESTATE47
+SHA1_RECOMPRESS(47)
+#endif
+
+#ifdef DOSTORESTATE48
+SHA1_RECOMPRESS(48)
+#endif
+
+#ifdef DOSTORESTATE49
+SHA1_RECOMPRESS(49)
+#endif
+
+#ifdef DOSTORESTATE50
+SHA1_RECOMPRESS(50)
+#endif
+
+#ifdef DOSTORESTATE51
+SHA1_RECOMPRESS(51)
+#endif
+
+#ifdef DOSTORESTATE52
+SHA1_RECOMPRESS(52)
+#endif
+
+#ifdef DOSTORESTATE53
+SHA1_RECOMPRESS(53)
+#endif
+
+#ifdef DOSTORESTATE54
+SHA1_RECOMPRESS(54)
+#endif
+
+#ifdef DOSTORESTATE55
+SHA1_RECOMPRESS(55)
+#endif
+
+#ifdef DOSTORESTATE56
+SHA1_RECOMPRESS(56)
+#endif
+
+#ifdef DOSTORESTATE57
+SHA1_RECOMPRESS(57)
+#endif
+
+#ifdef DOSTORESTATE58
+SHA1_RECOMPRESS(58)
+#endif
+
+#ifdef DOSTORESTATE59
+SHA1_RECOMPRESS(59)
+#endif
+
+#ifdef DOSTORESTATE60
+SHA1_RECOMPRESS(60)
+#endif
+
+#ifdef DOSTORESTATE61
+SHA1_RECOMPRESS(61)
+#endif
+
+#ifdef DOSTORESTATE62
+SHA1_RECOMPRESS(62)
+#endif
+
+#ifdef DOSTORESTATE63
+SHA1_RECOMPRESS(63)
+#endif
+
+#ifdef DOSTORESTATE64
+SHA1_RECOMPRESS(64)
+#endif
+
+#ifdef DOSTORESTATE65
+SHA1_RECOMPRESS(65)
+#endif
+
+#ifdef DOSTORESTATE66
+SHA1_RECOMPRESS(66)
+#endif
+
+#ifdef DOSTORESTATE67
+SHA1_RECOMPRESS(67)
+#endif
+
+#ifdef DOSTORESTATE68
+SHA1_RECOMPRESS(68)
+#endif
+
+#ifdef DOSTORESTATE69
+SHA1_RECOMPRESS(69)
+#endif
+
+#ifdef DOSTORESTATE70
+SHA1_RECOMPRESS(70)
+#endif
+
+#ifdef DOSTORESTATE71
+SHA1_RECOMPRESS(71)
+#endif
+
+#ifdef DOSTORESTATE72
+SHA1_RECOMPRESS(72)
+#endif
+
+#ifdef DOSTORESTATE73
+SHA1_RECOMPRESS(73)
+#endif
+
+#ifdef DOSTORESTATE74
+SHA1_RECOMPRESS(74)
+#endif
+
+#ifdef DOSTORESTATE75
+SHA1_RECOMPRESS(75)
+#endif
+
+#ifdef DOSTORESTATE76
+SHA1_RECOMPRESS(76)
+#endif
+
+#ifdef DOSTORESTATE77
+SHA1_RECOMPRESS(77)
+#endif
+
+#ifdef DOSTORESTATE78
+SHA1_RECOMPRESS(78)
+#endif
+
+#ifdef DOSTORESTATE79
+SHA1_RECOMPRESS(79)
+#endif
+
+#ifdef _MSC_VER
+#pragma warning(pop)
+#endif
+
+static void
+sha1_recompression_step(uint32_t step,
+ uint32_t ihvin[5],
+ uint32_t ihvout[5],
+ const uint32_t me2[80],
+ const uint32_t state[5])
+{
+ switch (step) {
+#ifdef DOSTORESTATE0
+ case 0:
+ sha1recompress_fast_0(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE1
+ case 1:
+ sha1recompress_fast_1(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE2
+ case 2:
+ sha1recompress_fast_2(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE3
+ case 3:
+ sha1recompress_fast_3(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE4
+ case 4:
+ sha1recompress_fast_4(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE5
+ case 5:
+ sha1recompress_fast_5(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE6
+ case 6:
+ sha1recompress_fast_6(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE7
+ case 7:
+ sha1recompress_fast_7(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE8
+ case 8:
+ sha1recompress_fast_8(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE9
+ case 9:
+ sha1recompress_fast_9(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE10
+ case 10:
+ sha1recompress_fast_10(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE11
+ case 11:
+ sha1recompress_fast_11(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE12
+ case 12:
+ sha1recompress_fast_12(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE13
+ case 13:
+ sha1recompress_fast_13(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE14
+ case 14:
+ sha1recompress_fast_14(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE15
+ case 15:
+ sha1recompress_fast_15(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE16
+ case 16:
+ sha1recompress_fast_16(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE17
+ case 17:
+ sha1recompress_fast_17(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE18
+ case 18:
+ sha1recompress_fast_18(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE19
+ case 19:
+ sha1recompress_fast_19(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE20
+ case 20:
+ sha1recompress_fast_20(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE21
+ case 21:
+ sha1recompress_fast_21(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE22
+ case 22:
+ sha1recompress_fast_22(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE23
+ case 23:
+ sha1recompress_fast_23(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE24
+ case 24:
+ sha1recompress_fast_24(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE25
+ case 25:
+ sha1recompress_fast_25(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE26
+ case 26:
+ sha1recompress_fast_26(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE27
+ case 27:
+ sha1recompress_fast_27(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE28
+ case 28:
+ sha1recompress_fast_28(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE29
+ case 29:
+ sha1recompress_fast_29(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE30
+ case 30:
+ sha1recompress_fast_30(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE31
+ case 31:
+ sha1recompress_fast_31(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE32
+ case 32:
+ sha1recompress_fast_32(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE33
+ case 33:
+ sha1recompress_fast_33(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE34
+ case 34:
+ sha1recompress_fast_34(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE35
+ case 35:
+ sha1recompress_fast_35(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE36
+ case 36:
+ sha1recompress_fast_36(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE37
+ case 37:
+ sha1recompress_fast_37(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE38
+ case 38:
+ sha1recompress_fast_38(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE39
+ case 39:
+ sha1recompress_fast_39(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE40
+ case 40:
+ sha1recompress_fast_40(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE41
+ case 41:
+ sha1recompress_fast_41(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE42
+ case 42:
+ sha1recompress_fast_42(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE43
+ case 43:
+ sha1recompress_fast_43(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE44
+ case 44:
+ sha1recompress_fast_44(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE45
+ case 45:
+ sha1recompress_fast_45(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE46
+ case 46:
+ sha1recompress_fast_46(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE47
+ case 47:
+ sha1recompress_fast_47(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE48
+ case 48:
+ sha1recompress_fast_48(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE49
+ case 49:
+ sha1recompress_fast_49(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE50
+ case 50:
+ sha1recompress_fast_50(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE51
+ case 51:
+ sha1recompress_fast_51(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE52
+ case 52:
+ sha1recompress_fast_52(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE53
+ case 53:
+ sha1recompress_fast_53(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE54
+ case 54:
+ sha1recompress_fast_54(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE55
+ case 55:
+ sha1recompress_fast_55(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE56
+ case 56:
+ sha1recompress_fast_56(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE57
+ case 57:
+ sha1recompress_fast_57(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE58
+ case 58:
+ sha1recompress_fast_58(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE59
+ case 59:
+ sha1recompress_fast_59(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE60
+ case 60:
+ sha1recompress_fast_60(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE61
+ case 61:
+ sha1recompress_fast_61(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE62
+ case 62:
+ sha1recompress_fast_62(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE63
+ case 63:
+ sha1recompress_fast_63(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE64
+ case 64:
+ sha1recompress_fast_64(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE65
+ case 65:
+ sha1recompress_fast_65(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE66
+ case 66:
+ sha1recompress_fast_66(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE67
+ case 67:
+ sha1recompress_fast_67(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE68
+ case 68:
+ sha1recompress_fast_68(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE69
+ case 69:
+ sha1recompress_fast_69(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE70
+ case 70:
+ sha1recompress_fast_70(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE71
+ case 71:
+ sha1recompress_fast_71(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE72
+ case 72:
+ sha1recompress_fast_72(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE73
+ case 73:
+ sha1recompress_fast_73(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE74
+ case 74:
+ sha1recompress_fast_74(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE75
+ case 75:
+ sha1recompress_fast_75(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE76
+ case 76:
+ sha1recompress_fast_76(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE77
+ case 77:
+ sha1recompress_fast_77(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE78
+ case 78:
+ sha1recompress_fast_78(ihvin, ihvout, me2, state);
+ break;
+#endif
+#ifdef DOSTORESTATE79
+ case 79:
+ sha1recompress_fast_79(ihvin, ihvout, me2, state);
+ break;
+#endif
+ default:
+ abort();
+ }
+}
+
+static void
+sha1_process(SHA1_CTX *ctx, const uint32_t block[16])
+{
+ unsigned i, j;
+ uint32_t ubc_dv_mask[DVMASKSIZE] = {0xFFFFFFFF};
+ uint32_t ihvtmp[5];
+
+ ctx->ihv1[0] = ctx->ihv[0];
+ ctx->ihv1[1] = ctx->ihv[1];
+ ctx->ihv1[2] = ctx->ihv[2];
+ ctx->ihv1[3] = ctx->ihv[3];
+ ctx->ihv1[4] = ctx->ihv[4];
+
+ sha1_compression_states(ctx->ihv, block, ctx->m1, ctx->states);
+
+ if (ctx->detect_coll) {
+ if (ctx->ubc_check) {
+ ubc_check(ctx->m1, ubc_dv_mask);
+ }
+
+ if (ubc_dv_mask[0] != 0) {
+ for (i = 0; sha1_dvs[i].dvType != 0; ++i) {
+ if (ubc_dv_mask[0] & ((uint32_t)(1) << sha1_dvs[i].maskb)) {
+ for (j = 0; j < 80; ++j)
+ ctx->m2[j] = ctx->m1[j] ^ sha1_dvs[i].dm[j];
+
+ sha1_recompression_step(sha1_dvs[i].testt,
+ ctx->ihv2,
+ ihvtmp,
+ ctx->m2,
+ ctx->states[sha1_dvs[i].testt]);
+
+ /* to verify SHA-1 collision detection code with collisions for
+ * reduced-step SHA-1 */
+ if ((0 == ((ihvtmp[0] ^ ctx->ihv[0]) | (ihvtmp[1] ^ ctx->ihv[1]) |
+ (ihvtmp[2] ^ ctx->ihv[2]) | (ihvtmp[3] ^ ctx->ihv[3]) |
+ (ihvtmp[4] ^ ctx->ihv[4]))) ||
+ (ctx->reduced_round_coll &&
+ 0 == ((ctx->ihv1[0] ^ ctx->ihv2[0]) | (ctx->ihv1[1] ^ ctx->ihv2[1]) |
+ (ctx->ihv1[2] ^ ctx->ihv2[2]) | (ctx->ihv1[3] ^ ctx->ihv2[3]) |
+ (ctx->ihv1[4] ^ ctx->ihv2[4])))) {
+ ctx->found_collision = 1;
+
+ if (ctx->safe_hash) {
+ sha1_compression_W(ctx->ihv, ctx->m1);
+ sha1_compression_W(ctx->ihv, ctx->m1);
+ }
+
+ break;
+ }
+ }
+ }
+ }
+ }
+}
+
+void
+SHA1DCInit(SHA1_CTX *ctx)
+{
+ ctx->total = 0;
+ ctx->ihv[0] = 0x67452301;
+ ctx->ihv[1] = 0xEFCDAB89;
+ ctx->ihv[2] = 0x98BADCFE;
+ ctx->ihv[3] = 0x10325476;
+ ctx->ihv[4] = 0xC3D2E1F0;
+ ctx->found_collision = 0;
+ ctx->safe_hash = SHA1DC_INIT_SAFE_HASH_DEFAULT;
+ ctx->ubc_check = 1;
+ ctx->detect_coll = 1;
+ ctx->reduced_round_coll = 0;
+ ctx->callback = NULL;
+}
+
+void
+SHA1DCSetSafeHash(SHA1_CTX *ctx, int safehash)
+{
+ if (safehash)
+ ctx->safe_hash = 1;
+ else
+ ctx->safe_hash = 0;
+}
+
+void
+SHA1DCSetUseUBC(SHA1_CTX *ctx, int ubc_check)
+{
+ if (ubc_check)
+ ctx->ubc_check = 1;
+ else
+ ctx->ubc_check = 0;
+}
+
+void
+SHA1DCSetUseDetectColl(SHA1_CTX *ctx, int detect_coll)
+{
+ if (detect_coll)
+ ctx->detect_coll = 1;
+ else
+ ctx->detect_coll = 0;
+}
+
+void
+SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *ctx, int reduced_round_coll)
+{
+ if (reduced_round_coll)
+ ctx->reduced_round_coll = 1;
+ else
+ ctx->reduced_round_coll = 0;
+}
+
+void
+SHA1DCSetCallback(SHA1_CTX *ctx, collision_block_callback callback)
+{
+ ctx->callback = callback;
+}
+
+void
+SHA1DCUpdate(SHA1_CTX *ctx, const char *buf, size_t len)
+{
+ unsigned left, fill;
+
+ if (len == 0)
+ return;
+
+ left = ctx->total & 63;
+ fill = 64 - left;
+
+ if (left && len >= fill) {
+ ctx->total += fill;
+ memcpy(ctx->buffer + left, buf, fill);
+ sha1_process(ctx, (uint32_t *) (ctx->buffer));
+ buf += fill;
+ len -= fill;
+ left = 0;
+ }
+ while (len >= 64) {
+ ctx->total += 64;
+
+#if defined(SHA1DC_ALLOW_UNALIGNED_ACCESS)
+ sha1_process(ctx, (uint32_t *) (buf));
+#else
+ memcpy(ctx->buffer, buf, 64);
+ sha1_process(ctx, (uint32_t *) (ctx->buffer));
+#endif /* defined(SHA1DC_ALLOW_UNALIGNED_ACCESS) */
+ buf += 64;
+ len -= 64;
+ }
+ if (len > 0) {
+ ctx->total += len;
+ memcpy(ctx->buffer + left, buf, len);
+ }
+}
+
+static const unsigned char sha1_padding[64] = {
+ 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
+
+int
+SHA1DCFinal(unsigned char output[20], SHA1_CTX *ctx)
+{
+ uint32_t last = ctx->total & 63;
+ uint32_t padn = (last < 56) ? (56 - last) : (120 - last);
+ uint64_t total;
+ SHA1DCUpdate(ctx, (const char *) (sha1_padding), padn);
+
+ total = ctx->total - padn;
+ total <<= 3;
+ ctx->buffer[56] = (unsigned char) (total >> 56);
+ ctx->buffer[57] = (unsigned char) (total >> 48);
+ ctx->buffer[58] = (unsigned char) (total >> 40);
+ ctx->buffer[59] = (unsigned char) (total >> 32);
+ ctx->buffer[60] = (unsigned char) (total >> 24);
+ ctx->buffer[61] = (unsigned char) (total >> 16);
+ ctx->buffer[62] = (unsigned char) (total >> 8);
+ ctx->buffer[63] = (unsigned char) (total);
+ sha1_process(ctx, (uint32_t *) (ctx->buffer));
+ output[0] = (unsigned char) (ctx->ihv[0] >> 24);
+ output[1] = (unsigned char) (ctx->ihv[0] >> 16);
+ output[2] = (unsigned char) (ctx->ihv[0] >> 8);
+ output[3] = (unsigned char) (ctx->ihv[0]);
+ output[4] = (unsigned char) (ctx->ihv[1] >> 24);
+ output[5] = (unsigned char) (ctx->ihv[1] >> 16);
+ output[6] = (unsigned char) (ctx->ihv[1] >> 8);
+ output[7] = (unsigned char) (ctx->ihv[1]);
+ output[8] = (unsigned char) (ctx->ihv[2] >> 24);
+ output[9] = (unsigned char) (ctx->ihv[2] >> 16);
+ output[10] = (unsigned char) (ctx->ihv[2] >> 8);
+ output[11] = (unsigned char) (ctx->ihv[2]);
+ output[12] = (unsigned char) (ctx->ihv[3] >> 24);
+ output[13] = (unsigned char) (ctx->ihv[3] >> 16);
+ output[14] = (unsigned char) (ctx->ihv[3] >> 8);
+ output[15] = (unsigned char) (ctx->ihv[3]);
+ output[16] = (unsigned char) (ctx->ihv[4] >> 24);
+ output[17] = (unsigned char) (ctx->ihv[4] >> 16);
+ output[18] = (unsigned char) (ctx->ihv[4] >> 8);
+ output[19] = (unsigned char) (ctx->ihv[4]);
+ return ctx->found_collision;
+}
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_C
+#endif
diff --git a/src/lib/crypto/sha1cd/sha1.h b/src/lib/crypto/sha1cd/sha1.h
new file mode 100644
index 0000000..5bc0925
--- /dev/null
+++ b/src/lib/crypto/sha1cd/sha1.h
@@ -0,0 +1,122 @@
+/***
+ * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
+ * Distributed under the MIT Software License.
+ * See accompanying file LICENSE.txt or copy at
+ * https://opensource.org/licenses/MIT
+ ***/
+
+#ifndef SHA1DC_SHA1_H
+#define SHA1DC_SHA1_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <stdint.h>
+#endif
+
+/* sha-1 compression function that takes an already expanded message, and additionally store
+ * intermediate states */
+/* only stores states ii (the state between step ii-1 and step ii) when DOSTORESTATEii is
+ * defined in ubc_check.h */
+void sha1_compression_states(uint32_t[5], const uint32_t[16], uint32_t[80], uint32_t[80][5]);
+
+/*
+// Function type for sha1_recompression_step_T (uint32_t ihvin[5], uint32_t ihvout[5], const
+uint32_t me2[80], const uint32_t state[5]).
+// Where 0 <= T < 80
+// me2 is an expanded message (the expansion of an original message block XOR'ed with a
+disturbance vector's message block difference.)
+// state is the internal state (a,b,c,d,e) before step T of the SHA-1 compression
+function while processing the original message block.
+// The function will return:
+// ihvin: The reconstructed input chaining value.
+// ihvout: The reconstructed output chaining value.
+*/
+typedef void (*sha1_recompression_type)(uint32_t *,
+ uint32_t *,
+ const uint32_t *,
+ const uint32_t *);
+
+/* A callback function type that can be set to be called when a collision block has been found:
+ */
+/* void collision_block_callback(uint64_t byteoffset, const uint32_t ihvin1[5], const uint32_t
+ * ihvin2[5], const uint32_t m1[80], const uint32_t m2[80]) */
+typedef void (*collision_block_callback)(
+ uint64_t, const uint32_t *, const uint32_t *, const uint32_t *, const uint32_t *);
+
+/* The SHA-1 context. */
+typedef struct {
+ uint64_t total;
+ uint32_t ihv[5];
+ unsigned char buffer[64];
+ int found_collision;
+ int safe_hash;
+ int detect_coll;
+ int ubc_check;
+ int reduced_round_coll;
+ collision_block_callback callback;
+
+ uint32_t ihv1[5];
+ uint32_t ihv2[5];
+ uint32_t m1[80];
+ uint32_t m2[80];
+ uint32_t states[80][5];
+} SHA1_CTX;
+
+/* Initialize SHA-1 context. */
+void SHA1DCInit(SHA1_CTX *);
+
+/*
+ Function to enable safe SHA-1 hashing:
+ Collision attacks are thwarted by hashing a detected near-collision block 3 times.
+ Think of it as extending SHA-1 from 80-steps to 240-steps for such blocks:
+ The best collision attacks against SHA-1 have complexity about 2^60,
+ thus for 240-steps an immediate lower-bound for the best cryptanalytic attacks would be
+ 2^180. An attacker would be better off using a generic birthday search of complexity 2^80.
+
+ Enabling safe SHA-1 hashing will result in the correct SHA-1 hash for messages where no
+ collision attack was detected, but it will result in a different SHA-1 hash for messages
+ where a collision attack was detected. This will automatically invalidate SHA-1 based
+ digital signature forgeries. Enabled by default.
+*/
+void SHA1DCSetSafeHash(SHA1_CTX *, int);
+
+/*
+ Function to disable or enable the use of Unavoidable Bitconditions (provides a significant
+ speed up). Enabled by default
+ */
+void SHA1DCSetUseUBC(SHA1_CTX *, int);
+
+/*
+ Function to disable or enable the use of Collision Detection.
+ Enabled by default.
+ */
+void SHA1DCSetUseDetectColl(SHA1_CTX *, int);
+
+/* function to disable or enable the detection of reduced-round SHA-1 collisions */
+/* disabled by default */
+void SHA1DCSetDetectReducedRoundCollision(SHA1_CTX *, int);
+
+/* function to set a callback function, pass NULL to disable */
+/* by default no callback set */
+void SHA1DCSetCallback(SHA1_CTX *, collision_block_callback);
+
+/* update SHA-1 context with buffer contents */
+void SHA1DCUpdate(SHA1_CTX *, const char *, size_t);
+
+/* obtain SHA-1 hash from SHA-1 context */
+/* returns: 0 = no collision detected, otherwise = collision found => warn user for active
+ * attack */
+int SHA1DCFinal(unsigned char[20], SHA1_CTX *);
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_SHA1_H
+#endif
+
+#endif
diff --git a/src/lib/crypto/sha1cd/ubc_check.c b/src/lib/crypto/sha1cd/ubc_check.c
new file mode 100644
index 0000000..c44c53d
--- /dev/null
+++ b/src/lib/crypto/sha1cd/ubc_check.c
@@ -0,0 +1,908 @@
+/***
+ * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
+ * Distributed under the MIT Software License.
+ * See accompanying file LICENSE.txt or copy at
+ * https://opensource.org/licenses/MIT
+ ***/
+
+/*
+// this file was generated by the 'parse_bitrel' program in the tools section
+// using the data files from directory 'tools/data/3565'
+//
+// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check
+// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper)
+// dm[80] is the expanded message block XOR-difference defined by the DV
+// testt is the step to do the recompression from for collision detection
+// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check
+//
+// ubc_check takes as input an expanded message block and verifies the unavoidable
+bitconditions for all listed DVs
+// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions
+for that DV have been met
+// thus one needs to do the recompression check for each DV that has its bit set
+//
+// ubc_check is programmatically generated and the unavoidable bitconditions have been
+hardcoded
+// a directly verifiable version named ubc_check_verify can be found in ubc_check_verify.c
+// ubc_check has been verified against ubc_check_verify using the 'ubc_check_test' program in
+the tools section
+*/
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <stdint.h>
+#endif
+#ifdef SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C
+#include SHA1DC_CUSTOM_INCLUDE_UBC_CHECK_C
+#endif
+#include "ubc_check.h"
+
+static const uint32_t DV_I_43_0_bit = (uint32_t)(1) << 0;
+static const uint32_t DV_I_44_0_bit = (uint32_t)(1) << 1;
+static const uint32_t DV_I_45_0_bit = (uint32_t)(1) << 2;
+static const uint32_t DV_I_46_0_bit = (uint32_t)(1) << 3;
+static const uint32_t DV_I_46_2_bit = (uint32_t)(1) << 4;
+static const uint32_t DV_I_47_0_bit = (uint32_t)(1) << 5;
+static const uint32_t DV_I_47_2_bit = (uint32_t)(1) << 6;
+static const uint32_t DV_I_48_0_bit = (uint32_t)(1) << 7;
+static const uint32_t DV_I_48_2_bit = (uint32_t)(1) << 8;
+static const uint32_t DV_I_49_0_bit = (uint32_t)(1) << 9;
+static const uint32_t DV_I_49_2_bit = (uint32_t)(1) << 10;
+static const uint32_t DV_I_50_0_bit = (uint32_t)(1) << 11;
+static const uint32_t DV_I_50_2_bit = (uint32_t)(1) << 12;
+static const uint32_t DV_I_51_0_bit = (uint32_t)(1) << 13;
+static const uint32_t DV_I_51_2_bit = (uint32_t)(1) << 14;
+static const uint32_t DV_I_52_0_bit = (uint32_t)(1) << 15;
+static const uint32_t DV_II_45_0_bit = (uint32_t)(1) << 16;
+static const uint32_t DV_II_46_0_bit = (uint32_t)(1) << 17;
+static const uint32_t DV_II_46_2_bit = (uint32_t)(1) << 18;
+static const uint32_t DV_II_47_0_bit = (uint32_t)(1) << 19;
+static const uint32_t DV_II_48_0_bit = (uint32_t)(1) << 20;
+static const uint32_t DV_II_49_0_bit = (uint32_t)(1) << 21;
+static const uint32_t DV_II_49_2_bit = (uint32_t)(1) << 22;
+static const uint32_t DV_II_50_0_bit = (uint32_t)(1) << 23;
+static const uint32_t DV_II_50_2_bit = (uint32_t)(1) << 24;
+static const uint32_t DV_II_51_0_bit = (uint32_t)(1) << 25;
+static const uint32_t DV_II_51_2_bit = (uint32_t)(1) << 26;
+static const uint32_t DV_II_52_0_bit = (uint32_t)(1) << 27;
+static const uint32_t DV_II_53_0_bit = (uint32_t)(1) << 28;
+static const uint32_t DV_II_54_0_bit = (uint32_t)(1) << 29;
+static const uint32_t DV_II_55_0_bit = (uint32_t)(1) << 30;
+static const uint32_t DV_II_56_0_bit = (uint32_t)(1) << 31;
+
+dv_info_t sha1_dvs[] = {
+ {1, 43, 0, 58, 0, 0, {0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000,
+ 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014,
+ 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010,
+ 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008,
+ 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010,
+ 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000,
+ 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010,
+ 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020,
+ 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080,
+ 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202,
+ 0x00000018, 0x00000164, 0x00000408, 0x800000e6, 0x8000004c, 0x00000803,
+ 0x80000161, 0x80000599}},
+ {1, 44, 0, 58, 0, 1, {0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010,
+ 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010,
+ 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000,
+ 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010,
+ 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000,
+ 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000,
+ 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000,
+ 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000,
+ 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
+ 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004,
+ 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012,
+ 0x80000202, 0x00000018, 0x00000164, 0x00000408, 0x800000e6, 0x8000004c,
+ 0x00000803, 0x80000161}},
+ {1, 45, 0, 58, 0, 2, {0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010, 0x08000010,
+ 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000, 0x90000014,
+ 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018,
+ 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010,
+ 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018,
+ 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000, 0x00000010,
+ 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000, 0x20000010,
+ 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000,
+ 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040, 0x40000002,
+ 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103, 0x80000009,
+ 0x80000012, 0x80000202, 0x00000018, 0x00000164, 0x00000408, 0x800000e6,
+ 0x8000004c, 0x00000803}},
+ {1, 46, 0, 58, 0, 3, {0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010,
+ 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008, 0xc0000000,
+ 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010, 0x48000000,
+ 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000,
+ 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010,
+ 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000,
+ 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010, 0x00000000,
+ 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010, 0xa0000000,
+ 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040,
+ 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049, 0x00000103,
+ 0x80000009, 0x80000012, 0x80000202, 0x00000018, 0x00000164, 0x00000408,
+ 0x800000e6, 0x8000004c}},
+ {1, 46, 2, 58, 0, 4, {0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043,
+ 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003,
+ 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040, 0x20000001,
+ 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022, 0x00000003,
+ 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, 0xc0000043,
+ 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x00000002,
+ 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000,
+ 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040, 0x80000002,
+ 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000101,
+ 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c,
+ 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590, 0x00001020,
+ 0x0000039a, 0x00000132}},
+ {1, 47, 0, 58, 0, 5, {0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c,
+ 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008,
+ 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000, 0x20000010,
+ 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010, 0x90000008,
+ 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000,
+ 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000,
+ 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010,
+ 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000, 0x00000010,
+ 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002,
+ 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049,
+ 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018, 0x00000164,
+ 0x00000408, 0x800000e6}},
+ {1, 47, 2, 58, 0, 6, {0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032,
+ 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020,
+ 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040,
+ 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043, 0x40000022,
+ 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, 0x00000001, 0x40000002,
+ 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002,
+ 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040,
+ 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040,
+ 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009,
+ 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124,
+ 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590,
+ 0x00001020, 0x0000039a}},
+ {1, 48, 0, 58, 0, 7, {0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000,
+ 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000,
+ 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000,
+ 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010, 0xf0000010,
+ 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008, 0x40000000,
+ 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010,
+ 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000,
+ 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000,
+ 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001,
+ 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006,
+ 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018,
+ 0x00000164, 0x00000408}},
+ {1, 48, 2, 58, 0, 8, {0xe000002a, 0x20000043, 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000,
+ 0x60000032, 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001,
+ 0x00000020, 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000,
+ 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043,
+ 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022, 0x00000001,
+ 0x40000002, 0xc0000043, 0x40000062, 0x80000001, 0x40000042, 0x40000042,
+ 0x40000002, 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002,
+ 0x80000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000,
+ 0x00000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000004, 0x00000080, 0x00000004,
+ 0x00000009, 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a,
+ 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060,
+ 0x00000590, 0x00001020}},
+ {1, 49, 0, 58, 0, 9, {0x18000000, 0xb800000a, 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008,
+ 0x08000000, 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000,
+ 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014,
+ 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010,
+ 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010, 0xb0000008,
+ 0x40000000, 0x90000000, 0xf0000010, 0x90000018, 0x60000000, 0x90000010,
+ 0x90000010, 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000,
+ 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010,
+ 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020,
+ 0x00000001, 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080,
+ 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202,
+ 0x00000018, 0x00000164}},
+ {1, 49, 2, 58, 0, 10, {0x60000000, 0xe000002a, 0x20000043, 0xb0000040, 0xd0000053,
+ 0xd0000022, 0x20000000, 0x60000032, 0x60000043, 0x20000040,
+ 0xe0000042, 0x60000002, 0x80000001, 0x00000020, 0x00000003,
+ 0x40000052, 0x40000040, 0xe0000052, 0xa0000000, 0x80000040,
+ 0x20000001, 0x20000060, 0x80000001, 0x40000042, 0xc0000043,
+ 0x40000022, 0x00000003, 0x40000042, 0xc0000043, 0xc0000022,
+ 0x00000001, 0x40000002, 0xc0000043, 0x40000062, 0x80000001,
+ 0x40000042, 0x40000042, 0x40000002, 0x00000002, 0x00000040,
+ 0x80000002, 0x80000000, 0x80000002, 0x80000040, 0x00000000,
+ 0x80000040, 0x80000000, 0x00000040, 0x80000000, 0x00000040,
+ 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004,
+ 0x00000080, 0x00000004, 0x00000009, 0x00000101, 0x00000009,
+ 0x00000012, 0x00000202, 0x0000001a, 0x00000124, 0x0000040c,
+ 0x00000026, 0x0000004a, 0x0000080a, 0x00000060, 0x00000590}},
+ {1, 50, 0, 65, 0, 11, {0x0800000c, 0x18000000, 0xb800000a, 0xc8000010, 0x2c000010,
+ 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c, 0xd8000010,
+ 0x08000010, 0xb8000010, 0x98000000, 0x60000000, 0x00000008,
+ 0xc0000000, 0x90000014, 0x10000010, 0xb8000014, 0x28000000,
+ 0x20000010, 0x48000000, 0x08000018, 0x60000000, 0x90000010,
+ 0xf0000010, 0x90000008, 0xc0000000, 0x90000010, 0xf0000010,
+ 0xb0000008, 0x40000000, 0x90000000, 0xf0000010, 0x90000018,
+ 0x60000000, 0x90000010, 0x90000010, 0x90000000, 0x80000000,
+ 0x00000010, 0xa0000000, 0x20000000, 0xa0000000, 0x20000010,
+ 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x20000000,
+ 0x00000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000040,
+ 0x40000002, 0x80000004, 0x80000080, 0x80000006, 0x00000049,
+ 0x00000103, 0x80000009, 0x80000012, 0x80000202, 0x00000018}},
+ {1, 50, 2, 65, 0, 12, {0x20000030, 0x60000000, 0xe000002a, 0x20000043, 0xb0000040,
+ 0xd0000053, 0xd0000022, 0x20000000, 0x60000032, 0x60000043,
+ 0x20000040, 0xe0000042, 0x60000002, 0x80000001, 0x00000020,
+ 0x00000003, 0x40000052, 0x40000040, 0xe0000052, 0xa0000000,
+ 0x80000040, 0x20000001, 0x20000060, 0x80000001, 0x40000042,
+ 0xc0000043, 0x40000022, 0x00000003, 0x40000042, 0xc0000043,
+ 0xc0000022, 0x00000001, 0x40000002, 0xc0000043, 0x40000062,
+ 0x80000001, 0x40000042, 0x40000042, 0x40000002, 0x00000002,
+ 0x00000040, 0x80000002, 0x80000000, 0x80000002, 0x80000040,
+ 0x00000000, 0x80000040, 0x80000000, 0x00000040, 0x80000000,
+ 0x00000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000101,
+ 0x00000009, 0x00000012, 0x00000202, 0x0000001a, 0x00000124,
+ 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a, 0x00000060}},
+ {1, 51, 0, 65, 0, 13, {0xe8000000, 0x0800000c, 0x18000000, 0xb800000a, 0xc8000010,
+ 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000, 0x9800000c,
+ 0xd8000010, 0x08000010, 0xb8000010, 0x98000000, 0x60000000,
+ 0x00000008, 0xc0000000, 0x90000014, 0x10000010, 0xb8000014,
+ 0x28000000, 0x20000010, 0x48000000, 0x08000018, 0x60000000,
+ 0x90000010, 0xf0000010, 0x90000008, 0xc0000000, 0x90000010,
+ 0xf0000010, 0xb0000008, 0x40000000, 0x90000000, 0xf0000010,
+ 0x90000018, 0x60000000, 0x90000010, 0x90000010, 0x90000000,
+ 0x80000000, 0x00000010, 0xa0000000, 0x20000000, 0xa0000000,
+ 0x20000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010,
+ 0x20000000, 0x00000010, 0xa0000000, 0x00000000, 0x20000000,
+ 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002,
+ 0x40000040, 0x40000002, 0x80000004, 0x80000080, 0x80000006,
+ 0x00000049, 0x00000103, 0x80000009, 0x80000012, 0x80000202}},
+ {1, 51, 2, 65, 0, 14, {0xa0000003, 0x20000030, 0x60000000, 0xe000002a, 0x20000043,
+ 0xb0000040, 0xd0000053, 0xd0000022, 0x20000000, 0x60000032,
+ 0x60000043, 0x20000040, 0xe0000042, 0x60000002, 0x80000001,
+ 0x00000020, 0x00000003, 0x40000052, 0x40000040, 0xe0000052,
+ 0xa0000000, 0x80000040, 0x20000001, 0x20000060, 0x80000001,
+ 0x40000042, 0xc0000043, 0x40000022, 0x00000003, 0x40000042,
+ 0xc0000043, 0xc0000022, 0x00000001, 0x40000002, 0xc0000043,
+ 0x40000062, 0x80000001, 0x40000042, 0x40000042, 0x40000002,
+ 0x00000002, 0x00000040, 0x80000002, 0x80000000, 0x80000002,
+ 0x80000040, 0x00000000, 0x80000040, 0x80000000, 0x00000040,
+ 0x80000000, 0x00000040, 0x80000002, 0x00000000, 0x80000000,
+ 0x80000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009,
+ 0x00000101, 0x00000009, 0x00000012, 0x00000202, 0x0000001a,
+ 0x00000124, 0x0000040c, 0x00000026, 0x0000004a, 0x0000080a}},
+ {1, 52, 0, 65, 0, 15, {0x04000010, 0xe8000000, 0x0800000c, 0x18000000, 0xb800000a,
+ 0xc8000010, 0x2c000010, 0xf4000014, 0xb4000008, 0x08000000,
+ 0x9800000c, 0xd8000010, 0x08000010, 0xb8000010, 0x98000000,
+ 0x60000000, 0x00000008, 0xc0000000, 0x90000014, 0x10000010,
+ 0xb8000014, 0x28000000, 0x20000010, 0x48000000, 0x08000018,
+ 0x60000000, 0x90000010, 0xf0000010, 0x90000008, 0xc0000000,
+ 0x90000010, 0xf0000010, 0xb0000008, 0x40000000, 0x90000000,
+ 0xf0000010, 0x90000018, 0x60000000, 0x90000010, 0x90000010,
+ 0x90000000, 0x80000000, 0x00000010, 0xa0000000, 0x20000000,
+ 0xa0000000, 0x20000010, 0x00000000, 0x20000010, 0x20000000,
+ 0x00000010, 0x20000000, 0x00000010, 0xa0000000, 0x00000000,
+ 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001,
+ 0x40000002, 0x40000040, 0x40000002, 0x80000004, 0x80000080,
+ 0x80000006, 0x00000049, 0x00000103, 0x80000009, 0x80000012}},
+ {2, 45, 0, 58, 0, 16, {0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004,
+ 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018,
+ 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000,
+ 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000,
+ 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000,
+ 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010,
+ 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000,
+ 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000,
+ 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000,
+ 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010,
+ 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041,
+ 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b,
+ 0x80000107, 0x00000089, 0x00000014, 0x8000024b, 0x0000011b,
+ 0x8000016d, 0x8000041a, 0x000002e4, 0x80000054, 0x00000967}},
+ {2, 46, 0, 58, 0, 17, {0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c,
+ 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010,
+ 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c,
+ 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000,
+ 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000,
+ 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000,
+ 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018,
+ 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000,
+ 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000,
+ 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000,
+ 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002,
+ 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046,
+ 0x4000004b, 0x80000107, 0x00000089, 0x00000014, 0x8000024b,
+ 0x0000011b, 0x8000016d, 0x8000041a, 0x000002e4, 0x80000054}},
+ {2, 46, 2, 58, 0, 18, {0x90000070, 0xb0000053, 0x30000008, 0x00000043, 0xd0000072,
+ 0xb0000010, 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042,
+ 0x20000060, 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072,
+ 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, 0x20000001,
+ 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0x00000000,
+ 0x00000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000,
+ 0x80000040, 0x80000000, 0x00000040, 0x80000001, 0x00000060,
+ 0x80000003, 0x40000002, 0xc0000040, 0xc0000002, 0x80000000,
+ 0x80000000, 0x80000002, 0x00000040, 0x00000002, 0x80000000,
+ 0x80000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000,
+ 0x80000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009,
+ 0x00000105, 0x00000089, 0x00000016, 0x0000020b, 0x0000011b,
+ 0x0000012d, 0x0000041e, 0x00000224, 0x00000050, 0x0000092e,
+ 0x0000046c, 0x000005b6, 0x0000106a, 0x00000b90, 0x00000152}},
+ {2, 47, 0, 58, 0, 19, {0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010,
+ 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c,
+ 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010,
+ 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c,
+ 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000,
+ 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010,
+ 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000,
+ 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000,
+ 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000,
+ 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010,
+ 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000,
+ 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001,
+ 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082,
+ 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, 0x00000014,
+ 0x8000024b, 0x0000011b, 0x8000016d, 0x8000041a, 0x000002e4}},
+ {2, 48, 0, 58, 0, 20, {0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002,
+ 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010,
+ 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014,
+ 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010,
+ 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010,
+ 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000,
+ 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010,
+ 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010,
+ 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010,
+ 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000,
+ 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000,
+ 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020,
+ 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005,
+ 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089,
+ 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d, 0x8000041a}},
+ {2, 49, 0, 58, 0, 21, {0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014,
+ 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018,
+ 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010,
+ 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004,
+ 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010,
+ 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000,
+ 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000,
+ 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000,
+ 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000,
+ 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000,
+ 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000,
+ 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
+ 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022,
+ 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107,
+ 0x00000089, 0x00000014, 0x8000024b, 0x0000011b, 0x8000016d}},
+ {2, 49, 2, 58, 0, 22, {0xf0000010, 0xf000006a, 0x80000040, 0x90000070, 0xb0000053,
+ 0x30000008, 0x00000043, 0xd0000072, 0xb0000010, 0xf0000062,
+ 0xc0000042, 0x00000030, 0xe0000042, 0x20000060, 0xe0000041,
+ 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003, 0xc0000012,
+ 0x60000041, 0xc0000032, 0x20000001, 0xc0000002, 0xe0000042,
+ 0x60000042, 0x80000002, 0x00000000, 0x00000000, 0x80000000,
+ 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000000,
+ 0x00000040, 0x80000001, 0x00000060, 0x80000003, 0x40000002,
+ 0xc0000040, 0xc0000002, 0x80000000, 0x80000000, 0x80000002,
+ 0x00000040, 0x00000002, 0x80000000, 0x80000000, 0x80000000,
+ 0x00000002, 0x00000040, 0x00000000, 0x80000040, 0x80000002,
+ 0x00000000, 0x80000000, 0x80000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000004,
+ 0x00000080, 0x00000004, 0x00000009, 0x00000105, 0x00000089,
+ 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d, 0x0000041e,
+ 0x00000224, 0x00000050, 0x0000092e, 0x0000046c, 0x000005b6}},
+ {2, 50, 0, 65, 0, 23, {0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c,
+ 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004,
+ 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018,
+ 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000,
+ 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000,
+ 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000,
+ 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010,
+ 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000,
+ 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000,
+ 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000,
+ 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010,
+ 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041,
+ 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b,
+ 0x80000107, 0x00000089, 0x00000014, 0x8000024b, 0x0000011b}},
+ {2, 50, 2, 65, 0, 24, {0xd0000072, 0xf0000010, 0xf000006a, 0x80000040, 0x90000070,
+ 0xb0000053, 0x30000008, 0x00000043, 0xd0000072, 0xb0000010,
+ 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042, 0x20000060,
+ 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072, 0xa0000003,
+ 0xc0000012, 0x60000041, 0xc0000032, 0x20000001, 0xc0000002,
+ 0xe0000042, 0x60000042, 0x80000002, 0x00000000, 0x00000000,
+ 0x80000000, 0x00000002, 0x00000040, 0x00000000, 0x80000040,
+ 0x80000000, 0x00000040, 0x80000001, 0x00000060, 0x80000003,
+ 0x40000002, 0xc0000040, 0xc0000002, 0x80000000, 0x80000000,
+ 0x80000002, 0x00000040, 0x00000002, 0x80000000, 0x80000000,
+ 0x80000000, 0x00000002, 0x00000040, 0x00000000, 0x80000040,
+ 0x80000002, 0x00000000, 0x80000000, 0x80000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000004, 0x00000080, 0x00000004, 0x00000009, 0x00000105,
+ 0x00000089, 0x00000016, 0x0000020b, 0x0000011b, 0x0000012d,
+ 0x0000041e, 0x00000224, 0x00000050, 0x0000092e, 0x0000046c}},
+ {2, 51, 0, 65, 0, 25, {0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010,
+ 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c,
+ 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010,
+ 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c,
+ 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000,
+ 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000,
+ 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000,
+ 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018,
+ 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000,
+ 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000,
+ 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000,
+ 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002,
+ 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046,
+ 0x4000004b, 0x80000107, 0x00000089, 0x00000014, 0x8000024b}},
+ {2, 51, 2, 65, 0, 26, {0x00000043, 0xd0000072, 0xf0000010, 0xf000006a, 0x80000040,
+ 0x90000070, 0xb0000053, 0x30000008, 0x00000043, 0xd0000072,
+ 0xb0000010, 0xf0000062, 0xc0000042, 0x00000030, 0xe0000042,
+ 0x20000060, 0xe0000041, 0x20000050, 0xc0000041, 0xe0000072,
+ 0xa0000003, 0xc0000012, 0x60000041, 0xc0000032, 0x20000001,
+ 0xc0000002, 0xe0000042, 0x60000042, 0x80000002, 0x00000000,
+ 0x00000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000,
+ 0x80000040, 0x80000000, 0x00000040, 0x80000001, 0x00000060,
+ 0x80000003, 0x40000002, 0xc0000040, 0xc0000002, 0x80000000,
+ 0x80000000, 0x80000002, 0x00000040, 0x00000002, 0x80000000,
+ 0x80000000, 0x80000000, 0x00000002, 0x00000040, 0x00000000,
+ 0x80000040, 0x80000002, 0x00000000, 0x80000000, 0x80000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000004, 0x00000080, 0x00000004, 0x00000009,
+ 0x00000105, 0x00000089, 0x00000016, 0x0000020b, 0x0000011b,
+ 0x0000012d, 0x0000041e, 0x00000224, 0x00000050, 0x0000092e}},
+ {2, 52, 0, 65, 0, 27, {0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a,
+ 0x20000010, 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010,
+ 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c,
+ 0xb8000010, 0x08000018, 0x78000010, 0x08000014, 0x70000010,
+ 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c,
+ 0x48000000, 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000,
+ 0x00000000, 0x00000000, 0x20000000, 0x80000000, 0x00000010,
+ 0x00000000, 0x20000010, 0x20000000, 0x00000010, 0x60000000,
+ 0x00000018, 0xe0000000, 0x90000000, 0x30000010, 0xb0000000,
+ 0x20000000, 0x20000000, 0xa0000000, 0x00000010, 0x80000000,
+ 0x20000000, 0x20000000, 0x20000000, 0x80000000, 0x00000010,
+ 0x00000000, 0x20000010, 0xa0000000, 0x00000000, 0x20000000,
+ 0x20000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000001, 0x00000020, 0x00000001,
+ 0x40000002, 0x40000041, 0x40000022, 0x80000005, 0xc0000082,
+ 0xc0000046, 0x4000004b, 0x80000107, 0x00000089, 0x00000014}},
+ {2, 53, 0, 65, 0, 28, {0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x3c000004,
+ 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014, 0x0c000002,
+ 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018, 0xb0000010,
+ 0x0000000c, 0xb8000010, 0x08000018, 0x78000010, 0x08000014,
+ 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004, 0x58000010,
+ 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010, 0x98000010,
+ 0xa0000000, 0x00000000, 0x00000000, 0x20000000, 0x80000000,
+ 0x00000010, 0x00000000, 0x20000010, 0x20000000, 0x00000010,
+ 0x60000000, 0x00000018, 0xe0000000, 0x90000000, 0x30000010,
+ 0xb0000000, 0x20000000, 0x20000000, 0xa0000000, 0x00000010,
+ 0x80000000, 0x20000000, 0x20000000, 0x20000000, 0x80000000,
+ 0x00000010, 0x00000000, 0x20000010, 0xa0000000, 0x00000000,
+ 0x20000000, 0x20000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000020,
+ 0x00000001, 0x40000002, 0x40000041, 0x40000022, 0x80000005,
+ 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107, 0x00000089}},
+ {2, 54, 0, 65, 0, 29, {0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010, 0xb400001c,
+ 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c, 0xec000014,
+ 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004, 0xbc000018,
+ 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018, 0x78000010,
+ 0x08000014, 0x70000010, 0xb800001c, 0xe8000000, 0xb0000004,
+ 0x58000010, 0xb000000c, 0x48000000, 0xb0000000, 0xb8000010,
+ 0x98000010, 0xa0000000, 0x00000000, 0x00000000, 0x20000000,
+ 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0x20000000,
+ 0x00000010, 0x60000000, 0x00000018, 0xe0000000, 0x90000000,
+ 0x30000010, 0xb0000000, 0x20000000, 0x20000000, 0xa0000000,
+ 0x00000010, 0x80000000, 0x20000000, 0x20000000, 0x20000000,
+ 0x80000000, 0x00000010, 0x00000000, 0x20000010, 0xa0000000,
+ 0x00000000, 0x20000000, 0x20000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
+ 0x00000020, 0x00000001, 0x40000002, 0x40000041, 0x40000022,
+ 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b, 0x80000107}},
+ {2, 55, 0, 65, 0, 30, {0x00000010, 0x0400001c, 0xcc000014, 0x0c000002, 0xc0000010,
+ 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010, 0x2400001c,
+ 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c, 0x2c000004,
+ 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010, 0x08000018,
+ 0x78000010, 0x08000014, 0x70000010, 0xb800001c, 0xe8000000,
+ 0xb0000004, 0x58000010, 0xb000000c, 0x48000000, 0xb0000000,
+ 0xb8000010, 0x98000010, 0xa0000000, 0x00000000, 0x00000000,
+ 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010,
+ 0x20000000, 0x00000010, 0x60000000, 0x00000018, 0xe0000000,
+ 0x90000000, 0x30000010, 0xb0000000, 0x20000000, 0x20000000,
+ 0xa0000000, 0x00000010, 0x80000000, 0x20000000, 0x20000000,
+ 0x20000000, 0x80000000, 0x00000010, 0x00000000, 0x20000010,
+ 0xa0000000, 0x00000000, 0x20000000, 0x20000000, 0x00000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000001, 0x00000020, 0x00000001, 0x40000002, 0x40000041,
+ 0x40000022, 0x80000005, 0xc0000082, 0xc0000046, 0x4000004b}},
+ {2, 56, 0, 65, 0, 31, {0x2600001a, 0x00000010, 0x0400001c, 0xcc000014, 0x0c000002,
+ 0xc0000010, 0xb400001c, 0x3c000004, 0xbc00001a, 0x20000010,
+ 0x2400001c, 0xec000014, 0x0c000002, 0xc0000010, 0xb400001c,
+ 0x2c000004, 0xbc000018, 0xb0000010, 0x0000000c, 0xb8000010,
+ 0x08000018, 0x78000010, 0x08000014, 0x70000010, 0xb800001c,
+ 0xe8000000, 0xb0000004, 0x58000010, 0xb000000c, 0x48000000,
+ 0xb0000000, 0xb8000010, 0x98000010, 0xa0000000, 0x00000000,
+ 0x00000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000,
+ 0x20000010, 0x20000000, 0x00000010, 0x60000000, 0x00000018,
+ 0xe0000000, 0x90000000, 0x30000010, 0xb0000000, 0x20000000,
+ 0x20000000, 0xa0000000, 0x00000010, 0x80000000, 0x20000000,
+ 0x20000000, 0x20000000, 0x80000000, 0x00000010, 0x00000000,
+ 0x20000010, 0xa0000000, 0x00000000, 0x20000000, 0x20000000,
+ 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
+ 0x00000000, 0x00000001, 0x00000020, 0x00000001, 0x40000002,
+ 0x40000041, 0x40000022, 0x80000005, 0xc0000082, 0xc0000046}},
+ {0, 0, 0, 0, 0, 0, {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
+ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}}};
+void
+ubc_check(const uint32_t W[80], uint32_t dvmask[1])
+{
+ uint32_t mask = ~((uint32_t)(0));
+ mask &= (((((W[44] ^ W[45]) >> 29) & 1) - 1) |
+ ~(DV_I_48_0_bit | DV_I_51_0_bit | DV_I_52_0_bit | DV_II_45_0_bit |
+ DV_II_46_0_bit | DV_II_50_0_bit | DV_II_51_0_bit));
+ mask &= (((((W[49] ^ W[50]) >> 29) & 1) - 1) |
+ ~(DV_I_46_0_bit | DV_II_45_0_bit | DV_II_50_0_bit | DV_II_51_0_bit |
+ DV_II_55_0_bit | DV_II_56_0_bit));
+ mask &= (((((W[48] ^ W[49]) >> 29) & 1) - 1) |
+ ~(DV_I_45_0_bit | DV_I_52_0_bit | DV_II_49_0_bit | DV_II_50_0_bit |
+ DV_II_54_0_bit | DV_II_55_0_bit));
+ mask &= ((((W[47] ^ (W[50] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit |
+ DV_II_51_0_bit | DV_II_56_0_bit));
+ mask &= (((((W[47] ^ W[48]) >> 29) & 1) - 1) |
+ ~(DV_I_44_0_bit | DV_I_51_0_bit | DV_II_48_0_bit | DV_II_49_0_bit |
+ DV_II_53_0_bit | DV_II_54_0_bit));
+ mask &= (((((W[46] >> 4) ^ (W[49] >> 29)) & 1) - 1) |
+ ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit | DV_II_50_0_bit |
+ DV_II_55_0_bit));
+ mask &= (((((W[46] ^ W[47]) >> 29) & 1) - 1) |
+ ~(DV_I_43_0_bit | DV_I_50_0_bit | DV_II_47_0_bit | DV_II_48_0_bit |
+ DV_II_52_0_bit | DV_II_53_0_bit));
+ mask &= (((((W[45] >> 4) ^ (W[48] >> 29)) & 1) - 1) |
+ ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit | DV_II_49_0_bit |
+ DV_II_54_0_bit));
+ mask &= (((((W[45] ^ W[46]) >> 29) & 1) - 1) |
+ ~(DV_I_49_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_47_0_bit |
+ DV_II_51_0_bit | DV_II_52_0_bit));
+ mask &= (((((W[44] >> 4) ^ (W[47] >> 29)) & 1) - 1) |
+ ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit | DV_II_48_0_bit |
+ DV_II_53_0_bit));
+ mask &= (((((W[43] >> 4) ^ (W[46] >> 29)) & 1) - 1) |
+ ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit | DV_II_47_0_bit |
+ DV_II_52_0_bit));
+ mask &= (((((W[43] ^ W[44]) >> 29) & 1) - 1) |
+ ~(DV_I_47_0_bit | DV_I_50_0_bit | DV_I_51_0_bit | DV_II_45_0_bit |
+ DV_II_49_0_bit | DV_II_50_0_bit));
+ mask &= (((((W[42] >> 4) ^ (W[45] >> 29)) & 1) - 1) |
+ ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit | DV_I_52_0_bit | DV_II_46_0_bit |
+ DV_II_51_0_bit));
+ mask &= (((((W[41] >> 4) ^ (W[44] >> 29)) & 1) - 1) |
+ ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit | DV_I_51_0_bit | DV_II_45_0_bit |
+ DV_II_50_0_bit));
+ mask &= (((((W[40] ^ W[41]) >> 29) & 1) - 1) |
+ ~(DV_I_44_0_bit | DV_I_47_0_bit | DV_I_48_0_bit | DV_II_46_0_bit |
+ DV_II_47_0_bit | DV_II_56_0_bit));
+ mask &=
+ (((((W[54] ^ W[55]) >> 29) & 1) - 1) |
+ ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_50_0_bit | DV_II_55_0_bit | DV_II_56_0_bit));
+ mask &=
+ (((((W[53] ^ W[54]) >> 29) & 1) - 1) |
+ ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_49_0_bit | DV_II_54_0_bit | DV_II_55_0_bit));
+ mask &=
+ (((((W[52] ^ W[53]) >> 29) & 1) - 1) |
+ ~(DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit | DV_II_53_0_bit | DV_II_54_0_bit));
+ mask &=
+ ((((W[50] ^ (W[53] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_50_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_48_0_bit | DV_II_54_0_bit));
+ mask &=
+ (((((W[50] ^ W[51]) >> 29) & 1) - 1) |
+ ~(DV_I_47_0_bit | DV_II_46_0_bit | DV_II_51_0_bit | DV_II_52_0_bit | DV_II_56_0_bit));
+ mask &=
+ ((((W[49] ^ (W[52] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit | DV_II_47_0_bit | DV_II_53_0_bit));
+ mask &=
+ ((((W[48] ^ (W[51] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit | DV_II_46_0_bit | DV_II_52_0_bit));
+ mask &=
+ (((((W[42] ^ W[43]) >> 29) & 1) - 1) |
+ ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_I_50_0_bit | DV_II_48_0_bit | DV_II_49_0_bit));
+ mask &=
+ (((((W[41] ^ W[42]) >> 29) & 1) - 1) |
+ ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_I_49_0_bit | DV_II_47_0_bit | DV_II_48_0_bit));
+ mask &=
+ (((((W[40] >> 4) ^ (W[43] >> 29)) & 1) - 1) |
+ ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_50_0_bit | DV_II_49_0_bit | DV_II_56_0_bit));
+ mask &=
+ (((((W[39] >> 4) ^ (W[42] >> 29)) & 1) - 1) |
+ ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_49_0_bit | DV_II_48_0_bit | DV_II_55_0_bit));
+ if (mask &
+ (DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit | DV_II_54_0_bit | DV_II_56_0_bit))
+ mask &= (((((W[38] >> 4) ^ (W[41] >> 29)) & 1) - 1) |
+ ~(DV_I_44_0_bit | DV_I_48_0_bit | DV_II_47_0_bit | DV_II_54_0_bit |
+ DV_II_56_0_bit));
+ mask &=
+ (((((W[37] >> 4) ^ (W[40] >> 29)) & 1) - 1) |
+ ~(DV_I_43_0_bit | DV_I_47_0_bit | DV_II_46_0_bit | DV_II_53_0_bit | DV_II_55_0_bit));
+ if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_51_0_bit | DV_II_56_0_bit))
+ mask &= (((((W[55] ^ W[56]) >> 29) & 1) - 1) |
+ ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_51_0_bit | DV_II_56_0_bit));
+ if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit | DV_II_56_0_bit))
+ mask &= ((((W[52] ^ (W[55] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_50_0_bit | DV_II_56_0_bit));
+ if (mask & (DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit | DV_II_55_0_bit))
+ mask &= ((((W[51] ^ (W[54] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_51_0_bit | DV_II_47_0_bit | DV_II_49_0_bit | DV_II_55_0_bit));
+ if (mask & (DV_I_48_0_bit | DV_II_47_0_bit | DV_II_52_0_bit | DV_II_53_0_bit))
+ mask &= (((((W[51] ^ W[52]) >> 29) & 1) - 1) |
+ ~(DV_I_48_0_bit | DV_II_47_0_bit | DV_II_52_0_bit | DV_II_53_0_bit));
+ if (mask & (DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit))
+ mask &= (((((W[36] >> 4) ^ (W[40] >> 29)) & 1) - 1) |
+ ~(DV_I_46_0_bit | DV_I_49_0_bit | DV_II_45_0_bit | DV_II_48_0_bit));
+ if (mask & (DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit))
+ mask &= ((0 - (((W[53] ^ W[56]) >> 29) & 1)) |
+ ~(DV_I_52_0_bit | DV_II_48_0_bit | DV_II_49_0_bit));
+ if (mask & (DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit))
+ mask &= ((0 - (((W[51] ^ W[54]) >> 29) & 1)) |
+ ~(DV_I_50_0_bit | DV_II_46_0_bit | DV_II_47_0_bit));
+ if (mask & (DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit))
+ mask &= ((0 - (((W[50] ^ W[52]) >> 29) & 1)) |
+ ~(DV_I_49_0_bit | DV_I_51_0_bit | DV_II_45_0_bit));
+ if (mask & (DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit))
+ mask &= ((0 - (((W[49] ^ W[51]) >> 29) & 1)) |
+ ~(DV_I_48_0_bit | DV_I_50_0_bit | DV_I_52_0_bit));
+ if (mask & (DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit))
+ mask &= ((0 - (((W[48] ^ W[50]) >> 29) & 1)) |
+ ~(DV_I_47_0_bit | DV_I_49_0_bit | DV_I_51_0_bit));
+ if (mask & (DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit))
+ mask &= ((0 - (((W[47] ^ W[49]) >> 29) & 1)) |
+ ~(DV_I_46_0_bit | DV_I_48_0_bit | DV_I_50_0_bit));
+ if (mask & (DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit))
+ mask &= ((0 - (((W[46] ^ W[48]) >> 29) & 1)) |
+ ~(DV_I_45_0_bit | DV_I_47_0_bit | DV_I_49_0_bit));
+ mask &= ((((W[45] ^ W[47]) & (1 << 6)) - (1 << 6)) |
+ ~(DV_I_47_2_bit | DV_I_49_2_bit | DV_I_51_2_bit));
+ if (mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit))
+ mask &= ((0 - (((W[45] ^ W[47]) >> 29) & 1)) |
+ ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_I_48_0_bit));
+ mask &=
+ (((((W[44] ^ W[46]) >> 6) & 1) - 1) | ~(DV_I_46_2_bit | DV_I_48_2_bit | DV_I_50_2_bit));
+ if (mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit))
+ mask &= ((0 - (((W[44] ^ W[46]) >> 29) & 1)) |
+ ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_I_47_0_bit));
+ mask &= ((0 - ((W[41] ^ (W[42] >> 5)) & (1 << 1))) |
+ ~(DV_I_48_2_bit | DV_II_46_2_bit | DV_II_51_2_bit));
+ mask &= ((0 - ((W[40] ^ (W[41] >> 5)) & (1 << 1))) |
+ ~(DV_I_47_2_bit | DV_I_51_2_bit | DV_II_50_2_bit));
+ if (mask & (DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit))
+ mask &= ((0 - (((W[40] ^ W[42]) >> 4) & 1)) |
+ ~(DV_I_44_0_bit | DV_I_46_0_bit | DV_II_56_0_bit));
+ mask &= ((0 - ((W[39] ^ (W[40] >> 5)) & (1 << 1))) |
+ ~(DV_I_46_2_bit | DV_I_50_2_bit | DV_II_49_2_bit));
+ if (mask & (DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit))
+ mask &= ((0 - (((W[39] ^ W[41]) >> 4) & 1)) |
+ ~(DV_I_43_0_bit | DV_I_45_0_bit | DV_II_55_0_bit));
+ if (mask & (DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit))
+ mask &= ((0 - (((W[38] ^ W[40]) >> 4) & 1)) |
+ ~(DV_I_44_0_bit | DV_II_54_0_bit | DV_II_56_0_bit));
+ if (mask & (DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit))
+ mask &= ((0 - (((W[37] ^ W[39]) >> 4) & 1)) |
+ ~(DV_I_43_0_bit | DV_II_53_0_bit | DV_II_55_0_bit));
+ mask &= ((0 - ((W[36] ^ (W[37] >> 5)) & (1 << 1))) |
+ ~(DV_I_47_2_bit | DV_I_50_2_bit | DV_II_46_2_bit));
+ if (mask & (DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit))
+ mask &= (((((W[35] >> 4) ^ (W[39] >> 29)) & 1) - 1) |
+ ~(DV_I_45_0_bit | DV_I_48_0_bit | DV_II_47_0_bit));
+ if (mask & (DV_I_48_0_bit | DV_II_48_0_bit))
+ mask &=
+ ((0 - ((W[63] ^ (W[64] >> 5)) & (1 << 0))) | ~(DV_I_48_0_bit | DV_II_48_0_bit));
+ if (mask & (DV_I_45_0_bit | DV_II_45_0_bit))
+ mask &=
+ ((0 - ((W[63] ^ (W[64] >> 5)) & (1 << 1))) | ~(DV_I_45_0_bit | DV_II_45_0_bit));
+ if (mask & (DV_I_47_0_bit | DV_II_47_0_bit))
+ mask &=
+ ((0 - ((W[62] ^ (W[63] >> 5)) & (1 << 0))) | ~(DV_I_47_0_bit | DV_II_47_0_bit));
+ if (mask & (DV_I_46_0_bit | DV_II_46_0_bit))
+ mask &=
+ ((0 - ((W[61] ^ (W[62] >> 5)) & (1 << 0))) | ~(DV_I_46_0_bit | DV_II_46_0_bit));
+ mask &= ((0 - ((W[61] ^ (W[62] >> 5)) & (1 << 2))) | ~(DV_I_46_2_bit | DV_II_46_2_bit));
+ if (mask & (DV_I_45_0_bit | DV_II_45_0_bit))
+ mask &=
+ ((0 - ((W[60] ^ (W[61] >> 5)) & (1 << 0))) | ~(DV_I_45_0_bit | DV_II_45_0_bit));
+ if (mask & (DV_II_51_0_bit | DV_II_54_0_bit))
+ mask &= (((((W[58] ^ W[59]) >> 29) & 1) - 1) | ~(DV_II_51_0_bit | DV_II_54_0_bit));
+ if (mask & (DV_II_50_0_bit | DV_II_53_0_bit))
+ mask &= (((((W[57] ^ W[58]) >> 29) & 1) - 1) | ~(DV_II_50_0_bit | DV_II_53_0_bit));
+ if (mask & (DV_II_52_0_bit | DV_II_54_0_bit))
+ mask &= ((((W[56] ^ (W[59] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_II_52_0_bit | DV_II_54_0_bit));
+ if (mask & (DV_II_51_0_bit | DV_II_52_0_bit))
+ mask &= ((0 - (((W[56] ^ W[59]) >> 29) & 1)) | ~(DV_II_51_0_bit | DV_II_52_0_bit));
+ if (mask & (DV_II_49_0_bit | DV_II_52_0_bit))
+ mask &= (((((W[56] ^ W[57]) >> 29) & 1) - 1) | ~(DV_II_49_0_bit | DV_II_52_0_bit));
+ if (mask & (DV_II_51_0_bit | DV_II_53_0_bit))
+ mask &= ((((W[55] ^ (W[58] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_II_51_0_bit | DV_II_53_0_bit));
+ if (mask & (DV_II_50_0_bit | DV_II_52_0_bit))
+ mask &= ((((W[54] ^ (W[57] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_II_50_0_bit | DV_II_52_0_bit));
+ if (mask & (DV_II_49_0_bit | DV_II_51_0_bit))
+ mask &= ((((W[53] ^ (W[56] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_II_49_0_bit | DV_II_51_0_bit));
+ mask &=
+ ((((W[51] ^ (W[50] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_50_2_bit | DV_II_46_2_bit));
+ mask &= ((((W[48] ^ W[50]) & (1 << 6)) - (1 << 6)) | ~(DV_I_50_2_bit | DV_II_46_2_bit));
+ if (mask & (DV_I_51_0_bit | DV_I_52_0_bit))
+ mask &= ((0 - (((W[48] ^ W[55]) >> 29) & 1)) | ~(DV_I_51_0_bit | DV_I_52_0_bit));
+ mask &= ((((W[47] ^ W[49]) & (1 << 6)) - (1 << 6)) | ~(DV_I_49_2_bit | DV_I_51_2_bit));
+ mask &=
+ ((((W[48] ^ (W[47] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_47_2_bit | DV_II_51_2_bit));
+ mask &= ((((W[46] ^ W[48]) & (1 << 6)) - (1 << 6)) | ~(DV_I_48_2_bit | DV_I_50_2_bit));
+ mask &=
+ ((((W[47] ^ (W[46] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_46_2_bit | DV_II_50_2_bit));
+ mask &= ((0 - ((W[44] ^ (W[45] >> 5)) & (1 << 1))) | ~(DV_I_51_2_bit | DV_II_49_2_bit));
+ mask &= ((((W[43] ^ W[45]) & (1 << 6)) - (1 << 6)) | ~(DV_I_47_2_bit | DV_I_49_2_bit));
+ mask &= (((((W[42] ^ W[44]) >> 6) & 1) - 1) | ~(DV_I_46_2_bit | DV_I_48_2_bit));
+ mask &=
+ ((((W[43] ^ (W[42] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_II_46_2_bit | DV_II_51_2_bit));
+ mask &=
+ ((((W[42] ^ (W[41] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_51_2_bit | DV_II_50_2_bit));
+ mask &=
+ ((((W[41] ^ (W[40] >> 5)) & (1 << 1)) - (1 << 1)) | ~(DV_I_50_2_bit | DV_II_49_2_bit));
+ if (mask & (DV_I_52_0_bit | DV_II_51_0_bit))
+ mask &= ((((W[39] ^ (W[43] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_52_0_bit | DV_II_51_0_bit));
+ if (mask & (DV_I_51_0_bit | DV_II_50_0_bit))
+ mask &= ((((W[38] ^ (W[42] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_51_0_bit | DV_II_50_0_bit));
+ if (mask & (DV_I_48_2_bit | DV_I_51_2_bit))
+ mask &= ((0 - ((W[37] ^ (W[38] >> 5)) & (1 << 1))) | ~(DV_I_48_2_bit | DV_I_51_2_bit));
+ if (mask & (DV_I_50_0_bit | DV_II_49_0_bit))
+ mask &= ((((W[37] ^ (W[41] >> 25)) & (1 << 4)) - (1 << 4)) |
+ ~(DV_I_50_0_bit | DV_II_49_0_bit));
+ if (mask & (DV_II_52_0_bit | DV_II_54_0_bit))
+ mask &= ((0 - ((W[36] ^ W[38]) & (1 << 4))) | ~(DV_II_52_0_bit | DV_II_54_0_bit));
+ mask &= ((0 - ((W[35] ^ (W[36] >> 5)) & (1 << 1))) | ~(DV_I_46_2_bit | DV_I_49_2_bit));
+ if (mask & (DV_I_51_0_bit | DV_II_47_0_bit))
+ mask &= ((((W[35] ^ (W[39] >> 25)) & (1 << 3)) - (1 << 3)) |
+ ~(DV_I_51_0_bit | DV_II_47_0_bit));
+ if (mask) {
+ if (mask & DV_I_43_0_bit)
+ if (!((W[61] ^ (W[62] >> 5)) & (1 << 1)) ||
+ !(!((W[59] ^ (W[63] >> 25)) & (1 << 5))) ||
+ !((W[58] ^ (W[63] >> 30)) & (1 << 0)))
+ mask &= ~DV_I_43_0_bit;
+ if (mask & DV_I_44_0_bit)
+ if (!((W[62] ^ (W[63] >> 5)) & (1 << 1)) ||
+ !(!((W[60] ^ (W[64] >> 25)) & (1 << 5))) ||
+ !((W[59] ^ (W[64] >> 30)) & (1 << 0)))
+ mask &= ~DV_I_44_0_bit;
+ if (mask & DV_I_46_2_bit)
+ mask &= ((~((W[40] ^ W[42]) >> 2)) | ~DV_I_46_2_bit);
+ if (mask & DV_I_47_2_bit)
+ if (!((W[62] ^ (W[63] >> 5)) & (1 << 2)) || !(!((W[41] ^ W[43]) & (1 << 6))))
+ mask &= ~DV_I_47_2_bit;
+ if (mask & DV_I_48_2_bit)
+ if (!((W[63] ^ (W[64] >> 5)) & (1 << 2)) ||
+ !(!((W[48] ^ (W[49] << 5)) & (1 << 6))))
+ mask &= ~DV_I_48_2_bit;
+ if (mask & DV_I_49_2_bit)
+ if (!(!((W[49] ^ (W[50] << 5)) & (1 << 6))) || !((W[42] ^ W[50]) & (1 << 1)) ||
+ !(!((W[39] ^ (W[40] << 5)) & (1 << 6))) || !((W[38] ^ W[40]) & (1 << 1)))
+ mask &= ~DV_I_49_2_bit;
+ if (mask & DV_I_50_0_bit)
+ mask &= ((((W[36] ^ W[37]) << 7)) | ~DV_I_50_0_bit);
+ if (mask & DV_I_50_2_bit)
+ mask &= ((((W[43] ^ W[51]) << 11)) | ~DV_I_50_2_bit);
+ if (mask & DV_I_51_0_bit)
+ mask &= ((((W[37] ^ W[38]) << 9)) | ~DV_I_51_0_bit);
+ if (mask & DV_I_51_2_bit)
+ if (!(!((W[51] ^ (W[52] << 5)) & (1 << 6))) || !(!((W[49] ^ W[51]) & (1 << 6))) ||
+ !(!((W[37] ^ (W[37] >> 5)) & (1 << 1))) ||
+ !(!((W[35] ^ (W[39] >> 25)) & (1 << 5))))
+ mask &= ~DV_I_51_2_bit;
+ if (mask & DV_I_52_0_bit)
+ mask &= ((((W[38] ^ W[39]) << 11)) | ~DV_I_52_0_bit);
+ if (mask & DV_II_46_2_bit)
+ mask &= ((((W[47] ^ W[51]) << 17)) | ~DV_II_46_2_bit);
+ if (mask & DV_II_48_0_bit)
+ if (!(!((W[36] ^ (W[40] >> 25)) & (1 << 3))) ||
+ !((W[35] ^ (W[40] << 2)) & (1 << 30)))
+ mask &= ~DV_II_48_0_bit;
+ if (mask & DV_II_49_0_bit)
+ if (!(!((W[37] ^ (W[41] >> 25)) & (1 << 3))) ||
+ !((W[36] ^ (W[41] << 2)) & (1 << 30)))
+ mask &= ~DV_II_49_0_bit;
+ if (mask & DV_II_49_2_bit)
+ if (!(!((W[53] ^ (W[54] << 5)) & (1 << 6))) || !(!((W[51] ^ W[53]) & (1 << 6))) ||
+ !((W[50] ^ W[54]) & (1 << 1)) || !(!((W[45] ^ (W[46] << 5)) & (1 << 6))) ||
+ !(!((W[37] ^ (W[41] >> 25)) & (1 << 5))) ||
+ !((W[36] ^ (W[41] >> 30)) & (1 << 0)))
+ mask &= ~DV_II_49_2_bit;
+ if (mask & DV_II_50_0_bit)
+ if (!((W[55] ^ W[58]) & (1 << 29)) || !(!((W[38] ^ (W[42] >> 25)) & (1 << 3))) ||
+ !((W[37] ^ (W[42] << 2)) & (1 << 30)))
+ mask &= ~DV_II_50_0_bit;
+ if (mask & DV_II_50_2_bit)
+ if (!(!((W[54] ^ (W[55] << 5)) & (1 << 6))) || !(!((W[52] ^ W[54]) & (1 << 6))) ||
+ !((W[51] ^ W[55]) & (1 << 1)) || !((W[45] ^ W[47]) & (1 << 1)) ||
+ !(!((W[38] ^ (W[42] >> 25)) & (1 << 5))) ||
+ !((W[37] ^ (W[42] >> 30)) & (1 << 0)))
+ mask &= ~DV_II_50_2_bit;
+ if (mask & DV_II_51_0_bit)
+ if (!(!((W[39] ^ (W[43] >> 25)) & (1 << 3))) ||
+ !((W[38] ^ (W[43] << 2)) & (1 << 30)))
+ mask &= ~DV_II_51_0_bit;
+ if (mask & DV_II_51_2_bit)
+ if (!(!((W[55] ^ (W[56] << 5)) & (1 << 6))) || !(!((W[53] ^ W[55]) & (1 << 6))) ||
+ !((W[52] ^ W[56]) & (1 << 1)) || !((W[46] ^ W[48]) & (1 << 1)) ||
+ !(!((W[39] ^ (W[43] >> 25)) & (1 << 5))) ||
+ !((W[38] ^ (W[43] >> 30)) & (1 << 0)))
+ mask &= ~DV_II_51_2_bit;
+ if (mask & DV_II_52_0_bit)
+ if (!(!((W[59] ^ W[60]) & (1 << 29))) ||
+ !(!((W[40] ^ (W[44] >> 25)) & (1 << 3))) ||
+ !(!((W[40] ^ (W[44] >> 25)) & (1 << 4))) ||
+ !((W[39] ^ (W[44] << 2)) & (1 << 30)))
+ mask &= ~DV_II_52_0_bit;
+ if (mask & DV_II_53_0_bit)
+ if (!((W[58] ^ W[61]) & (1 << 29)) || !(!((W[57] ^ (W[61] >> 25)) & (1 << 4))) ||
+ !(!((W[41] ^ (W[45] >> 25)) & (1 << 3))) ||
+ !(!((W[41] ^ (W[45] >> 25)) & (1 << 4))))
+ mask &= ~DV_II_53_0_bit;
+ if (mask & DV_II_54_0_bit)
+ if (!(!((W[58] ^ (W[62] >> 25)) & (1 << 4))) ||
+ !(!((W[42] ^ (W[46] >> 25)) & (1 << 3))) ||
+ !(!((W[42] ^ (W[46] >> 25)) & (1 << 4))))
+ mask &= ~DV_II_54_0_bit;
+ if (mask & DV_II_55_0_bit)
+ if (!(!((W[59] ^ (W[63] >> 25)) & (1 << 4))) ||
+ !(!((W[57] ^ (W[59] >> 25)) & (1 << 4))) ||
+ !(!((W[43] ^ (W[47] >> 25)) & (1 << 3))) ||
+ !(!((W[43] ^ (W[47] >> 25)) & (1 << 4))))
+ mask &= ~DV_II_55_0_bit;
+ if (mask & DV_II_56_0_bit)
+ if (!(!((W[60] ^ (W[64] >> 25)) & (1 << 4))) ||
+ !(!((W[44] ^ (W[48] >> 25)) & (1 << 3))) ||
+ !(!((W[44] ^ (W[48] >> 25)) & (1 << 4))))
+ mask &= ~DV_II_56_0_bit;
+ }
+
+ dvmask[0] = mask;
+}
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_C
+#endif
diff --git a/src/lib/crypto/sha1cd/ubc_check.h b/src/lib/crypto/sha1cd/ubc_check.h
new file mode 100644
index 0000000..a43c7b6
--- /dev/null
+++ b/src/lib/crypto/sha1cd/ubc_check.h
@@ -0,0 +1,62 @@
+/***
+ * Copyright 2017 Marc Stevens <marc@marc-stevens.nl>, Dan Shumow <danshu@microsoft.com>
+ * Distributed under the MIT Software License.
+ * See accompanying file LICENSE.txt or copy at
+ * https://opensource.org/licenses/MIT
+ ***/
+
+/*
+// this file was generated by the 'parse_bitrel' program in the tools section
+// using the data files from directory 'tools/data/3565'
+//
+// sha1_dvs contains a list of SHA-1 Disturbance Vectors (DV) to check
+// dvType, dvK and dvB define the DV: I(K,B) or II(K,B) (see the paper)
+// dm[80] is the expanded message block XOR-difference defined by the DV
+// testt is the step to do the recompression from for collision detection
+// maski and maskb define the bit to check for each DV in the dvmask returned by ubc_check
+//
+// ubc_check takes as input an expanded message block and verifies the unavoidable
+bitconditions for all listed DVs
+// it returns a dvmask where each bit belonging to a DV is set if all unavoidable bitconditions
+for that DV have been met
+// thus one needs to do the recompression check for each DV that has its bit set
+*/
+
+#ifndef SHA1DC_UBC_CHECK_H
+#define SHA1DC_UBC_CHECK_H
+
+#if defined(__cplusplus)
+extern "C" {
+#endif
+
+#ifndef SHA1DC_NO_STANDARD_INCLUDES
+#include <stdint.h>
+#endif
+
+#define DVMASKSIZE 1
+typedef struct {
+ int dvType;
+ int dvK;
+ int dvB;
+ int testt;
+ int maski;
+ int maskb;
+ uint32_t dm[80];
+} dv_info_t;
+extern dv_info_t sha1_dvs[];
+void ubc_check(const uint32_t W[80], uint32_t dvmask[DVMASKSIZE]);
+
+#define DOSTORESTATE58
+#define DOSTORESTATE65
+
+#define CHECK_DVMASK(_DVMASK) (0 != _DVMASK[0])
+
+#if defined(__cplusplus)
+}
+#endif
+
+#ifdef SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H
+#include SHA1DC_CUSTOM_TRAILING_INCLUDE_UBC_CHECK_H
+#endif
+
+#endif
diff --git a/src/lib/crypto/signatures.cpp b/src/lib/crypto/signatures.cpp
new file mode 100644
index 0000000..ea39935
--- /dev/null
+++ b/src/lib/crypto/signatures.cpp
@@ -0,0 +1,281 @@
+/*
+ * 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:
+ * 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 <string.h>
+#include "crypto/signatures.h"
+#include "librepgp/stream-packet.h"
+#include "librepgp/stream-sig.h"
+#include "utils.h"
+#include "sec_profile.hpp"
+
+/**
+ * @brief Add signature fields to the hash context and finish it.
+ * @param hash initialized hash context fed with signed data (document, key, etc).
+ * It is finalized in this function.
+ * @param sig populated or loaded signature
+ * @param hbuf buffer to store the resulting hash. Must be large enough for hash output.
+ * @param hlen on success will be filled with the hash size, otherwise zeroed
+ * @return RNP_SUCCESS on success or some error otherwise
+ */
+static void
+signature_hash_finish(const pgp_signature_t &sig, rnp::Hash &hash, uint8_t *hbuf, size_t &hlen)
+{
+ hash.add(sig.hashed_data, sig.hashed_len);
+ if (sig.version > PGP_V3) {
+ uint8_t trailer[6] = {0x04, 0xff, 0x00, 0x00, 0x00, 0x00};
+ STORE32BE(&trailer[2], sig.hashed_len);
+ hash.add(trailer, 6);
+ }
+ hlen = hash.finish(hbuf);
+}
+
+std::unique_ptr<rnp::Hash>
+signature_init(const pgp_key_material_t &key, pgp_hash_alg_t 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);
+ 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,
+ rnp::Hash & hash,
+ rnp::SecurityContext &ctx)
+{
+ uint8_t hval[PGP_MAX_HASH_SIZE];
+ size_t hlen = 0;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ const pgp_hash_alg_t hash_alg = hash.alg();
+
+ /* Finalize hash first, since function is required to do this */
+ try {
+ signature_hash_finish(sig, hash, hval, hlen);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to finalize hash: %s", e.what());
+ throw;
+ }
+
+ if (!seckey.secret) {
+ RNP_LOG("Secret key is required.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ if (sig.palg != seckey.alg) {
+ RNP_LOG("Signature and secret key do not agree on algorithm type.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ /* Validate key material if didn't before */
+ seckey.validate(ctx, false);
+ if (!seckey.valid()) {
+ RNP_LOG("Attempt to sign with invalid key material.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ /* copy left 16 bits to signature */
+ memcpy(sig.lbits, hval, 2);
+
+ /* sign */
+ pgp_signature_material_t material = {};
+ switch (sig.palg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ ret = rsa_sign_pkcs1(&ctx.rng, &material.rsa, sig.halg, hval, hlen, &seckey.rsa);
+ if (ret) {
+ RNP_LOG("rsa signing failed");
+ }
+ break;
+ case PGP_PKA_EDDSA:
+ ret = eddsa_sign(&ctx.rng, &material.ecc, hval, hlen, &seckey.ec);
+ if (ret) {
+ RNP_LOG("eddsa signing failed");
+ }
+ break;
+ case PGP_PKA_DSA:
+ ret = dsa_sign(&ctx.rng, &material.dsa, hval, hlen, &seckey.dsa);
+ if (ret != RNP_SUCCESS) {
+ RNP_LOG("DSA signing failed");
+ }
+ break;
+ /*
+ * ECDH is signed with ECDSA. This must be changed when ECDH will support
+ * X25519, but I need to check how it should be done exactly.
+ */
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2: {
+ const ec_curve_desc_t *curve = get_curve_desc(seckey.ec.curve);
+ if (!curve) {
+ RNP_LOG("Unknown curve");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ break;
+ }
+ if (!curve_supported(seckey.ec.curve)) {
+ RNP_LOG("EC sign: curve %s is not supported.", curve->pgp_name);
+ ret = RNP_ERROR_NOT_SUPPORTED;
+ break;
+ }
+ /* "-2" because ECDSA on P-521 must work with SHA-512 digest */
+ if (BITS_TO_BYTES(curve->bitlen) - 2 > hlen) {
+ RNP_LOG("Message hash too small");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ break;
+ }
+
+ if (sig.palg == PGP_PKA_SM2) {
+#if defined(ENABLE_SM2)
+ ret = sm2_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec);
+ if (ret) {
+ RNP_LOG("SM2 signing failed");
+ }
+#else
+ RNP_LOG("SM2 signing is not available.");
+ ret = RNP_ERROR_NOT_IMPLEMENTED;
+#endif
+ break;
+ }
+
+ ret = ecdsa_sign(&ctx.rng, &material.ecc, hash_alg, hval, hlen, &seckey.ec);
+ if (ret) {
+ RNP_LOG("ECDSA signing failed");
+ }
+ break;
+ }
+ default:
+ RNP_LOG("Unsupported algorithm %d", sig.palg);
+ break;
+ }
+ if (ret) {
+ throw rnp::rnp_exception(ret);
+ }
+ try {
+ sig.write_material(material);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ throw;
+ }
+}
+
+rnp_result_t
+signature_validate(const pgp_signature_t & sig,
+ const pgp_key_material_t & key,
+ rnp::Hash & hash,
+ const rnp::SecurityContext &ctx)
+{
+ 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;
+ }
+
+ /* 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;
+ try {
+ signature_hash_finish(sig, hash, hval, hlen);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to finalize signature hash.");
+ return RNP_ERROR_GENERIC;
+ }
+
+ /* compare lbits */
+ if (memcmp(hval, sig.lbits, 2)) {
+ RNP_LOG("wrong lbits");
+ return RNP_ERROR_SIGNATURE_INVALID;
+ }
+
+ /* validate signature */
+ pgp_signature_material_t material = {};
+ try {
+ sig.parse_material(material);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ switch (sig.palg) {
+ case PGP_PKA_DSA:
+ ret = dsa_verify(&material.dsa, hval, hlen, &key.dsa);
+ break;
+ case PGP_PKA_EDDSA:
+ ret = eddsa_verify(&material.ecc, hval, hlen, &key.ec);
+ break;
+ case PGP_PKA_SM2:
+#if defined(ENABLE_SM2)
+ ret = sm2_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec);
+#else
+ RNP_LOG("SM2 verification is not available.");
+ ret = RNP_ERROR_NOT_IMPLEMENTED;
+#endif
+ break;
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ ret = rsa_verify_pkcs1(&material.rsa, sig.halg, hval, hlen, &key.rsa);
+ break;
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ RNP_LOG("RSA encrypt-only signature considered as invalid.");
+ ret = RNP_ERROR_SIGNATURE_INVALID;
+ break;
+ case PGP_PKA_ECDSA:
+ if (!curve_supported(key.ec.curve)) {
+ RNP_LOG("ECDSA verify: curve %d is not supported.", (int) key.ec.curve);
+ ret = RNP_ERROR_NOT_SUPPORTED;
+ break;
+ }
+ ret = ecdsa_verify(&material.ecc, hash.alg(), hval, hlen, &key.ec);
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ RNP_LOG("ElGamal are considered as invalid.");
+ ret = RNP_ERROR_SIGNATURE_INVALID;
+ break;
+ default:
+ RNP_LOG("Unknown algorithm");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ }
+ return ret;
+}
diff --git a/src/lib/crypto/signatures.h b/src/lib/crypto/signatures.h
new file mode 100644
index 0000000..6ba64ce
--- /dev/null
+++ b/src/lib/crypto/signatures.h
@@ -0,0 +1,69 @@
+/*
+ * 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:
+ * 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 RNP_SIGNATURES_H_
+#define RNP_SIGNATURES_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
+ */
+std::unique_ptr<rnp::Hash> 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
+ * @param hash pre-populated with signed data hash context. It is finalized and destroyed
+ * during the execution. Signature fields and trailer are hashed in this function.
+ * @param rng random number generator
+ */
+void signature_calculate(pgp_signature_t & sig,
+ pgp_key_material_t & seckey,
+ rnp::Hash & hash,
+ rnp::SecurityContext &ctx);
+
+/**
+ * @brief Validate a signature with pre-populated hash. This method just checks correspondence
+ * between the hash and signature material. Expiration time and other fields are not
+ * checked for validity.
+ * @param sig signature to validate
+ * @param key public key material of the verifying key
+ * @param hash pre-populated with signed data hash context. It is finalized
+ * during the execution. Signature fields and trailer are hashed in this function.
+ * @return RNP_SUCCESS if signature was successfully validated or error code otherwise.
+ */
+rnp_result_t signature_validate(const pgp_signature_t & sig,
+ const pgp_key_material_t & key,
+ rnp::Hash & hash,
+ const rnp::SecurityContext &ctx);
+
+#endif
diff --git a/src/lib/crypto/sm2.cpp b/src/lib/crypto/sm2.cpp
new file mode 100644
index 0000000..2af537d
--- /dev/null
+++ b/src/lib/crypto/sm2.cpp
@@ -0,0 +1,383 @@
+/*-
+ * 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.
+ */
+
+#include <string.h>
+#include <botan/ffi.h>
+#include "hash_botan.hpp"
+#include "sm2.h"
+#include "utils.h"
+#include "bn.h"
+
+static bool
+sm2_load_public_key(botan_pubkey_t *pubkey, const pgp_ec_key_t *keydata)
+{
+ const ec_curve_desc_t *curve = NULL;
+ botan_mp_t px = NULL;
+ botan_mp_t py = NULL;
+ size_t sz;
+ bool res = false;
+
+ if (!(curve = get_curve_desc(keydata->curve))) {
+ return false;
+ }
+
+ const size_t sign_half_len = BITS_TO_BYTES(curve->bitlen);
+ sz = mpi_bytes(&keydata->p);
+ if (!sz || (sz != (2 * sign_half_len + 1)) || (keydata->p.mpi[0] != 0x04)) {
+ goto end;
+ }
+
+ if (botan_mp_init(&px) || botan_mp_init(&py) ||
+ botan_mp_from_bin(px, &keydata->p.mpi[1], sign_half_len) ||
+ botan_mp_from_bin(py, &keydata->p.mpi[1 + sign_half_len], sign_half_len)) {
+ goto end;
+ }
+ res = !botan_pubkey_load_sm2(pubkey, px, py, curve->botan_name);
+end:
+ botan_mp_destroy(px);
+ botan_mp_destroy(py);
+ return res;
+}
+
+static bool
+sm2_load_secret_key(botan_privkey_t *seckey, const pgp_ec_key_t *keydata)
+{
+ const ec_curve_desc_t *curve = NULL;
+ bignum_t * x = NULL;
+ bool res = false;
+
+ if (!(curve = get_curve_desc(keydata->curve))) {
+ return false;
+ }
+ if (!(x = mpi2bn(&keydata->x))) {
+ return false;
+ }
+ res = !botan_privkey_load_sm2(seckey, BN_HANDLE_PTR(x), curve->botan_name);
+ bn_free(x);
+ return res;
+}
+
+rnp_result_t
+sm2_compute_za(const pgp_ec_key_t &key, rnp::Hash &hash, const char *ident_field)
+{
+ rnp_result_t result = RNP_ERROR_GENERIC;
+ botan_pubkey_t sm2_key = NULL;
+ int rc;
+
+ 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;
+ }
+
+ if (!sm2_load_public_key(&sm2_key, &key)) {
+ RNP_LOG("Failed to load SM2 key");
+ goto done;
+ }
+
+ if (ident_field == NULL)
+ ident_field = "1234567812345678";
+
+ rc = botan_pubkey_sm2_compute_za(digest_buf, &digest_len, ident_field, hash_algo, sm2_key);
+
+ if (rc != 0) {
+ RNP_LOG("compute_za failed %d", rc);
+ goto done;
+ }
+
+ try {
+ hash.add(digest_buf, digest_len);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to update hash: %s", e.what());
+ goto done;
+ }
+
+ result = RNP_SUCCESS;
+done:
+ free(digest_buf);
+ botan_pubkey_destroy(sm2_key);
+ return result;
+}
+
+rnp_result_t
+sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ botan_pubkey_t bpkey = NULL;
+ botan_privkey_t bskey = NULL;
+ rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS;
+
+ if (!sm2_load_public_key(&bpkey, key) || botan_pubkey_check_key(bpkey, rng->handle(), 0)) {
+ goto done;
+ }
+
+ if (!secret) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ if (!sm2_load_secret_key(&bskey, key) ||
+ botan_privkey_check_key(bskey, rng->handle(), 0)) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ botan_privkey_destroy(bskey);
+ botan_pubkey_destroy(bpkey);
+ return ret;
+}
+
+rnp_result_t
+sm2_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key)
+{
+ const ec_curve_desc_t *curve = NULL;
+ botan_pk_op_sign_t signer = NULL;
+ botan_privkey_t b_key = NULL;
+ uint8_t out_buf[2 * MAX_CURVE_BYTELEN] = {0};
+ size_t sign_half_len = 0;
+ size_t sig_len = 0;
+ rnp_result_t ret = RNP_ERROR_SIGNING_FAILED;
+
+ if (botan_ffi_supports_api(20180713) != 0) {
+ RNP_LOG("SM2 signatures requires Botan 2.8 or higher");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+
+ if (hash_len != rnp::Hash::size(hash_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (!(curve = get_curve_desc(key->curve))) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ sign_half_len = BITS_TO_BYTES(curve->bitlen);
+ sig_len = 2 * sign_half_len;
+
+ if (!sm2_load_secret_key(&b_key, key)) {
+ RNP_LOG("Can't load private key");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto end;
+ }
+
+ if (botan_pk_op_sign_create(&signer, b_key, ",Raw", 0)) {
+ goto end;
+ }
+
+ if (botan_pk_op_sign_update(signer, hash, hash_len)) {
+ goto end;
+ }
+
+ if (botan_pk_op_sign_finish(signer, rng->handle(), out_buf, &sig_len)) {
+ RNP_LOG("Signing failed");
+ goto end;
+ }
+
+ // Allocate memory and copy results
+ if (mem2mpi(&sig->r, out_buf, sign_half_len) &&
+ mem2mpi(&sig->s, out_buf + sign_half_len, sign_half_len)) {
+ // All good now
+ ret = RNP_SUCCESS;
+ }
+end:
+ botan_privkey_destroy(b_key);
+ botan_pk_op_sign_destroy(signer);
+ return ret;
+}
+
+rnp_result_t
+sm2_verify(const pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key)
+{
+ const ec_curve_desc_t *curve = NULL;
+ botan_pubkey_t pub = NULL;
+ botan_pk_op_verify_t verifier = NULL;
+ rnp_result_t ret = RNP_ERROR_SIGNATURE_INVALID;
+ uint8_t sign_buf[2 * MAX_CURVE_BYTELEN] = {0};
+ size_t r_blen, s_blen, sign_half_len;
+
+ if (botan_ffi_supports_api(20180713) != 0) {
+ RNP_LOG("SM2 signatures requires Botan 2.8 or higher");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+
+ if (hash_len != rnp::Hash::size(hash_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ curve = get_curve_desc(key->curve);
+ if (curve == NULL) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ sign_half_len = BITS_TO_BYTES(curve->bitlen);
+
+ if (!sm2_load_public_key(&pub, key)) {
+ RNP_LOG("Failed to load public key");
+ goto end;
+ }
+
+ if (botan_pk_op_verify_create(&verifier, pub, ",Raw", 0)) {
+ goto end;
+ }
+
+ if (botan_pk_op_verify_update(verifier, hash, hash_len)) {
+ goto end;
+ }
+
+ r_blen = sig->r.len;
+ s_blen = sig->s.len;
+ if (!r_blen || (r_blen > sign_half_len) || !s_blen || (s_blen > sign_half_len) ||
+ (sign_half_len > MAX_CURVE_BYTELEN)) {
+ goto end;
+ }
+
+ mpi2mem(&sig->r, sign_buf + sign_half_len - r_blen);
+ mpi2mem(&sig->s, sign_buf + 2 * sign_half_len - s_blen);
+
+ if (!botan_pk_op_verify_finish(verifier, sign_buf, sign_half_len * 2)) {
+ ret = RNP_SUCCESS;
+ }
+end:
+ botan_pubkey_destroy(pub);
+ botan_pk_op_verify_destroy(verifier);
+ return ret;
+}
+
+rnp_result_t
+sm2_encrypt(rnp::RNG * rng,
+ pgp_sm2_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ pgp_hash_alg_t hash_algo,
+ const pgp_ec_key_t * key)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ const ec_curve_desc_t *curve = NULL;
+ botan_pubkey_t sm2_key = NULL;
+ botan_pk_op_encrypt_t enc_op = NULL;
+ size_t point_len;
+ size_t hash_alg_len;
+ size_t ctext_len;
+
+ curve = get_curve_desc(key->curve);
+ if (curve == NULL) {
+ return RNP_ERROR_GENERIC;
+ }
+ point_len = BITS_TO_BYTES(curve->bitlen);
+ hash_alg_len = rnp::Hash::size(hash_algo);
+ if (!hash_alg_len) {
+ RNP_LOG("Unknown hash algorithm for SM2 encryption");
+ goto done;
+ }
+
+ /*
+ * Format of SM2 ciphertext is a point (2*point_len+1) plus
+ * the masked ciphertext (out_len) plus a hash.
+ */
+ ctext_len = (2 * point_len + 1) + in_len + hash_alg_len;
+ if (ctext_len > PGP_MPINT_SIZE) {
+ RNP_LOG("too large output for SM2 encryption");
+ goto done;
+ }
+
+ if (!sm2_load_public_key(&sm2_key, key)) {
+ RNP_LOG("Failed to load public key");
+ goto done;
+ }
+
+ /*
+ 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_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) {
+ out->m.mpi[out->m.len++] = hash_algo;
+ ret = RNP_SUCCESS;
+ }
+done:
+ botan_pk_op_encrypt_destroy(enc_op);
+ botan_pubkey_destroy(sm2_key);
+ return ret;
+}
+
+rnp_result_t
+sm2_decrypt(uint8_t * out,
+ size_t * out_len,
+ const pgp_sm2_encrypted_t *in,
+ const pgp_ec_key_t * key)
+{
+ const ec_curve_desc_t *curve;
+ botan_pk_op_decrypt_t decrypt_op = NULL;
+ botan_privkey_t b_key = NULL;
+ size_t in_len;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ uint8_t hash_id;
+ const char * hash_name = NULL;
+
+ curve = get_curve_desc(key->curve);
+ in_len = mpi_bytes(&in->m);
+ if (curve == NULL || in_len < 64) {
+ goto done;
+ }
+
+ if (!sm2_load_secret_key(&b_key, key)) {
+ RNP_LOG("Can't load private key");
+ goto done;
+ }
+
+ hash_id = in->m.mpi[in_len - 1];
+ 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;
+ }
+
+ if (botan_pk_op_decrypt_create(&decrypt_op, b_key, hash_name, 0) != 0) {
+ goto done;
+ }
+
+ if (botan_pk_op_decrypt(decrypt_op, out, out_len, in->m.mpi, in_len - 1) == 0) {
+ ret = RNP_SUCCESS;
+ }
+done:
+ botan_privkey_destroy(b_key);
+ botan_pk_op_decrypt_destroy(decrypt_op);
+ return ret;
+}
diff --git a/src/lib/crypto/sm2.h b/src/lib/crypto/sm2.h
new file mode 100644
index 0000000..a16f7fa
--- /dev/null
+++ b/src/lib/crypto/sm2.h
@@ -0,0 +1,79 @@
+/*-
+ * 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 RNP_SM2_H_
+#define RNP_SM2_H_
+
+#include "config.h"
+#include "ec.h"
+
+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);
+
+/**
+ * Compute the SM2 "ZA" field, and add it to the hash object
+ *
+ * If ident_field is null, uses the default value
+ */
+rnp_result_t sm2_compute_za(const pgp_ec_key_t &key,
+ rnp::Hash & hash,
+ const char * ident_field = NULL);
+
+rnp_result_t sm2_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key);
+
+rnp_result_t sm2_verify(const pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key);
+
+rnp_result_t sm2_encrypt(rnp::RNG * rng,
+ pgp_sm2_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ pgp_hash_alg_t hash_algo,
+ const pgp_ec_key_t * key);
+
+rnp_result_t sm2_decrypt(uint8_t * out,
+ size_t * out_len,
+ const pgp_sm2_encrypted_t *in,
+ const pgp_ec_key_t * key);
+#endif // defined(ENABLE_SM2)
+
+#endif // SM2_H_
diff --git a/src/lib/crypto/sm2_ossl.cpp b/src/lib/crypto/sm2_ossl.cpp
new file mode 100644
index 0000000..4c1eaf1
--- /dev/null
+++ b/src/lib/crypto/sm2_ossl.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (c) 2021, [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 <string.h>
+#include "sm2.h"
+#include "utils.h"
+
+rnp_result_t
+sm2_validate_key(rnp::RNG *rng, const pgp_ec_key_t *key, bool secret)
+{
+ return RNP_ERROR_NOT_IMPLEMENTED;
+}
+
+rnp_result_t
+sm2_sign(rnp::RNG * rng,
+ pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t *key)
+{
+ return RNP_ERROR_NOT_IMPLEMENTED;
+}
+
+rnp_result_t
+sm2_verify(const pgp_ec_signature_t *sig,
+ pgp_hash_alg_t hash_alg,
+ const uint8_t * hash,
+ size_t hash_len,
+ const pgp_ec_key_t * key)
+{
+ return RNP_ERROR_NOT_IMPLEMENTED;
+}
+
+rnp_result_t
+sm2_encrypt(rnp::RNG * rng,
+ pgp_sm2_encrypted_t *out,
+ const uint8_t * in,
+ size_t in_len,
+ pgp_hash_alg_t hash_algo,
+ const pgp_ec_key_t * key)
+{
+ return RNP_ERROR_NOT_IMPLEMENTED;
+}
+
+rnp_result_t
+sm2_decrypt(uint8_t * out,
+ size_t * out_len,
+ const pgp_sm2_encrypted_t *in,
+ const pgp_ec_key_t * key)
+{
+ return RNP_ERROR_NOT_IMPLEMENTED;
+}
diff --git a/src/lib/crypto/symmetric.cpp b/src/lib/crypto/symmetric.cpp
new file mode 100644
index 0000000..aeed784
--- /dev/null
+++ b/src/lib/crypto/symmetric.cpp
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "crypto.h"
+#include "config.h"
+#include "defaults.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <botan/ffi.h>
+#include "utils.h"
+
+static const char *
+pgp_sa_to_botan_string(int alg, bool silent = false)
+{
+ switch (alg) {
+#if defined(BOTAN_HAS_IDEA) && defined(ENABLE_IDEA)
+ case PGP_SA_IDEA:
+ return "IDEA";
+#endif
+
+#if defined(BOTAN_HAS_DES)
+ case PGP_SA_TRIPLEDES:
+ return "TripleDES";
+#endif
+
+#if defined(BOTAN_HAS_CAST) && defined(ENABLE_CAST5)
+ case PGP_SA_CAST5:
+ return "CAST-128";
+#endif
+
+#if defined(BOTAN_HAS_BLOWFISH) && defined(ENABLE_BLOWFISH)
+ case PGP_SA_BLOWFISH:
+ return "Blowfish";
+#endif
+
+#if defined(BOTAN_HAS_AES)
+ case PGP_SA_AES_128:
+ return "AES-128";
+ case PGP_SA_AES_192:
+ return "AES-192";
+ case PGP_SA_AES_256:
+ return "AES-256";
+#endif
+
+#if defined(BOTAN_HAS_SM4) && defined(ENABLE_SM2)
+ case PGP_SA_SM4:
+ return "SM4";
+#endif
+
+#if defined(BOTAN_HAS_TWOFISH) && defined(ENABLE_TWOFISH)
+ case PGP_SA_TWOFISH:
+ return "Twofish";
+#endif
+
+#if defined(BOTAN_HAS_CAMELLIA)
+ case PGP_SA_CAMELLIA_128:
+ return "Camellia-128";
+ case PGP_SA_CAMELLIA_192:
+ return "Camellia-192";
+ case PGP_SA_CAMELLIA_256:
+ return "Camellia-256";
+#endif
+
+ default:
+ if (!silent) {
+ RNP_LOG("Unsupported symmetric algorithm %d", alg);
+ }
+ return NULL;
+ }
+}
+
+#if defined(ENABLE_AEAD)
+static bool
+pgp_aead_to_botan_string(pgp_symm_alg_t ealg, pgp_aead_alg_t aalg, char *buf, size_t len)
+{
+ const char *ealg_name = pgp_sa_to_botan_string(ealg);
+ size_t ealg_len;
+
+ if (!ealg_name) {
+ return false;
+ }
+
+ ealg_len = strlen(ealg_name);
+
+ if (len < ealg_len + 5) {
+ RNP_LOG("buffer too small");
+ return false;
+ }
+
+ switch (aalg) {
+ case PGP_AEAD_EAX:
+ memcpy(buf, ealg_name, ealg_len);
+ strncpy(buf + ealg_len, "/EAX", len - ealg_len);
+ break;
+ case PGP_AEAD_OCB:
+ memcpy(buf, ealg_name, ealg_len);
+ strncpy(buf + ealg_len, "/OCB", len - ealg_len);
+ break;
+ default:
+ RNP_LOG("unsupported AEAD alg %d", (int) aalg);
+ return false;
+ }
+
+ return true;
+}
+#endif
+
+bool
+pgp_cipher_cfb_start(pgp_crypt_t * crypt,
+ pgp_symm_alg_t alg,
+ const uint8_t *key,
+ const uint8_t *iv)
+{
+ memset(crypt, 0x0, sizeof(*crypt));
+
+ const char *cipher_name = pgp_sa_to_botan_string(alg);
+ if (!cipher_name) {
+ return false;
+ }
+
+ crypt->alg = alg;
+ crypt->blocksize = pgp_block_size(alg);
+
+ // This shouldn't happen if pgp_sa_to_botan_string returned a ptr
+ if (botan_block_cipher_init(&(crypt->cfb.obj), cipher_name) != 0) {
+ RNP_LOG("Block cipher '%s' not available", cipher_name);
+ return false;
+ }
+
+ const size_t keysize = pgp_key_size(alg);
+
+ if (botan_block_cipher_set_key(crypt->cfb.obj, key, keysize) != 0) {
+ RNP_LOG("Failure setting key on block cipher object");
+ return false;
+ }
+
+ if (iv != NULL) {
+ // Otherwise left as all zeros via memset at start of function
+ memcpy(crypt->cfb.iv, iv, crypt->blocksize);
+ }
+
+ crypt->cfb.remaining = 0;
+
+ return true;
+}
+
+void
+pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf)
+{
+ /* iv will be encrypted in the upcoming call to encrypt/decrypt */
+ memcpy(crypt->cfb.iv, buf, crypt->blocksize);
+ crypt->cfb.remaining = 0;
+}
+
+int
+pgp_cipher_cfb_finish(pgp_crypt_t *crypt)
+{
+ if (!crypt) {
+ return 0;
+ }
+ if (crypt->cfb.obj) {
+ botan_block_cipher_destroy(crypt->cfb.obj);
+ crypt->cfb.obj = NULL;
+ }
+ botan_scrub_mem((uint8_t *) crypt, sizeof(*crypt));
+ return 0;
+}
+
+/* we rely on fact that in and out could be the same */
+int
+pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes)
+{
+ uint64_t *in64;
+ uint64_t buf64[512]; // 4KB - page size
+ uint64_t iv64[2];
+ size_t blocks, blockb;
+ unsigned blsize = crypt->blocksize;
+
+ /* encrypting till the block boundary */
+ while (bytes && crypt->cfb.remaining) {
+ *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ /* encrypting full blocks */
+ if (bytes > blsize) {
+ memcpy(iv64, crypt->cfb.iv, blsize);
+ while ((blocks = bytes & ~(blsize - 1)) > 0) {
+ if (blocks > sizeof(buf64)) {
+ blocks = sizeof(buf64);
+ }
+ bytes -= blocks;
+ blockb = blocks;
+ memcpy(buf64, in, blockb);
+ in64 = buf64;
+
+ if (blsize == 16) {
+ blocks >>= 4;
+ while (blocks--) {
+ botan_block_cipher_encrypt_blocks(
+ crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1);
+ *in64 ^= iv64[0];
+ iv64[0] = *in64++;
+ *in64 ^= iv64[1];
+ iv64[1] = *in64++;
+ }
+ } else {
+ blocks >>= 3;
+ while (blocks--) {
+ botan_block_cipher_encrypt_blocks(
+ crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1);
+ *in64 ^= iv64[0];
+ iv64[0] = *in64++;
+ }
+ }
+
+ memcpy(out, buf64, blockb);
+ out += blockb;
+ in += blockb;
+ }
+
+ memcpy(crypt->cfb.iv, iv64, blsize);
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ botan_block_cipher_encrypt_blocks(crypt->cfb.obj, crypt->cfb.iv, crypt->cfb.iv, 1);
+ crypt->cfb.remaining = blsize;
+
+ /* encrypting tail */
+ while (bytes) {
+ *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ return 0;
+}
+
+/* we rely on fact that in and out could be the same */
+int
+pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes)
+{
+ /* for better code readability */
+ uint64_t *out64, *in64;
+ uint64_t inbuf64[512]; // 4KB - page size
+ uint64_t outbuf64[512];
+ uint64_t iv64[2];
+ size_t blocks, blockb;
+ unsigned blsize = crypt->blocksize;
+
+ /* decrypting till the block boundary */
+ while (bytes && crypt->cfb.remaining) {
+ uint8_t c = *in++;
+ *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = c;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ /* decrypting full blocks */
+ if (bytes > blsize) {
+ memcpy(iv64, crypt->cfb.iv, blsize);
+
+ while ((blocks = bytes & ~(blsize - 1)) > 0) {
+ if (blocks > sizeof(inbuf64)) {
+ blocks = sizeof(inbuf64);
+ }
+ bytes -= blocks;
+ blockb = blocks;
+ memcpy(inbuf64, in, blockb);
+ out64 = outbuf64;
+ in64 = inbuf64;
+
+ if (blsize == 16) {
+ blocks >>= 4;
+ while (blocks--) {
+ botan_block_cipher_encrypt_blocks(
+ crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1);
+ *out64++ = *in64 ^ iv64[0];
+ iv64[0] = *in64++;
+ *out64++ = *in64 ^ iv64[1];
+ iv64[1] = *in64++;
+ }
+ } else {
+ blocks >>= 3;
+ while (blocks--) {
+ botan_block_cipher_encrypt_blocks(
+ crypt->cfb.obj, (uint8_t *) iv64, (uint8_t *) iv64, 1);
+ *out64++ = *in64 ^ iv64[0];
+ iv64[0] = *in64++;
+ }
+ }
+
+ memcpy(out, outbuf64, blockb);
+ out += blockb;
+ in += blockb;
+ }
+
+ memcpy(crypt->cfb.iv, iv64, blsize);
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ botan_block_cipher_encrypt_blocks(crypt->cfb.obj, crypt->cfb.iv, crypt->cfb.iv, 1);
+ crypt->cfb.remaining = blsize;
+
+ /* decrypting tail */
+ while (bytes) {
+ uint8_t c = *in++;
+ *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = c;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ return 0;
+}
+
+size_t
+pgp_cipher_block_size(pgp_crypt_t *crypt)
+{
+ return crypt->blocksize;
+}
+
+unsigned
+pgp_block_size(pgp_symm_alg_t alg)
+{
+ switch (alg) {
+ case PGP_SA_IDEA:
+ case PGP_SA_TRIPLEDES:
+ case PGP_SA_CAST5:
+ case PGP_SA_BLOWFISH:
+ return 8;
+ case PGP_SA_AES_128:
+ case PGP_SA_AES_192:
+ case PGP_SA_AES_256:
+ case PGP_SA_TWOFISH:
+ case PGP_SA_CAMELLIA_128:
+ case PGP_SA_CAMELLIA_192:
+ case PGP_SA_CAMELLIA_256:
+ case PGP_SA_SM4:
+ return 16;
+ default:
+ return 0;
+ }
+}
+
+unsigned
+pgp_key_size(pgp_symm_alg_t alg)
+{
+ /* Update MAX_SYMM_KEY_SIZE after adding algorithm
+ * with bigger key size.
+ */
+ static_assert(32 == MAX_SYMM_KEY_SIZE, "MAX_SYMM_KEY_SIZE must be updated");
+
+ switch (alg) {
+ case PGP_SA_IDEA:
+ case PGP_SA_CAST5:
+ case PGP_SA_BLOWFISH:
+ case PGP_SA_AES_128:
+ case PGP_SA_CAMELLIA_128:
+ case PGP_SA_SM4:
+ return 16;
+
+ case PGP_SA_TRIPLEDES:
+ case PGP_SA_AES_192:
+ case PGP_SA_CAMELLIA_192:
+ return 24;
+
+ case PGP_SA_TWOFISH:
+ case PGP_SA_AES_256:
+ case PGP_SA_CAMELLIA_256:
+ return 32;
+
+ default:
+ return 0;
+ }
+}
+
+bool
+pgp_is_sa_supported(int alg, bool silent)
+{
+ return pgp_sa_to_botan_string(alg, silent);
+}
+
+#if defined(ENABLE_AEAD)
+bool
+pgp_cipher_aead_init(pgp_crypt_t * crypt,
+ pgp_symm_alg_t ealg,
+ pgp_aead_alg_t aalg,
+ const uint8_t *key,
+ bool decrypt)
+{
+ char cipher_name[32];
+ uint32_t flags;
+
+ memset(crypt, 0x0, sizeof(*crypt));
+
+ if (!pgp_aead_to_botan_string(ealg, aalg, cipher_name, sizeof(cipher_name))) {
+ return false;
+ }
+
+ crypt->alg = ealg;
+ crypt->blocksize = pgp_block_size(ealg);
+ crypt->aead.alg = aalg;
+ crypt->aead.decrypt = decrypt;
+ crypt->aead.taglen = PGP_AEAD_EAX_OCB_TAG_LEN; /* it's the same for EAX and OCB */
+
+ flags = decrypt ? BOTAN_CIPHER_INIT_FLAG_DECRYPT : BOTAN_CIPHER_INIT_FLAG_ENCRYPT;
+
+ if (botan_cipher_init(&(crypt->aead.obj), cipher_name, flags)) {
+ RNP_LOG("cipher %s is not available", cipher_name);
+ return false;
+ }
+
+ if (botan_cipher_set_key(crypt->aead.obj, key, (size_t) pgp_key_size(ealg))) {
+ RNP_LOG("failed to set key");
+ return false;
+ }
+
+ if (botan_cipher_get_update_granularity(crypt->aead.obj, &crypt->aead.granularity)) {
+ RNP_LOG("failed to get update granularity");
+ return false;
+ }
+
+ return true;
+}
+
+size_t
+pgp_cipher_aead_granularity(pgp_crypt_t *crypt)
+{
+ return crypt->aead.granularity;
+}
+#endif
+
+size_t
+pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg)
+{
+ switch (aalg) {
+ case PGP_AEAD_EAX:
+ return PGP_AEAD_EAX_NONCE_LEN;
+ case PGP_AEAD_OCB:
+ return PGP_AEAD_OCB_NONCE_LEN;
+ default:
+ return 0;
+ }
+}
+
+size_t
+pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg)
+{
+ switch (aalg) {
+ case PGP_AEAD_EAX:
+ case PGP_AEAD_OCB:
+ return PGP_AEAD_EAX_OCB_TAG_LEN;
+ default:
+ return 0;
+ }
+}
+
+#if defined(ENABLE_AEAD)
+bool
+pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len)
+{
+ return botan_cipher_set_associated_data(crypt->aead.obj, ad, len) == 0;
+}
+
+bool
+pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len)
+{
+ return botan_cipher_start(crypt->aead.obj, nonce, len) == 0;
+}
+
+bool
+pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len)
+{
+ size_t outwr = 0;
+ size_t inread = 0;
+
+ if (len % crypt->aead.granularity) {
+ RNP_LOG("aead wrong update len");
+ return false;
+ }
+
+ if (botan_cipher_update(crypt->aead.obj, 0, out, len, &outwr, in, len, &inread) != 0) {
+ RNP_LOG("aead update failed");
+ return false;
+ }
+
+ if ((outwr != len) || (inread != len)) {
+ RNP_LOG("wrong aead usage");
+ return false;
+ }
+
+ return true;
+}
+
+void
+pgp_cipher_aead_reset(pgp_crypt_t *crypt)
+{
+ botan_cipher_reset(crypt->aead.obj);
+}
+
+bool
+pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len)
+{
+ uint32_t flags = BOTAN_CIPHER_UPDATE_FLAG_FINAL;
+ size_t inread = 0;
+ size_t outwr = 0;
+ int res;
+
+ if (crypt->aead.decrypt) {
+ size_t datalen = len - crypt->aead.taglen;
+ /* for decryption we should have tag for the final update call */
+ res =
+ botan_cipher_update(crypt->aead.obj, flags, out, datalen, &outwr, in, len, &inread);
+ if (res != 0) {
+ if (res != BOTAN_FFI_ERROR_BAD_MAC) {
+ RNP_LOG("aead finish failed: %d", res);
+ }
+ return false;
+ }
+
+ if ((outwr != datalen) || (inread != len)) {
+ RNP_LOG("wrong decrypt aead finish usage");
+ return false;
+ }
+ } else {
+ /* for encryption tag will be generated */
+ size_t outlen = len + crypt->aead.taglen;
+ if (botan_cipher_update(
+ crypt->aead.obj, flags, out, outlen, &outwr, in, len, &inread) != 0) {
+ RNP_LOG("aead finish failed");
+ return false;
+ }
+
+ if ((outwr != outlen) || (inread != len)) {
+ RNP_LOG("wrong encrypt aead finish usage");
+ return false;
+ }
+ }
+
+ pgp_cipher_aead_reset(crypt);
+ return true;
+}
+
+void
+pgp_cipher_aead_destroy(pgp_crypt_t *crypt)
+{
+ botan_cipher_destroy(crypt->aead.obj);
+}
+
+size_t
+pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, const uint8_t *iv, uint8_t *nonce, size_t index)
+{
+ switch (aalg) {
+ case PGP_AEAD_EAX:
+ /* The nonce for EAX mode is computed by treating the starting
+ initialization vector as a 16-octet, big-endian value and
+ exclusive-oring the low eight octets of it with the chunk index.
+ */
+ memcpy(nonce, iv, PGP_AEAD_EAX_NONCE_LEN);
+ for (int i = 15; (i > 7) && index; i--) {
+ nonce[i] ^= index & 0xff;
+ index = index >> 8;
+ }
+ return PGP_AEAD_EAX_NONCE_LEN;
+ case PGP_AEAD_OCB:
+ /* The nonce for a chunk of chunk index "i" in OCB processing is defined as:
+ OCB-Nonce_{i} = IV[1..120] xor i
+ */
+ memcpy(nonce, iv, PGP_AEAD_OCB_NONCE_LEN);
+ for (int i = 14; (i >= 0) && index; i--) {
+ nonce[i] ^= index & 0xff;
+ index = index >> 8;
+ }
+ return PGP_AEAD_OCB_NONCE_LEN;
+ default:
+ return 0;
+ }
+}
+#endif // ENABLE_AEAD
diff --git a/src/lib/crypto/symmetric.h b/src/lib/crypto/symmetric.h
new file mode 100644
index 0000000..a50fe9a
--- /dev/null
+++ b/src/lib/crypto/symmetric.h
@@ -0,0 +1,252 @@
+/*
+ * Copyright (c) 2017, 2021 [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SYMMETRIC_CRYPTO_H_
+#define SYMMETRIC_CRYPTO_H_
+
+#include <repgp/repgp_def.h>
+#include "crypto/rng.h"
+#include "config.h"
+#ifdef CRYPTO_BACKEND_OPENSSL
+#include <openssl/evp.h>
+#include "mem.h"
+#endif
+
+/* Nonce len for AEAD/EAX */
+#define PGP_AEAD_EAX_NONCE_LEN 16
+
+/* Nonce len for AEAD/OCB */
+#define PGP_AEAD_OCB_NONCE_LEN 15
+
+/* Maximum AEAD nonce length */
+#define PGP_AEAD_MAX_NONCE_LEN 16
+
+/* Authentication tag len for AEAD/EAX and AEAD/OCB */
+#define PGP_AEAD_EAX_OCB_TAG_LEN 16
+
+/* Maximal size of symmetric key */
+#define MAX_SYMM_KEY_SIZE 32
+
+/* Maximum AEAD tag length */
+#define PGP_AEAD_MAX_TAG_LEN 16
+
+/* Maximum authenticated data length for AEAD */
+#define PGP_AEAD_MAX_AD_LEN 32
+
+struct pgp_crypt_cfb_param_t {
+#ifdef CRYPTO_BACKEND_BOTAN
+ struct botan_block_cipher_struct *obj;
+#endif
+#ifdef CRYPTO_BACKEND_OPENSSL
+ EVP_CIPHER_CTX *obj;
+#endif
+ size_t remaining;
+ uint8_t iv[PGP_MAX_BLOCK_SIZE];
+};
+
+struct pgp_crypt_aead_param_t {
+#ifdef CRYPTO_BACKEND_BOTAN
+ struct botan_cipher_struct *obj;
+#endif
+#ifdef CRYPTO_BACKEND_OPENSSL
+ EVP_CIPHER_CTX * obj;
+ const EVP_CIPHER * cipher;
+ rnp::secure_vector<uint8_t> *key;
+ uint8_t ad[PGP_AEAD_MAX_AD_LEN];
+ size_t ad_len;
+ size_t n_len;
+#endif
+ pgp_aead_alg_t alg;
+ bool decrypt;
+ size_t granularity;
+ size_t taglen;
+};
+
+/** pgp_crypt_t */
+typedef struct pgp_crypt_t {
+ union {
+ struct pgp_crypt_cfb_param_t cfb;
+#if defined(ENABLE_AEAD)
+ struct pgp_crypt_aead_param_t aead;
+#endif
+ };
+
+ pgp_symm_alg_t alg;
+ size_t blocksize;
+ rnp::RNG * rng;
+} pgp_crypt_t;
+
+unsigned pgp_block_size(pgp_symm_alg_t);
+unsigned pgp_key_size(pgp_symm_alg_t);
+bool pgp_is_sa_supported(int alg, bool silent = false);
+size_t pgp_cipher_block_size(pgp_crypt_t *crypt);
+
+/**
+ * Initialize a cipher object.
+ * @param iv if null an all-zero IV is assumed
+ */
+bool pgp_cipher_cfb_start(pgp_crypt_t * crypt,
+ pgp_symm_alg_t alg,
+ const uint8_t *key,
+ const uint8_t *iv);
+
+// Deallocate all storage
+int pgp_cipher_cfb_finish(pgp_crypt_t *crypt);
+// CFB encryption/decryption
+int pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len);
+int pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len);
+
+void pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf);
+
+#if defined(ENABLE_AEAD)
+/** @brief Initialize AEAD cipher instance
+ * @param crypt pgp crypto object
+ * @param ealg symmetric encryption algorithm to use together with AEAD cipher mode
+ * @param aalg AEAD cipher mode. Only EAX is supported now
+ * @param key key buffer. Number of key bytes is determined by ealg.
+ * @param decrypt true for decryption, or false for encryption
+ * @return true on success or false otherwise.
+ */
+bool pgp_cipher_aead_init(pgp_crypt_t * crypt,
+ pgp_symm_alg_t ealg,
+ pgp_aead_alg_t aalg,
+ const uint8_t *key,
+ bool decrypt);
+
+/** @brief Return the AEAD cipher update granularity. Botan FFI will consume chunks which are
+ * multiple of this value. See the description of pgp_cipher_aead_update()
+ * @param crypt initialized AEAD crypto
+ * @return Update granularity value in bytes
+ */
+size_t pgp_cipher_aead_granularity(pgp_crypt_t *crypt);
+#endif
+
+/** @brief Return the AEAD cipher tag length
+ * @param aalg OpenPGP AEAD algorithm
+ * @return length of authentication tag in bytes, or 0 for unknown algorithm
+ */
+size_t pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg);
+
+/** @brief Return the AEAD cipher nonce and IV length
+ * @param aalg OpenPGP AEAD algorithm
+ * @return length of nonce in bytes, or 0 for unknown algorithm
+ */
+size_t pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg);
+
+#if defined(ENABLE_AEAD)
+/** @brief Set associated data
+ * @param crypt initialized AEAD crypto
+ * @param ad buffer with data. Cannot be NULL.
+ * @param len number of bytes in ad
+ * @return true on success or false otherwise.
+ */
+bool pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len);
+
+/** @brief Start the cipher operation, using the given nonce
+ * @param crypt initialized AEAD crypto
+ * @param nonce buffer with nonce, cannot be NULL.
+ * @param len number of bytes in nonce. Must conform to the cipher properties.
+ * @return true on success or false otherwise.
+ */
+bool pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len);
+
+/** @brief Update the cipher. This should be called for non-final data, respecting the
+ * update granularity of underlying botan cipher. Now it is 256 bytes.
+ * @param crypt initialized AEAD crypto
+ * @param out buffer to put processed data. Cannot be NULL, and should be large enough to put
+ * len bytes
+ * @param in buffer with input, cannot be NULL
+ * @param len number of bytes to process. Should be multiple of update granularity.
+ * @return true on success or false otherwise. On success exactly len processed bytes will be
+ * stored in out buffer
+ */
+bool pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len);
+
+/** @brief Do final update on the cipher. For decryption final chunk should contain at least
+ * authentication tag, for encryption input could be zero-size.
+ * @param crypt initialized AEAD crypto
+ * @param out buffer to put processed data. For decryption it should be large enough to put
+ * len bytes minus authentication tag, for encryption it should be large enough to
+ * put len byts plus a tag.
+ * @param in buffer with input, if any. May be NULL for encryption, then len should be zero.
+ * For decryption it should contain at least authentication tag.
+ * @param len number of input bytes bytes
+ * @return true on success or false otherwise. On success for decryption len minus tag size
+ * bytes will be stored in out, for encryption out will contain len bytes plus
+ * tag size.
+ */
+bool pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len);
+
+/** @brief Reset the AEAD cipher's state, calling the finish() and ignoring the result
+ * @param crypt initialized AEAD crypto
+ */
+void pgp_cipher_aead_reset(pgp_crypt_t *crypt);
+
+/** @brief Destroy the cipher object, deallocating all the memory.
+ * @param crypt initialized AEAD crypto
+ */
+void pgp_cipher_aead_destroy(pgp_crypt_t *crypt);
+
+/** @brief Helper function to set AEAD nonce for the chunk by its index.
+ * iv and nonce should be large enough to hold max nonce bytes
+ * @param aalg AEAD algorithm used
+ * @param iv Initial vector for the message, must have 16 bytes of data
+ * @param nonce Nonce to fill up, should have space for 16 bytes of data
+ * @param index Chunk's index
+ * @return Length of the nonce, or 0 if algorithm is unknown
+ */
+size_t pgp_cipher_aead_nonce(pgp_aead_alg_t aalg,
+ const uint8_t *iv,
+ uint8_t * nonce,
+ size_t index);
+#endif // ENABLE_AEAD
+
+#endif
diff --git a/src/lib/crypto/symmetric_ossl.cpp b/src/lib/crypto/symmetric_ossl.cpp
new file mode 100644
index 0000000..98e90ed
--- /dev/null
+++ b/src/lib/crypto/symmetric_ossl.cpp
@@ -0,0 +1,644 @@
+/*-
+ * 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 "crypto.h"
+#include "config.h"
+#include "defaults.h"
+
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <openssl/evp.h>
+#include <openssl/err.h>
+#include "mem.h"
+#include "utils.h"
+
+static const char *
+pgp_sa_to_openssl_string(int alg, bool silent = false)
+{
+ switch (alg) {
+#if defined(ENABLE_IDEA)
+ case PGP_SA_IDEA:
+ return "idea-ecb";
+#endif
+ case PGP_SA_TRIPLEDES:
+ return "des-ede3";
+#if defined(ENABLE_CAST5)
+ case PGP_SA_CAST5:
+ return "cast5-ecb";
+#endif
+#if defined(ENABLE_BLOWFISH)
+ case PGP_SA_BLOWFISH:
+ return "bf-ecb";
+#endif
+ case PGP_SA_AES_128:
+ return "aes-128-ecb";
+ case PGP_SA_AES_192:
+ return "aes-192-ecb";
+ case PGP_SA_AES_256:
+ return "aes-256-ecb";
+#if defined(ENABLE_SM2)
+ case PGP_SA_SM4:
+ return "sm4-ecb";
+#endif
+ case PGP_SA_CAMELLIA_128:
+ return "camellia-128-ecb";
+ case PGP_SA_CAMELLIA_192:
+ return "camellia-192-ecb";
+ case PGP_SA_CAMELLIA_256:
+ return "camellia-256-ecb";
+ default:
+ if (!silent) {
+ RNP_LOG("Unsupported symmetric algorithm %d", alg);
+ }
+ return NULL;
+ }
+}
+
+bool
+pgp_cipher_cfb_start(pgp_crypt_t * crypt,
+ pgp_symm_alg_t alg,
+ const uint8_t *key,
+ const uint8_t *iv)
+{
+ memset(crypt, 0x0, sizeof(*crypt));
+
+ const char *cipher_name = pgp_sa_to_openssl_string(alg);
+ if (!cipher_name) {
+ RNP_LOG("Unsupported algorithm: %d", alg);
+ return false;
+ }
+
+ const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_name);
+ if (!cipher) {
+ RNP_LOG("Cipher %s is not supported by OpenSSL.", cipher_name);
+ return false;
+ }
+
+ crypt->alg = alg;
+ crypt->blocksize = pgp_block_size(alg);
+
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ int res = EVP_EncryptInit_ex(ctx, cipher, NULL, key, iv);
+ if (res != 1) {
+ RNP_LOG("Failed to initialize cipher.");
+ EVP_CIPHER_CTX_free(ctx);
+ return false;
+ }
+ crypt->cfb.obj = ctx;
+
+ if (iv) {
+ // Otherwise left as all zeros via memset at start of function
+ memcpy(crypt->cfb.iv, iv, crypt->blocksize);
+ }
+
+ crypt->cfb.remaining = 0;
+ return true;
+}
+
+void
+pgp_cipher_cfb_resync(pgp_crypt_t *crypt, const uint8_t *buf)
+{
+ /* iv will be encrypted in the upcoming call to encrypt/decrypt */
+ memcpy(crypt->cfb.iv, buf, crypt->blocksize);
+ crypt->cfb.remaining = 0;
+}
+
+int
+pgp_cipher_cfb_finish(pgp_crypt_t *crypt)
+{
+ if (!crypt) {
+ return 0;
+ }
+ if (crypt->cfb.obj) {
+ EVP_CIPHER_CTX_free(crypt->cfb.obj);
+ crypt->cfb.obj = NULL;
+ }
+ OPENSSL_cleanse((uint8_t *) crypt, sizeof(*crypt));
+ return 0;
+}
+
+/* we rely on fact that in and out could be the same */
+int
+pgp_cipher_cfb_encrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes)
+{
+ uint64_t *in64;
+ uint64_t buf64[512]; // 4KB - page size
+ uint64_t iv64[2];
+ size_t blocks, blockb;
+ unsigned blsize = crypt->blocksize;
+
+ /* encrypting till the block boundary */
+ while (bytes && crypt->cfb.remaining) {
+ *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ /* encrypting full blocks */
+ if (bytes > blsize) {
+ memcpy(iv64, crypt->cfb.iv, blsize);
+ while ((blocks = bytes & ~(blsize - 1)) > 0) {
+ if (blocks > sizeof(buf64)) {
+ blocks = sizeof(buf64);
+ }
+ bytes -= blocks;
+ blockb = blocks;
+ memcpy(buf64, in, blockb);
+ in64 = buf64;
+
+ if (blsize == 16) {
+ blocks >>= 4;
+ while (blocks--) {
+ int outlen = 16;
+ EVP_EncryptUpdate(
+ crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 16);
+ if (outlen != 16) {
+ RNP_LOG("Bad outlen: must be 16");
+ }
+ *in64 ^= iv64[0];
+ iv64[0] = *in64++;
+ *in64 ^= iv64[1];
+ iv64[1] = *in64++;
+ }
+ } else {
+ blocks >>= 3;
+ while (blocks--) {
+ int outlen = 8;
+ EVP_EncryptUpdate(
+ crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 8);
+ if (outlen != 8) {
+ RNP_LOG("Bad outlen: must be 8");
+ }
+ *in64 ^= iv64[0];
+ iv64[0] = *in64++;
+ }
+ }
+
+ memcpy(out, buf64, blockb);
+ out += blockb;
+ in += blockb;
+ }
+
+ memcpy(crypt->cfb.iv, iv64, blsize);
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ int outlen = blsize;
+ EVP_EncryptUpdate(crypt->cfb.obj, crypt->cfb.iv, &outlen, crypt->cfb.iv, (int) blsize);
+ if (outlen != (int) blsize) {
+ RNP_LOG("Bad outlen: must be %u", blsize);
+ }
+ crypt->cfb.remaining = blsize;
+
+ /* encrypting tail */
+ while (bytes) {
+ *out = *in++ ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = *out++;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ return 0;
+}
+
+/* we rely on fact that in and out could be the same */
+int
+pgp_cipher_cfb_decrypt(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t bytes)
+{
+ /* for better code readability */
+ uint64_t *out64, *in64;
+ uint64_t inbuf64[512]; // 4KB - page size
+ uint64_t outbuf64[512];
+ uint64_t iv64[2];
+ size_t blocks, blockb;
+ unsigned blsize = crypt->blocksize;
+
+ /* decrypting till the block boundary */
+ while (bytes && crypt->cfb.remaining) {
+ uint8_t c = *in++;
+ *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = c;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ /* decrypting full blocks */
+ if (bytes > blsize) {
+ memcpy(iv64, crypt->cfb.iv, blsize);
+
+ while ((blocks = bytes & ~(blsize - 1)) > 0) {
+ if (blocks > sizeof(inbuf64)) {
+ blocks = sizeof(inbuf64);
+ }
+ bytes -= blocks;
+ blockb = blocks;
+ memcpy(inbuf64, in, blockb);
+ out64 = outbuf64;
+ in64 = inbuf64;
+
+ if (blsize == 16) {
+ blocks >>= 4;
+ while (blocks--) {
+ int outlen = 16;
+ EVP_EncryptUpdate(
+ crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 16);
+ if (outlen != 16) {
+ RNP_LOG("Bad outlen: must be 16");
+ }
+ *out64++ = *in64 ^ iv64[0];
+ iv64[0] = *in64++;
+ *out64++ = *in64 ^ iv64[1];
+ iv64[1] = *in64++;
+ }
+ } else {
+ blocks >>= 3;
+ while (blocks--) {
+ int outlen = 8;
+ EVP_EncryptUpdate(
+ crypt->cfb.obj, (uint8_t *) iv64, &outlen, (uint8_t *) iv64, 8);
+ if (outlen != 8) {
+ RNP_LOG("Bad outlen: must be 8");
+ }
+ *out64++ = *in64 ^ iv64[0];
+ iv64[0] = *in64++;
+ }
+ }
+
+ memcpy(out, outbuf64, blockb);
+ out += blockb;
+ in += blockb;
+ }
+
+ memcpy(crypt->cfb.iv, iv64, blsize);
+ }
+
+ if (!bytes) {
+ return 0;
+ }
+
+ int outlen = blsize;
+ EVP_EncryptUpdate(crypt->cfb.obj, crypt->cfb.iv, &outlen, crypt->cfb.iv, (int) blsize);
+ if (outlen != (int) blsize) {
+ RNP_LOG("Bad outlen: must be %u", blsize);
+ }
+ crypt->cfb.remaining = blsize;
+
+ /* decrypting tail */
+ while (bytes) {
+ uint8_t c = *in++;
+ *out++ = c ^ crypt->cfb.iv[blsize - crypt->cfb.remaining];
+ crypt->cfb.iv[blsize - crypt->cfb.remaining] = c;
+ crypt->cfb.remaining--;
+ bytes--;
+ }
+
+ return 0;
+}
+
+size_t
+pgp_cipher_block_size(pgp_crypt_t *crypt)
+{
+ return crypt->blocksize;
+}
+
+unsigned
+pgp_block_size(pgp_symm_alg_t alg)
+{
+ switch (alg) {
+ case PGP_SA_IDEA:
+ case PGP_SA_TRIPLEDES:
+ case PGP_SA_CAST5:
+ case PGP_SA_BLOWFISH:
+ return 8;
+ case PGP_SA_AES_128:
+ case PGP_SA_AES_192:
+ case PGP_SA_AES_256:
+ case PGP_SA_TWOFISH:
+ case PGP_SA_CAMELLIA_128:
+ case PGP_SA_CAMELLIA_192:
+ case PGP_SA_CAMELLIA_256:
+ case PGP_SA_SM4:
+ return 16;
+ default:
+ return 0;
+ }
+}
+
+unsigned
+pgp_key_size(pgp_symm_alg_t alg)
+{
+ /* Update MAX_SYMM_KEY_SIZE after adding algorithm
+ * with bigger key size.
+ */
+ static_assert(32 == MAX_SYMM_KEY_SIZE, "MAX_SYMM_KEY_SIZE must be updated");
+
+ switch (alg) {
+ case PGP_SA_IDEA:
+ case PGP_SA_CAST5:
+ case PGP_SA_BLOWFISH:
+ case PGP_SA_AES_128:
+ case PGP_SA_CAMELLIA_128:
+ case PGP_SA_SM4:
+ return 16;
+ case PGP_SA_TRIPLEDES:
+ case PGP_SA_AES_192:
+ case PGP_SA_CAMELLIA_192:
+ return 24;
+ case PGP_SA_TWOFISH:
+ case PGP_SA_AES_256:
+ case PGP_SA_CAMELLIA_256:
+ return 32;
+ default:
+ return 0;
+ }
+}
+
+bool
+pgp_is_sa_supported(int alg, bool silent)
+{
+ return pgp_sa_to_openssl_string(alg, silent);
+}
+
+#if defined(ENABLE_AEAD)
+
+static const char *
+openssl_aead_name(pgp_symm_alg_t ealg, pgp_aead_alg_t aalg)
+{
+ switch (aalg) {
+ case PGP_AEAD_OCB:
+ break;
+ default:
+ RNP_LOG("Only OCB mode is supported by the OpenSSL backend.");
+ return NULL;
+ }
+ switch (ealg) {
+ case PGP_SA_AES_128:
+ return "AES-128-OCB";
+ case PGP_SA_AES_192:
+ return "AES-192-OCB";
+ case PGP_SA_AES_256:
+ return "AES-256-OCB";
+ default:
+ RNP_LOG("Only AES-OCB is supported by the OpenSSL backend.");
+ return NULL;
+ }
+}
+
+bool
+pgp_cipher_aead_init(pgp_crypt_t * crypt,
+ pgp_symm_alg_t ealg,
+ pgp_aead_alg_t aalg,
+ const uint8_t *key,
+ bool decrypt)
+{
+ memset(crypt, 0x0, sizeof(*crypt));
+ /* OpenSSL backend currently supports only AES-OCB */
+ const char *algname = openssl_aead_name(ealg, aalg);
+ if (!algname) {
+ return false;
+ }
+ auto cipher = EVP_get_cipherbyname(algname);
+ if (!cipher) {
+ RNP_LOG("Cipher %s is not supported.", algname);
+ return false;
+ }
+ /* Create and setup context */
+ EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
+ if (!ctx) {
+ RNP_LOG("Failed to create cipher context: %lu", ERR_peek_last_error());
+ return false;
+ }
+
+ crypt->aead.key = new rnp::secure_vector<uint8_t>(key, key + pgp_key_size(ealg));
+ crypt->alg = ealg;
+ crypt->blocksize = pgp_block_size(ealg);
+ crypt->aead.cipher = cipher;
+ crypt->aead.obj = ctx;
+ crypt->aead.alg = aalg;
+ crypt->aead.decrypt = decrypt;
+ crypt->aead.granularity = crypt->blocksize;
+ crypt->aead.taglen = PGP_AEAD_EAX_OCB_TAG_LEN;
+ crypt->aead.ad_len = 0;
+ crypt->aead.n_len = pgp_cipher_aead_nonce_len(aalg);
+ return true;
+}
+
+size_t
+pgp_cipher_aead_granularity(pgp_crypt_t *crypt)
+{
+ return crypt->aead.granularity;
+}
+#endif
+
+size_t
+pgp_cipher_aead_nonce_len(pgp_aead_alg_t aalg)
+{
+ switch (aalg) {
+ case PGP_AEAD_EAX:
+ return PGP_AEAD_EAX_NONCE_LEN;
+ case PGP_AEAD_OCB:
+ return PGP_AEAD_OCB_NONCE_LEN;
+ default:
+ return 0;
+ }
+}
+
+size_t
+pgp_cipher_aead_tag_len(pgp_aead_alg_t aalg)
+{
+ switch (aalg) {
+ case PGP_AEAD_EAX:
+ case PGP_AEAD_OCB:
+ return PGP_AEAD_EAX_OCB_TAG_LEN;
+ default:
+ return 0;
+ }
+}
+
+#if defined(ENABLE_AEAD)
+bool
+pgp_cipher_aead_set_ad(pgp_crypt_t *crypt, const uint8_t *ad, size_t len)
+{
+ assert(len <= sizeof(crypt->aead.ad));
+ memcpy(crypt->aead.ad, ad, len);
+ crypt->aead.ad_len = len;
+ return true;
+}
+
+bool
+pgp_cipher_aead_start(pgp_crypt_t *crypt, const uint8_t *nonce, size_t len)
+{
+ auto &aead = crypt->aead;
+ auto ctx = aead.obj;
+ int enc = aead.decrypt ? 0 : 1;
+ assert(len == aead.n_len);
+ EVP_CIPHER_CTX_reset(ctx);
+ if (EVP_CipherInit_ex(ctx, aead.cipher, NULL, NULL, NULL, enc) != 1) {
+ RNP_LOG("Failed to initialize cipher: %lu", ERR_peek_last_error());
+ return false;
+ }
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_IVLEN, aead.n_len, NULL) != 1) {
+ RNP_LOG("Failed to set nonce length: %lu", ERR_peek_last_error());
+ return false;
+ }
+ if (EVP_CipherInit_ex(ctx, NULL, NULL, aead.key->data(), nonce, enc) != 1) {
+ RNP_LOG("Failed to start cipher: %lu", ERR_peek_last_error());
+ return false;
+ }
+ int adlen = 0;
+ if (EVP_CipherUpdate(ctx, NULL, &adlen, aead.ad, aead.ad_len) != 1) {
+ RNP_LOG("Failed to set AD: %lu", ERR_peek_last_error());
+ return false;
+ }
+ return true;
+}
+
+bool
+pgp_cipher_aead_update(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len)
+{
+ if (!len) {
+ return true;
+ }
+ int out_len = 0;
+ bool res = EVP_CipherUpdate(crypt->aead.obj, out, &out_len, in, len) == 1;
+ if (!res) {
+ RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error());
+ }
+ assert(out_len == (int) len);
+ return res;
+}
+
+void
+pgp_cipher_aead_reset(pgp_crypt_t *crypt)
+{
+ /* Do nothing as subsequent pgp_cipher_aead_start() call will reset context */
+}
+
+bool
+pgp_cipher_aead_finish(pgp_crypt_t *crypt, uint8_t *out, const uint8_t *in, size_t len)
+{
+ auto &aead = crypt->aead;
+ auto ctx = aead.obj;
+ if (aead.decrypt) {
+ assert(len >= aead.taglen);
+ if (len < aead.taglen) {
+ RNP_LOG("Invalid state: too few input bytes.");
+ return false;
+ }
+ size_t data_len = len - aead.taglen;
+ int out_len = 0;
+ if (EVP_CipherUpdate(ctx, out, &out_len, in, data_len) != 1) {
+ RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error());
+ return false;
+ }
+ uint8_t tag[PGP_AEAD_MAX_TAG_LEN] = {0};
+ memcpy(tag, in + data_len, aead.taglen);
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_SET_TAG, aead.taglen, tag) != 1) {
+ RNP_LOG("Failed to set tag: %lu", ERR_peek_last_error());
+ return false;
+ }
+ int out_len2 = 0;
+ if (EVP_CipherFinal_ex(ctx, out + out_len, &out_len2) != 1) {
+ /* Zero value if auth tag is incorrect */
+ if (ERR_peek_last_error()) {
+ RNP_LOG("Failed to finish AEAD decryption: %lu", ERR_peek_last_error());
+ }
+ return false;
+ }
+ assert(out_len + out_len2 == (int) (len - aead.taglen));
+ } else {
+ int out_len = 0;
+ if (EVP_CipherUpdate(ctx, out, &out_len, in, len) != 1) {
+ RNP_LOG("Failed to update cipher: %lu", ERR_peek_last_error());
+ return false;
+ }
+ int out_len2 = 0;
+ if (EVP_CipherFinal_ex(ctx, out + out_len, &out_len2) != 1) {
+ RNP_LOG("Failed to finish AEAD encryption: %lu", ERR_peek_last_error());
+ return false;
+ }
+ assert(out_len + out_len2 == (int) len);
+ if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_AEAD_GET_TAG, aead.taglen, out + len) != 1) {
+ RNP_LOG("Failed to get tag: %lu", ERR_peek_last_error());
+ return false;
+ }
+ }
+ return true;
+}
+
+void
+pgp_cipher_aead_destroy(pgp_crypt_t *crypt)
+{
+ if (crypt->aead.obj) {
+ EVP_CIPHER_CTX_free(crypt->aead.obj);
+ }
+ delete crypt->aead.key;
+ memset(crypt, 0x0, sizeof(*crypt));
+}
+
+size_t
+pgp_cipher_aead_nonce(pgp_aead_alg_t aalg, const uint8_t *iv, uint8_t *nonce, size_t index)
+{
+ switch (aalg) {
+ case PGP_AEAD_EAX:
+ /* The nonce for EAX mode is computed by treating the starting
+ initialization vector as a 16-octet, big-endian value and
+ exclusive-oring the low eight octets of it with the chunk index.
+ */
+ memcpy(nonce, iv, PGP_AEAD_EAX_NONCE_LEN);
+ for (int i = 15; (i > 7) && index; i--) {
+ nonce[i] ^= index & 0xff;
+ index = index >> 8;
+ }
+ return PGP_AEAD_EAX_NONCE_LEN;
+ case PGP_AEAD_OCB:
+ /* The nonce for a chunk of chunk index "i" in OCB processing is defined as:
+ OCB-Nonce_{i} = IV[1..120] xor i
+ */
+ memcpy(nonce, iv, PGP_AEAD_OCB_NONCE_LEN);
+ for (int i = 14; (i >= 0) && index; i--) {
+ nonce[i] ^= index & 0xff;
+ index = index >> 8;
+ }
+ return PGP_AEAD_OCB_NONCE_LEN;
+ default:
+ return 0;
+ }
+}
+#endif
diff --git a/src/lib/defaults.h b/src/lib/defaults.h
new file mode 100644
index 0000000..22a3c46
--- /dev/null
+++ b/src/lib/defaults.h
@@ -0,0 +1,86 @@
+/*
+ * Copyright (c) 2018-2021, [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 DEFAULTS_H_
+#define DEFAULTS_H_
+
+/* Default hash algorithm as PGP constant */
+#define DEFAULT_PGP_HASH_ALG PGP_HASH_SHA256
+
+/* Default symmetric algorithm as PGP constant */
+#define DEFAULT_PGP_SYMM_ALG PGP_SA_AES_256
+
+/* Default number of msec to run S2K derivation */
+#define DEFAULT_S2K_MSEC 150
+
+/* Default number of msec to run S2K tuning */
+#define DEFAULT_S2K_TUNE_MSEC 10
+
+/* Default compression algorithm and level */
+#define DEFAULT_Z_ALG "ZIP"
+#define DEFAULT_Z_LEVEL 6
+
+/* Default AEAD algorithm */
+#define DEFAULT_AEAD_ALG PGP_AEAD_OCB
+
+/* 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 */
+#define DEFAULT_PGP_CIPHER_MODE PGP_CIPHER_MODE_CFB
+
+/* Default public key algorithm for new key generation */
+#define DEFAULT_PK_ALG PGP_PKA_RSA
+
+/* Default RSA key length */
+#define DEFAULT_RSA_NUMBITS 2048
+
+/* Default ElGamal key length */
+#define DEFAULT_ELGAMAL_NUMBITS 2048
+#define ELGAMAL_MIN_P_BITLEN 1024
+#define ELGAMAL_MAX_P_BITLEN 4096
+
+/* Default, min and max DSA key length */
+#define DSA_MIN_P_BITLEN 1024
+#define DSA_MAX_P_BITLEN 3072
+#define DSA_DEFAULT_P_BITLEN 2048
+
+/* Default EC curve */
+#define DEFAULT_CURVE "NIST P-256"
+
+/* Default maximum password request attempts */
+#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/src/lib/ffi-priv-types.h b/src/lib/ffi-priv-types.h
new file mode 100644
index 0000000..beb624c
--- /dev/null
+++ b/src/lib/ffi-priv-types.h
@@ -0,0 +1,240 @@
+/*-
+ * Copyright (c) 2019 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 <rnp/rnp.h>
+#include <json.h>
+#include "utils.h"
+#include <list>
+#include <crypto/mem.h>
+#include "sec_profile.hpp"
+
+struct rnp_key_handle_st {
+ rnp_ffi_t ffi;
+ pgp_key_search_t locator;
+ pgp_key_t * pub;
+ pgp_key_t * sec;
+};
+
+struct rnp_uid_handle_st {
+ rnp_ffi_t ffi;
+ pgp_key_t *key;
+ size_t idx;
+};
+
+struct rnp_signature_handle_st {
+ rnp_ffi_t ffi;
+ const pgp_key_t *key;
+ pgp_subsig_t * sig;
+ bool own_sig;
+};
+
+struct rnp_recipient_handle_st {
+ rnp_ffi_t ffi;
+ uint8_t keyid[PGP_KEY_ID_SIZE];
+ pgp_pubkey_alg_t palg;
+};
+
+struct rnp_symenc_handle_st {
+ rnp_ffi_t ffi;
+ pgp_symm_alg_t alg;
+ pgp_hash_alg_t halg;
+ pgp_s2k_specifier_t s2k_type;
+ uint32_t iterations;
+ pgp_aead_alg_t aalg;
+};
+
+struct rnp_ffi_st {
+ FILE * errs;
+ rnp_key_store_t * pubring;
+ rnp_key_store_t * secring;
+ rnp_get_key_cb getkeycb;
+ void * getkeycb_ctx;
+ rnp_password_cb getpasscb;
+ void * getpasscb_ctx;
+ pgp_key_provider_t key_provider;
+ pgp_password_provider_t pass_provider;
+ rnp::SecurityContext context;
+
+ rnp_ffi_st(pgp_key_store_format_t pub_fmt, pgp_key_store_format_t sec_fmt);
+ ~rnp_ffi_st();
+
+ rnp::RNG & rng() noexcept;
+ rnp::SecurityProfile &profile() noexcept;
+};
+
+struct rnp_input_st {
+ /* either src or src_directory are valid, not both */
+ pgp_source_t src;
+ 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;
+ char * dst_directory;
+ rnp_output_writer_t *writer;
+ rnp_output_closer_t *closer;
+ void * app_ctx;
+ bool keep;
+};
+
+struct rnp_op_generate_st {
+ rnp_ffi_t ffi{};
+ bool primary{};
+ pgp_key_t *primary_sec{};
+ pgp_key_t *primary_pub{};
+ pgp_key_t *gen_sec{};
+ pgp_key_t *gen_pub{};
+ /* password used to encrypt the key, if specified */
+ rnp::secure_vector<char> password;
+ /* request password for key encryption via ffi's password provider */
+ bool request_password{};
+ /* we don't use top-level keygen action here for easier fields access */
+ rnp_keygen_crypto_params_t crypto{};
+ rnp_key_protection_params_t protection{};
+ rnp_selfsig_cert_info_t cert{};
+ rnp_selfsig_binding_info_t binding{};
+};
+
+struct rnp_op_sign_signature_st {
+ rnp_ffi_t ffi{};
+ rnp_signer_info_t signer{};
+ bool expiry_set : 1;
+ bool create_set : 1;
+ bool hash_set : 1;
+};
+
+typedef std::list<rnp_op_sign_signature_st> rnp_op_sign_signatures_t;
+
+struct rnp_op_sign_st {
+ rnp_ffi_t ffi{};
+ rnp_input_t input{};
+ rnp_output_t output{};
+ rnp_ctx_t rnpctx{};
+ rnp_op_sign_signatures_t signatures{};
+};
+
+struct rnp_op_verify_signature_st {
+ rnp_ffi_t ffi;
+ rnp_result_t verify_status;
+ pgp_signature_t sig_pkt;
+};
+
+struct rnp_op_verify_st {
+ rnp_ffi_t ffi{};
+ rnp_input_t input{};
+ rnp_input_t detached_input{}; /* for detached signature will be source file/data */
+ rnp_output_t output{};
+ rnp_ctx_t rnpctx{};
+ /* these fields are filled after operation execution */
+ rnp_op_verify_signature_t signatures{};
+ size_t signature_count{};
+ char * filename{};
+ uint32_t file_mtime{};
+ /* encryption information */
+ bool encrypted{};
+ bool mdc{};
+ bool validated{};
+ pgp_aead_alg_t aead{};
+ pgp_symm_alg_t salg{};
+ bool ignore_sigs{};
+ bool require_all_sigs{};
+ bool allow_hidden{};
+ /* recipient/symenc information */
+ rnp_recipient_handle_t recipients{};
+ size_t recipient_count{};
+ rnp_recipient_handle_t used_recipient{};
+ rnp_symenc_handle_t symencs{};
+ size_t symenc_count{};
+ rnp_symenc_handle_t used_symenc{};
+ size_t encrypted_layers{};
+
+ ~rnp_op_verify_st();
+};
+
+struct rnp_op_encrypt_st {
+ rnp_ffi_t ffi{};
+ rnp_input_t input{};
+ rnp_output_t output{};
+ rnp_ctx_t rnpctx{};
+ rnp_op_sign_signatures_t signatures{};
+};
+
+#define RNP_LOCATOR_MAX_SIZE (MAX_ID_LENGTH + 1)
+static_assert(RNP_LOCATOR_MAX_SIZE > PGP_FINGERPRINT_SIZE * 2, "Locator size mismatch.");
+static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_ID_SIZE * 2, "Locator size mismatch.");
+static_assert(RNP_LOCATOR_MAX_SIZE > PGP_KEY_GRIP_SIZE * 2, "Locator size mismatch.");
+static_assert(RNP_LOCATOR_MAX_SIZE > MAX_ID_LENGTH, "Locator size mismatch.");
+
+struct rnp_identifier_iterator_st {
+ rnp_ffi_t ffi;
+ pgp_key_search_type_t type;
+ rnp_key_store_t * store;
+ std::list<pgp_key_t>::iterator *keyp;
+ unsigned uididx;
+ json_object * tbl;
+ char buf[RNP_LOCATOR_MAX_SIZE];
+};
+
+struct rnp_decryption_kp_param_t {
+ rnp_op_verify_t op;
+ bool has_hidden; /* key provider had hidden keyid request */
+ pgp_key_t * last; /* last key, returned in hidden keyid request */
+
+ rnp_decryption_kp_param_t(rnp_op_verify_t opobj)
+ : op(opobj), has_hidden(false), last(NULL){};
+};
+
+/* This is just for readability at the call site and will hopefully reduce mistakes.
+ *
+ * Instead of:
+ * void do_something(rnp_ffi_t ffi, bool with_secret_keys);
+ * do_something(ffi, true);
+ * do_something(ffi, false);
+ *
+ * You can have something a bit clearer:
+ * void do_something(rnp_ffi_t ffi, key_type_t key_type);
+ * do_something(ffi, KEY_TYPE_PUBLIC);
+ * do_something(ffi, KEY_TYPE_SECRET);
+ */
+typedef enum key_type_t {
+ KEY_TYPE_NONE,
+ KEY_TYPE_PUBLIC,
+ KEY_TYPE_SECRET,
+ KEY_TYPE_ANY
+} key_type_t;
diff --git a/src/lib/fingerprint.cpp b/src/lib/fingerprint.cpp
new file mode 100644
index 0000000..c937c74
--- /dev/null
+++ b/src/lib/fingerprint.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include "fingerprint.h"
+#include "crypto/hash.hpp"
+#include <librepgp/stream-key.h>
+#include <librepgp/stream-sig.h>
+#include <librepgp/stream-packet.h>
+#include "utils.h"
+
+rnp_result_t
+pgp_fingerprint(pgp_fingerprint_t &fp, const pgp_key_pkt_t &key)
+{
+ if ((key.version == PGP_V2) || (key.version == PGP_V3)) {
+ if (!is_rsa_key_alg(key.alg)) {
+ RNP_LOG("bad algorithm");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ try {
+ 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) {
+ 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.
+ * \param keyid Space for the calculated ID to be stored
+ * \param key The key for which the ID is calculated
+ */
+
+rnp_result_t
+pgp_keyid(pgp_key_id_t &keyid, const pgp_key_pkt_t &key)
+{
+ pgp_fingerprint_t fp;
+ rnp_result_t ret;
+ size_t n;
+
+ if ((key.version == PGP_V2) || (key.version == PGP_V3)) {
+ if (!is_rsa_key_alg(key.alg)) {
+ RNP_LOG("bad algorithm");
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ n = mpi_bytes(&key.material.rsa.n);
+ (void) memcpy(keyid.data(), key.material.rsa.n.mpi + n - keyid.size(), keyid.size());
+ return RNP_SUCCESS;
+ }
+
+ if ((ret = pgp_fingerprint(fp, key))) {
+ return ret;
+ }
+ (void) memcpy(keyid.data(), fp.fingerprint + fp.length - keyid.size(), keyid.size());
+ return RNP_SUCCESS;
+}
+
+bool
+pgp_fingerprint_t::operator==(const pgp_fingerprint_t &src) const
+{
+ return (length == src.length) && !memcmp(fingerprint, src.fingerprint, length);
+}
+
+bool
+pgp_fingerprint_t::operator!=(const pgp_fingerprint_t &src) const
+{
+ return !(*this == src);
+}
diff --git a/src/lib/fingerprint.h b/src/lib/fingerprint.h
new file mode 100644
index 0000000..e8d4713
--- /dev/null
+++ b/src/lib/fingerprint.h
@@ -0,0 +1,39 @@
+/*-
+ * Copyright (c) 2017 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 RNP_FINGERPRINT_H_
+#define RNP_FINGERPRINT_H_
+
+#include <rnp/rnp_def.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include "types.h"
+
+rnp_result_t pgp_fingerprint(pgp_fingerprint_t &fp, const pgp_key_pkt_t &key);
+
+rnp_result_t pgp_keyid(pgp_key_id_t &keyid, const pgp_key_pkt_t &key);
+
+#endif
diff --git a/src/lib/generate-key.cpp b/src/lib/generate-key.cpp
new file mode 100644
index 0000000..dfd5556
--- /dev/null
+++ b/src/lib/generate-key.cpp
@@ -0,0 +1,467 @@
+/*
+ * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * 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 <stdbool.h>
+#include <stdint.h>
+#include <assert.h>
+#include <string.h>
+
+#include <rekey/rnp_key_store.h>
+#include <librekey/key_store_pgp.h>
+#include <librekey/key_store_g10.h>
+#include <librepgp/stream-packet.h>
+#include "crypto.h"
+#include "pgp-key.h"
+#include "defaults.h"
+#include "utils.h"
+
+static const uint8_t DEFAULT_SYMMETRIC_ALGS[] = {
+ PGP_SA_AES_256, PGP_SA_AES_192, PGP_SA_AES_128};
+static const uint8_t DEFAULT_HASH_ALGS[] = {
+ PGP_HASH_SHA256, PGP_HASH_SHA384, PGP_HASH_SHA512, PGP_HASH_SHA224};
+static const uint8_t DEFAULT_COMPRESS_ALGS[] = {
+ PGP_C_ZLIB, PGP_C_BZIP2, PGP_C_ZIP, PGP_C_NONE};
+
+static const id_str_pair pubkey_alg_map[] = {
+ {PGP_PKA_RSA, "RSA (Encrypt or Sign)"},
+ {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA Encrypt-Only"},
+ {PGP_PKA_RSA_SIGN_ONLY, "RSA Sign-Only"},
+ {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"},
+ {PGP_PKA_DSA, "DSA"},
+ {PGP_PKA_ECDH, "ECDH"},
+ {PGP_PKA_ECDSA, "ECDSA"},
+ {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Reserved (formerly Elgamal Encrypt or Sign"},
+ {PGP_PKA_RESERVED_DH, "Reserved for Diffie-Hellman (X9.42)"},
+ {PGP_PKA_EDDSA, "EdDSA"},
+ {PGP_PKA_SM2, "SM2"},
+ {PGP_PKA_PRIVATE00, "Private/Experimental"},
+ {PGP_PKA_PRIVATE01, "Private/Experimental"},
+ {PGP_PKA_PRIVATE02, "Private/Experimental"},
+ {PGP_PKA_PRIVATE03, "Private/Experimental"},
+ {PGP_PKA_PRIVATE04, "Private/Experimental"},
+ {PGP_PKA_PRIVATE05, "Private/Experimental"},
+ {PGP_PKA_PRIVATE06, "Private/Experimental"},
+ {PGP_PKA_PRIVATE07, "Private/Experimental"},
+ {PGP_PKA_PRIVATE08, "Private/Experimental"},
+ {PGP_PKA_PRIVATE09, "Private/Experimental"},
+ {PGP_PKA_PRIVATE10, "Private/Experimental"},
+ {0, NULL}};
+
+static bool
+load_generated_g10_key(pgp_key_t * dst,
+ pgp_key_pkt_t * newkey,
+ pgp_key_t * primary_key,
+ pgp_key_t * pubkey,
+ rnp::SecurityContext &ctx)
+{
+ // 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);
+
+ // this would be better on the stack but the key store does not allow it
+ std::unique_ptr<rnp_key_store_t> key_store(new (std::nothrow) rnp_key_store_t(ctx));
+ if (!key_store) {
+ return false;
+ }
+ /* 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<pgp_key_t *> key_ptrs; /* holds primary and pubkey, when used */
+ // if this is a subkey, add the primary in first
+ 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);
+
+ 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_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()));
+ *dst = pgp_key_t(key_store->keys.front());
+ return true;
+}
+
+static uint8_t
+pk_alg_default_flags(pgp_pubkey_alg_t alg)
+{
+ // just use the full capabilities as the ultimate fallback
+ return pgp_pk_alg_capabilities(alg);
+}
+
+// TODO: Similar as pgp_pick_hash_alg but different enough to
+// keep another version. This will be changed when refactoring crypto
+static void
+adjust_hash_alg(rnp_keygen_crypto_params_t &crypto)
+{
+ if (!crypto.hash_alg) {
+ crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0];
+ }
+
+ if ((crypto.key_alg != PGP_PKA_DSA) && (crypto.key_alg != PGP_PKA_ECDSA)) {
+ return;
+ }
+
+ pgp_hash_alg_t min_hash = (crypto.key_alg == PGP_PKA_ECDSA) ?
+ ecdsa_get_min_hash(crypto.ecc.curve) :
+ dsa_get_min_hash(crypto.dsa.q_bitlen);
+
+ if (rnp::Hash::size(crypto.hash_alg) < rnp::Hash::size(min_hash)) {
+ crypto.hash_alg = min_hash;
+ }
+}
+
+static void
+keygen_merge_crypto_defaults(rnp_keygen_crypto_params_t &crypto)
+{
+ // default to RSA
+ if (!crypto.key_alg) {
+ crypto.key_alg = PGP_PKA_RSA;
+ }
+
+ switch (crypto.key_alg) {
+ case PGP_PKA_RSA:
+ if (!crypto.rsa.modulus_bit_len) {
+ crypto.rsa.modulus_bit_len = DEFAULT_RSA_NUMBITS;
+ }
+ break;
+
+ case PGP_PKA_SM2:
+ if (!crypto.hash_alg) {
+ crypto.hash_alg = PGP_HASH_SM3;
+ }
+ if (!crypto.ecc.curve) {
+ crypto.ecc.curve = PGP_CURVE_SM2_P_256;
+ }
+ break;
+
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA: {
+ if (!crypto.hash_alg) {
+ crypto.hash_alg = (pgp_hash_alg_t) DEFAULT_HASH_ALGS[0];
+ }
+ break;
+ }
+
+ case PGP_PKA_EDDSA:
+ if (!crypto.ecc.curve) {
+ crypto.ecc.curve = PGP_CURVE_ED25519;
+ }
+ break;
+
+ case PGP_PKA_DSA: {
+ if (!crypto.dsa.p_bitlen) {
+ crypto.dsa.p_bitlen = DSA_DEFAULT_P_BITLEN;
+ }
+ if (!crypto.dsa.q_bitlen) {
+ crypto.dsa.q_bitlen = dsa_choose_qsize_by_psize(crypto.dsa.p_bitlen);
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ adjust_hash_alg(crypto);
+}
+
+static bool
+validate_keygen_primary(const rnp_keygen_primary_desc_t &desc)
+{
+ /* Confirm that the specified pk alg can certify.
+ * gpg requires this, though the RFC only says that a V4 primary
+ * key SHOULD be a key capable of certification.
+ */
+ if (!(pgp_pk_alg_capabilities(desc.crypto.key_alg) & PGP_KF_CERTIFY)) {
+ RNP_LOG("primary key alg (%d) must be able to sign", desc.crypto.key_alg);
+ return false;
+ }
+
+ // check key flags
+ if (!desc.cert.key_flags) {
+ // these are probably not *technically* required
+ RNP_LOG("key flags are required");
+ return false;
+ } else if (desc.cert.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) {
+ // check the flags against the alg capabilities
+ RNP_LOG("usage not permitted for pk algorithm");
+ return false;
+ }
+ // require a userid
+ if (!desc.cert.userid[0]) {
+ RNP_LOG("userid is required for primary key");
+ return false;
+ }
+ return true;
+}
+
+static uint32_t
+get_numbits(const rnp_keygen_crypto_params_t *crypto)
+{
+ switch (crypto->key_alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return crypto->rsa.modulus_bit_len;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2: {
+ if (const ec_curve_desc_t *curve = get_curve_desc(crypto->ecc.curve)) {
+ return curve->bitlen;
+ } else {
+ return 0;
+ }
+ }
+ case PGP_PKA_DSA:
+ return crypto->dsa.p_bitlen;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return crypto->elgamal.key_bitlen;
+ default:
+ return 0;
+ }
+}
+
+static void
+set_default_user_prefs(pgp_user_prefs_t &prefs)
+{
+ if (prefs.symm_algs.empty()) {
+ prefs.set_symm_algs(
+ std::vector<uint8_t>(DEFAULT_SYMMETRIC_ALGS,
+ DEFAULT_SYMMETRIC_ALGS + ARRAY_SIZE(DEFAULT_SYMMETRIC_ALGS)));
+ }
+ if (prefs.hash_algs.empty()) {
+ prefs.set_hash_algs(std::vector<uint8_t>(
+ DEFAULT_HASH_ALGS, DEFAULT_HASH_ALGS + ARRAY_SIZE(DEFAULT_HASH_ALGS)));
+ }
+ if (prefs.z_algs.empty()) {
+ prefs.set_z_algs(std::vector<uint8_t>(
+ DEFAULT_COMPRESS_ALGS, DEFAULT_COMPRESS_ALGS + ARRAY_SIZE(DEFAULT_COMPRESS_ALGS)));
+ }
+}
+
+static void
+keygen_primary_merge_defaults(rnp_keygen_primary_desc_t &desc)
+{
+ keygen_merge_crypto_defaults(desc.crypto);
+ set_default_user_prefs(desc.cert.prefs);
+
+ 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.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,
+ bool merge_defaults,
+ pgp_key_t & primary_sec,
+ pgp_key_t & primary_pub,
+ pgp_key_store_format_t secformat)
+{
+ // validate args
+ if (primary_sec.type() || primary_pub.type()) {
+ RNP_LOG("invalid parameters (should be zeroed)");
+ return false;
+ }
+
+ try {
+ // merge some defaults in, if requested
+ if (merge_defaults) {
+ keygen_primary_merge_defaults(desc);
+ }
+ // now validate the keygen fields
+ if (!validate_keygen_primary(desc)) {
+ return false;
+ }
+
+ // generate the raw key and fill tag/secret fields
+ pgp_key_pkt_t secpkt;
+ if (!pgp_generate_seckey(desc.crypto, secpkt, true)) {
+ return false;
+ }
+
+ pgp_key_t sec(secpkt);
+ pgp_key_t pub(secpkt, true);
+ sec.add_uid_cert(desc.cert, desc.crypto.hash_alg, *desc.crypto.ctx, &pub);
+
+ switch (secformat) {
+ case PGP_KEY_STORE_GPG:
+ case PGP_KEY_STORE_KBX:
+ primary_sec = std::move(sec);
+ primary_pub = std::move(pub);
+ break;
+ case PGP_KEY_STORE_G10:
+ primary_pub = std::move(pub);
+ if (!load_generated_g10_key(
+ &primary_sec, &secpkt, NULL, &primary_pub, *desc.crypto.ctx)) {
+ RNP_LOG("failed to load generated key");
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("invalid format");
+ return false;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("Failure: %s", e.what());
+ return false;
+ }
+
+ /* mark it as valid */
+ primary_pub.mark_valid();
+ primary_sec.mark_valid();
+ /* refresh key's data */
+ return primary_pub.refresh_data(*desc.crypto.ctx) &&
+ primary_sec.refresh_data(*desc.crypto.ctx);
+}
+
+static bool
+validate_keygen_subkey(rnp_keygen_subkey_desc_t &desc)
+{
+ if (!desc.binding.key_flags) {
+ RNP_LOG("key flags are required");
+ return false;
+ } else if (desc.binding.key_flags & ~pgp_pk_alg_capabilities(desc.crypto.key_alg)) {
+ // check the flags against the alg capabilities
+ RNP_LOG("usage not permitted for pk algorithm");
+ return false;
+ }
+ return true;
+}
+
+static void
+keygen_subkey_merge_defaults(rnp_keygen_subkey_desc_t &desc)
+{
+ keygen_merge_crypto_defaults(desc.crypto);
+ if (!desc.binding.key_flags) {
+ // set some default key flags if none are provided
+ desc.binding.key_flags = pk_alg_default_flags(desc.crypto.key_alg);
+ }
+}
+
+bool
+pgp_generate_subkey(rnp_keygen_subkey_desc_t & desc,
+ bool merge_defaults,
+ pgp_key_t & primary_sec,
+ pgp_key_t & primary_pub,
+ pgp_key_t & subkey_sec,
+ pgp_key_t & subkey_pub,
+ const pgp_password_provider_t &password_provider,
+ pgp_key_store_format_t secformat)
+{
+ // validate args
+ if (!primary_sec.is_primary() || !primary_pub.is_primary() || !primary_sec.is_secret() ||
+ !primary_pub.is_public()) {
+ RNP_LOG("invalid parameters");
+ return false;
+ }
+ if (subkey_sec.type() || subkey_pub.type()) {
+ RNP_LOG("invalid parameters (should be zeroed)");
+ return false;
+ }
+
+ // merge some defaults in, if requested
+ if (merge_defaults) {
+ keygen_subkey_merge_defaults(desc);
+ }
+
+ // now validate the keygen fields
+ if (!validate_keygen_subkey(desc)) {
+ return false;
+ }
+
+ try {
+ /* decrypt the primary seckey if needed (for signatures) */
+ rnp::KeyLocker primlock(primary_sec);
+ if (primary_sec.encrypted() &&
+ !primary_sec.unlock(password_provider, PGP_OP_ADD_SUBKEY)) {
+ RNP_LOG("Failed to unlock primary key.");
+ return false;
+ }
+ /* generate the raw subkey */
+ pgp_key_pkt_t secpkt;
+ if (!pgp_generate_seckey(desc.crypto, secpkt, false)) {
+ return false;
+ }
+ pgp_key_pkt_t pubpkt = pgp_key_pkt_t(secpkt, true);
+ pgp_key_t sec(secpkt, primary_sec);
+ pgp_key_t pub(pubpkt, primary_pub);
+ /* add binding */
+ primary_sec.add_sub_binding(
+ sec, pub, desc.binding, desc.crypto.hash_alg, *desc.crypto.ctx);
+ /* copy to the result */
+ subkey_pub = std::move(pub);
+ switch (secformat) {
+ case PGP_KEY_STORE_GPG:
+ case PGP_KEY_STORE_KBX:
+ subkey_sec = std::move(sec);
+ break;
+ case PGP_KEY_STORE_G10:
+ if (!load_generated_g10_key(
+ &subkey_sec, &secpkt, &primary_sec, &subkey_pub, *desc.crypto.ctx)) {
+ RNP_LOG("failed to load generated key");
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("invalid format");
+ return false;
+ }
+
+ subkey_pub.mark_valid();
+ subkey_sec.mark_valid();
+ return subkey_pub.refresh_data(&primary_pub, *desc.crypto.ctx) &&
+ subkey_sec.refresh_data(&primary_sec, *desc.crypto.ctx);
+ } catch (const std::exception &e) {
+ RNP_LOG("Subkey generation failed: %s", e.what());
+ return false;
+ }
+}
diff --git a/src/lib/json-utils.cpp b/src/lib/json-utils.cpp
new file mode 100644
index 0000000..742fbc1
--- /dev/null
+++ b/src/lib/json-utils.cpp
@@ -0,0 +1,103 @@
+/*
+ * Copyright (c) 2021, [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 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 "json-utils.h"
+#include "logging.h"
+#include "crypto/mem.h"
+
+/* Shortcut function to add field checking it for null to avoid allocation failure.
+ Please note that it deallocates val on failure. */
+bool
+obj_add_field_json(json_object *obj, const char *name, json_object *val)
+{
+ if (!val) {
+ return false;
+ }
+ // TODO: in JSON-C 0.13 json_object_object_add returns bool instead of void
+ json_object_object_add(obj, name, val);
+ if (!json_object_object_get_ex(obj, name, NULL)) {
+ json_object_put(val);
+ return false;
+ }
+
+ return true;
+}
+
+bool
+json_add(json_object *obj, const char *name, const char *value)
+{
+ return obj_add_field_json(obj, name, json_object_new_string(value));
+}
+
+bool
+json_add(json_object *obj, const char *name, bool value)
+{
+ return obj_add_field_json(obj, name, json_object_new_boolean(value));
+}
+
+bool
+json_add(json_object *obj, const char *name, const char *value, size_t len)
+{
+ return obj_add_field_json(obj, name, json_object_new_string_len(value, len));
+}
+
+bool
+obj_add_hex_json(json_object *obj, const char *name, const uint8_t *val, size_t val_len)
+{
+ if (val_len > 1024 * 1024) {
+ RNP_LOG("too large json hex field: %zu", val_len);
+ val_len = 1024 * 1024;
+ }
+
+ char smallbuf[64] = {0};
+ size_t hexlen = val_len * 2 + 1;
+
+ char *hexbuf = hexlen < sizeof(smallbuf) ? smallbuf : (char *) malloc(hexlen);
+ if (!hexbuf) {
+ return false;
+ }
+
+ bool res = rnp::hex_encode(val, val_len, hexbuf, hexlen, rnp::HEX_LOWERCASE) &&
+ obj_add_field_json(obj, name, json_object_new_string(hexbuf));
+
+ if (hexbuf != smallbuf) {
+ free(hexbuf);
+ }
+ return res;
+}
+
+bool
+array_add_element_json(json_object *obj, json_object *val)
+{
+ if (!val) {
+ return false;
+ }
+ if (json_object_array_add(obj, val)) {
+ json_object_put(val);
+ return false;
+ }
+ return true;
+}
diff --git a/src/lib/json-utils.h b/src/lib/json-utils.h
new file mode 100644
index 0000000..c60615b
--- /dev/null
+++ b/src/lib/json-utils.h
@@ -0,0 +1,91 @@
+/*
+ * Copyright (c) 2019, [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 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 RNP_JSON_UTILS_H_
+#define RNP_JSON_UTILS_H_
+
+#include <stdio.h>
+#include "types.h"
+#include <limits.h>
+#include "json_object.h"
+#include "json.h"
+
+/**
+ * @brief Add field to the json object.
+ * Note: this function is for convenience, it will check val for NULL and destroy val
+ * on failure.
+ * @param obj allocated json_object of object type.
+ * @param name name of the field
+ * @param val json object of any type. Will be checked for NULL.
+ * @return true if val is not NULL and field was added successfully, false otherwise.
+ */
+bool obj_add_field_json(json_object *obj, const char *name, json_object *val);
+
+/**
+ * @brief Shortcut to add string via obj_add_field_json().
+ */
+bool json_add(json_object *obj, const char *name, const char *value);
+
+/**
+ * @brief Shortcut to add string with length via obj_add_field_json().
+ */
+bool json_add(json_object *obj, const char *name, const char *value, size_t len);
+
+/**
+ * @brief Shortcut to add bool via obj_add_field_json().
+ */
+bool json_add(json_object *obj, const char *name, bool value);
+
+/**
+ * @brief Add hex representation of binary data as string field to JSON object.
+ * Note: this function follows conventions of obj_add_field_json().
+ */
+bool obj_add_hex_json(json_object *obj, const char *name, const uint8_t *val, size_t val_len);
+
+/**
+ * @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/src/lib/key-provider.cpp b/src/lib/key-provider.cpp
new file mode 100644
index 0000000..2a64b63
--- /dev/null
+++ b/src/lib/key-provider.cpp
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2017, [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 <assert.h>
+#include <string.h>
+#include "key-provider.h"
+#include "pgp-key.h"
+#include "fingerprint.h"
+#include "types.h"
+#include "utils.h"
+#include <rekey/rnp_key_store.h>
+
+bool
+rnp_key_matches_search(const pgp_key_t *key, const pgp_key_search_t *search)
+{
+ if (!key) {
+ return false;
+ }
+ switch (search->type) {
+ case PGP_KEY_SEARCH_KEYID:
+ return (key->keyid() == search->by.keyid) || (search->by.keyid == pgp_key_id_t({}));
+ case PGP_KEY_SEARCH_FINGERPRINT:
+ return key->fp() == search->by.fingerprint;
+ case PGP_KEY_SEARCH_GRIP:
+ return key->grip() == search->by.grip;
+ case PGP_KEY_SEARCH_USERID:
+ if (key->has_uid(search->by.userid)) {
+ return true;
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ return false;
+}
+
+pgp_key_t *
+pgp_request_key(const pgp_key_provider_t *provider, const pgp_key_request_ctx_t *ctx)
+{
+ pgp_key_t *key = NULL;
+ if (!provider || !provider->callback || !ctx) {
+ return NULL;
+ }
+ if (!(key = provider->callback(ctx, provider->userdata))) {
+ return NULL;
+ }
+ // confirm that the key actually matches the search criteria
+ if (!rnp_key_matches_search(key, &ctx->search) && key->is_secret() == ctx->secret) {
+ return NULL;
+ }
+ return key;
+}
+
+pgp_key_t *
+rnp_key_provider_key_ptr_list(const pgp_key_request_ctx_t *ctx, void *userdata)
+{
+ std::vector<pgp_key_t *> *key_list = (std::vector<pgp_key_t *> *) userdata;
+ for (auto key : *key_list) {
+ if (rnp_key_matches_search(key, &ctx->search) && (key->is_secret() == ctx->secret)) {
+ return key;
+ }
+ }
+ return NULL;
+}
+
+pgp_key_t *
+rnp_key_provider_chained(const pgp_key_request_ctx_t *ctx, void *userdata)
+{
+ for (pgp_key_provider_t **pprovider = (pgp_key_provider_t **) userdata;
+ pprovider && *pprovider;
+ pprovider++) {
+ pgp_key_provider_t *provider = *pprovider;
+ pgp_key_t * key = NULL;
+ if ((key = provider->callback(ctx, provider->userdata))) {
+ return key;
+ }
+ }
+ return NULL;
+}
+
+pgp_key_t *
+rnp_key_provider_store(const pgp_key_request_ctx_t *ctx, void *userdata)
+{
+ rnp_key_store_t *ks = (rnp_key_store_t *) userdata;
+
+ for (pgp_key_t *key = rnp_key_store_search(ks, &ctx->search, NULL); key;
+ key = rnp_key_store_search(ks, &ctx->search, key)) {
+ if (key->is_secret() == ctx->secret) {
+ return key;
+ }
+ }
+ return NULL;
+}
diff --git a/src/lib/key-provider.h b/src/lib/key-provider.h
new file mode 100644
index 0000000..4d09e2f
--- /dev/null
+++ b/src/lib/key-provider.h
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2017, [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_KEY_PROVIDER_H
+#define RNP_KEY_PROVIDER_H
+
+#include "types.h"
+#include "fingerprint.h"
+
+typedef struct pgp_key_t pgp_key_t;
+
+typedef enum {
+ PGP_KEY_SEARCH_UNKNOWN,
+ PGP_KEY_SEARCH_KEYID,
+ PGP_KEY_SEARCH_FINGERPRINT,
+ PGP_KEY_SEARCH_GRIP,
+ PGP_KEY_SEARCH_USERID
+} pgp_key_search_type_t;
+
+typedef struct pgp_key_search_t {
+ pgp_key_search_type_t type;
+ union {
+ 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),
+ * that is left up to the caller.
+ *
+ * @param key the key to check
+ * @param search the search criteria to check against
+ * @return true if the key satisfies the search criteria, false otherwise
+ **/
+bool rnp_key_matches_search(const pgp_key_t *key, const pgp_key_search_t *search);
+
+/** @brief request public or secret pgp key, according to information stored in ctx
+ * @param ctx information about the request - which operation requested the key, which search
+ * criteria should be used and whether secret or public key is needed
+ * @param key pointer to the key structure will be stored here on success
+ * @return a key pointer on success, or NULL if key was not found otherwise
+ **/
+pgp_key_t *pgp_request_key(const pgp_key_provider_t * provider,
+ const pgp_key_request_ctx_t *ctx);
+
+/** key provider callback that searches a list of pgp_key_t pointers
+ *
+ * @param ctx
+ * @param userdata must be a list of key pgp_key_t**
+ */
+pgp_key_t *rnp_key_provider_key_ptr_list(const pgp_key_request_ctx_t *ctx, void *userdata);
+
+/** key provider callback that searches a given store
+ *
+ * @param ctx
+ * @param userdata must be a pointer to rnp_key_store_t
+ */
+pgp_key_t *rnp_key_provider_store(const pgp_key_request_ctx_t *ctx, void *userdata);
+
+/** key provider that calls other key providers
+ *
+ * @param ctx
+ * @param userdata must be an array pgp_key_provider_t pointers,
+ * ending with a NULL.
+ */
+pgp_key_t *rnp_key_provider_chained(const pgp_key_request_ctx_t *ctx, void *userdata);
+
+#endif
diff --git a/src/lib/librnp.3.adoc b/src/lib/librnp.3.adoc
new file mode 100644
index 0000000..9af84ab
--- /dev/null
+++ b/src/lib/librnp.3.adoc
@@ -0,0 +1,89 @@
+= librnp(3)
+RNP
+:doctype: manpage
+:release-version: {component-version}
+:man manual: RNP Manual
+:man source: RNP {release-version}
+
+== NAME
+
+librnp - OpenPGP implementation, available via FFI interface.
+
+== SYNOPSIS
+
+*#include <rnp/rnp.h>* +
+*#include <rnp/rnp_err.h>*
+
+
+== DESCRIPTION
+
+*librnp* is part of the *RNP* suite and forms the basis for the _rnp(1)_ and _rnpkeys(1)_ command-line utilities.
+
+It provides an FFI interface to functions required for operations needed by the OpenPGP protocol.
+
+Interface to the library is exposed via _<rnp/rnp.h>_ and _<rnp/rnp_err.h>_ headers.
+You will also need to link to _librnp_.
+
+Please see its headers for the full function list and detailed documentation.
+
+== EXAMPLES
+
+A number of examples are provided in *src/examples* folder of the *RNP* suite source tree.
+
+*generate.c*::
+Demonstrates generation of an OpenPGP keypair using the JSON key description mechanism.
+May be used to generate any custom key types that are supported by the *RNP* suite.
+
+*encrypt.c*::
+Demonstrates how to build OpenPGP-encrypted messages.
+A message is encrypted with keys, generated via *./generate*, with a hardcoded password.
+
+*decrypt.c*::
+Demonstrates how to decrypt OpenPGP messages.
+Running this example requires the *./encrypt* example to be first run
+in order to produce the sample encrypted message for decryption.
+
+*sign.c*::
+Demonstrates how to sign OpenPGP messages.
+Running this example requires the *./generate* example to be first run
+in order to generate and write out secret keys.
+
+*verify.c*::
+Demonstrates verify OpenPGP signed messages.
+Again, running this example requires the *./sign* example to be first run
+in order to generate a signed OpenPGP message.
+
+== BUGS
+
+Please report _issues_ via the RNP public issue tracker at:
+https://github.com/rnpgp/rnp/issues.
+
+_Security reports_ or _security-sensitive feedback_ should be reported
+according to the instructions at:
+https://www.rnpgp.org/feedback.
+
+
+== AUTHORS
+
+*RNP* is an open source project led by Ribose and has
+received contributions from numerous individuals and
+organizations.
+
+
+== RESOURCES
+
+*Web site*: https://www.rnpgp.org
+
+*Source repository*: https://github.com/rnpgp/rnp
+
+
+== COPYING
+
+Copyright \(C) 2017-2021 Ribose.
+The RNP software suite is _freely licensed_:
+please refer to the *LICENSE* file for details.
+
+
+== SEE ALSO
+
+*rnp(1)*, *rnpkeys(1)*
diff --git a/src/lib/librnp.symbols b/src/lib/librnp.symbols
new file mode 100644
index 0000000..d8667ce
--- /dev/null
+++ b/src/lib/librnp.symbols
@@ -0,0 +1 @@
+_rnp_*
diff --git a/src/lib/librnp.vsc b/src/lib/librnp.vsc
new file mode 100644
index 0000000..460db98
--- /dev/null
+++ b/src/lib/librnp.vsc
@@ -0,0 +1,4 @@
+{
+ global: rnp_*;
+ local: *;
+};
diff --git a/src/lib/logging.cpp b/src/lib/logging.cpp
new file mode 100644
index 0000000..74c67e3
--- /dev/null
+++ b/src/lib/logging.cpp
@@ -0,0 +1,75 @@
+/*-
+ * Copyright (c) 2017-2021 Ribose Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "string.h"
+#include "logging.h"
+
+/* -1 -- not initialized
+ 0 -- logging is off
+ 1 -- logging is on
+*/
+static int8_t _rnp_log_switch =
+#ifdef NDEBUG
+ -1 // lazy-initialize later
+#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;
+}
+
+bool
+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_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/src/lib/logging.h b/src/lib/logging.h
new file mode 100644
index 0000000..7335e57
--- /dev/null
+++ b/src/lib/logging.h
@@ -0,0 +1,97 @@
+/*-
+ * 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 RNP_LOGGING_H_
+#define RNP_LOGGING_H_
+
+#include <stdlib.h>
+#include <stdint.h>
+
+/* 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 {
+ bool stop_;
+
+ public:
+ LogStop(bool stop = true) : stop_(stop)
+ {
+ if (stop_) {
+ rnp_log_stop();
+ }
+ }
+ ~LogStop()
+ {
+ if (stop_) {
+ rnp_log_continue();
+ }
+ }
+};
+} // namespace rnp
+
+#define RNP_LOG_FD(fd, ...) \
+ do { \
+ if (!rnp_log_switch()) \
+ break; \
+ (void) fprintf((fd), "[%s() %s:%d] ", __func__, __FILE__, __LINE__); \
+ (void) fprintf((fd), __VA_ARGS__); \
+ (void) fprintf((fd), "\n"); \
+ } while (0)
+
+#define RNP_LOG(...) RNP_LOG_FD(stderr, __VA_ARGS__)
+
+#define RNP_LOG_KEY(msg, key) \
+ do { \
+ if (!(key)) { \
+ RNP_LOG(msg, "(null)"); \
+ break; \
+ } \
+ char keyid[PGP_KEY_ID_SIZE * 2 + 1] = {0}; \
+ const pgp_key_id_t &id = key->keyid(); \
+ rnp::hex_encode(id.data(), id.size(), keyid, sizeof(keyid), rnp::HEX_LOWERCASE); \
+ RNP_LOG(msg, keyid); \
+ } while (0)
+
+#define RNP_LOG_KEY_PKT(msg, key) \
+ do { \
+ pgp_key_id_t keyid = {}; \
+ if (pgp_keyid(keyid, (key))) { \
+ RNP_LOG(msg, "unknown"); \
+ break; \
+ }; \
+ char keyidhex[PGP_KEY_ID_SIZE * 2 + 1] = {0}; \
+ rnp::hex_encode( \
+ keyid.data(), keyid.size(), keyidhex, sizeof(keyidhex), rnp::HEX_LOWERCASE); \
+ RNP_LOG(msg, keyidhex); \
+ } while (0)
+
+#endif \ No newline at end of file
diff --git a/src/lib/pass-provider.cpp b/src/lib/pass-provider.cpp
new file mode 100644
index 0000000..788fc23
--- /dev/null
+++ b/src/lib/pass-provider.cpp
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2017 - 2019, [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 "pass-provider.h"
+#include <stdio.h>
+#include <string.h>
+
+bool
+rnp_password_provider_string(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata)
+{
+ char *passc = (char *) userdata;
+
+ if (!passc || strlen(passc) >= (password_size - 1)) {
+ return false;
+ }
+
+ strncpy(password, passc, password_size - 1);
+ return true;
+}
+
+bool
+pgp_request_password(const pgp_password_provider_t *provider,
+ const pgp_password_ctx_t * ctx,
+ char * password,
+ size_t password_size)
+{
+ if (!provider || !provider->callback || !ctx || !password || !password_size) {
+ return false;
+ }
+ return provider->callback(ctx, password, password_size, provider->userdata);
+}
diff --git a/src/lib/pass-provider.h b/src/lib/pass-provider.h
new file mode 100644
index 0000000..fd79fc5
--- /dev/null
+++ b/src/lib/pass-provider.h
@@ -0,0 +1,61 @@
+/*
+ * Copyright (c) 2017 - 2019, [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_PASS_PROVIDER_H
+#define RNP_PASS_PROVIDER_H
+
+#include <cstddef>
+#include <cstdint>
+
+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,
+ size_t password_size);
+bool rnp_password_provider_string(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata);
+#endif
diff --git a/src/lib/pgp-key.cpp b/src/lib/pgp-key.cpp
new file mode 100644
index 0000000..4300331
--- /dev/null
+++ b/src/lib/pgp-key.cpp
@@ -0,0 +1,2776 @@
+/*
+ * 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
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "pgp-key.h"
+#include "utils.h"
+#include <librekey/key_store_pgp.h>
+#include <librekey/key_store_g10.h>
+#include "crypto.h"
+#include "crypto/s2k.h"
+#include "crypto/mem.h"
+#include "crypto/signatures.h"
+#include "fingerprint.h"
+
+#include <librepgp/stream-packet.h>
+#include <librepgp/stream-key.h>
+#include <librepgp/stream-sig.h>
+#include <librepgp/stream-armor.h>
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <assert.h>
+#include <time.h>
+#include <algorithm>
+#include <stdexcept>
+#include "defaults.h"
+
+pgp_key_pkt_t *
+pgp_decrypt_seckey_pgp(const pgp_rawpacket_t &raw,
+ const pgp_key_pkt_t & pubkey,
+ const char * password)
+{
+ try {
+ rnp::MemorySource src(raw.raw.data(), raw.raw.size(), false);
+ auto res = std::unique_ptr<pgp_key_pkt_t>(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());
+ 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.
+ * - In the case of an unprotected key, it simply re-loads
+ * key->key.seckey by parsing the key data in packets[0].
+ */
+pgp_key_pkt_t *
+pgp_decrypt_seckey(const pgp_key_t & key,
+ const pgp_password_provider_t &provider,
+ const pgp_password_ctx_t & ctx)
+{
+ // sanity checks
+ if (!key.is_secret()) {
+ RNP_LOG("invalid args");
+ return NULL;
+ }
+ // ask the provider for a password
+ rnp::secure_array<char, MAX_PASSWORD_LENGTH> password;
+ if (key.is_protected() &&
+ !pgp_request_password(&provider, &ctx, password.data(), password.size())) {
+ return NULL;
+ }
+ // attempt to decrypt with the provided password
+ switch (key.format) {
+ case PGP_KEY_STORE_GPG:
+ case PGP_KEY_STORE_KBX:
+ return pgp_decrypt_seckey_pgp(key.rawpkt(), key.pkt(), password.data());
+ case PGP_KEY_STORE_G10:
+ return g10_decrypt_seckey(key.rawpkt(), key.pkt(), password.data());
+ default:
+ RNP_LOG("unexpected format: %d", key.format);
+ return NULL;
+ }
+}
+
+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_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;
+ } else if (sig.sig.has_keyid()) {
+ ctx.search.by.keyid = sig.sig.keyid();
+ ctx.search.type = PGP_KEY_SEARCH_KEYID;
+ } 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;
+ }
+ return pgp_request_key(prov, &ctx);
+}
+
+static const id_str_pair ss_rr_code_map[] = {
+ {PGP_REVOCATION_NO_REASON, "No reason specified"},
+ {PGP_REVOCATION_SUPERSEDED, "Key is superseded"},
+ {PGP_REVOCATION_COMPROMISED, "Key material has been compromised"},
+ {PGP_REVOCATION_RETIRED, "Key is retired and no longer used"},
+ {PGP_REVOCATION_NO_LONGER_VALID, "User ID information is no longer valid"},
+ {0x00, NULL},
+};
+
+pgp_key_t *
+pgp_key_get_subkey(const pgp_key_t *key, rnp_key_store_t *store, size_t idx)
+{
+ try {
+ return rnp_key_store_get_key_by_fpr(store, key->get_subkey_fp(idx));
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return NULL;
+ }
+}
+
+pgp_key_flags_t
+pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg)
+{
+ switch (alg) {
+ case PGP_PKA_RSA:
+ return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH | PGP_KF_ENCRYPT);
+
+ case PGP_PKA_RSA_SIGN_ONLY:
+ // deprecated, but still usable
+ return PGP_KF_SIGN;
+
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ // deprecated, but still usable
+ return PGP_KF_ENCRYPT;
+
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: /* deprecated */
+ // These are no longer permitted per the RFC
+ return PGP_KF_NONE;
+
+ case PGP_PKA_DSA:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH);
+
+ case PGP_PKA_SM2:
+ return pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH | PGP_KF_ENCRYPT);
+
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ELGAMAL:
+ return PGP_KF_ENCRYPT;
+
+ default:
+ RNP_LOG("unknown pk alg: %d\n", alg);
+ return PGP_KF_NONE;
+ }
+}
+
+bool
+pgp_key_t::write_sec_pgp(pgp_dest_t & dst,
+ pgp_key_pkt_t & seckey,
+ const std::string &password,
+ rnp::RNG & rng)
+{
+ bool res = false;
+ pgp_pkt_type_t oldtag = seckey.tag;
+
+ seckey.tag = type();
+ if (encrypt_secret_key(&seckey, password.c_str(), rng)) {
+ goto done;
+ }
+ try {
+ seckey.write(dst);
+ res = !dst.werr;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ }
+done:
+ seckey.tag = oldtag;
+ return res;
+}
+
+bool
+pgp_key_t::write_sec_rawpkt(pgp_key_pkt_t & seckey,
+ const std::string & password,
+ rnp::SecurityContext &ctx)
+{
+ // 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.dst(), seckey, password, ctx.rng)) {
+ RNP_LOG("failed to write secret key");
+ return false;
+ }
+ break;
+ case PGP_KEY_STORE_G10:
+ if (!g10_write_seckey(&memdst.dst(), &seckey, password.c_str(), ctx)) {
+ RNP_LOG("failed to write g10 secret key");
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("invalid format");
+ return false;
+ }
+
+ rawpkt_ = pgp_rawpacket_t((uint8_t *) memdst.memory(), memdst.writeb(), type());
+ return true;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+}
+
+static bool
+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(create);
+ return true;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+}
+
+bool
+pgp_key_set_expiration(pgp_key_t * key,
+ pgp_key_t * seckey,
+ uint32_t expiry,
+ const pgp_password_provider_t &prov,
+ rnp::SecurityContext & ctx)
+{
+ if (!key->is_primary()) {
+ RNP_LOG("Not a primary key");
+ return false;
+ }
+
+ std::vector<pgp_sig_id_t> sigs;
+ /* update expiration for the latest direct-key signature and self-signature for each userid
+ */
+ pgp_subsig_t *sig = key->latest_selfsig(PGP_UID_NONE);
+ if (sig) {
+ sigs.push_back(sig->sigid);
+ }
+ for (size_t uid = 0; uid < key->uid_count(); uid++) {
+ sig = key->latest_selfsig(uid);
+ if (sig) {
+ sigs.push_back(sig->sigid);
+ }
+ }
+ if (sigs.empty()) {
+ RNP_LOG("No valid self-signature(s)");
+ return false;
+ }
+
+ rnp::KeyLocker seclock(*seckey);
+ for (const auto &sigid : sigs) {
+ pgp_subsig_t &sig = key->get_sig(sigid);
+ /* update signature and re-sign it */
+ if (!expiry && !sig.sig.has_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)) {
+ continue;
+ }
+
+ /* unlock secret key if needed */
+ if (seckey->is_locked() && !seckey->unlock(prov)) {
+ RNP_LOG("Failed to unlock secret key");
+ return false;
+ }
+
+ pgp_signature_t newsig;
+ pgp_sig_id_t oldsigid = sigid;
+ if (!update_sig_expiration(&newsig, &sig.sig, ctx.time(), expiry)) {
+ return false;
+ }
+ try {
+ if (sig.is_cert()) {
+ if (sig.uid >= key->uid_count()) {
+ RNP_LOG("uid not found");
+ return false;
+ }
+ seckey->sign_cert(key->pkt(), key->get_uid(sig.uid).pkt, newsig, ctx);
+ } else {
+ /* direct-key signature case */
+ seckey->sign_direct(key->pkt(), newsig, ctx);
+ }
+ /* replace signature, first for secret key since it may be replaced in public */
+ if (seckey->has_sig(oldsigid)) {
+ seckey->replace_sig(oldsigid, newsig);
+ }
+ if (key != seckey) {
+ key->replace_sig(oldsigid, newsig);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("failed to calculate or add signature: %s", e.what());
+ return false;
+ }
+ }
+
+ if (!seckey->refresh_data(ctx)) {
+ RNP_LOG("Failed to refresh seckey data.");
+ return false;
+ }
+ if ((key != seckey) && !key->refresh_data(ctx)) {
+ RNP_LOG("Failed to refresh key data.");
+ return false;
+ }
+ return true;
+}
+
+bool
+pgp_subkey_set_expiration(pgp_key_t * sub,
+ pgp_key_t * primsec,
+ pgp_key_t * secsub,
+ uint32_t expiry,
+ const pgp_password_provider_t &prov,
+ rnp::SecurityContext & ctx)
+{
+ if (!sub->is_subkey()) {
+ RNP_LOG("Not a subkey");
+ return false;
+ }
+
+ /* find the latest valid subkey binding */
+ pgp_subsig_t *subsig = sub->latest_binding();
+ if (!subsig) {
+ RNP_LOG("No valid subkey binding");
+ return false;
+ }
+ if (!expiry && !subsig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY)) {
+ return true;
+ }
+
+ rnp::KeyLocker primlock(*primsec);
+ if (primsec->is_locked() && !primsec->unlock(prov)) {
+ RNP_LOG("Failed to unlock primary key");
+ return false;
+ }
+ bool subsign = secsub->can_sign();
+ rnp::KeyLocker sublock(*secsub);
+ if (subsign && secsub->is_locked() && !secsub->unlock(prov)) {
+ RNP_LOG("Failed to unlock subkey");
+ return false;
+ }
+
+ try {
+ /* update signature and re-sign */
+ pgp_signature_t newsig;
+ pgp_sig_id_t oldsigid = subsig->sigid;
+ 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)) {
+ secsub->replace_sig(oldsigid, newsig);
+ if (!secsub->refresh_data(primsec, ctx)) {
+ return false;
+ }
+ }
+ if (sub == secsub) {
+ return true;
+ }
+ sub->replace_sig(oldsigid, newsig);
+ return sub->refresh_data(primsec, ctx);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+}
+
+pgp_key_t *
+find_suitable_key(pgp_op_t op,
+ pgp_key_t * key,
+ pgp_key_provider_t *key_provider,
+ bool no_primary)
+{
+ if (!key) {
+ return NULL;
+ }
+ bool secret = false;
+ switch (op) {
+ case PGP_OP_ENCRYPT:
+ break;
+ case PGP_OP_SIGN:
+ case PGP_OP_CERTIFY:
+ secret = true;
+ break;
+ default:
+ RNP_LOG("Unsupported operation: %d", (int) op);
+ return NULL;
+ }
+ /* Return if specified primary key fits our needs */
+ if (!no_primary && key->usable_for(op)) {
+ return key;
+ }
+ /* Check for the case when we need to look up for a secret key */
+ pgp_key_request_ctx_t ctx(op, secret, PGP_KEY_SEARCH_FINGERPRINT);
+ if (!no_primary && secret && key->is_public() && key->usable_for(op, true)) {
+ ctx.search.by.fingerprint = key->fp();
+ pgp_key_t *sec = pgp_request_key(key_provider, &ctx);
+ if (sec && sec->usable_for(op)) {
+ return sec;
+ }
+ }
+ /* Now look up for subkeys */
+ 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);
+ if (!cur || !cur->usable_for(op)) {
+ continue;
+ }
+ if (!subkey || (cur->creation() > subkey->creation())) {
+ subkey = cur;
+ }
+ }
+ return subkey;
+}
+
+pgp_hash_alg_t
+pgp_hash_adjust_alg_to_key(pgp_hash_alg_t hash, const pgp_key_pkt_t *pubkey)
+{
+ if ((pubkey->alg != PGP_PKA_DSA) && (pubkey->alg != PGP_PKA_ECDSA)) {
+ return hash;
+ }
+
+ pgp_hash_alg_t hash_min;
+ if (pubkey->alg == PGP_PKA_ECDSA) {
+ hash_min = ecdsa_get_min_hash(pubkey->material.ec.curve);
+ } else {
+ hash_min = dsa_get_min_hash(mpi_bits(&pubkey->material.dsa.q));
+ }
+
+ if (rnp::Hash::size(hash) < rnp::Hash::size(hash_min)) {
+ return hash_min;
+ }
+ return hash;
+}
+
+static void
+bytevec_append_uniq(std::vector<uint8_t> &vec, uint8_t val)
+{
+ if (std::find(vec.begin(), vec.end(), val) == vec.end()) {
+ vec.push_back(val);
+ }
+}
+
+void
+pgp_user_prefs_t::set_symm_algs(const std::vector<uint8_t> &algs)
+{
+ symm_algs = algs;
+}
+
+void
+pgp_user_prefs_t::add_symm_alg(pgp_symm_alg_t alg)
+{
+ bytevec_append_uniq(symm_algs, alg);
+}
+
+void
+pgp_user_prefs_t::set_hash_algs(const std::vector<uint8_t> &algs)
+{
+ hash_algs = algs;
+}
+
+void
+pgp_user_prefs_t::add_hash_alg(pgp_hash_alg_t alg)
+{
+ bytevec_append_uniq(hash_algs, alg);
+}
+
+void
+pgp_user_prefs_t::set_z_algs(const std::vector<uint8_t> &algs)
+{
+ z_algs = algs;
+}
+
+void
+pgp_user_prefs_t::add_z_alg(pgp_compression_type_t alg)
+{
+ bytevec_append_uniq(z_algs, alg);
+}
+
+void
+pgp_user_prefs_t::set_ks_prefs(const std::vector<uint8_t> &prefs)
+{
+ ks_prefs = prefs;
+}
+
+void
+pgp_user_prefs_t::add_ks_pref(pgp_key_server_prefs_t pref)
+{
+ bytevec_append_uniq(ks_prefs, pref);
+}
+
+pgp_rawpacket_t::pgp_rawpacket_t(const pgp_signature_t &sig)
+{
+ 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)
+{
+ 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)
+{
+ rnp::MemoryDest dst;
+ uid.write(dst.dst());
+ raw = dst.to_vector();
+ tag = uid.tag;
+}
+
+void
+pgp_rawpacket_t::write(pgp_dest_t &dst) const
+{
+ dst_write(&dst, raw.data(), raw.size());
+}
+
+void
+pgp_validity_t::mark_valid()
+{
+ validated = true;
+ valid = true;
+ expired = false;
+}
+
+void
+pgp_validity_t::reset()
+{
+ validated = false;
+ valid = false;
+ expired = false;
+}
+
+pgp_subsig_t::pgp_subsig_t(const pgp_signature_t &pkt)
+{
+ sig = pkt;
+ sigid = sig.get_id();
+ if (sig.has_subpkt(PGP_SIG_SUBPKT_TRUST)) {
+ trustlevel = sig.trust_level();
+ trustamount = sig.trust_amount();
+ }
+ prefs.set_symm_algs(sig.preferred_symm_algs());
+ prefs.set_hash_algs(sig.preferred_hash_algs());
+ prefs.set_z_algs(sig.preferred_z_algs());
+
+ if (sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) {
+ key_flags = sig.key_flags();
+ }
+ if (sig.has_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS)) {
+ prefs.set_ks_prefs({sig.key_server_prefs()});
+ }
+ if (sig.has_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV)) {
+ prefs.key_server = sig.key_server();
+ }
+ /* add signature rawpacket */
+ rawpkt = pgp_rawpacket_t(sig);
+}
+
+bool
+pgp_subsig_t::valid() const
+{
+ return validity.validated && validity.valid && !validity.expired;
+}
+
+bool
+pgp_subsig_t::validated() const
+{
+ return validity.validated;
+}
+
+bool
+pgp_subsig_t::is_cert() const
+{
+ pgp_sig_type_t type = sig.type();
+ return (type == PGP_CERT_CASUAL) || (type == PGP_CERT_GENERIC) ||
+ (type == PGP_CERT_PERSONA) || (type == PGP_CERT_POSITIVE);
+}
+
+bool
+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;
+ }
+ return expiration + sig.creation() < at;
+}
+
+pgp_userid_t::pgp_userid_t(const pgp_userid_pkt_t &uidpkt)
+{
+ /* copy packet data */
+ pkt = uidpkt;
+ rawpkt = pgp_rawpacket_t(uidpkt);
+ /* populate uid string */
+ if (uidpkt.tag == PGP_PKT_USER_ID) {
+ str = std::string(uidpkt.uid, uidpkt.uid + uidpkt.uid_len);
+ } else {
+ str = "(photo)";
+ }
+}
+
+size_t
+pgp_userid_t::sig_count() const
+{
+ return sigs_.size();
+}
+
+const pgp_sig_id_t &
+pgp_userid_t::get_sig(size_t idx) const
+{
+ if (idx >= sigs_.size()) {
+ throw std::out_of_range("idx");
+ }
+ return sigs_[idx];
+}
+
+bool
+pgp_userid_t::has_sig(const pgp_sig_id_t &id) const
+{
+ return std::find(sigs_.begin(), sigs_.end(), id) != sigs_.end();
+}
+
+void
+pgp_userid_t::add_sig(const pgp_sig_id_t &sig)
+{
+ sigs_.push_back(sig);
+}
+
+void
+pgp_userid_t::replace_sig(const pgp_sig_id_t &id, const pgp_sig_id_t &newsig)
+{
+ auto it = std::find(sigs_.begin(), sigs_.end(), id);
+ if (it == sigs_.end()) {
+ throw std::invalid_argument("id");
+ }
+ *it = newsig;
+}
+
+bool
+pgp_userid_t::del_sig(const pgp_sig_id_t &id)
+{
+ auto it = std::find(sigs_.begin(), sigs_.end(), id);
+ if (it == sigs_.end()) {
+ return false;
+ }
+ sigs_.erase(it);
+ return true;
+}
+
+void
+pgp_userid_t::clear_sigs()
+{
+ sigs_.clear();
+}
+
+pgp_revoke_t::pgp_revoke_t(pgp_subsig_t &sig)
+{
+ uid = sig.uid;
+ sigid = sig.sigid;
+ if (!sig.sig.has_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON)) {
+ RNP_LOG("Warning: no revocation reason in the revocation");
+ code = PGP_REVOCATION_NO_REASON;
+ } else {
+ code = sig.sig.revocation_code();
+ reason = sig.sig.revocation_reason();
+ }
+ if (reason.empty()) {
+ reason = id_str_pair::lookup(ss_rr_code_map, code);
+ }
+}
+
+pgp_key_t::pgp_key_t(const pgp_key_pkt_t &keypkt) : pkt_(keypkt)
+{
+ if (!is_key_pkt(pkt_.tag) || !pkt_.material.alg) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ if (pgp_keyid(keyid_, pkt_) || pgp_fingerprint(fingerprint_, pkt_) ||
+ !rnp_key_store_get_key_grip(&pkt_.material, grip_)) {
+ throw rnp::rnp_exception(RNP_ERROR_GENERIC);
+ }
+
+ /* parse secret key if not encrypted */
+ if (is_secret_key_pkt(pkt_.tag)) {
+ bool cleartext = pkt_.sec_protection.s2k.usage == PGP_S2KU_NONE;
+ if (cleartext && decrypt_secret_key(&pkt_, NULL)) {
+ RNP_LOG("failed to setup key fields");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ /* decryption resets validity */
+ pkt_.material.validity = keypkt.material.validity;
+ }
+ /* add rawpacket */
+ rawpkt_ = pgp_rawpacket_t(pkt_);
+ format = PGP_KEY_STORE_GPG;
+}
+
+pgp_key_t::pgp_key_t(const pgp_key_pkt_t &pkt, pgp_key_t &primary) : pgp_key_t(pkt)
+{
+ primary.link_subkey_fp(*this);
+}
+
+pgp_key_t::pgp_key_t(const pgp_key_t &src, bool pubonly)
+{
+ /* Do some checks for g10 keys */
+ if (src.format == PGP_KEY_STORE_G10) {
+ if (pubonly) {
+ RNP_LOG("attempt to copy public part from g10 key");
+ throw std::invalid_argument("pubonly");
+ }
+ }
+
+ if (pubonly) {
+ pkt_ = pgp_key_pkt_t(src.pkt_, true);
+ rawpkt_ = pgp_rawpacket_t(pkt_);
+ } else {
+ pkt_ = src.pkt_;
+ rawpkt_ = src.rawpkt_;
+ }
+
+ uids_ = src.uids_;
+ sigs_ = src.sigs_;
+ sigs_map_ = src.sigs_map_;
+ keysigs_ = src.keysigs_;
+ subkey_fps_ = src.subkey_fps_;
+ primary_fp_set_ = src.primary_fp_set_;
+ primary_fp_ = src.primary_fp_;
+ expiration_ = src.expiration_;
+ flags_ = src.flags_;
+ keyid_ = src.keyid_;
+ fingerprint_ = src.fingerprint_;
+ grip_ = src.grip_;
+ uid0_ = src.uid0_;
+ uid0_set_ = src.uid0_set_;
+ revoked_ = src.revoked_;
+ revocation_ = src.revocation_;
+ format = src.format;
+ validity_ = src.validity_;
+ valid_till_ = src.valid_till_;
+}
+
+pgp_key_t::pgp_key_t(const pgp_transferable_key_t &src) : pgp_key_t(src.key)
+{
+ /* add direct-key signatures */
+ for (auto &sig : src.signatures) {
+ add_sig(sig);
+ }
+
+ /* add userids and their signatures */
+ for (auto &uid : src.userids) {
+ add_uid(uid);
+ }
+}
+
+pgp_key_t::pgp_key_t(const pgp_transferable_subkey_t &src, pgp_key_t *primary)
+ : pgp_key_t(src.subkey)
+{
+ /* add subkey binding signatures */
+ for (auto &sig : src.signatures) {
+ add_sig(sig);
+ }
+
+ /* setup key grips if primary is available */
+ if (primary) {
+ primary->link_subkey_fp(*this);
+ }
+}
+
+size_t
+pgp_key_t::sig_count() const
+{
+ return sigs_.size();
+}
+
+pgp_subsig_t &
+pgp_key_t::get_sig(size_t idx)
+{
+ if (idx >= sigs_.size()) {
+ throw std::out_of_range("idx");
+ }
+ return get_sig(sigs_[idx]);
+}
+
+const pgp_subsig_t &
+pgp_key_t::get_sig(size_t idx) const
+{
+ if (idx >= sigs_.size()) {
+ throw std::out_of_range("idx");
+ }
+ return get_sig(sigs_[idx]);
+}
+
+bool
+pgp_key_t::has_sig(const pgp_sig_id_t &id) const
+{
+ return sigs_map_.count(id);
+}
+
+pgp_subsig_t &
+pgp_key_t::get_sig(const pgp_sig_id_t &id)
+{
+ if (!has_sig(id)) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ return sigs_map_.at(id);
+}
+
+const pgp_subsig_t &
+pgp_key_t::get_sig(const pgp_sig_id_t &id) const
+{
+ if (!has_sig(id)) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ return sigs_map_.at(id);
+}
+
+pgp_subsig_t &
+pgp_key_t::replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig)
+{
+ /* save oldsig's uid */
+ size_t uid = get_sig(id).uid;
+ /* delete first old sig since we may have theoretically the same sigid */
+ pgp_sig_id_t oldid = id;
+ sigs_map_.erase(oldid);
+ auto &res = sigs_map_.emplace(std::make_pair(newsig.get_id(), newsig)).first->second;
+ res.uid = uid;
+ auto it = std::find(sigs_.begin(), sigs_.end(), oldid);
+ if (it == sigs_.end()) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+ *it = res.sigid;
+ if (uid == PGP_UID_NONE) {
+ auto it = std::find(keysigs_.begin(), keysigs_.end(), oldid);
+ if (it == keysigs_.end()) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+ *it = res.sigid;
+ } else {
+ uids_[uid].replace_sig(oldid, res.sigid);
+ }
+ return res;
+}
+
+pgp_subsig_t &
+pgp_key_t::add_sig(const pgp_signature_t &sig, size_t uid)
+{
+ const pgp_sig_id_t sigid = sig.get_id();
+ sigs_map_.erase(sigid);
+ pgp_subsig_t &res = sigs_map_.emplace(std::make_pair(sigid, sig)).first->second;
+ res.uid = uid;
+ sigs_.push_back(sigid);
+ if (uid == PGP_UID_NONE) {
+ keysigs_.push_back(sigid);
+ } else {
+ uids_[uid].add_sig(sigid);
+ }
+ return res;
+}
+
+bool
+pgp_key_t::del_sig(const pgp_sig_id_t &sigid)
+{
+ if (!has_sig(sigid)) {
+ return false;
+ }
+ uint32_t uid = get_sig(sigid).uid;
+ if (uid == PGP_UID_NONE) {
+ /* signature over the key itself */
+ auto it = std::find(keysigs_.begin(), keysigs_.end(), sigid);
+ if (it != keysigs_.end()) {
+ keysigs_.erase(it);
+ }
+ } else if (uid < uids_.size()) {
+ /* userid-related signature */
+ uids_[uid].del_sig(sigid);
+ }
+ auto it = std::find(sigs_.begin(), sigs_.end(), sigid);
+ if (it != sigs_.end()) {
+ sigs_.erase(it);
+ }
+ return sigs_map_.erase(sigid);
+}
+
+size_t
+pgp_key_t::del_sigs(const std::vector<pgp_sig_id_t> &sigs)
+{
+ /* delete actual signatures */
+ size_t res = 0;
+ for (auto &sig : sigs) {
+ res += sigs_map_.erase(sig);
+ }
+ /* rebuild vectors with signatures order */
+ keysigs_.clear();
+ for (auto &uid : uids_) {
+ uid.clear_sigs();
+ }
+ std::vector<pgp_sig_id_t> newsigs;
+ newsigs.reserve(sigs_map_.size());
+ for (auto &sigid : sigs_) {
+ if (!sigs_map_.count(sigid)) {
+ continue;
+ }
+ newsigs.push_back(sigid);
+ uint32_t uid = get_sig(sigid).uid;
+ if (uid == PGP_UID_NONE) {
+ keysigs_.push_back(sigid);
+ } else {
+ uids_[uid].add_sig(sigid);
+ }
+ }
+ sigs_ = std::move(newsigs);
+ return res;
+}
+
+size_t
+pgp_key_t::keysig_count() const
+{
+ return keysigs_.size();
+}
+
+pgp_subsig_t &
+pgp_key_t::get_keysig(size_t idx)
+{
+ if (idx >= keysigs_.size()) {
+ throw std::out_of_range("idx");
+ }
+ return get_sig(keysigs_[idx]);
+}
+
+size_t
+pgp_key_t::uid_count() const
+{
+ return uids_.size();
+}
+
+pgp_userid_t &
+pgp_key_t::get_uid(size_t idx)
+{
+ if (idx >= uids_.size()) {
+ throw std::out_of_range("idx");
+ }
+ return uids_[idx];
+}
+
+const pgp_userid_t &
+pgp_key_t::get_uid(size_t idx) const
+{
+ if (idx >= uids_.size()) {
+ throw std::out_of_range("idx");
+ }
+ return uids_[idx];
+}
+
+bool
+pgp_key_t::has_uid(const std::string &uidstr) const
+{
+ for (auto &userid : uids_) {
+ if (!userid.valid) {
+ continue;
+ }
+ if (userid.str == uidstr) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void
+pgp_key_t::del_uid(size_t idx)
+{
+ if (idx >= uids_.size()) {
+ throw std::out_of_range("idx");
+ }
+
+ std::vector<pgp_sig_id_t> newsigs;
+ /* copy sigs which do not belong to uid */
+ newsigs.reserve(sigs_.size());
+ for (auto &id : sigs_) {
+ if (get_sig(id).uid == idx) {
+ sigs_map_.erase(id);
+ continue;
+ }
+ newsigs.push_back(id);
+ }
+ sigs_ = newsigs;
+ uids_.erase(uids_.begin() + idx);
+ /* update uids */
+ if (idx == uids_.size()) {
+ return;
+ }
+ for (auto &sig : sigs_map_) {
+ if ((sig.second.uid == PGP_UID_NONE) || (sig.second.uid <= idx)) {
+ continue;
+ }
+ sig.second.uid--;
+ }
+}
+
+bool
+pgp_key_t::has_primary_uid() const
+{
+ return uid0_set_;
+}
+
+uint32_t
+pgp_key_t::get_primary_uid() const
+{
+ if (!uid0_set_) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ return uid0_;
+}
+
+pgp_userid_t &
+pgp_key_t::add_uid(const pgp_transferable_userid_t &uid)
+{
+ /* construct userid */
+ uids_.emplace_back(uid.uid);
+ /* add certifications */
+ for (auto &sig : uid.signatures) {
+ add_sig(sig, uid_count() - 1);
+ }
+ return uids_.back();
+}
+
+bool
+pgp_key_t::revoked() const
+{
+ return revoked_;
+}
+
+const pgp_revoke_t &
+pgp_key_t::revocation() const
+{
+ if (!revoked_) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ return revocation_;
+}
+
+void
+pgp_key_t::clear_revokes()
+{
+ revoked_ = false;
+ revocation_ = {};
+ for (auto &uid : uids_) {
+ uid.revoked = false;
+ uid.revocation = {};
+ }
+}
+
+const pgp_key_pkt_t &
+pgp_key_t::pkt() const
+{
+ return pkt_;
+}
+
+pgp_key_pkt_t &
+pgp_key_t::pkt()
+{
+ return pkt_;
+}
+
+void
+pgp_key_t::set_pkt(const pgp_key_pkt_t &pkt)
+{
+ pkt_ = pkt;
+}
+
+pgp_key_material_t &
+pgp_key_t::material()
+{
+ return pkt_.material;
+}
+
+pgp_pubkey_alg_t
+pgp_key_t::alg() const
+{
+ return pkt_.alg;
+}
+
+pgp_curve_t
+pgp_key_t::curve() const
+{
+ switch (alg()) {
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ return pkt_.material.ec.curve;
+ default:
+ return PGP_CURVE_UNKNOWN;
+ }
+}
+
+pgp_version_t
+pgp_key_t::version() const
+{
+ return pkt().version;
+}
+
+pgp_pkt_type_t
+pgp_key_t::type() const
+{
+ return pkt().tag;
+}
+
+bool
+pgp_key_t::encrypted() const
+{
+ return is_secret() && !pkt().material.secret;
+}
+
+uint8_t
+pgp_key_t::flags() const
+{
+ return flags_;
+}
+
+bool
+pgp_key_t::can_sign() const
+{
+ return flags_ & PGP_KF_SIGN;
+}
+
+bool
+pgp_key_t::can_certify() const
+{
+ return flags_ & PGP_KF_CERTIFY;
+}
+
+bool
+pgp_key_t::can_encrypt() const
+{
+ return flags_ & PGP_KF_ENCRYPT;
+}
+
+bool
+pgp_key_t::has_secret() const
+{
+ if (!is_secret()) {
+ return false;
+ }
+ if ((format == PGP_KEY_STORE_GPG) && !pkt_.sec_len) {
+ return false;
+ }
+ if (pkt_.sec_protection.s2k.usage == PGP_S2KU_NONE) {
+ return true;
+ }
+ switch (pkt_.sec_protection.s2k.specifier) {
+ case PGP_S2KS_SIMPLE:
+ case PGP_S2KS_SALTED:
+ case PGP_S2KS_ITERATED_AND_SALTED:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+pgp_key_t::usable_for(pgp_op_t op, bool if_secret) const
+{
+ switch (op) {
+ case PGP_OP_ADD_SUBKEY:
+ return is_primary() && can_sign() && (if_secret || has_secret());
+ case PGP_OP_SIGN:
+ return can_sign() && valid() && (if_secret || has_secret());
+ case PGP_OP_CERTIFY:
+ return can_certify() && valid() && (if_secret || has_secret());
+ case PGP_OP_DECRYPT:
+ return can_encrypt() && valid() && (if_secret || has_secret());
+ case PGP_OP_UNLOCK:
+ case PGP_OP_PROTECT:
+ case PGP_OP_UNPROTECT:
+ return has_secret();
+ case PGP_OP_VERIFY:
+ return can_sign() && valid();
+ case PGP_OP_ADD_USERID:
+ return is_primary() && can_sign() && (if_secret || has_secret());
+ case PGP_OP_ENCRYPT:
+ return can_encrypt() && valid();
+ default:
+ return false;
+ }
+}
+
+uint32_t
+pgp_key_t::expiration() const
+{
+ if (pkt_.version >= 4) {
+ return expiration_;
+ }
+ /* too large value for pkt.v3_days may overflow uint32_t */
+ if (pkt_.v3_days > (0xffffffffu / 86400)) {
+ return 0xffffffffu;
+ }
+ return (uint32_t) pkt_.v3_days * 86400;
+}
+
+bool
+pgp_key_t::expired() const
+{
+ return validity_.expired;
+}
+
+uint32_t
+pgp_key_t::creation() const
+{
+ return pkt_.creation_time;
+}
+
+bool
+pgp_key_t::is_public() const
+{
+ return is_public_key_pkt(pkt_.tag);
+}
+
+bool
+pgp_key_t::is_secret() const
+{
+ return is_secret_key_pkt(pkt_.tag);
+}
+
+bool
+pgp_key_t::is_primary() const
+{
+ return is_primary_key_pkt(pkt_.tag);
+}
+
+bool
+pgp_key_t::is_subkey() const
+{
+ return is_subkey_pkt(pkt_.tag);
+}
+
+bool
+pgp_key_t::is_locked() const
+{
+ if (!is_secret()) {
+ RNP_LOG("key is not a secret key");
+ return false;
+ }
+ return encrypted();
+}
+
+bool
+pgp_key_t::is_protected() const
+{
+ // sanity check
+ if (!is_secret()) {
+ RNP_LOG("Warning: this is not a secret key");
+ }
+ return pkt_.sec_protection.s2k.usage != PGP_S2KU_NONE;
+}
+
+bool
+pgp_key_t::valid() const
+{
+ return validity_.validated && validity_.valid && !validity_.expired;
+}
+
+bool
+pgp_key_t::validated() const
+{
+ return validity_.validated;
+}
+
+uint64_t
+pgp_key_t::valid_till_common(bool expiry) const
+{
+ if (!validated()) {
+ return 0;
+ }
+ uint64_t till = expiration() ? (uint64_t) creation() + expiration() : UINT64_MAX;
+ if (valid()) {
+ return till;
+ }
+ if (revoked()) {
+ /* we should not believe to the compromised key at all */
+ if (revocation_.code == PGP_REVOCATION_COMPROMISED) {
+ return 0;
+ }
+ const pgp_subsig_t &revsig = get_sig(revocation_.sigid);
+ if (revsig.sig.creation() > creation()) {
+ /* pick less time from revocation time and expiration time */
+ return std::min((uint64_t) revsig.sig.creation(), till);
+ }
+ return 0;
+ }
+ /* if key is not marked as expired then it wasn't valid at all */
+ return expiry ? till : 0;
+}
+
+uint64_t
+pgp_key_t::valid_till() const
+{
+ return valid_till_;
+}
+
+bool
+pgp_key_t::valid_at(uint64_t timestamp) const
+{
+ /* TODO: consider implementing more sophisticated checks, as key validity time could
+ * possibly be non-continuous */
+ return (timestamp >= creation()) && timestamp && (timestamp <= valid_till());
+}
+
+const pgp_key_id_t &
+pgp_key_t::keyid() const
+{
+ return keyid_;
+}
+
+const pgp_fingerprint_t &
+pgp_key_t::fp() const
+{
+ return fingerprint_;
+}
+
+const pgp_key_grip_t &
+pgp_key_t::grip() const
+{
+ return grip_;
+}
+
+const pgp_fingerprint_t &
+pgp_key_t::primary_fp() const
+{
+ if (!primary_fp_set_) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ return primary_fp_;
+}
+
+bool
+pgp_key_t::has_primary_fp() const
+{
+ return primary_fp_set_;
+}
+
+void
+pgp_key_t::unset_primary_fp()
+{
+ primary_fp_set_ = false;
+ primary_fp_ = {};
+}
+
+void
+pgp_key_t::link_subkey_fp(pgp_key_t &subkey)
+{
+ if (!is_primary() || !subkey.is_subkey()) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ subkey.primary_fp_ = fp();
+ subkey.primary_fp_set_ = true;
+ add_subkey_fp(subkey.fp());
+}
+
+void
+pgp_key_t::add_subkey_fp(const pgp_fingerprint_t &fp)
+{
+ if (std::find(subkey_fps_.begin(), subkey_fps_.end(), fp) == subkey_fps_.end()) {
+ subkey_fps_.push_back(fp);
+ }
+}
+
+size_t
+pgp_key_t::subkey_count() const
+{
+ return subkey_fps_.size();
+}
+
+void
+pgp_key_t::remove_subkey_fp(const pgp_fingerprint_t &fp)
+{
+ auto it = std::find(subkey_fps_.begin(), subkey_fps_.end(), fp);
+ if (it != subkey_fps_.end()) {
+ subkey_fps_.erase(it);
+ }
+}
+
+const pgp_fingerprint_t &
+pgp_key_t::get_subkey_fp(size_t idx) const
+{
+ return subkey_fps_[idx];
+}
+
+const std::vector<pgp_fingerprint_t> &
+pgp_key_t::subkey_fps() const
+{
+ return subkey_fps_;
+}
+
+size_t
+pgp_key_t::rawpkt_count() const
+{
+ if (format == PGP_KEY_STORE_G10) {
+ return 1;
+ }
+ return 1 + uid_count() + sig_count();
+}
+
+pgp_rawpacket_t &
+pgp_key_t::rawpkt()
+{
+ return rawpkt_;
+}
+
+const pgp_rawpacket_t &
+pgp_key_t::rawpkt() const
+{
+ return rawpkt_;
+}
+
+void
+pgp_key_t::set_rawpkt(const pgp_rawpacket_t &src)
+{
+ rawpkt_ = src;
+}
+
+bool
+pgp_key_t::unlock(const pgp_password_provider_t &provider, pgp_op_t op)
+{
+ // sanity checks
+ if (!usable_for(PGP_OP_UNLOCK)) {
+ return false;
+ }
+ // see if it's already unlocked
+ if (!is_locked()) {
+ return true;
+ }
+
+ pgp_password_ctx_t ctx(op, this);
+ pgp_key_pkt_t * decrypted_seckey = pgp_decrypt_seckey(*this, provider, ctx);
+ if (!decrypted_seckey) {
+ return false;
+ }
+
+ // this shouldn't really be necessary, but just in case
+ forget_secret_key_fields(&pkt_.material);
+ // copy the decrypted mpis into the pgp_key_t
+ pkt_.material = decrypted_seckey->material;
+ pkt_.material.secret = true;
+ delete decrypted_seckey;
+ return true;
+}
+
+bool
+pgp_key_t::lock()
+{
+ // sanity checks
+ if (!is_secret()) {
+ RNP_LOG("invalid args");
+ return false;
+ }
+
+ // see if it's already locked
+ if (is_locked()) {
+ return true;
+ }
+
+ forget_secret_key_fields(&pkt_.material);
+ return true;
+}
+
+bool
+pgp_key_t::protect(const rnp_key_protection_params_t &protection,
+ const pgp_password_provider_t & password_provider,
+ rnp::SecurityContext & sctx)
+{
+ pgp_password_ctx_t ctx(PGP_OP_PROTECT, this);
+
+ // ask the provider for a password
+ rnp::secure_array<char, MAX_PASSWORD_LENGTH> password;
+ if (!pgp_request_password(&password_provider, &ctx, password.data(), password.size())) {
+ return false;
+ }
+ 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::SecurityContext & ctx)
+{
+ if (!is_secret()) {
+ RNP_LOG("Warning: this is not a secret key");
+ return false;
+ }
+ bool ownpkt = &decrypted == &pkt_;
+ if (!decrypted.material.secret) {
+ RNP_LOG("Decrypted secret key must be provided");
+ return false;
+ }
+
+ /* force encrypted-and-hashed and iterated-and-salted as it's the only method we support*/
+ pkt_.sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED;
+ pkt_.sec_protection.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED;
+ /* use default values where needed */
+ pkt_.sec_protection.symm_alg =
+ protection.symm_alg ? protection.symm_alg : DEFAULT_PGP_SYMM_ALG;
+ pkt_.sec_protection.cipher_mode =
+ 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 = 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, ctx);
+}
+
+bool
+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;
+ }
+ /* already unprotected */
+ if (!is_protected()) {
+ return true;
+ }
+ /* simple case */
+ if (!encrypted()) {
+ pkt_.sec_protection.s2k.usage = PGP_S2KU_NONE;
+ return write_sec_rawpkt(pkt_, "", secctx);
+ }
+
+ 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, "", secctx)) {
+ delete decrypted_seckey;
+ return false;
+ }
+ pkt_ = std::move(*decrypted_seckey);
+ /* current logic is that unprotected key should be additionally unlocked */
+ forget_secret_key_fields(&pkt_.material);
+ delete decrypted_seckey;
+ return true;
+}
+
+void
+pgp_key_t::write(pgp_dest_t &dst) const
+{
+ /* write key rawpacket */
+ rawpkt_.write(dst);
+
+ if (format == PGP_KEY_STORE_G10) {
+ return;
+ }
+
+ /* write signatures on key */
+ for (auto &sigid : keysigs_) {
+ get_sig(sigid).rawpkt.write(dst);
+ }
+
+ /* write uids and their signatures */
+ for (const auto &uid : uids_) {
+ uid.rawpkt.write(dst);
+ for (size_t idx = 0; idx < uid.sig_count(); idx++) {
+ get_sig(uid.get_sig(idx)).rawpkt.write(dst);
+ }
+ }
+}
+
+void
+pgp_key_t::write_xfer(pgp_dest_t &dst, const rnp_key_store_t *keyring) const
+{
+ write(dst);
+ if (dst.werr) {
+ RNP_LOG("Failed to export primary key");
+ return;
+ }
+
+ if (!keyring) {
+ return;
+ }
+
+ // Export subkeys
+ for (auto &fp : subkey_fps_) {
+ const pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(keyring, fp);
+ if (!subkey) {
+ char fphex[PGP_FINGERPRINT_SIZE * 2 + 1] = {0};
+ rnp::hex_encode(
+ fp.fingerprint, fp.length, fphex, sizeof(fphex), rnp::HEX_LOWERCASE);
+ RNP_LOG("Warning! Subkey %s not found.", fphex);
+ continue;
+ }
+ subkey->write(dst);
+ if (dst.werr) {
+ RNP_LOG("Error occurred when exporting a subkey");
+ return;
+ }
+ }
+}
+
+bool
+pgp_key_t::write_autocrypt(pgp_dest_t &dst, pgp_key_t &sub, uint32_t uid)
+{
+ pgp_subsig_t *cert = latest_uid_selfcert(uid);
+ if (!cert) {
+ RNP_LOG("No valid uid certification");
+ return false;
+ }
+ pgp_subsig_t *binding = sub.latest_binding();
+ if (!binding) {
+ RNP_LOG("No valid binding for subkey");
+ return false;
+ }
+ if (is_secret() || sub.is_secret()) {
+ RNP_LOG("Public key required");
+ return false;
+ }
+
+ try {
+ /* write all or nothing */
+ rnp::MemoryDest memdst;
+ pkt().write(memdst.dst());
+ get_uid(uid).pkt.write(memdst.dst());
+ cert->sig.write(memdst.dst());
+ sub.pkt().write(memdst.dst());
+ 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;
+ }
+}
+
+/* look only for primary userids */
+#define PGP_UID_PRIMARY ((uint32_t) -2)
+/* look for any uid, except PGP_UID_NONE) */
+#define PGP_UID_ANY ((uint32_t) -3)
+
+pgp_subsig_t *
+pgp_key_t::latest_selfsig(uint32_t uid)
+{
+ uint32_t latest = 0;
+ pgp_subsig_t *res = nullptr;
+
+ for (auto &sigid : sigs_) {
+ auto &sig = get_sig(sigid);
+ if (!sig.valid()) {
+ continue;
+ }
+ bool skip = false;
+ switch (uid) {
+ case PGP_UID_NONE:
+ skip = (sig.uid != PGP_UID_NONE) || !is_direct_self(sig);
+ break;
+ case PGP_UID_PRIMARY: {
+ pgp_sig_subpkt_t *subpkt = sig.sig.get_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID);
+ skip = !is_self_cert(sig) || !subpkt || !subpkt->fields.primary_uid ||
+ (sig.uid == PGP_UID_NONE);
+ break;
+ }
+ case PGP_UID_ANY:
+ skip = !is_self_cert(sig) || (sig.uid == PGP_UID_NONE);
+ break;
+ default:
+ skip = (sig.uid != uid) || !is_self_cert(sig);
+ break;
+ }
+ if (skip) {
+ continue;
+ }
+
+ uint32_t creation = sig.sig.creation();
+ if (creation >= latest) {
+ latest = creation;
+ res = &sig;
+ }
+ }
+
+ /* if there is later self-sig for the same uid without primary flag, then drop res */
+ if ((uid == PGP_UID_PRIMARY) && res) {
+ pgp_subsig_t *overres = latest_selfsig(res->uid);
+ if (overres && (overres->sig.creation() > res->sig.creation())) {
+ res = nullptr;
+ }
+ }
+ return res;
+}
+
+pgp_subsig_t *
+pgp_key_t::latest_binding(bool validated)
+{
+ uint32_t latest = 0;
+ pgp_subsig_t *res = NULL;
+
+ for (auto &sigid : sigs_) {
+ auto &sig = get_sig(sigid);
+ if (validated && !sig.valid()) {
+ continue;
+ }
+ if (!is_binding(sig)) {
+ continue;
+ }
+
+ uint32_t creation = sig.sig.creation();
+ if (creation >= latest) {
+ latest = creation;
+ res = &sig;
+ }
+ }
+ return res;
+}
+
+pgp_subsig_t *
+pgp_key_t::latest_uid_selfcert(uint32_t uid)
+{
+ uint32_t latest = 0;
+ pgp_subsig_t *res = NULL;
+
+ if (uid >= uids_.size()) {
+ return NULL;
+ }
+
+ for (size_t idx = 0; idx < uids_[uid].sig_count(); idx++) {
+ auto &sig = get_sig(uids_[uid].get_sig(idx));
+ if (!sig.valid() || (sig.uid != uid)) {
+ continue;
+ }
+ if (!is_self_cert(sig)) {
+ continue;
+ }
+
+ uint32_t creation = sig.sig.creation();
+ if (creation >= latest) {
+ latest = creation;
+ res = &sig;
+ }
+ }
+ return res;
+}
+
+bool
+pgp_key_t::is_signer(const pgp_subsig_t &sig) const
+{
+ /* if we have fingerprint let's check it */
+ if (sig.sig.has_keyfp()) {
+ return sig.sig.keyfp() == fp();
+ }
+ if (!sig.sig.has_keyid()) {
+ return false;
+ }
+ return keyid() == sig.sig.keyid();
+}
+
+bool
+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;
+ }
+ return expiration + creation() < at;
+}
+
+bool
+pgp_key_t::is_self_cert(const pgp_subsig_t &sig) const
+{
+ return is_primary() && sig.is_cert() && is_signer(sig);
+}
+
+bool
+pgp_key_t::is_direct_self(const pgp_subsig_t &sig) const
+{
+ return is_primary() && (sig.sig.type() == PGP_SIG_DIRECT) && is_signer(sig);
+}
+
+bool
+pgp_key_t::is_revocation(const pgp_subsig_t &sig) const
+{
+ return is_primary() ? (sig.sig.type() == PGP_SIG_REV_KEY) :
+ (sig.sig.type() == PGP_SIG_REV_SUBKEY);
+}
+
+bool
+pgp_key_t::is_uid_revocation(const pgp_subsig_t &sig) const
+{
+ return is_primary() && (sig.sig.type() == PGP_SIG_REV_CERT);
+}
+
+bool
+pgp_key_t::is_binding(const pgp_subsig_t &sig) const
+{
+ return is_subkey() && (sig.sig.type() == PGP_SIG_SUBKEY);
+}
+
+void
+pgp_key_t::validate_sig(const pgp_key_t & key,
+ pgp_subsig_t & sig,
+ const rnp::SecurityContext &ctx) const noexcept
+{
+ sig.validity.reset();
+
+ pgp_signature_info_t sinfo = {};
+ sinfo.sig = &sig.sig;
+ sinfo.signer_valid = true;
+ if (key.is_self_cert(sig) || key.is_binding(sig)) {
+ sinfo.ignore_expiry = true;
+ }
+
+ pgp_sig_type_t stype = sig.sig.type();
+ try {
+ switch (stype) {
+ case PGP_SIG_BINARY:
+ case PGP_SIG_TEXT:
+ case PGP_SIG_STANDALONE:
+ case PGP_SIG_PRIMARY:
+ RNP_LOG("Invalid key signature type: %d", (int) stype);
+ return;
+ case PGP_CERT_GENERIC:
+ case PGP_CERT_PERSONA:
+ case PGP_CERT_CASUAL:
+ case PGP_CERT_POSITIVE:
+ case PGP_SIG_REV_CERT: {
+ if (sig.uid >= key.uid_count()) {
+ RNP_LOG("Userid not found");
+ return;
+ }
+ validate_cert(sinfo, key.pkt(), key.get_uid(sig.uid).pkt, ctx);
+ break;
+ }
+ case PGP_SIG_SUBKEY:
+ if (!is_signer(sig)) {
+ RNP_LOG("Invalid subkey binding's signer.");
+ return;
+ }
+ validate_binding(sinfo, key, ctx);
+ break;
+ case PGP_SIG_DIRECT:
+ case PGP_SIG_REV_KEY:
+ validate_direct(sinfo, ctx);
+ break;
+ case PGP_SIG_REV_SUBKEY:
+ if (!is_signer(sig)) {
+ RNP_LOG("Invalid subkey revocation's signer.");
+ return;
+ }
+ validate_sub_rev(sinfo, key.pkt(), ctx);
+ break;
+ default:
+ RNP_LOG("Unsupported key signature type: %d", (int) stype);
+ return;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("Key signature validation failed: %s", e.what());
+ }
+
+ sig.validity.validated = true;
+ sig.validity.valid = sinfo.valid;
+ /* revocation signature cannot expire */
+ if ((stype != PGP_SIG_REV_KEY) && (stype != PGP_SIG_REV_SUBKEY) &&
+ (stype != PGP_SIG_REV_CERT)) {
+ sig.validity.expired = sinfo.expired;
+ }
+}
+
+void
+pgp_key_t::validate_sig(pgp_signature_info_t & sinfo,
+ rnp::Hash & hash,
+ const rnp::SecurityContext &ctx) const noexcept
+{
+ sinfo.no_signer = false;
+ sinfo.valid = false;
+ sinfo.expired = false;
+
+ /* Validate signature itself */
+ if (sinfo.signer_valid || valid_at(sinfo.sig->creation())) {
+ sinfo.valid = !signature_validate(*sinfo.sig, pkt_.material, hash, ctx);
+ } else {
+ sinfo.valid = false;
+ RNP_LOG("invalid or untrusted key");
+ }
+
+ /* Check signature's expiration time */
+ 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));
+ sinfo.expired = true;
+ }
+ if (create && expiry && (create + expiry < now)) {
+ /* signature expired */
+ RNP_LOG("signature expired");
+ sinfo.expired = true;
+ }
+
+ /* check key creation time vs signature creation */
+ if (creation() > create) {
+ RNP_LOG("key is newer than signature");
+ sinfo.valid = false;
+ }
+
+ /* check whether key was not expired when sig created */
+ if (!sinfo.ignore_expiry && expiration() && (creation() + expiration() < create)) {
+ RNP_LOG("signature made after key expiration");
+ sinfo.valid = false;
+ }
+
+ /* Check signer's fingerprint */
+ if (sinfo.sig->has_keyfp() && (sinfo.sig->keyfp() != fp())) {
+ RNP_LOG("issuer fingerprint doesn't match signer's one");
+ sinfo.valid = false;
+ }
+
+ /* Check for unknown critical notations */
+ for (auto &subpkt : sinfo.sig->subpkts) {
+ if (!subpkt.critical || (subpkt.type != PGP_SIG_SUBPKT_NOTATION_DATA)) {
+ continue;
+ }
+ std::string name(subpkt.fields.notation.name,
+ subpkt.fields.notation.name + subpkt.fields.notation.nlen);
+ RNP_LOG("unknown critical notation: %s", name.c_str());
+ sinfo.valid = false;
+ }
+}
+
+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
+{
+ 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
+{
+ if (!is_primary() || !subkey.is_subkey()) {
+ RNP_LOG("Invalid binding signature key type(s)");
+ sinfo.valid = false;
+ return;
+ }
+ 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 */
+ sinfo.valid = false;
+ pgp_sig_subpkt_t *subpkt = sinfo.sig->get_subpkt(PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, false);
+ if (!subpkt) {
+ RNP_LOG("error! no primary key binding signature");
+ return;
+ }
+ if (!subpkt->parsed) {
+ RNP_LOG("invalid embedded signature subpacket");
+ return;
+ }
+ if (subpkt->fields.sig->type() != PGP_SIG_PRIMARY) {
+ RNP_LOG("invalid primary key binding signature");
+ return;
+ }
+ if (subpkt->fields.sig->version < PGP_V4) {
+ RNP_LOG("invalid primary key binding signature version");
+ return;
+ }
+
+ 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);
+ 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
+{
+ 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
+{
+ auto hash = signature_hash_direct(*sinfo.sig, pkt());
+ validate_sig(sinfo, *hash, ctx);
+}
+
+void
+pgp_key_t::validate_self_signatures(const rnp::SecurityContext &ctx)
+{
+ for (auto &sigid : sigs_) {
+ pgp_subsig_t &sig = get_sig(sigid);
+ if (sig.validity.validated) {
+ continue;
+ }
+
+ if (is_direct_self(sig) || is_self_cert(sig) || is_uid_revocation(sig) ||
+ is_revocation(sig)) {
+ validate_sig(*this, sig, ctx);
+ }
+ }
+}
+
+void
+pgp_key_t::validate_self_signatures(pgp_key_t &primary, const rnp::SecurityContext &ctx)
+{
+ for (auto &sigid : sigs_) {
+ pgp_subsig_t &sig = get_sig(sigid);
+ if (sig.validity.validated) {
+ continue;
+ }
+
+ if (is_binding(sig) || is_revocation(sig)) {
+ primary.validate_sig(*this, sig, ctx);
+ }
+ }
+}
+
+void
+pgp_key_t::validate_primary(rnp_key_store_t &keyring)
+{
+ /* validate signatures if needed */
+ validate_self_signatures(keyring.secctx);
+
+ /* consider public key as valid on this level if it is not expired and has at least one
+ * valid self-signature, and is not revoked */
+ validity_.reset();
+ validity_.validated = true;
+ bool has_cert = false;
+ bool has_expired = false;
+ /* check whether key is revoked */
+ for (auto &sigid : sigs_) {
+ pgp_subsig_t &sig = get_sig(sigid);
+ if (!sig.valid()) {
+ continue;
+ }
+ 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, 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, 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, now);
+ has_cert = !has_expired;
+ }
+
+ /* we have at least one non-expiring key self-signature */
+ if (has_cert) {
+ validity_.valid = true;
+ return;
+ }
+ /* we have valid self-signature which expires key */
+ if (has_expired) {
+ validity_.expired = true;
+ return;
+ }
+
+ /* let's check whether key has at least one valid subkey binding */
+ for (size_t i = 0; i < subkey_count(); i++) {
+ pgp_key_t *sub = pgp_key_get_subkey(this, &keyring, i);
+ if (!sub) {
+ continue;
+ }
+ sub->validate_self_signatures(*this, keyring.secctx);
+ 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, now)) {
+ continue;
+ }
+ validity_.valid = true;
+ return;
+ }
+}
+
+void
+pgp_key_t::validate_subkey(pgp_key_t *primary, const rnp::SecurityContext &ctx)
+{
+ /* consider subkey as valid on this level if it has valid primary key, has at least one
+ * non-expired binding signature, and is not revoked. */
+ validity_.reset();
+ validity_.validated = true;
+ if (!primary || (!primary->valid() && !primary->expired())) {
+ return;
+ }
+ /* validate signatures if needed */
+ validate_self_signatures(*primary, ctx);
+
+ bool has_binding = false;
+ bool has_expired = false;
+ for (auto &sigid : sigs_) {
+ pgp_subsig_t &sig = get_sig(sigid);
+ if (!sig.valid()) {
+ continue;
+ }
+
+ if (is_binding(sig) && !has_binding) {
+ /* check whether subkey is expired */
+ if (expired_with(sig, ctx.time())) {
+ has_expired = true;
+ continue;
+ }
+ has_binding = true;
+ } else if (is_revocation(sig)) {
+ return;
+ }
+ }
+ validity_.valid = has_binding && primary->valid();
+ if (!validity_.valid) {
+ validity_.expired = has_expired;
+ }
+}
+
+void
+pgp_key_t::validate(rnp_key_store_t &keyring)
+{
+ validity_.reset();
+ if (!is_subkey()) {
+ validate_primary(keyring);
+ } else {
+ pgp_key_t *primary = NULL;
+ if (has_primary_fp()) {
+ primary = rnp_key_store_get_key_by_fpr(&keyring, primary_fp());
+ }
+ validate_subkey(primary, keyring.secctx);
+ }
+}
+
+void
+pgp_key_t::revalidate(rnp_key_store_t &keyring)
+{
+ if (is_subkey()) {
+ pgp_key_t *primary = rnp_key_store_get_primary_key(&keyring, this);
+ if (primary) {
+ primary->revalidate(keyring);
+ } else {
+ validate_subkey(NULL, keyring.secctx);
+ }
+ return;
+ }
+
+ validate(keyring);
+ if (!refresh_data(keyring.secctx)) {
+ RNP_LOG("Failed to refresh key data");
+ }
+ /* validate/re-validate all subkeys as well */
+ for (auto &fp : subkey_fps_) {
+ pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(&keyring, fp);
+ if (subkey) {
+ subkey->validate_subkey(this, keyring.secctx);
+ if (!subkey->refresh_data(this, keyring.secctx)) {
+ RNP_LOG("Failed to refresh subkey data");
+ }
+ }
+ }
+}
+
+void
+pgp_key_t::mark_valid()
+{
+ validity_.mark_valid();
+ for (size_t i = 0; i < sig_count(); i++) {
+ get_sig(i).validity.mark_valid();
+ }
+}
+
+void
+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(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)
+{
+ sig.fill_hashed_data();
+ 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)
+{
+ sig.fill_hashed_data();
+ 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)
+{
+ sig.fill_hashed_data();
+ 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, 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);
+ } else {
+ sign_binding(key, sig, ctx);
+ }
+}
+
+void
+pgp_key_t::sign_subkey_binding(pgp_key_t & sub,
+ pgp_signature_t & sig,
+ rnp::SecurityContext &ctx,
+ bool subsign)
+{
+ if (!is_primary()) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ 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, ctx.time());
+ embsig.set_type(PGP_SIG_PRIMARY);
+ sub.sign_binding(pkt(), embsig, ctx);
+ sig.set_embedded_sig(embsig);
+ }
+}
+
+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.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(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) {
+ RNP_LOG("Unsupported key store type");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+ // We only support modifying v4 and newer keys
+ if (pkt().version < PGP_V4) {
+ RNP_LOG("adding a userid to V2/V3 key is not supported");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+ /* TODO: if key has at least one uid then has_primary_uid() will be always true! */
+ if (has_primary_uid() && cert.primary) {
+ RNP_LOG("changing the primary userid is not supported");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ /* Fill the transferable userid */
+ pgp_userid_pkt_t uid;
+ pgp_signature_t sig;
+ 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());
+ throw;
+ }
+ /* add uid and signature to the key and pubkey, if non-NULL */
+ uids_.emplace_back(uid);
+ add_sig(sig, uid_count() - 1);
+ refresh_data(ctx);
+ if (!pubkey) {
+ return;
+ }
+ pubkey->uids_.emplace_back(uid);
+ pubkey->add_sig(sig, pubkey->uid_count() - 1);
+ pubkey->refresh_data(ctx);
+}
+
+void
+pgp_key_t::add_sub_binding(pgp_key_t & subsec,
+ pgp_key_t & subpub,
+ const rnp_selfsig_binding_info_t &binding,
+ pgp_hash_alg_t hash,
+ rnp::SecurityContext & ctx)
+{
+ if (!is_primary()) {
+ RNP_LOG("must be called on primary key");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ /* populate signature */
+ pgp_signature_t sig;
+ 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) {
+ sig.set_key_flags(binding.key_flags);
+ }
+ /* calculate binding */
+ pgp_key_flags_t realkf = (pgp_key_flags_t) binding.key_flags;
+ if (!realkf) {
+ realkf = pgp_pk_alg_capabilities(subsec.alg());
+ }
+ sign_subkey_binding(subsec, sig, ctx, realkf & PGP_KF_SIGN);
+ /* add to the secret and public key */
+ subsec.add_sig(sig);
+ subpub.add_sig(sig);
+}
+
+bool
+pgp_key_t::refresh_data(const rnp::SecurityContext &ctx)
+{
+ if (!is_primary()) {
+ RNP_LOG("key must be primary");
+ return false;
+ }
+ /* validate self-signatures if not done yet */
+ validate_self_signatures(ctx);
+ /* key expiration */
+ expiration_ = 0;
+ /* if we have direct-key signature, then it has higher priority */
+ pgp_subsig_t *dirsig = latest_selfsig(PGP_UID_NONE);
+ if (dirsig) {
+ expiration_ = dirsig->sig.key_expiration();
+ }
+ /* if we have primary uid and it is more restrictive, then use it as well */
+ pgp_subsig_t *prisig = latest_selfsig(PGP_UID_PRIMARY);
+ if (prisig && prisig->sig.key_expiration() &&
+ (!expiration_ || (prisig->sig.key_expiration() < expiration_))) {
+ expiration_ = prisig->sig.key_expiration();
+ }
+ /* if we don't have direct-key sig and primary uid, use the latest self-cert */
+ pgp_subsig_t *latest = latest_selfsig(PGP_UID_ANY);
+ if (!dirsig && !prisig && latest) {
+ expiration_ = latest->sig.key_expiration();
+ }
+ /* key flags: check in direct-key sig first, then primary uid, and then latest */
+ if (dirsig && dirsig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) {
+ flags_ = dirsig->key_flags;
+ } else if (prisig && prisig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) {
+ flags_ = prisig->key_flags;
+ } else if (latest && latest->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) {
+ flags_ = latest->key_flags;
+ } else {
+ flags_ = pgp_pk_alg_capabilities(alg());
+ }
+ /* revocation(s) */
+ clear_revokes();
+ for (size_t i = 0; i < sig_count(); i++) {
+ pgp_subsig_t &sig = get_sig(i);
+ if (!sig.valid()) {
+ continue;
+ }
+ try {
+ if (is_revocation(sig)) {
+ if (revoked_) {
+ continue;
+ }
+ revoked_ = true;
+ revocation_ = pgp_revoke_t(sig);
+ } else if (is_uid_revocation(sig)) {
+ if (sig.uid >= uid_count()) {
+ RNP_LOG("Invalid uid index");
+ continue;
+ }
+ pgp_userid_t &uid = get_uid(sig.uid);
+ if (uid.revoked) {
+ continue;
+ }
+ uid.revoked = true;
+ uid.revocation = pgp_revoke_t(sig);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ }
+ /* valid till */
+ valid_till_ = valid_till_common(expired());
+ /* userid validities */
+ for (size_t i = 0; i < uid_count(); i++) {
+ 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(ctx.time())) {
+ continue;
+ }
+ if (sig.uid >= uid_count()) {
+ continue;
+ }
+ get_uid(sig.uid).valid = true;
+ }
+ /* check whether uid is revoked */
+ for (size_t i = 0; i < uid_count(); i++) {
+ pgp_userid_t &uid = get_uid(i);
+ if (uid.revoked) {
+ uid.valid = false;
+ }
+ }
+ /* 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;
+ }
+ return true;
+}
+
+bool
+pgp_key_t::refresh_data(pgp_key_t *primary, const rnp::SecurityContext &ctx)
+{
+ /* validate self-signatures if not done yet */
+ if (primary) {
+ validate_self_signatures(*primary, ctx);
+ }
+ pgp_subsig_t *sig = latest_binding(primary);
+ /* subkey expiration */
+ expiration_ = sig ? sig->sig.key_expiration() : 0;
+ /* subkey flags */
+ if (sig && sig->sig.has_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS)) {
+ flags_ = sig->key_flags;
+ } else {
+ flags_ = pgp_pk_alg_capabilities(alg());
+ }
+ /* revocation */
+ clear_revokes();
+ for (size_t i = 0; i < sig_count(); i++) {
+ pgp_subsig_t &sig = get_sig(i);
+ if (!sig.valid() || !is_revocation(sig)) {
+ continue;
+ }
+ revoked_ = true;
+ try {
+ revocation_ = pgp_revoke_t(sig);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ break;
+ }
+ /* valid till */
+ if (primary) {
+ valid_till_ =
+ std::min(primary->valid_till(), valid_till_common(expired() || primary->expired()));
+ } else {
+ valid_till_ = valid_till_common(expired());
+ }
+ return true;
+}
+
+void
+pgp_key_t::merge_validity(const pgp_validity_t &src)
+{
+ validity_.valid = validity_.valid && src.valid;
+ /* We may safely leave validated status only if both merged keys are valid && validated.
+ * Otherwise we'll need to revalidate. For instance, one validated but invalid key may add
+ * revocation signature, or valid key may add certification to the invalid one. */
+ validity_.validated = validity_.valid && validity_.validated && src.validated;
+ /* if expired is true at least in one case then valid and validated are false */
+ validity_.expired = false;
+}
+
+bool
+pgp_key_t::merge(const pgp_key_t &src)
+{
+ if (is_subkey() || src.is_subkey()) {
+ RNP_LOG("wrong key merge call");
+ return false;
+ }
+
+ pgp_transferable_key_t dstkey;
+ if (transferable_key_from_key(dstkey, *this)) {
+ RNP_LOG("failed to get transferable key from dstkey");
+ return false;
+ }
+
+ pgp_transferable_key_t srckey;
+ if (transferable_key_from_key(srckey, src)) {
+ RNP_LOG("failed to get transferable key from srckey");
+ return false;
+ }
+
+ /* if src is secret key then merged key will become secret as well. */
+ if (is_secret_key_pkt(srckey.key.tag) && !is_secret_key_pkt(dstkey.key.tag)) {
+ pgp_key_pkt_t tmp = dstkey.key;
+ dstkey.key = srckey.key;
+ srckey.key = tmp;
+ /* no subkey processing here - they are separated from the main key */
+ }
+
+ if (transferable_key_merge(dstkey, srckey)) {
+ RNP_LOG("failed to merge transferable keys");
+ return false;
+ }
+
+ pgp_key_t tmpkey;
+ try {
+ tmpkey = std::move(dstkey);
+ for (auto &fp : subkey_fps()) {
+ tmpkey.add_subkey_fp(fp);
+ }
+ for (auto &fp : src.subkey_fps()) {
+ tmpkey.add_subkey_fp(fp);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("failed to process key/add subkey fps: %s", e.what());
+ return false;
+ }
+ /* check whether key was unlocked and assign secret key data */
+ if (is_secret() && !is_locked()) {
+ /* we may do thing below only because key material is opaque structure without
+ * pointers! */
+ tmpkey.pkt().material = pkt().material;
+ } else if (src.is_secret() && !src.is_locked()) {
+ tmpkey.pkt().material = src.pkt().material;
+ }
+ /* copy validity status */
+ tmpkey.validity_ = validity_;
+ tmpkey.merge_validity(src.validity_);
+
+ *this = std::move(tmpkey);
+ return true;
+}
+
+bool
+pgp_key_t::merge(const pgp_key_t &src, pgp_key_t *primary)
+{
+ if (!is_subkey() || !src.is_subkey()) {
+ RNP_LOG("wrong subkey merge call");
+ return false;
+ }
+
+ pgp_transferable_subkey_t dstkey;
+ if (transferable_subkey_from_key(dstkey, *this)) {
+ RNP_LOG("failed to get transferable key from dstkey");
+ return false;
+ }
+
+ pgp_transferable_subkey_t srckey;
+ if (transferable_subkey_from_key(srckey, src)) {
+ RNP_LOG("failed to get transferable key from srckey");
+ return false;
+ }
+
+ /* if src is secret key then merged key will become secret as well. */
+ if (is_secret_key_pkt(srckey.subkey.tag) && !is_secret_key_pkt(dstkey.subkey.tag)) {
+ pgp_key_pkt_t tmp = dstkey.subkey;
+ dstkey.subkey = srckey.subkey;
+ srckey.subkey = tmp;
+ }
+
+ if (transferable_subkey_merge(dstkey, srckey)) {
+ RNP_LOG("failed to merge transferable subkeys");
+ return false;
+ }
+
+ pgp_key_t tmpkey;
+ try {
+ tmpkey = pgp_key_t(dstkey, primary);
+ } catch (const std::exception &e) {
+ RNP_LOG("failed to process subkey: %s", e.what());
+ return false;
+ }
+
+ /* check whether key was unlocked and assign secret key data */
+ if (is_secret() && !is_locked()) {
+ /* we may do thing below only because key material is opaque structure without
+ * pointers! */
+ tmpkey.pkt().material = pkt().material;
+ } else if (src.is_secret() && !src.is_locked()) {
+ tmpkey.pkt().material = src.pkt().material;
+ }
+ /* copy validity status */
+ tmpkey.validity_ = validity_;
+ tmpkey.merge_validity(src.validity_);
+
+ *this = std::move(tmpkey);
+ return true;
+}
+
+size_t
+pgp_key_material_t::bits() const
+{
+ switch (alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return 8 * mpi_bytes(&rsa.n);
+ case PGP_PKA_DSA:
+ return 8 * mpi_bytes(&dsa.p);
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return 8 * mpi_bytes(&eg.y);
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2: {
+ // bn_num_bytes returns value <= curve order
+ const ec_curve_desc_t *curve = get_curve_desc(ec.curve);
+ return curve ? curve->bitlen : 0;
+ }
+ default:
+ RNP_LOG("Unknown public key alg: %d", (int) alg);
+ return 0;
+ }
+}
+
+size_t
+pgp_key_material_t::qbits() const
+{
+ if (alg != PGP_PKA_DSA) {
+ return 0;
+ }
+ return 8 * mpi_bytes(&dsa.q);
+}
+
+void
+pgp_key_material_t::validate(rnp::SecurityContext &ctx, bool reset)
+{
+ if (!reset && validity.validated) {
+ return;
+ }
+ validity.reset();
+ validity.valid = !validate_pgp_key_material(this, &ctx.rng);
+ validity.validated = true;
+}
+
+bool
+pgp_key_material_t::valid() const
+{
+ return validity.validated && validity.valid;
+}
diff --git a/src/lib/pgp-key.h b/src/lib/pgp-key.h
new file mode 100644
index 0000000..aa088bb
--- /dev/null
+++ b/src/lib/pgp-key.h
@@ -0,0 +1,671 @@
+/*
+ * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef RNP_PACKET_KEY_H
+#define RNP_PACKET_KEY_H
+
+#include <stdbool.h>
+#include <stdio.h>
+#include <vector>
+#include <unordered_map>
+#include "pass-provider.h"
+#include "../librepgp/stream-key.h"
+#include <rekey/rnp_key_store.h>
+#include "../librepgp/stream-packet.h"
+#include "crypto/symmetric.h"
+#include "types.h"
+#include "sec_profile.hpp"
+
+/** pgp_rawpacket_t */
+typedef struct pgp_rawpacket_t {
+ pgp_pkt_type_t tag;
+ std::vector<uint8_t> raw;
+
+ pgp_rawpacket_t() = default;
+ pgp_rawpacket_t(const uint8_t *data, size_t len, pgp_pkt_type_t tag)
+ : tag(tag),
+ raw(data ? std::vector<uint8_t>(data, data + len) : std::vector<uint8_t>()){};
+ pgp_rawpacket_t(const pgp_signature_t &sig);
+ pgp_rawpacket_t(pgp_key_pkt_t &key);
+ pgp_rawpacket_t(const pgp_userid_pkt_t &uid);
+
+ void write(pgp_dest_t &dst) const;
+} pgp_rawpacket_t;
+
+/** information about the signature */
+typedef struct pgp_subsig_t {
+ uint32_t uid{}; /* index in userid array in key for certification sig */
+ pgp_signature_t sig{}; /* signature packet */
+ pgp_sig_id_t sigid{}; /* signature identifier */
+ pgp_rawpacket_t rawpkt{}; /* signature's rawpacket */
+ uint8_t trustlevel{}; /* level of trust */
+ uint8_t trustamount{}; /* amount of trust */
+ uint8_t key_flags{}; /* key flags for certification/direct key sig */
+ pgp_user_prefs_t prefs{}; /* user preferences for certification sig */
+ pgp_validity_t validity{}; /* signature validity information */
+
+ pgp_subsig_t() = delete;
+ pgp_subsig_t(const pgp_signature_t &sig);
+
+ 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(uint64_t at) const;
+} pgp_subsig_t;
+
+typedef std::unordered_map<pgp_sig_id_t, pgp_subsig_t> pgp_sig_map_t;
+
+/* userid, built on top of userid packet structure */
+typedef struct pgp_userid_t {
+ private:
+ std::vector<pgp_sig_id_t> sigs_{}; /* all signatures related to this userid */
+ public:
+ pgp_userid_pkt_t pkt{}; /* User ID or User Attribute packet as it was loaded */
+ pgp_rawpacket_t rawpkt{}; /* Raw packet contents */
+ std::string str{}; /* Human-readable representation of the userid */
+ bool valid{}; /* User ID is valid, i.e. has valid, non-expired self-signature */
+ bool revoked{};
+ pgp_revoke_t revocation{};
+
+ pgp_userid_t(const pgp_userid_pkt_t &pkt);
+
+ size_t sig_count() const;
+ const pgp_sig_id_t &get_sig(size_t idx) const;
+ bool has_sig(const pgp_sig_id_t &id) const;
+ void add_sig(const pgp_sig_id_t &sig);
+ void replace_sig(const pgp_sig_id_t &id, const pgp_sig_id_t &newsig);
+ bool del_sig(const pgp_sig_id_t &id);
+ void clear_sigs();
+} pgp_userid_t;
+
+#define PGP_UID_NONE ((uint32_t) -1)
+
+typedef struct rnp_key_store_t rnp_key_store_t;
+
+/* describes a user's key */
+struct pgp_key_t {
+ private:
+ pgp_sig_map_t sigs_map_{}; /* map with subsigs stored by their id */
+ std::vector<pgp_sig_id_t> sigs_{}; /* subsig ids to lookup actual sig in map */
+ std::vector<pgp_sig_id_t> keysigs_{}; /* direct-key signature ids in the original order */
+ std::vector<pgp_userid_t> uids_{}; /* array of user ids */
+ pgp_key_pkt_t pkt_{}; /* pubkey/seckey data packet */
+ uint8_t flags_{}; /* key flags */
+ uint32_t expiration_{}; /* key expiration time, if available */
+ pgp_key_id_t keyid_{};
+ pgp_fingerprint_t fingerprint_{};
+ pgp_key_grip_t grip_{};
+ pgp_fingerprint_t primary_fp_{}; /* fingerprint of the primary key (for subkeys) */
+ bool primary_fp_set_{};
+ std::vector<pgp_fingerprint_t>
+ subkey_fps_{}; /* array of subkey fingerprints (for primary keys) */
+ pgp_rawpacket_t rawpkt_{}; /* key raw packet */
+ uint32_t uid0_{}; /* primary uid index in uids array */
+ bool uid0_set_{}; /* flag for the above */
+ bool revoked_{}; /* key has been revoked */
+ pgp_revoke_t revocation_{}; /* revocation reason */
+ pgp_validity_t validity_{}; /* key's validity */
+ uint64_t valid_till_{}; /* date till which key is/was valid */
+
+ pgp_subsig_t *latest_uid_selfcert(uint32_t uid);
+ void validate_primary(rnp_key_store_t &keyring);
+ void merge_validity(const pgp_validity_t &src);
+ uint64_t valid_till_common(bool expiry) const;
+ bool write_sec_pgp(pgp_dest_t & dst,
+ pgp_key_pkt_t & seckey,
+ const std::string &password,
+ rnp::RNG & rng);
+
+ public:
+ pgp_key_store_format_t format{}; /* the format of the key in packets[0] */
+
+ pgp_key_t() = default;
+ pgp_key_t(const pgp_key_pkt_t &pkt);
+ pgp_key_t(const pgp_key_pkt_t &pkt, pgp_key_t &primary);
+ pgp_key_t(const pgp_key_t &src, bool pubonly = false);
+ pgp_key_t(const pgp_transferable_key_t &src);
+ pgp_key_t(const pgp_transferable_subkey_t &src, pgp_key_t *primary);
+ pgp_key_t &operator=(const pgp_key_t &) = default;
+ pgp_key_t &operator=(pgp_key_t &&) = default;
+
+ size_t sig_count() const;
+ pgp_subsig_t & get_sig(size_t idx);
+ const pgp_subsig_t &get_sig(size_t idx) const;
+ bool has_sig(const pgp_sig_id_t &id) const;
+ pgp_subsig_t & replace_sig(const pgp_sig_id_t &id, const pgp_signature_t &newsig);
+ pgp_subsig_t & get_sig(const pgp_sig_id_t &id);
+ const pgp_subsig_t &get_sig(const pgp_sig_id_t &id) const;
+ pgp_subsig_t & add_sig(const pgp_signature_t &sig, size_t uid = PGP_UID_NONE);
+ bool del_sig(const pgp_sig_id_t &sigid);
+ size_t del_sigs(const std::vector<pgp_sig_id_t> &sigs);
+ size_t keysig_count() const;
+ pgp_subsig_t & get_keysig(size_t idx);
+ size_t uid_count() const;
+ pgp_userid_t & get_uid(size_t idx);
+ const pgp_userid_t &get_uid(size_t idx) const;
+ pgp_userid_t & add_uid(const pgp_transferable_userid_t &uid);
+ bool has_uid(const std::string &uid) const;
+ void del_uid(size_t idx);
+ bool has_primary_uid() const;
+ uint32_t get_primary_uid() const;
+ bool revoked() const;
+ const pgp_revoke_t &revocation() const;
+ void clear_revokes();
+
+ const pgp_key_pkt_t &pkt() const;
+ pgp_key_pkt_t & pkt();
+ void set_pkt(const pgp_key_pkt_t &pkt);
+
+ pgp_key_material_t &material();
+
+ pgp_pubkey_alg_t alg() const;
+ pgp_curve_t curve() const;
+ pgp_version_t version() const;
+ pgp_pkt_type_t type() const;
+ bool encrypted() const;
+ uint8_t flags() const;
+ bool can_sign() const;
+ bool can_certify() const;
+ bool can_encrypt() const;
+ bool has_secret() const;
+ /**
+ * @brief Check whether key is usable for the specified operation.
+ *
+ * @param op operation to check.
+ * @param if_secret check whether secret part of this key could be usable for op.
+ * @return true if key (or corresponding secret key) is usable or false otherwise.
+ */
+ bool usable_for(pgp_op_t op, bool if_secret = false) const;
+ /** @brief Get key's expiration time in seconds. If 0 then it doesn't expire. */
+ uint32_t expiration() const;
+ /** @brief Check whether key is expired. Must be validated before that. */
+ bool expired() const;
+ /** @brief Get key's creation time in seconds since Jan, 1 1970. */
+ uint32_t creation() const;
+ bool is_public() const;
+ bool is_secret() const;
+ bool is_primary() const;
+ bool is_subkey() const;
+ /** @brief check if a key is currently locked, i.e. secret fields are not decrypted.
+ * Note: Key locking does not apply to unprotected keys.
+ */
+ bool is_locked() const;
+ /** @brief check if a key is currently protected, i.e. its secret data is encrypted */
+ bool is_protected() const;
+
+ bool valid() const;
+ bool validated() const;
+ /** @brief return time till which key is considered to be valid */
+ uint64_t valid_till() const;
+ /** @brief check whether key was/will be valid at the specified time */
+ bool valid_at(uint64_t timestamp) const;
+
+ /** @brief Get key's id */
+ const pgp_key_id_t &keyid() const;
+ /** @brief Get key's fingerprint */
+ const pgp_fingerprint_t &fp() const;
+ /** @brief Get key's grip */
+ const pgp_key_grip_t &grip() const;
+ /** @brief Get primary key's fingerprint for the subkey, if it is available.
+ * Note: will throw if it is not available, use has_primary_fp() to check.
+ */
+ const pgp_fingerprint_t &primary_fp() const;
+ /** @brief Check whether key has primary key's fingerprint */
+ bool has_primary_fp() const;
+ /** @brief Clean primary_fp */
+ void unset_primary_fp();
+ /** @brief Link key with subkey via primary_fp and subkey_fps list */
+ void link_subkey_fp(pgp_key_t &subkey);
+ /**
+ * @brief Add subkey fp to key's list.
+ * Note: this function will check for duplicates.
+ */
+ void add_subkey_fp(const pgp_fingerprint_t &fp);
+ /** @brief Get the number of pgp key's subkeys. */
+ size_t subkey_count() const;
+ /** @brief Remove subkey fingerprint from key's list. */
+ void remove_subkey_fp(const pgp_fingerprint_t &fp);
+ /**
+ * @brief Get the pgp key's subkey fingerprint
+ * @return fingerprint or throws std::out_of_range exception
+ */
+ const pgp_fingerprint_t & get_subkey_fp(size_t idx) const;
+ const std::vector<pgp_fingerprint_t> &subkey_fps() const;
+
+ 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::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.
+ *
+ * @param pass_provider the password provider that may be used to unlock the key
+ * @param op operation for which secret key should be unloacked
+ * @return true if the key was unlocked, false otherwise
+ **/
+ bool unlock(const pgp_password_provider_t &provider, pgp_op_t op = PGP_OP_UNLOCK);
+ /** @brief Lock a key, i.e. cleanup decrypted secret data.
+ * Note: Key locking does not apply to unprotected keys.
+ *
+ * @param key the key
+ * @return true if the key was locked, false otherwise
+ **/
+ 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::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::SecurityContext & ctx);
+ /** @brief Remove protection from a key, i.e. leave secret fields unencrypted */
+ 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
+ *
+ * @param dst stream to write packets
+ * @param keyring keyring, which will be searched for subkeys. Pass NULL to skip subkeys.
+ * @return void, but error may be checked via dst.werr
+ */
+ void write_xfer(pgp_dest_t &dst, const rnp_key_store_t *keyring = NULL) const;
+ /**
+ * @brief Export key with subkey as it is required by Autocrypt (5-packet sequence: key,
+ * uid, sig, subkey, sig).
+ *
+ * @param dst stream to write packets
+ * @param sub subkey
+ * @param uid index of uid to export
+ * @return true on success or false otherwise
+ */
+ bool write_autocrypt(pgp_dest_t &dst, pgp_key_t &sub, uint32_t uid);
+
+ /**
+ * @brief Get the latest valid self-signature with information about the primary key for
+ * the specified uid (including the special cases). It could be userid certification
+ * or direct-key signature.
+ *
+ * @param uid uid for which latest self-signature should be returned,
+ * PGP_UID_NONE for direct-key signature,
+ * PGP_UID_PRIMARY for any primary key,
+ * PGP_UID_ANY for any uid.
+ * @return pointer to signature object or NULL if failed/not found.
+ */
+ pgp_subsig_t *latest_selfsig(uint32_t uid);
+
+ /**
+ * @brief Get the latest valid subkey binding. Should be called on subkey.
+ *
+ * @param validated set to true whether binding signature must be validated
+ * @return pointer to signature object or NULL if failed/not found.
+ */
+ pgp_subsig_t *latest_binding(bool validated = true);
+
+ /** @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, 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 */
+ bool is_direct_self(const pgp_subsig_t &sig) const;
+
+ /** @brief Check whether signature is key's/subkey's revocation */
+ bool is_revocation(const pgp_subsig_t &sig) const;
+
+ /** @brief Check whether signature is userid revocation */
+ bool is_uid_revocation(const pgp_subsig_t &sig) const;
+
+ /** @brief Check whether signature is subkey binding */
+ bool is_binding(const pgp_subsig_t &sig) const;
+
+ /**
+ * @brief Validate key's signature, assuming that 'this' is a signing key.
+ *
+ * @param key key or subkey to which signature belongs.
+ * @param sig signature to validate.
+ * @param ctx Populated security context.
+ */
+ void validate_sig(const pgp_key_t & key,
+ pgp_subsig_t & sig,
+ const rnp::SecurityContext &ctx) const noexcept;
+
+ /**
+ * @brief Validate signature, assuming that 'this' is a signing key.
+ *
+ * @param sinfo populated signature info. Validation results will be stored here.
+ * @param hash hash, feed with all signed data except signature trailer.
+ * @param ctx Populated security context.
+ */
+ void validate_sig(pgp_signature_info_t & sinfo,
+ rnp::Hash & hash,
+ const rnp::SecurityContext &ctx) const noexcept;
+
+ /**
+ * @brief Validate certification.
+ *
+ * @param sinfo populated signature info. Validation results will be stored here.
+ * @param key key packet to which certification belongs.
+ * @param uid userid which is bound by certification to the key packet.
+ */
+ void validate_cert(pgp_signature_info_t & sinfo,
+ const pgp_key_pkt_t & key,
+ const pgp_userid_pkt_t & uid,
+ const rnp::SecurityContext &ctx) const;
+
+ /**
+ * @brief Validate subkey binding.
+ *
+ * @param sinfo populated signature info. Validation results will be stored here.
+ * @param subkey subkey packet.
+ */
+ void validate_binding(pgp_signature_info_t & sinfo,
+ const pgp_key_t & subkey,
+ const rnp::SecurityContext &ctx) const;
+
+ /**
+ * @brief Validate subkey revocation.
+ *
+ * @param sinfo populated signature info. Validation results will be stored here.
+ * @param subkey subkey packet.
+ */
+ void validate_sub_rev(pgp_signature_info_t & sinfo,
+ const pgp_key_pkt_t & subkey,
+ const rnp::SecurityContext &ctx) const;
+
+ /**
+ * @brief Validate direct-key signature.
+ *
+ * @param sinfo populated signature info. Validation results will be stored here.
+ */
+ void validate_direct(pgp_signature_info_t &sinfo, const rnp::SecurityContext &ctx) const;
+
+ void validate_self_signatures(const rnp::SecurityContext &ctx);
+ void validate_self_signatures(pgp_key_t &primary, const rnp::SecurityContext &ctx);
+ void validate(rnp_key_store_t &keyring);
+ void validate_subkey(pgp_key_t *primary, const rnp::SecurityContext &ctx);
+ void revalidate(rnp_key_store_t &keyring);
+ void mark_valid();
+ /**
+ * @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, 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
+ * packet for self-signature, or any other one for cross-key certification.
+ * @param uid uid to certify.
+ * @param sig signature, pre-populated with all of the required data, except the
+ * signature material.
+ */
+ void sign_cert(const pgp_key_pkt_t & key,
+ const pgp_userid_pkt_t &uid,
+ pgp_signature_t & sig,
+ rnp::SecurityContext & ctx);
+
+ /**
+ * @brief Calculate direct-key signature.
+ * Note: secret key must be unlocked before calling this function.
+ *
+ * @param key key packet to sign. May be both public and secret.
+ * @param sig signature, pre-populated with all of the required data, except the
+ * signature material.
+ */
+ void sign_direct(const pgp_key_pkt_t & key,
+ pgp_signature_t & sig,
+ rnp::SecurityContext &ctx);
+
+ /**
+ * @brief Calculate subkey or primary key binding.
+ * Note: this will not embed primary key binding for the signing subkey, it should
+ * be added by the caller.
+ *
+ * @param key subkey or primary key packet, may be both public or secret.
+ * @param sig signature, pre-populated with all of the required data, except the
+ * signature material.
+ */
+ void sign_binding(const pgp_key_pkt_t & key,
+ pgp_signature_t & sig,
+ rnp::SecurityContext &ctx);
+
+ /**
+ * @brief Calculate subkey binding.
+ * Note: secret key must be unlocked before calling this function. If subsign is
+ * true then subkey must be secret and unlocked as well so function can calculate
+ * primary key binding.
+ *
+ * @param sub subkey to bind to the primary key. If subsign is true then must be unlocked
+ * secret key.
+ * @param sig signature, pre-populated with all of the required data, except the
+ * signature material.
+ */
+ void sign_subkey_binding(pgp_key_t & sub,
+ pgp_signature_t & sig,
+ rnp::SecurityContext &ctx,
+ bool subsign = false);
+
+ /**
+ * @brief Generate key or subkey revocation signature.
+ *
+ * @param revoke revocation information.
+ * @param key key or subkey packet to revoke.
+ * @param sig object to store revocation signature. Will be populated in method call.
+ */
+ void 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);
+
+ /**
+ * @brief Add and certify userid.
+ * Note: secret key must be unlocked before calling this function.
+ *
+ * @param cert certification and userid parameters.
+ * @param hash hash algorithm to use during signing. See sign_init() for more details.
+ * @param ctx security context.
+ * @param pubkey if non-NULL then userid and certification will be added to this key as
+ * well.
+ */
+ void add_uid_cert(rnp_selfsig_cert_info_t &cert,
+ pgp_hash_alg_t hash,
+ rnp::SecurityContext & ctx,
+ pgp_key_t * pubkey = nullptr);
+
+ /**
+ * @brief Calculate and add subkey binding signature.
+ * Note: must be called on the unlocked secret primary key. Calculated signature is
+ * added to the subkey.
+ *
+ * @param subsec secret subkey.
+ * @param subpub subkey's public part (so signature is added to both).
+ * @param binding information about subkey to put to the signature.
+ * @param hash hash algorithm to use (may be adjusted according to key and subkey
+ * algorithms)
+ */
+ void add_sub_binding(pgp_key_t & subsec,
+ pgp_key_t & subpub,
+ const rnp_selfsig_binding_info_t &binding,
+ pgp_hash_alg_t hash,
+ rnp::SecurityContext & ctx);
+
+ /** @brief Refresh internal fields after primary key is updated */
+ bool refresh_data(const rnp::SecurityContext &ctx);
+ /** @brief Refresh internal fields after subkey is updated */
+ bool refresh_data(pgp_key_t *primary, const rnp::SecurityContext &ctx);
+ /** @brief Merge primary key with the src, i.e. add all new userids/signatures/subkeys */
+ bool merge(const pgp_key_t &src);
+ /** @brief Merge subkey with the source, i.e. add all new signatures */
+ bool merge(const pgp_key_t &src, pgp_key_t *primary);
+};
+
+namespace rnp {
+class KeyLocker {
+ bool lock_;
+ pgp_key_t &key_;
+
+ public:
+ KeyLocker(pgp_key_t &key) : lock_(key.is_locked()), key_(key)
+ {
+ }
+
+ ~KeyLocker()
+ {
+ if (lock_ && !key_.is_locked()) {
+ key_.lock();
+ }
+ }
+};
+}; // namespace rnp
+
+pgp_key_pkt_t *pgp_decrypt_seckey_pgp(const pgp_rawpacket_t &raw,
+ const pgp_key_pkt_t & key,
+ const char * password);
+
+pgp_key_pkt_t *pgp_decrypt_seckey(const pgp_key_t &,
+ const pgp_password_provider_t &,
+ const pgp_password_ctx_t &);
+
+/**
+ * @brief Get the signer's key for signature
+ *
+ * @param sig signature
+ * @param keyring keyring to search for the key. May be NULL.
+ * @param prov key provider to request needed key, may be NULL.
+ * @return pointer to the key or NULL if key is not found.
+ */
+pgp_key_t *pgp_sig_get_signer(const pgp_subsig_t &sig,
+ rnp_key_store_t * keyring,
+ pgp_key_provider_t *prov);
+
+/**
+ * @brief Get the key's subkey by its index
+ *
+ * @param key primary key
+ * @param store key store which will be searched for subkeys
+ * @param idx index of the subkey
+ * @return pointer to the subkey or NULL if subkey not found
+ */
+pgp_key_t *pgp_key_get_subkey(const pgp_key_t *key, rnp_key_store_t *store, size_t idx);
+
+pgp_key_flags_t pgp_pk_alg_capabilities(pgp_pubkey_alg_t alg);
+
+bool pgp_key_set_expiration(pgp_key_t * key,
+ pgp_key_t * signer,
+ uint32_t expiry,
+ const pgp_password_provider_t &prov,
+ rnp::SecurityContext & ctx);
+
+bool pgp_subkey_set_expiration(pgp_key_t * sub,
+ pgp_key_t * primsec,
+ pgp_key_t * secsub,
+ uint32_t expiry,
+ const pgp_password_provider_t &prov,
+ rnp::SecurityContext & ctx);
+
+/** Find a key or it's subkey, suitable for a particular operation
+ *
+ * If the key passed is suitable, it will be returned.
+ * Otherwise, its subkeys (if it is a primary w/subs)
+ * will be checked. NULL will be returned if no suitable
+ * key is found.
+ *
+ * @param op the operation for which the key should be suitable
+ * @param key the key
+ * @param key_provider the key provider. This will be used
+ * if/when subkeys are checked.
+ * @param no_primary set true if only subkeys must be returned
+ *
+ * @returns key or last created subkey with desired usage flag
+ * set or NULL if not found
+ */
+pgp_key_t *find_suitable_key(pgp_op_t op,
+ pgp_key_t * key,
+ pgp_key_provider_t *key_provider,
+ bool no_primary = false);
+
+/*
+ * Picks up hash algorithm according to domain parameters set
+ * in `pubkey' and user provided hash. That's mostly because DSA
+ * and ECDSA needs special treatment.
+ *
+ * @param hash set by the caller
+ * @param pubkey initialized public key
+ *
+ * @returns hash algorithm that must be use for operation (mostly
+ signing with secure key which corresponds to 'pubkey')
+ */
+pgp_hash_alg_t pgp_hash_adjust_alg_to_key(pgp_hash_alg_t hash, const pgp_key_pkt_t *pubkey);
+
+#endif // RNP_PACKET_KEY_H
diff --git a/src/lib/rnp.cpp b/src/lib/rnp.cpp
new file mode 100644
index 0000000..24c46f9
--- /dev/null
+++ b/src/lib/rnp.cpp
@@ -0,0 +1,8403 @@
+/*-
+ * Copyright (c) 2017-2021, Ribose Inc.
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
+ * CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "crypto.h"
+#include "crypto/common.h"
+#include "pgp-key.h"
+#include "defaults.h"
+#include <assert.h>
+#include <json_object.h>
+#include <json.h>
+#include <librekey/key_store_pgp.h>
+#include <librepgp/stream-ctx.h>
+#include <librepgp/stream-common.h>
+#include <librepgp/stream-armor.h>
+#include <librepgp/stream-parse.h>
+#include <librepgp/stream-write.h>
+#include <librepgp/stream-sig.h>
+#include <librepgp/stream-packet.h>
+#include <librepgp/stream-key.h>
+#include <librepgp/stream-dump.h>
+#include <rnp/rnp.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#ifdef _MSC_VER
+#include "uniwin.h"
+#include <inttypes.h>
+#else
+#include <unistd.h>
+#endif
+#include <string.h>
+#include <sys/stat.h>
+#include <stdexcept>
+#include "utils.h"
+#include "str-utils.h"
+#include "json-utils.h"
+#include "version.h"
+#include "ffi-priv-types.h"
+#include "file-utils.h"
+
+#define FFI_LOG(ffi, ...) \
+ do { \
+ FILE *fp = stderr; \
+ if (ffi && ffi->errs) { \
+ fp = ffi->errs; \
+ } \
+ RNP_LOG_FD(fp, __VA_ARGS__); \
+ } while (0)
+
+static pgp_key_t *get_key_require_public(rnp_key_handle_t handle);
+static pgp_key_t *get_key_prefer_public(rnp_key_handle_t handle);
+static pgp_key_t *get_key_require_secret(rnp_key_handle_t handle);
+
+static bool locator_to_str(const pgp_key_search_t &locator,
+ const char ** identifier_type,
+ char * identifier,
+ size_t identifier_size);
+
+static bool rnp_password_cb_bounce(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata_void);
+
+static rnp_result_t rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result);
+
+static bool
+call_key_callback(rnp_ffi_t ffi, const pgp_key_search_t &search, bool secret)
+{
+ if (!ffi->getkeycb) {
+ return false;
+ }
+ char identifier[RNP_LOCATOR_MAX_SIZE];
+ const char *identifier_type = NULL;
+ if (!locator_to_str(search, &identifier_type, identifier, sizeof(identifier))) {
+ return false;
+ }
+
+ ffi->getkeycb(ffi, ffi->getkeycb_ctx, identifier_type, identifier, secret);
+ return true;
+}
+
+static pgp_key_t *
+find_key(rnp_ffi_t ffi,
+ const pgp_key_search_t &search,
+ bool secret,
+ bool try_key_provider,
+ pgp_key_t * after = NULL)
+{
+ pgp_key_t *key =
+ rnp_key_store_search(secret ? ffi->secring : ffi->pubring, &search, after);
+ if (!key && try_key_provider && call_key_callback(ffi, search, secret)) {
+ // recurse and try the store search above once more
+ return find_key(ffi, search, secret, false, after);
+ }
+ return key;
+}
+
+static pgp_key_t *
+ffi_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata)
+{
+ rnp_ffi_t ffi = (rnp_ffi_t) userdata;
+ return find_key(ffi, ctx->search, ctx->secret, true);
+}
+
+static void
+rnp_ctx_init_ffi(rnp_ctx_t &ctx, rnp_ffi_t ffi)
+{
+ ctx.ctx = &ffi->context;
+ ctx.ealg = DEFAULT_PGP_SYMM_ALG;
+ ctx.aalg = PGP_AEAD_NONE;
+ ctx.abits = DEFAULT_AEAD_CHUNK_BITS;
+}
+
+static const id_str_pair sig_type_map[] = {{PGP_SIG_BINARY, "binary"},
+ {PGP_SIG_TEXT, "text"},
+ {PGP_SIG_STANDALONE, "standalone"},
+ {PGP_CERT_GENERIC, "certification (generic)"},
+ {PGP_CERT_PERSONA, "certification (persona)"},
+ {PGP_CERT_CASUAL, "certification (casual)"},
+ {PGP_CERT_POSITIVE, "certification (positive)"},
+ {PGP_SIG_SUBKEY, "subkey binding"},
+ {PGP_SIG_PRIMARY, "primary key binding"},
+ {PGP_SIG_DIRECT, "direct"},
+ {PGP_SIG_REV_KEY, "key revocation"},
+ {PGP_SIG_REV_SUBKEY, "subkey revocation"},
+ {PGP_SIG_REV_CERT, "certification revocation"},
+ {PGP_SIG_TIMESTAMP, "timestamp"},
+ {PGP_SIG_3RD_PARTY, "third-party"},
+ {0, NULL}};
+
+static const id_str_pair pubkey_alg_map[] = {
+ {PGP_PKA_RSA, RNP_ALGNAME_RSA},
+ {PGP_PKA_RSA_ENCRYPT_ONLY, RNP_ALGNAME_RSA},
+ {PGP_PKA_RSA_SIGN_ONLY, RNP_ALGNAME_RSA},
+ {PGP_PKA_ELGAMAL, RNP_ALGNAME_ELGAMAL},
+ {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, RNP_ALGNAME_ELGAMAL},
+ {PGP_PKA_DSA, RNP_ALGNAME_DSA},
+ {PGP_PKA_ECDH, RNP_ALGNAME_ECDH},
+ {PGP_PKA_ECDSA, RNP_ALGNAME_ECDSA},
+ {PGP_PKA_EDDSA, RNP_ALGNAME_EDDSA},
+ {PGP_PKA_SM2, RNP_ALGNAME_SM2},
+ {0, NULL}};
+
+static const id_str_pair symm_alg_map[] = {{PGP_SA_IDEA, RNP_ALGNAME_IDEA},
+ {PGP_SA_TRIPLEDES, RNP_ALGNAME_TRIPLEDES},
+ {PGP_SA_CAST5, RNP_ALGNAME_CAST5},
+ {PGP_SA_BLOWFISH, RNP_ALGNAME_BLOWFISH},
+ {PGP_SA_AES_128, RNP_ALGNAME_AES_128},
+ {PGP_SA_AES_192, RNP_ALGNAME_AES_192},
+ {PGP_SA_AES_256, RNP_ALGNAME_AES_256},
+ {PGP_SA_TWOFISH, RNP_ALGNAME_TWOFISH},
+ {PGP_SA_CAMELLIA_128, RNP_ALGNAME_CAMELLIA_128},
+ {PGP_SA_CAMELLIA_192, RNP_ALGNAME_CAMELLIA_192},
+ {PGP_SA_CAMELLIA_256, RNP_ALGNAME_CAMELLIA_256},
+ {PGP_SA_SM4, RNP_ALGNAME_SM4},
+ {0, NULL}};
+
+static const id_str_pair aead_alg_map[] = {
+ {PGP_AEAD_NONE, "None"}, {PGP_AEAD_EAX, "EAX"}, {PGP_AEAD_OCB, "OCB"}, {0, NULL}};
+
+static const id_str_pair cipher_mode_map[] = {{PGP_CIPHER_MODE_CFB, "CFB"},
+ {PGP_CIPHER_MODE_CBC, "CBC"},
+ {PGP_CIPHER_MODE_OCB, "OCB"},
+ {0, NULL}};
+
+static const id_str_pair compress_alg_map[] = {{PGP_C_NONE, "Uncompressed"},
+ {PGP_C_ZIP, "ZIP"},
+ {PGP_C_ZLIB, "ZLIB"},
+ {PGP_C_BZIP2, "BZip2"},
+ {0, NULL}};
+
+static const id_str_pair hash_alg_map[] = {{PGP_HASH_MD5, RNP_ALGNAME_MD5},
+ {PGP_HASH_SHA1, RNP_ALGNAME_SHA1},
+ {PGP_HASH_RIPEMD, RNP_ALGNAME_RIPEMD160},
+ {PGP_HASH_SHA256, RNP_ALGNAME_SHA256},
+ {PGP_HASH_SHA384, RNP_ALGNAME_SHA384},
+ {PGP_HASH_SHA512, RNP_ALGNAME_SHA512},
+ {PGP_HASH_SHA224, RNP_ALGNAME_SHA224},
+ {PGP_HASH_SHA3_256, RNP_ALGNAME_SHA3_256},
+ {PGP_HASH_SHA3_512, RNP_ALGNAME_SHA3_512},
+ {PGP_HASH_SM3, RNP_ALGNAME_SM3},
+ {0, NULL}};
+
+static const id_str_pair s2k_type_map[] = {
+ {PGP_S2KS_SIMPLE, "Simple"},
+ {PGP_S2KS_SALTED, "Salted"},
+ {PGP_S2KS_ITERATED_AND_SALTED, "Iterated and salted"},
+ {0, NULL}};
+
+static const id_str_pair key_usage_map[] = {
+ {PGP_KF_SIGN, "sign"},
+ {PGP_KF_CERTIFY, "certify"},
+ {PGP_KF_ENCRYPT, "encrypt"},
+ {PGP_KF_AUTH, "authenticate"},
+ {0, NULL},
+};
+
+static const id_str_pair key_flags_map[] = {
+ {PGP_KF_SPLIT, "split"},
+ {PGP_KF_SHARED, "shared"},
+ {0, NULL},
+};
+
+static const id_str_pair identifier_type_map[] = {{PGP_KEY_SEARCH_USERID, "userid"},
+ {PGP_KEY_SEARCH_KEYID, "keyid"},
+ {PGP_KEY_SEARCH_FINGERPRINT, "fingerprint"},
+ {PGP_KEY_SEARCH_GRIP, "grip"},
+ {0, NULL}};
+
+static const id_str_pair key_server_prefs_map[] = {{PGP_KEY_SERVER_NO_MODIFY, "no-modify"},
+ {0, NULL}};
+
+static const id_str_pair armor_type_map[] = {{PGP_ARMORED_MESSAGE, "message"},
+ {PGP_ARMORED_PUBLIC_KEY, "public key"},
+ {PGP_ARMORED_SECRET_KEY, "secret key"},
+ {PGP_ARMORED_SIGNATURE, "signature"},
+ {PGP_ARMORED_CLEARTEXT, "cleartext"},
+ {0, NULL}};
+
+static const id_str_pair key_import_status_map[] = {
+ {PGP_KEY_IMPORT_STATUS_UNKNOWN, "unknown"},
+ {PGP_KEY_IMPORT_STATUS_UNCHANGED, "unchanged"},
+ {PGP_KEY_IMPORT_STATUS_UPDATED, "updated"},
+ {PGP_KEY_IMPORT_STATUS_NEW, "new"},
+ {0, NULL}};
+
+static const id_str_pair sig_import_status_map[] = {
+ {PGP_SIG_IMPORT_STATUS_UNKNOWN, "unknown"},
+ {PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY, "unknown key"},
+ {PGP_SIG_IMPORT_STATUS_UNCHANGED, "unchanged"},
+ {PGP_SIG_IMPORT_STATUS_NEW, "new"},
+ {0, NULL}};
+
+static const id_str_pair revocation_code_map[] = {
+ {PGP_REVOCATION_NO_REASON, "no"},
+ {PGP_REVOCATION_SUPERSEDED, "superseded"},
+ {PGP_REVOCATION_COMPROMISED, "compromised"},
+ {PGP_REVOCATION_RETIRED, "retired"},
+ {PGP_REVOCATION_NO_LONGER_VALID, "no longer valid"},
+ {0, NULL}};
+
+static bool
+symm_alg_supported(int alg)
+{
+ return pgp_is_sa_supported(alg, true);
+}
+
+static bool
+hash_alg_supported(int alg)
+{
+ switch (alg) {
+ case PGP_HASH_MD5:
+ case PGP_HASH_SHA1:
+#if defined(ENABLE_RIPEMD160)
+ case PGP_HASH_RIPEMD:
+#endif
+ case PGP_HASH_SHA256:
+ case PGP_HASH_SHA384:
+ case PGP_HASH_SHA512:
+ case PGP_HASH_SHA224:
+ case PGP_HASH_SHA3_256:
+ case PGP_HASH_SHA3_512:
+#if defined(ENABLE_SM2)
+ case PGP_HASH_SM3:
+#endif
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+aead_alg_supported(int alg)
+{
+ switch (alg) {
+ case PGP_AEAD_NONE:
+#if defined(ENABLE_AEAD)
+#if !defined(CRYPTO_BACKEND_OPENSSL)
+ case PGP_AEAD_EAX:
+#endif
+ case PGP_AEAD_OCB:
+#endif
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+pub_alg_supported(int alg)
+{
+ switch (alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_DSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+#if defined(ENABLE_SM2)
+ case PGP_PKA_SM2:
+#endif
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+z_alg_supported(int alg)
+{
+ switch (alg) {
+ case PGP_C_NONE:
+ case PGP_C_ZIP:
+ case PGP_C_ZLIB:
+ case PGP_C_BZIP2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+curve_str_to_type(const char *str, pgp_curve_t *value)
+{
+ *value = find_curve_by_name(str);
+ return curve_supported(*value);
+}
+
+static bool
+curve_type_to_str(pgp_curve_t type, const char **str)
+{
+ const ec_curve_desc_t *desc = get_curve_desc(type);
+ if (!desc) {
+ return false;
+ }
+ *str = desc->pgp_name;
+ return true;
+}
+
+static bool
+str_to_cipher(const char *str, pgp_symm_alg_t *cipher)
+{
+ auto alg = id_str_pair::lookup(symm_alg_map, str, PGP_SA_UNKNOWN);
+ if (!symm_alg_supported(alg)) {
+ return false;
+ }
+ *cipher = static_cast<pgp_symm_alg_t>(alg);
+ return true;
+}
+
+static bool
+str_to_hash_alg(const char *str, pgp_hash_alg_t *hash_alg)
+{
+ auto alg = id_str_pair::lookup(hash_alg_map, str, PGP_HASH_UNKNOWN);
+ if (!hash_alg_supported(alg)) {
+ return false;
+ }
+ *hash_alg = static_cast<pgp_hash_alg_t>(alg);
+ return true;
+}
+
+static bool
+str_to_aead_alg(const char *str, pgp_aead_alg_t *aead_alg)
+{
+ auto alg = id_str_pair::lookup(aead_alg_map, str, PGP_AEAD_UNKNOWN);
+ if (!aead_alg_supported(alg)) {
+ return false;
+ }
+ *aead_alg = static_cast<pgp_aead_alg_t>(alg);
+ return true;
+}
+
+static bool
+str_to_compression_alg(const char *str, pgp_compression_type_t *zalg)
+{
+ auto alg = id_str_pair::lookup(compress_alg_map, str, PGP_C_UNKNOWN);
+ if (!z_alg_supported(alg)) {
+ return false;
+ }
+ *zalg = static_cast<pgp_compression_type_t>(alg);
+ return true;
+}
+
+static bool
+str_to_revocation_type(const char *str, pgp_revocation_type_t *code)
+{
+ pgp_revocation_type_t rev = static_cast<pgp_revocation_type_t>(
+ id_str_pair::lookup(revocation_code_map, str, PGP_REVOCATION_NO_REASON));
+ if ((rev == PGP_REVOCATION_NO_REASON) && !rnp::str_case_eq(str, "no")) {
+ return false;
+ }
+ *code = rev;
+ return true;
+}
+
+static bool
+str_to_cipher_mode(const char *str, pgp_cipher_mode_t *mode)
+{
+ pgp_cipher_mode_t c_mode = static_cast<pgp_cipher_mode_t>(
+ id_str_pair::lookup(cipher_mode_map, str, PGP_CIPHER_MODE_NONE));
+ if (c_mode == PGP_CIPHER_MODE_NONE) {
+ return false;
+ }
+
+ *mode = c_mode;
+ return true;
+}
+
+static bool
+str_to_pubkey_alg(const char *str, pgp_pubkey_alg_t *pub_alg)
+{
+ auto alg = id_str_pair::lookup(pubkey_alg_map, str, PGP_PKA_NOTHING);
+ if (!pub_alg_supported(alg)) {
+ return false;
+ }
+ *pub_alg = static_cast<pgp_pubkey_alg_t>(alg);
+ return true;
+}
+
+static bool
+str_to_key_flag(const char *str, uint8_t *flag)
+{
+ uint8_t _flag = id_str_pair::lookup(key_usage_map, str);
+ if (!_flag) {
+ return false;
+ }
+ *flag = _flag;
+ return true;
+}
+
+static bool
+parse_ks_format(pgp_key_store_format_t *key_store_format, const char *format)
+{
+ if (!strcmp(format, RNP_KEYSTORE_GPG)) {
+ *key_store_format = PGP_KEY_STORE_GPG;
+ } else if (!strcmp(format, RNP_KEYSTORE_KBX)) {
+ *key_store_format = PGP_KEY_STORE_KBX;
+ } else if (!strcmp(format, RNP_KEYSTORE_G10)) {
+ *key_store_format = PGP_KEY_STORE_G10;
+ } else {
+ return false;
+ }
+ return true;
+}
+
+static rnp_result_t
+hex_encode_value(const uint8_t * value,
+ size_t len,
+ char ** res,
+ rnp::hex_format_t format = rnp::HEX_UPPERCASE)
+{
+ size_t hex_len = len * 2 + 1;
+ *res = (char *) malloc(hex_len);
+ if (!*res) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!rnp::hex_encode(value, len, *res, hex_len, format)) {
+ free(*res);
+ *res = NULL;
+ return RNP_ERROR_GENERIC;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+get_map_value(const id_str_pair *map, int val, char **res)
+{
+ const char *str = id_str_pair::lookup(map, val, NULL);
+ if (!str) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ char *strcp = strdup(str);
+ if (!strcp) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ *res = strcp;
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+ret_str_value(const char *str, char **res)
+{
+ if (!str) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ char *strcp = strdup(str);
+ if (!strcp) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ *res = strcp;
+ return RNP_SUCCESS;
+}
+
+static uint32_t
+ffi_exception(FILE *fp, const char *func, const char *msg, uint32_t ret = RNP_ERROR_GENERIC)
+{
+ if (rnp_log_switch()) {
+ fprintf(
+ fp, "[%s()] Error 0x%08X (%s): %s\n", func, ret, rnp_result_to_string(ret), msg);
+ }
+ return ret;
+}
+
+#define FFI_GUARD_FP(fp) \
+ catch (rnp::rnp_exception & e) \
+ { \
+ return ffi_exception((fp), __func__, e.what(), e.code()); \
+ } \
+ catch (std::bad_alloc &) \
+ { \
+ return ffi_exception((fp), __func__, "bad_alloc", RNP_ERROR_OUT_OF_MEMORY); \
+ } \
+ catch (std::exception & e) \
+ { \
+ return ffi_exception((fp), __func__, e.what()); \
+ } \
+ catch (...) \
+ { \
+ return ffi_exception((fp), __func__, "unknown exception"); \
+ }
+
+#define FFI_GUARD FFI_GUARD_FP((stderr))
+
+rnp_ffi_st::rnp_ffi_st(pgp_key_store_format_t pub_fmt, pgp_key_store_format_t sec_fmt)
+{
+ errs = stderr;
+ pubring = new rnp_key_store_t(pub_fmt, "", context);
+ secring = new rnp_key_store_t(sec_fmt, "", context);
+ getkeycb = NULL;
+ getkeycb_ctx = NULL;
+ getpasscb = NULL;
+ getpasscb_ctx = NULL;
+ key_provider.callback = ffi_key_provider;
+ key_provider.userdata = this;
+ pass_provider.callback = rnp_password_cb_bounce;
+ pass_provider.userdata = this;
+}
+
+rnp::RNG &
+rnp_ffi_st::rng() noexcept
+{
+ return context.rng;
+}
+
+rnp::SecurityProfile &
+rnp_ffi_st::profile() noexcept
+{
+ return context.profile;
+}
+
+rnp_result_t
+rnp_ffi_create(rnp_ffi_t *ffi, const char *pub_format, const char *sec_format)
+try {
+ // checks
+ if (!ffi || !pub_format || !sec_format) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_store_format_t pub_ks_format = PGP_KEY_STORE_UNKNOWN;
+ pgp_key_store_format_t sec_ks_format = PGP_KEY_STORE_UNKNOWN;
+ if (!parse_ks_format(&pub_ks_format, pub_format) ||
+ !parse_ks_format(&sec_ks_format, sec_format)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ struct rnp_ffi_st *ob = new rnp_ffi_st(pub_ks_format, sec_ks_format);
+ *ffi = ob;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static bool
+is_std_file(FILE *fp)
+{
+ return fp == stdout || fp == stderr;
+}
+
+static void
+close_io_file(FILE **fp)
+{
+ if (*fp && !is_std_file(*fp)) {
+ fclose(*fp);
+ }
+ *fp = NULL;
+}
+
+rnp_ffi_st::~rnp_ffi_st()
+{
+ close_io_file(&errs);
+ delete pubring;
+ delete secring;
+}
+
+rnp_result_t
+rnp_ffi_destroy(rnp_ffi_t ffi)
+try {
+ if (ffi) {
+ delete ffi;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_ffi_set_log_fd(rnp_ffi_t ffi, int fd)
+try {
+ // checks
+ if (!ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // open
+ FILE *errs = rnp_fdopen(fd, "a");
+ if (!errs) {
+ return RNP_ERROR_ACCESS;
+ }
+ // close previous streams and replace them
+ close_io_file(&ffi->errs);
+ ffi->errs = errs;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_ffi_set_key_provider(rnp_ffi_t ffi, rnp_get_key_cb getkeycb, void *getkeycb_ctx)
+try {
+ if (!ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ ffi->getkeycb = getkeycb;
+ ffi->getkeycb_ctx = getkeycb_ctx;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_ffi_set_pass_provider(rnp_ffi_t ffi, rnp_password_cb getpasscb, void *getpasscb_ctx)
+try {
+ if (!ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ ffi->getpasscb = getpasscb;
+ ffi->getpasscb_ctx = getpasscb_ctx;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static const char *
+operation_description(uint8_t op)
+{
+ switch (op) {
+ case PGP_OP_ADD_SUBKEY:
+ return "add subkey";
+ case PGP_OP_ADD_USERID:
+ return "add userid";
+ case PGP_OP_SIGN:
+ return "sign";
+ case PGP_OP_DECRYPT:
+ return "decrypt";
+ case PGP_OP_UNLOCK:
+ return "unlock";
+ case PGP_OP_PROTECT:
+ return "protect";
+ case PGP_OP_UNPROTECT:
+ return "unprotect";
+ case PGP_OP_DECRYPT_SYM:
+ return "decrypt (symmetric)";
+ case PGP_OP_ENCRYPT_SYM:
+ return "encrypt (symmetric)";
+ default:
+ return "unknown";
+ }
+}
+
+static bool
+rnp_password_cb_bounce(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata_void)
+{
+ rnp_ffi_t ffi = (rnp_ffi_t) userdata_void;
+
+ if (!ffi || !ffi->getpasscb) {
+ return false;
+ }
+
+ struct rnp_key_handle_st key = {};
+ key.ffi = ffi;
+ key.sec = (pgp_key_t *) ctx->key;
+ return ffi->getpasscb(ffi,
+ ffi->getpasscb_ctx,
+ ctx->key ? &key : NULL,
+ operation_description(ctx->op),
+ password,
+ password_size);
+}
+
+const char *
+rnp_result_to_string(rnp_result_t result)
+{
+ switch (result) {
+ case RNP_SUCCESS:
+ return "Success";
+
+ case RNP_ERROR_GENERIC:
+ return "Unknown error";
+ case RNP_ERROR_BAD_FORMAT:
+ return "Bad format";
+ case RNP_ERROR_BAD_PARAMETERS:
+ return "Bad parameters";
+ case RNP_ERROR_NOT_IMPLEMENTED:
+ return "Not implemented";
+ case RNP_ERROR_NOT_SUPPORTED:
+ return "Not supported";
+ case RNP_ERROR_OUT_OF_MEMORY:
+ return "Out of memory";
+ case RNP_ERROR_SHORT_BUFFER:
+ return "Buffer too short";
+ case RNP_ERROR_NULL_POINTER:
+ return "Null pointer";
+
+ case RNP_ERROR_ACCESS:
+ return "Error accessing file";
+ case RNP_ERROR_READ:
+ return "Error reading file";
+ case RNP_ERROR_WRITE:
+ return "Error writing file";
+
+ case RNP_ERROR_BAD_STATE:
+ return "Bad state";
+ case RNP_ERROR_MAC_INVALID:
+ return "Invalid MAC";
+ case RNP_ERROR_SIGNATURE_INVALID:
+ return "Invalid signature";
+ case RNP_ERROR_KEY_GENERATION:
+ return "Error during key generation";
+ case RNP_ERROR_BAD_PASSWORD:
+ return "Bad password";
+ case RNP_ERROR_KEY_NOT_FOUND:
+ return "Key not found";
+ case RNP_ERROR_NO_SUITABLE_KEY:
+ return "No suitable key";
+ case RNP_ERROR_DECRYPT_FAILED:
+ return "Decryption failed";
+ case RNP_ERROR_RNG:
+ return "Failure of random number generator";
+ case RNP_ERROR_SIGNING_FAILED:
+ return "Signing failed";
+ case RNP_ERROR_NO_SIGNATURES_FOUND:
+ return "No signatures found cannot verify";
+
+ case RNP_ERROR_SIGNATURE_EXPIRED:
+ return "Expired signature";
+ case RNP_ERROR_VERIFICATION_FAILED:
+ return "Signature verification failed cannot verify";
+ case RNP_ERROR_SIGNATURE_UNKNOWN:
+ return "Unknown signature";
+
+ case RNP_ERROR_NOT_ENOUGH_DATA:
+ return "Not enough data";
+ case RNP_ERROR_UNKNOWN_TAG:
+ return "Unknown tag";
+ case RNP_ERROR_PACKET_NOT_CONSUMED:
+ return "Packet not consumed";
+ case RNP_ERROR_NO_USERID:
+ return "No userid";
+ case RNP_ERROR_EOF:
+ return "EOF detected";
+ }
+
+ return "Unsupported error code";
+}
+
+const char *
+rnp_version_string()
+{
+ return RNP_VERSION_STRING;
+}
+
+const char *
+rnp_version_string_full()
+{
+ return RNP_VERSION_STRING_FULL;
+}
+
+uint32_t
+rnp_version()
+{
+ return RNP_VERSION_CODE;
+}
+
+uint32_t
+rnp_version_for(uint32_t major, uint32_t minor, uint32_t patch)
+{
+ if (major > RNP_VERSION_COMPONENT_MASK || minor > RNP_VERSION_COMPONENT_MASK ||
+ patch > RNP_VERSION_COMPONENT_MASK) {
+ RNP_LOG("invalid version, out of range: %d.%d.%d", major, minor, patch);
+ return 0;
+ }
+ return RNP_VERSION_CODE_FOR(major, minor, patch);
+}
+
+uint32_t
+rnp_version_major(uint32_t version)
+{
+ return (version >> RNP_VERSION_MAJOR_SHIFT) & RNP_VERSION_COMPONENT_MASK;
+}
+
+uint32_t
+rnp_version_minor(uint32_t version)
+{
+ return (version >> RNP_VERSION_MINOR_SHIFT) & RNP_VERSION_COMPONENT_MASK;
+}
+
+uint32_t
+rnp_version_patch(uint32_t version)
+{
+ return (version >> RNP_VERSION_PATCH_SHIFT) & RNP_VERSION_COMPONENT_MASK;
+}
+
+uint64_t
+rnp_version_commit_timestamp()
+{
+ return RNP_VERSION_COMMIT_TIMESTAMP;
+}
+
+#ifndef RNP_NO_DEPRECATED
+rnp_result_t
+rnp_enable_debug(const char *file)
+try {
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+#endif
+
+#ifndef RNP_NO_DEPRECATED
+rnp_result_t
+rnp_disable_debug()
+try {
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+#endif
+
+rnp_result_t
+rnp_get_default_homedir(char **homedir)
+try {
+ // checks
+ if (!homedir) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // get the users home dir
+ auto home = rnp::path::HOME(".rnp");
+ if (home.empty()) {
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ *homedir = strdup(home.c_str());
+ if (!*homedir) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_detect_homedir_info(
+ const char *homedir, char **pub_format, char **pub_path, char **sec_format, char **sec_path)
+try {
+ // checks
+ if (!homedir || !pub_format || !pub_path || !sec_format || !sec_path) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // we only support the common cases of GPG+GPG or GPG+G10, we don't
+ // support unused combinations like KBX+KBX
+
+ *pub_format = NULL;
+ *pub_path = NULL;
+ *sec_format = NULL;
+ *sec_path = NULL;
+
+ // check for pubring.kbx file and for private-keys-v1.d dir
+ std::string pub = rnp::path::append(homedir, "pubring.kbx");
+ std::string sec = rnp::path::append(homedir, "private-keys-v1.d");
+ if (rnp::path::exists(pub) && rnp::path::exists(sec, true)) {
+ *pub_format = strdup("KBX");
+ *sec_format = strdup("G10");
+ } else {
+ // check for pubring.gpg and secring.gpg
+ pub = rnp::path::append(homedir, "pubring.gpg");
+ sec = rnp::path::append(homedir, "secring.gpg");
+ if (rnp::path::exists(pub) && rnp::path::exists(sec)) {
+ *pub_format = strdup("GPG");
+ *sec_format = strdup("GPG");
+ } else {
+ // we leave the *formats as NULL if we were not able to determine the format
+ // (but no error occurred)
+ return RNP_SUCCESS;
+ }
+ }
+
+ // set pathes
+ *pub_path = strdup(pub.c_str());
+ *sec_path = strdup(sec.c_str());
+
+ // check for allocation failures
+ if (*pub_format && *pub_path && *sec_format && *sec_path) {
+ return RNP_SUCCESS;
+ }
+
+ free(*pub_format);
+ *pub_format = NULL;
+ free(*pub_path);
+ *pub_path = NULL;
+ free(*sec_format);
+ *sec_format = NULL;
+ free(*sec_path);
+ *sec_path = NULL;
+ return RNP_ERROR_OUT_OF_MEMORY;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_detect_key_format(const uint8_t buf[], size_t buf_len, char **format)
+try {
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ // checks
+ if (!buf || !format) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!buf_len) {
+ return RNP_ERROR_SHORT_BUFFER;
+ }
+
+ *format = NULL;
+ // ordered from most reliable detection to least
+ const char *guess = NULL;
+ if (buf_len >= 12 && memcmp(buf + 8, "KBXf", 4) == 0) {
+ // KBX has a magic KBXf marker
+ guess = "KBX";
+ } else if (buf_len >= 5 && memcmp(buf, "-----", 5) == 0) {
+ // likely armored GPG
+ guess = "GPG";
+ } else if (buf[0] == '(') {
+ // G10 is s-exprs and should start end end with parentheses
+ guess = "G10";
+ } else if (buf[0] & PGP_PTAG_ALWAYS_SET) {
+ // this is harder to reliably determine, but could likely be improved
+ guess = "GPG";
+ }
+ if (guess) {
+ *format = strdup(guess);
+ if (!*format) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ }
+
+ // success
+ ret = RNP_SUCCESS;
+done:
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_calculate_iterations(const char *hash, size_t msec, size_t *iterations)
+try {
+ if (!hash || !iterations) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_hash_alg_t halg = PGP_HASH_UNKNOWN;
+ if (!str_to_hash_alg(hash, &halg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *iterations = pgp_s2k_compute_iters(halg, msec, 0);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_supports_feature(const char *type, const char *name, bool *supported)
+try {
+ if (!type || !name || !supported) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) {
+ pgp_symm_alg_t alg = PGP_SA_UNKNOWN;
+ *supported = str_to_cipher(name, &alg);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) {
+ pgp_aead_alg_t alg = PGP_AEAD_UNKNOWN;
+ *supported = str_to_aead_alg(name, &alg);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) {
+ // for now we support only CFB for key encryption
+ *supported = rnp::str_case_eq(name, "CFB");
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) {
+ pgp_pubkey_alg_t alg = PGP_PKA_NOTHING;
+ *supported = str_to_pubkey_alg(name, &alg);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) {
+ pgp_hash_alg_t alg = PGP_HASH_UNKNOWN;
+ *supported = str_to_hash_alg(name, &alg);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) {
+ pgp_compression_type_t alg = PGP_C_UNKNOWN;
+ *supported = str_to_compression_alg(name, &alg);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) {
+ pgp_curve_t curve = PGP_CURVE_UNKNOWN;
+ *supported = curve_str_to_type(name, &curve);
+ } else {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+json_array_add_id_str(json_object *arr, const id_str_pair *map, bool (*check)(int))
+{
+ while (map->str) {
+ if (check(map->id) && !array_add_element_json(arr, json_object_new_string(map->str))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ map++;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_supported_features(const char *type, char **result)
+try {
+ if (!type || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ json_object *features = json_object_new_array();
+ if (!features) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ rnp_result_t ret = RNP_ERROR_BAD_PARAMETERS;
+
+ if (rnp::str_case_eq(type, RNP_FEATURE_SYMM_ALG)) {
+ ret = json_array_add_id_str(features, symm_alg_map, symm_alg_supported);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_AEAD_ALG)) {
+ ret = json_array_add_id_str(features, aead_alg_map, aead_alg_supported);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_PROT_MODE)) {
+ ret = json_array_add_id_str(
+ features, cipher_mode_map, [](int alg) { return alg == PGP_CIPHER_MODE_CFB; });
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_PK_ALG)) {
+ ret = json_array_add_id_str(features, pubkey_alg_map, pub_alg_supported);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_HASH_ALG)) {
+ ret = json_array_add_id_str(features, hash_alg_map, hash_alg_supported);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_COMP_ALG)) {
+ ret = json_array_add_id_str(features, compress_alg_map, z_alg_supported);
+ } else if (rnp::str_case_eq(type, RNP_FEATURE_CURVE)) {
+ for (pgp_curve_t curve = PGP_CURVE_NIST_P_256; curve < PGP_CURVE_MAX;
+ curve = (pgp_curve_t)(curve + 1)) {
+ const ec_curve_desc_t *desc = get_curve_desc(curve);
+ if (!desc) {
+ ret = RNP_ERROR_BAD_STATE;
+ goto done;
+ }
+ if (!desc->supported) {
+ continue;
+ }
+ if (!array_add_element_json(features, json_object_new_string(desc->pgp_name))) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ }
+ ret = RNP_SUCCESS;
+ }
+
+ if (ret) {
+ goto done;
+ }
+
+ *result = (char *) json_object_to_json_string_ext(features, JSON_C_TO_STRING_PRETTY);
+ if (!*result) {
+ ret = RNP_ERROR_BAD_STATE;
+ goto done;
+ }
+ *result = strdup(*result);
+ if (!*result) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ }
+done:
+ json_object_put(features);
+ return ret;
+}
+FFI_GUARD
+
+static bool
+get_feature_sec_value(
+ rnp_ffi_t ffi, const char *stype, const char *sname, rnp::FeatureType &type, int &value)
+{
+ /* check type */
+ if (!rnp::str_case_eq(stype, RNP_FEATURE_HASH_ALG)) {
+ FFI_LOG(ffi, "Unsupported feature type: %s", stype);
+ return false;
+ }
+ type = rnp::FeatureType::Hash;
+ /* check feature name */
+ pgp_hash_alg_t alg = PGP_HASH_UNKNOWN;
+ if (sname && !str_to_hash_alg(sname, &alg)) {
+ FFI_LOG(ffi, "Unknown hash algorithm: %s", sname);
+ return false;
+ }
+ value = alg;
+ return true;
+}
+
+static bool
+get_feature_sec_level(rnp_ffi_t ffi, uint32_t flevel, rnp::SecurityLevel &level)
+{
+ switch (flevel) {
+ case RNP_SECURITY_PROHIBITED:
+ level = rnp::SecurityLevel::Disabled;
+ break;
+ case RNP_SECURITY_INSECURE:
+ level = rnp::SecurityLevel::Insecure;
+ break;
+ case RNP_SECURITY_DEFAULT:
+ level = rnp::SecurityLevel::Default;
+ break;
+ default:
+ FFI_LOG(ffi, "Invalid security level : %" PRIu32, flevel);
+ return false;
+ }
+ return true;
+}
+
+static bool
+extract_flag(uint32_t &flags, uint32_t flag)
+{
+ bool res = flags & flag;
+ flags &= ~flag;
+ return res;
+}
+
+rnp_result_t
+rnp_add_security_rule(rnp_ffi_t ffi,
+ const char *type,
+ const char *name,
+ uint32_t flags,
+ uint64_t from,
+ uint32_t level)
+try {
+ if (!ffi || !type || !name) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ /* convert values */
+ rnp::FeatureType ftype;
+ int fvalue;
+ rnp::SecurityLevel sec_level;
+ if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) ||
+ !get_feature_sec_level(ffi, level, sec_level)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* check flags */
+ bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE);
+ bool verify_key = extract_flag(flags, RNP_SECURITY_VERIFY_KEY);
+ bool verify_data = extract_flag(flags, RNP_SECURITY_VERIFY_DATA);
+ if (flags) {
+ FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* add rule */
+ rnp::SecurityRule newrule(ftype, fvalue, sec_level, from);
+ newrule.override = rule_override;
+ /* Add rule for any action */
+ if (!verify_key && !verify_data) {
+ ffi->profile().add_rule(newrule);
+ return RNP_SUCCESS;
+ }
+ /* Add rule for each specified key usage */
+ if (verify_key) {
+ newrule.action = rnp::SecurityAction::VerifyKey;
+ ffi->profile().add_rule(newrule);
+ }
+ if (verify_data) {
+ newrule.action = rnp::SecurityAction::VerifyData;
+ ffi->profile().add_rule(newrule);
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp::SecurityAction
+get_security_action(uint32_t flags)
+{
+ if (flags & RNP_SECURITY_VERIFY_KEY) {
+ return rnp::SecurityAction::VerifyKey;
+ }
+ if (flags & RNP_SECURITY_VERIFY_DATA) {
+ return rnp::SecurityAction::VerifyData;
+ }
+ return rnp::SecurityAction::Any;
+}
+
+rnp_result_t
+rnp_get_security_rule(rnp_ffi_t ffi,
+ const char *type,
+ const char *name,
+ uint64_t time,
+ uint32_t * flags,
+ uint64_t * from,
+ uint32_t * level)
+try {
+ if (!ffi || !type || !name || !level) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ /* convert values */
+ rnp::FeatureType ftype;
+ int fvalue;
+ if (!get_feature_sec_value(ffi, type, name, ftype, fvalue)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* init default rule */
+ rnp::SecurityRule rule(ftype, fvalue, ffi->profile().def_level());
+ /* Check whether limited usage is requested */
+ auto action = get_security_action(flags ? *flags : 0);
+ /* check whether rule exists */
+ if (ffi->profile().has_rule(ftype, fvalue, time, action)) {
+ rule = ffi->profile().get_rule(ftype, fvalue, time, action);
+ }
+ /* fill the results */
+ if (flags) {
+ *flags = rule.override ? RNP_SECURITY_OVERRIDE : 0;
+ switch (rule.action) {
+ case rnp::SecurityAction::VerifyKey:
+ *flags |= RNP_SECURITY_VERIFY_KEY;
+ break;
+ case rnp::SecurityAction::VerifyData:
+ *flags |= RNP_SECURITY_VERIFY_DATA;
+ break;
+ default:
+ break;
+ }
+ }
+ if (from) {
+ *from = rule.from;
+ }
+ switch (rule.level) {
+ case rnp::SecurityLevel::Disabled:
+ *level = RNP_SECURITY_PROHIBITED;
+ break;
+ case rnp::SecurityLevel::Insecure:
+ *level = RNP_SECURITY_INSECURE;
+ break;
+ case rnp::SecurityLevel::Default:
+ *level = RNP_SECURITY_DEFAULT;
+ break;
+ default:
+ FFI_LOG(ffi, "Invalid security level.");
+ return RNP_ERROR_BAD_STATE;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_remove_security_rule(rnp_ffi_t ffi,
+ const char *type,
+ const char *name,
+ uint32_t level,
+ uint32_t flags,
+ uint64_t from,
+ size_t * removed)
+try {
+ if (!ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ /* check flags */
+ bool remove_all = extract_flag(flags, RNP_SECURITY_REMOVE_ALL);
+ bool rule_override = extract_flag(flags, RNP_SECURITY_OVERRIDE);
+ rnp::SecurityAction action = get_security_action(flags);
+ extract_flag(flags, RNP_SECURITY_VERIFY_DATA | RNP_SECURITY_VERIFY_KEY);
+ if (flags) {
+ FFI_LOG(ffi, "Unknown flags: %" PRIu32, flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* remove all rules */
+ size_t rules = ffi->profile().size();
+ if (!type) {
+ ffi->profile().clear_rules();
+ goto success;
+ }
+ rnp::FeatureType ftype;
+ int fvalue;
+ rnp::SecurityLevel flevel;
+ if (!get_feature_sec_value(ffi, type, name, ftype, fvalue) ||
+ !get_feature_sec_level(ffi, level, flevel)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* remove all rules for the specified type */
+ if (!name) {
+ ffi->profile().clear_rules(ftype);
+ goto success;
+ }
+ if (remove_all) {
+ /* remove all rules for the specified type and name */
+ ffi->profile().clear_rules(ftype, fvalue);
+ } else {
+ /* remove specific rule */
+ rnp::SecurityRule rule(ftype, fvalue, flevel, from, action);
+ rule.override = rule_override;
+ ffi->profile().del_rule(rule);
+ }
+success:
+ if (removed) {
+ *removed = rules - ffi->profile().size();
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_request_password(rnp_ffi_t ffi, rnp_key_handle_t key, const char *context, char **password)
+try {
+ if (!ffi || !password || !ffi->getpasscb) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ rnp::secure_vector<char> pass(MAX_PASSWORD_LENGTH, '\0');
+ bool req_res =
+ ffi->getpasscb(ffi, ffi->getpasscb_ctx, key, context, pass.data(), pass.size());
+ if (!req_res) {
+ return RNP_ERROR_GENERIC;
+ }
+ size_t pass_len = strlen(pass.data()) + 1;
+ *password = (char *) malloc(pass_len);
+ if (!*password) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(*password, pass.data(), pass_len);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_set_timestamp(rnp_ffi_t ffi, uint64_t time)
+try {
+ if (!ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ ffi->context.set_time(time);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+load_keys_from_input(rnp_ffi_t ffi, rnp_input_t input, rnp_key_store_t *store)
+{
+ pgp_key_provider_t chained(rnp_key_provider_store, store);
+ const pgp_key_provider_t *key_providers[] = {&chained, &ffi->key_provider, NULL};
+ const pgp_key_provider_t key_provider(rnp_key_provider_chained, key_providers);
+
+ if (!input->src_directory.empty()) {
+ // load the keys
+ store->path = input->src_directory;
+ if (!rnp_key_store_load_from_path(store, &key_provider)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ return RNP_SUCCESS;
+ }
+
+ // load the keys
+ if (!rnp_key_store_load_from_src(store, &input->src, &key_provider)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ return RNP_SUCCESS;
+}
+
+static bool
+key_needs_conversion(const pgp_key_t *key, const rnp_key_store_t *store)
+{
+ pgp_key_store_format_t key_format = key->format;
+ pgp_key_store_format_t store_format = store->format;
+ /* pgp_key_t->format is only ever GPG or G10.
+ *
+ * The key store, however, could have a format of KBX, GPG, or G10.
+ * A KBX (and GPG) key store can only handle a pgp_key_t with a format of GPG.
+ * A G10 key store can only handle a pgp_key_t with a format of G10.
+ */
+ // should never be the case
+ assert(key_format != PGP_KEY_STORE_KBX);
+ // normalize the store format
+ if (store_format == PGP_KEY_STORE_KBX) {
+ store_format = PGP_KEY_STORE_GPG;
+ }
+ // from here, both the key and store formats can only be GPG or G10
+ return key_format != store_format;
+}
+
+static rnp_result_t
+do_load_keys(rnp_ffi_t ffi,
+ rnp_input_t input,
+ pgp_key_store_format_t format,
+ key_type_t key_type)
+{
+ // create a temporary key store to hold the keys
+ std::unique_ptr<rnp_key_store_t> tmp_store;
+ try {
+ tmp_store =
+ std::unique_ptr<rnp_key_store_t>(new rnp_key_store_t(format, "", ffi->context));
+ } catch (const std::invalid_argument &e) {
+ FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // load keys into our temporary store
+ rnp_result_t tmpret = load_keys_from_input(ffi, input, tmp_store.get());
+ if (tmpret) {
+ return tmpret;
+ }
+ // go through all the loaded keys
+ for (auto &key : tmp_store->keys) {
+ // check that the key is the correct type and has not already been loaded
+ // add secret key part if it is and we need it
+ if (key.is_secret() && ((key_type == KEY_TYPE_SECRET) || (key_type == KEY_TYPE_ANY))) {
+ if (key_needs_conversion(&key, ffi->secring)) {
+ FFI_LOG(ffi, "This key format conversion is not yet supported");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!rnp_key_store_add_key(ffi->secring, &key)) {
+ FFI_LOG(ffi, "Failed to add secret key");
+ return RNP_ERROR_GENERIC;
+ }
+ }
+
+ // add public key part if needed
+ if ((key.format == PGP_KEY_STORE_G10) ||
+ ((key_type != KEY_TYPE_ANY) && (key_type != KEY_TYPE_PUBLIC))) {
+ continue;
+ }
+
+ pgp_key_t keycp;
+ try {
+ keycp = pgp_key_t(key, true);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to copy public key part: %s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+
+ /* TODO: We could do this a few different ways. There isn't an obvious reason
+ * to restrict what formats we load, so we don't necessarily need to require a
+ * conversion just to load and use a G10 key when using GPG keyrings, for
+ * example. We could just convert when saving.
+ */
+
+ if (key_needs_conversion(&key, ffi->pubring)) {
+ FFI_LOG(ffi, "This key format conversion is not yet supported");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+ }
+
+ if (!rnp_key_store_add_key(ffi->pubring, &keycp)) {
+ FFI_LOG(ffi, "Failed to add public key");
+ return RNP_ERROR_GENERIC;
+ }
+ }
+ // success, even if we didn't actually load any
+ return RNP_SUCCESS;
+}
+
+static key_type_t
+flags_to_key_type(uint32_t *flags)
+{
+ key_type_t type = KEY_TYPE_NONE;
+ // figure out what type of keys to operate on, based on flags
+ if ((*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) && (*flags & RNP_LOAD_SAVE_SECRET_KEYS)) {
+ type = KEY_TYPE_ANY;
+ extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS);
+ } else if (*flags & RNP_LOAD_SAVE_PUBLIC_KEYS) {
+ type = KEY_TYPE_PUBLIC;
+ extract_flag(*flags, RNP_LOAD_SAVE_PUBLIC_KEYS);
+ } else if (*flags & RNP_LOAD_SAVE_SECRET_KEYS) {
+ type = KEY_TYPE_SECRET;
+ extract_flag(*flags, RNP_LOAD_SAVE_SECRET_KEYS);
+ }
+ return type;
+}
+
+rnp_result_t
+rnp_load_keys(rnp_ffi_t ffi, const char *format, rnp_input_t input, uint32_t flags)
+try {
+ // checks
+ if (!ffi || !format || !input) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ key_type_t type = flags_to_key_type(&flags);
+ if (!type) {
+ FFI_LOG(ffi, "invalid flags - must have public and/or secret keys");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN;
+ if (!parse_ks_format(&ks_format, format)) {
+ FFI_LOG(ffi, "invalid key store format: %s", format);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // check for any unrecognized flags (not forward-compat, but maybe still a good idea)
+ if (flags) {
+ FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return do_load_keys(ffi, input, ks_format, type);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_unload_keys(rnp_ffi_t ffi, uint32_t flags)
+try {
+ if (!ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (flags & ~(RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (flags & RNP_KEY_UNLOAD_PUBLIC) {
+ rnp_key_store_clear(ffi->pubring);
+ }
+ if (flags & RNP_KEY_UNLOAD_SECRET) {
+ rnp_key_store_clear(ffi->secring);
+ }
+
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+rnp_input_dearmor_if_needed(rnp_input_t input, bool noheaders = false)
+{
+ if (!input) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!input->src_directory.empty()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ bool require_armor = false;
+ /* check whether we already have armored stream */
+ if (input->src.type == PGP_STREAM_ARMORED) {
+ if (!src_eof(&input->src)) {
+ /* be ready for the case of damaged armoring */
+ return src_error(&input->src) ? RNP_ERROR_READ : RNP_SUCCESS;
+ }
+ /* eof - probably next we have another armored message */
+ src_close(&input->src);
+ rnp_input_st *base = (rnp_input_st *) input->app_ctx;
+ *input = std::move(*base);
+ delete base;
+ /* we should not mix armored data with binary */
+ require_armor = true;
+ }
+ if (src_eof(&input->src)) {
+ return RNP_ERROR_EOF;
+ }
+ /* check whether input is armored only if base64 is not forced */
+ if (!noheaders && !is_armored_source(&input->src)) {
+ return require_armor ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS;
+ }
+
+ /* Store original input in app_ctx and replace src/app_ctx with armored data */
+ rnp_input_t app_ctx = new rnp_input_st();
+ *app_ctx = std::move(*input);
+
+ rnp_result_t ret = init_armored_src(&input->src, &app_ctx->src, noheaders);
+ if (ret) {
+ /* original src may be changed during init_armored_src call, so copy it back */
+ *input = std::move(*app_ctx);
+ delete app_ctx;
+ return ret;
+ }
+ input->app_ctx = app_ctx;
+ return RNP_SUCCESS;
+}
+
+static const char *
+key_status_to_str(pgp_key_import_status_t status)
+{
+ if (status == PGP_KEY_IMPORT_STATUS_UNKNOWN) {
+ return "none";
+ }
+ return id_str_pair::lookup(key_import_status_map, status, "none");
+}
+
+static rnp_result_t
+add_key_status(json_object * keys,
+ const pgp_key_t * key,
+ pgp_key_import_status_t pub,
+ pgp_key_import_status_t sec)
+{
+ json_object *jsokey = json_object_new_object();
+ if (!jsokey) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!obj_add_field_json(
+ jsokey, "public", json_object_new_string(key_status_to_str(pub))) ||
+ !obj_add_field_json(
+ jsokey, "secret", json_object_new_string(key_status_to_str(sec))) ||
+ !obj_add_hex_json(jsokey, "fingerprint", key->fp().fingerprint, key->fp().length) ||
+ !array_add_element_json(keys, jsokey)) {
+ json_object_put(jsokey);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_import_keys(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results)
+try {
+ if (!ffi || !input) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ bool sec = extract_flag(flags, RNP_LOAD_SAVE_SECRET_KEYS);
+ bool pub = extract_flag(flags, RNP_LOAD_SAVE_PUBLIC_KEYS);
+ if (!pub && !sec) {
+ FFI_LOG(ffi, "bad flags: need to specify public and/or secret keys");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ bool skipbad = extract_flag(flags, RNP_LOAD_SAVE_PERMISSIVE);
+ bool single = extract_flag(flags, RNP_LOAD_SAVE_SINGLE);
+ bool base64 = extract_flag(flags, RNP_LOAD_SAVE_BASE64);
+ if (flags) {
+ FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ rnp_key_store_t tmp_store(PGP_KEY_STORE_GPG, "", ffi->context);
+
+ /* check whether input is base64 */
+ if (base64 && is_base64_source(input->src)) {
+ ret = rnp_input_dearmor_if_needed(input, true);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ // load keys to temporary keystore.
+ if (single) {
+ /* we need to init and handle dearmor on this layer since it may be used for the next
+ * keys import */
+ ret = rnp_input_dearmor_if_needed(input);
+ if (ret == RNP_ERROR_EOF) {
+ return ret;
+ }
+ if (ret) {
+ FFI_LOG(ffi, "Failed to init/check dearmor.");
+ return ret;
+ }
+ ret = rnp_key_store_pgp_read_key_from_src(tmp_store, input->src, skipbad);
+ if (ret) {
+ return ret;
+ }
+ } else {
+ ret = rnp_key_store_pgp_read_from_src(&tmp_store, &input->src, skipbad);
+ if (ret) {
+ return ret;
+ }
+ }
+
+ json_object *jsores = json_object_new_object();
+ if (!jsores) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp::JSONObject jsowrap(jsores);
+ json_object * jsokeys = json_object_new_array();
+ if (!obj_add_field_json(jsores, "keys", jsokeys)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ // import keys to the main keystore.
+ for (auto &key : tmp_store.keys) {
+ pgp_key_import_status_t pub_status = PGP_KEY_IMPORT_STATUS_UNKNOWN;
+ pgp_key_import_status_t sec_status = PGP_KEY_IMPORT_STATUS_UNKNOWN;
+ if (!pub && key.is_public()) {
+ continue;
+ }
+ // if we got here then we add public key itself or public part of the secret key
+ if (!rnp_key_store_import_key(ffi->pubring, &key, true, &pub_status)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ // import secret key part if available and requested
+ if (sec && key.is_secret()) {
+ if (!rnp_key_store_import_key(ffi->secring, &key, false, &sec_status)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ // add uids, certifications and other stuff from the public key if any
+ pgp_key_t *expub = rnp_key_store_get_key_by_fpr(ffi->pubring, key.fp());
+ if (expub && !rnp_key_store_import_key(ffi->secring, expub, true, NULL)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+ // now add key fingerprint to json based on statuses
+ rnp_result_t tmpret = add_key_status(jsokeys, &key, pub_status, sec_status);
+ if (tmpret) {
+ return tmpret;
+ }
+ }
+
+ if (results) {
+ *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY);
+ if (!*results) {
+ return RNP_ERROR_GENERIC;
+ }
+ *results = strdup(*results);
+ if (!*results) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static const char *
+sig_status_to_str(pgp_sig_import_status_t status)
+{
+ if (status == PGP_SIG_IMPORT_STATUS_UNKNOWN) {
+ return "none";
+ }
+ return id_str_pair::lookup(sig_import_status_map, status, "none");
+}
+
+static rnp_result_t
+add_sig_status(json_object * sigs,
+ const pgp_key_t * signer,
+ pgp_sig_import_status_t pub,
+ pgp_sig_import_status_t sec)
+{
+ json_object *jsosig = json_object_new_object();
+ if (!jsosig) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (!obj_add_field_json(
+ jsosig, "public", json_object_new_string(sig_status_to_str(pub))) ||
+ !obj_add_field_json(
+ jsosig, "secret", json_object_new_string(sig_status_to_str(sec)))) {
+ json_object_put(jsosig);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (signer) {
+ const pgp_fingerprint_t &fp = signer->fp();
+ if (!obj_add_hex_json(jsosig, "signer fingerprint", fp.fingerprint, fp.length)) {
+ json_object_put(jsosig);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ if (!array_add_element_json(sigs, jsosig)) {
+ json_object_put(jsosig);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_import_signatures(rnp_ffi_t ffi, rnp_input_t input, uint32_t flags, char **results)
+try {
+ if (!ffi || !input) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (flags) {
+ FFI_LOG(ffi, "wrong flags: %d", (int) flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_signature_list_t sigs;
+ rnp_result_t sigret = process_pgp_signatures(input->src, sigs);
+ if (sigret) {
+ FFI_LOG(ffi, "failed to parse signature(s)");
+ return sigret;
+ }
+
+ json_object *jsores = json_object_new_object();
+ if (!jsores) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp::JSONObject jsowrap(jsores);
+ json_object * jsosigs = json_object_new_array();
+ if (!obj_add_field_json(jsores, "sigs", jsosigs)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ for (auto &sig : sigs) {
+ pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ pgp_key_t *pkey = rnp_key_store_import_signature(ffi->pubring, &sig, &pub_status);
+ pgp_key_t *skey = rnp_key_store_import_signature(ffi->secring, &sig, &sec_status);
+ sigret = add_sig_status(jsosigs, pkey ? pkey : skey, pub_status, sec_status);
+ if (sigret) {
+ return sigret;
+ }
+ }
+
+ if (results) {
+ *results = (char *) json_object_to_json_string_ext(jsores, JSON_C_TO_STRING_PRETTY);
+ if (!*results) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ *results = strdup(*results);
+ if (!*results) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static bool
+copy_store_keys(rnp_ffi_t ffi, rnp_key_store_t *dest, rnp_key_store_t *src)
+{
+ for (auto &key : src->keys) {
+ if (!rnp_key_store_add_key(dest, &key)) {
+ FFI_LOG(ffi, "failed to add key to the store");
+ return false;
+ }
+ }
+ return true;
+}
+
+static rnp_result_t
+do_save_keys(rnp_ffi_t ffi,
+ rnp_output_t output,
+ pgp_key_store_format_t format,
+ key_type_t key_type)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ // create a temporary key store to hold the keys
+ rnp_key_store_t *tmp_store = NULL;
+ try {
+ tmp_store = new rnp_key_store_t(format, "", ffi->context);
+ } catch (const std::invalid_argument &e) {
+ FFI_LOG(ffi, "Failed to create key store of format: %d", (int) format);
+ return RNP_ERROR_BAD_PARAMETERS;
+ } catch (const std::exception &e) {
+ FFI_LOG(ffi, "%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // include the public keys, if desired
+ if (key_type == KEY_TYPE_PUBLIC || key_type == KEY_TYPE_ANY) {
+ if (!copy_store_keys(ffi, tmp_store, ffi->pubring)) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ }
+ // include the secret keys, if desired
+ if (key_type == KEY_TYPE_SECRET || key_type == KEY_TYPE_ANY) {
+ if (!copy_store_keys(ffi, tmp_store, ffi->secring)) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ }
+ // preliminary check on the format
+ for (auto &key : tmp_store->keys) {
+ if (key_needs_conversion(&key, tmp_store)) {
+ FFI_LOG(ffi, "This key format conversion is not yet supported");
+ ret = RNP_ERROR_NOT_IMPLEMENTED;
+ goto done;
+ }
+ }
+ // write
+ if (output->dst_directory) {
+ try {
+ tmp_store->path = output->dst_directory;
+ } catch (const std::exception &e) {
+ FFI_LOG(ffi, "%s", e.what());
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ if (!rnp_key_store_write_to_path(tmp_store)) {
+ ret = RNP_ERROR_WRITE;
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+ } else {
+ if (!rnp_key_store_write_to_dst(tmp_store, &output->dst)) {
+ ret = RNP_ERROR_WRITE;
+ goto done;
+ }
+ dst_flush(&output->dst);
+ output->keep = (output->dst.werr == RNP_SUCCESS);
+ ret = output->dst.werr;
+ }
+
+done:
+ delete tmp_store;
+ return ret;
+}
+
+rnp_result_t
+rnp_save_keys(rnp_ffi_t ffi, const char *format, rnp_output_t output, uint32_t flags)
+try {
+ // checks
+ if (!ffi || !format || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ key_type_t type = flags_to_key_type(&flags);
+ if (!type) {
+ FFI_LOG(ffi, "invalid flags - must have public and/or secret keys");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ // check for any unrecognized flags (not forward-compat, but maybe still a good idea)
+ if (flags) {
+ FFI_LOG(ffi, "unexpected flags remaining: 0x%X", flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_store_format_t ks_format = PGP_KEY_STORE_UNKNOWN;
+ if (!parse_ks_format(&ks_format, format)) {
+ FFI_LOG(ffi, "unknown key store format: %s", format);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return do_save_keys(ffi, output, ks_format, type);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_get_public_key_count(rnp_ffi_t ffi, size_t *count)
+try {
+ if (!ffi || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *count = rnp_key_store_get_key_count(ffi->pubring);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_get_secret_key_count(rnp_ffi_t ffi, size_t *count)
+try {
+ if (!ffi || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *count = rnp_key_store_get_key_count(ffi->secring);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_input_st::rnp_input_st() : reader(NULL), closer(NULL), app_ctx(NULL)
+{
+ memset(&src, 0, sizeof(src));
+}
+
+rnp_input_st &
+rnp_input_st::operator=(rnp_input_st &&input)
+{
+ src_close(&src);
+ src = std::move(input.src);
+ memset(&input.src, 0, sizeof(input.src));
+ reader = input.reader;
+ input.reader = NULL;
+ closer = input.closer;
+ input.closer = NULL;
+ app_ctx = input.app_ctx;
+ input.app_ctx = NULL;
+ src_directory = std::move(input.src_directory);
+ return *this;
+}
+
+rnp_input_st::~rnp_input_st()
+{
+ bool armored = src.type == PGP_STREAM_ARMORED;
+ src_close(&src);
+ if (armored) {
+ rnp_input_t armored = (rnp_input_t) app_ctx;
+ delete armored;
+ app_ctx = NULL;
+ }
+}
+
+rnp_result_t
+rnp_input_from_path(rnp_input_t *input, const char *path)
+try {
+ if (!input || !path) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ rnp_input_st *ob = new rnp_input_st();
+ struct stat st = {0};
+ if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+ // a bit hacky, just save the directory path
+ ob->src_directory = path;
+ // return error on attempt to read from this source
+ (void) init_null_src(&ob->src);
+ } else {
+ // simple input from a file
+ rnp_result_t ret = init_file_src(&ob->src, path);
+ if (ret) {
+ delete ob;
+ return ret;
+ }
+ }
+ *input = ob;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_input_from_stdin(rnp_input_t *input)
+try {
+ if (!input) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *input = new rnp_input_st();
+ rnp_result_t ret = init_stdin_src(&(*input)->src);
+ if (ret) {
+ delete *input;
+ *input = NULL;
+ return ret;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_input_from_memory(rnp_input_t *input, const uint8_t buf[], size_t buf_len, bool do_copy)
+try {
+ if (!input || !buf) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!buf_len) {
+ return RNP_ERROR_SHORT_BUFFER;
+ }
+ *input = new rnp_input_st();
+ uint8_t *data = (uint8_t *) buf;
+ if (do_copy) {
+ data = (uint8_t *) malloc(buf_len);
+ if (!data) {
+ delete *input;
+ *input = NULL;
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(data, buf, buf_len);
+ }
+ rnp_result_t ret = init_mem_src(&(*input)->src, data, buf_len, do_copy);
+ if (ret) {
+ if (do_copy) {
+ free(data);
+ }
+ delete *input;
+ *input = NULL;
+ return ret;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static bool
+input_reader_bounce(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+ rnp_input_t input = (rnp_input_t) src->param;
+ if (!input->reader) {
+ return false;
+ }
+ return input->reader(input->app_ctx, buf, len, read);
+}
+
+static void
+input_closer_bounce(pgp_source_t *src)
+{
+ rnp_input_t input = (rnp_input_t) src->param;
+ if (input->closer) {
+ input->closer(input->app_ctx);
+ }
+}
+
+rnp_result_t
+rnp_input_from_callback(rnp_input_t * input,
+ rnp_input_reader_t *reader,
+ rnp_input_closer_t *closer,
+ void * app_ctx)
+try {
+ // checks
+ if (!input || !reader) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ rnp_input_st *obj = new rnp_input_st();
+ pgp_source_t *src = &obj->src;
+ obj->reader = reader;
+ obj->closer = closer;
+ obj->app_ctx = app_ctx;
+ if (!init_src_common(src, 0)) {
+ delete obj;
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ src->param = obj;
+ src->read = input_reader_bounce;
+ src->close = input_closer_bounce;
+ src->type = PGP_STREAM_MEMORY;
+ *input = obj;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_input_destroy(rnp_input_t input)
+try {
+ delete input;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_to_path(rnp_output_t *output, const char *path)
+try {
+ struct rnp_output_st *ob = NULL;
+ struct stat st = {0};
+
+ if (!output || !path) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ ob = (rnp_output_st *) calloc(1, sizeof(*ob));
+ if (!ob) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (rnp_stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
+ // a bit hacky, just save the directory path
+ ob->dst_directory = strdup(path);
+ if (!ob->dst_directory) {
+ free(ob);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ } else {
+ // simple output to a file
+ rnp_result_t ret = init_file_dest(&ob->dst, path, true);
+ if (ret) {
+ free(ob);
+ return ret;
+ }
+ }
+ *output = ob;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_to_file(rnp_output_t *output, const char *path, uint32_t flags)
+try {
+ if (!output || !path) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ bool overwrite = extract_flag(flags, RNP_OUTPUT_FILE_OVERWRITE);
+ bool random = extract_flag(flags, RNP_OUTPUT_FILE_RANDOM);
+ if (flags) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res));
+ if (!res) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ if (random) {
+ ret = init_tmpfile_dest(&res->dst, path, overwrite);
+ } else {
+ ret = init_file_dest(&res->dst, path, overwrite);
+ }
+ if (ret) {
+ free(res);
+ return ret;
+ }
+ *output = res;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_to_stdout(rnp_output_t *output)
+try {
+ if (!output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ rnp_output_t res = (rnp_output_t) calloc(1, sizeof(*res));
+ if (!res) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t ret = init_stdout_dest(&res->dst);
+ if (ret) {
+ free(res);
+ return ret;
+ }
+ *output = res;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_to_memory(rnp_output_t *output, size_t max_alloc)
+try {
+ // checks
+ if (!output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *output = (rnp_output_t) calloc(1, sizeof(**output));
+ if (!*output) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t ret = init_mem_dest(&(*output)->dst, NULL, max_alloc);
+ if (ret) {
+ free(*output);
+ *output = NULL;
+ return ret;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_to_armor(rnp_output_t base, rnp_output_t *output, const char *type)
+try {
+ if (!base || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_armored_msg_t msgtype = PGP_ARMORED_MESSAGE;
+ if (type) {
+ msgtype = static_cast<pgp_armored_msg_t>(
+ id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN));
+ if (msgtype == PGP_ARMORED_UNKNOWN) {
+ RNP_LOG("Unsupported armor type: %s", type);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+ *output = (rnp_output_t) calloc(1, sizeof(**output));
+ if (!*output) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t ret = init_armored_dst(&(*output)->dst, &base->dst, msgtype);
+ if (ret) {
+ free(*output);
+ *output = NULL;
+ return ret;
+ }
+ (*output)->app_ctx = base;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_memory_get_buf(rnp_output_t output, uint8_t **buf, size_t *len, bool do_copy)
+try {
+ if (!output || !buf || !len) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *len = output->dst.writeb;
+ *buf = (uint8_t *) mem_dest_get_memory(&output->dst);
+ if (!*buf) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (do_copy) {
+ uint8_t *tmp_buf = *buf;
+ *buf = (uint8_t *) malloc(*len);
+ if (!*buf) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(*buf, tmp_buf, *len);
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+output_writer_bounce(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ rnp_output_t output = (rnp_output_t) dst->param;
+ if (!output->writer) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!output->writer(output->app_ctx, buf, len)) {
+ return RNP_ERROR_WRITE;
+ }
+ return RNP_SUCCESS;
+}
+
+static void
+output_closer_bounce(pgp_dest_t *dst, bool discard)
+{
+ rnp_output_t output = (rnp_output_t) dst->param;
+ if (output->closer) {
+ output->closer(output->app_ctx, discard);
+ }
+}
+
+rnp_result_t
+rnp_output_to_null(rnp_output_t *output)
+try {
+ // checks
+ if (!output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *output = (rnp_output_t) calloc(1, sizeof(**output));
+ if (!*output) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t ret = init_null_dest(&(*output)->dst);
+ if (ret) {
+ free(*output);
+ *output = NULL;
+ return ret;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_write(rnp_output_t output, const void *data, size_t size, size_t *written)
+try {
+ if (!output || (!data && size)) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!data && !size) {
+ if (written) {
+ *written = 0;
+ }
+ return RNP_SUCCESS;
+ }
+ size_t old = output->dst.writeb + output->dst.clen;
+ dst_write(&output->dst, data, size);
+ if (!output->dst.werr && written) {
+ *written = output->dst.writeb + output->dst.clen - old;
+ }
+ output->keep = !output->dst.werr;
+ return output->dst.werr;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_to_callback(rnp_output_t * output,
+ rnp_output_writer_t *writer,
+ rnp_output_closer_t *closer,
+ void * app_ctx)
+try {
+ // checks
+ if (!output || !writer) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *output = (rnp_output_t) calloc(1, sizeof(**output));
+ if (!*output) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ (*output)->writer = writer;
+ (*output)->closer = closer;
+ (*output)->app_ctx = app_ctx;
+
+ pgp_dest_t *dst = &(*output)->dst;
+ dst->write = output_writer_bounce;
+ dst->close = output_closer_bounce;
+ dst->param = *output;
+ dst->type = PGP_STREAM_MEMORY;
+ dst->writeb = 0;
+ dst->werr = RNP_SUCCESS;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_finish(rnp_output_t output)
+try {
+ if (!output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return dst_finish(&output->dst);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_destroy(rnp_output_t output)
+try {
+ if (output) {
+ if (output->dst.type == PGP_STREAM_ARMORED) {
+ ((rnp_output_t) output->app_ctx)->keep = output->keep;
+ }
+ dst_close(&output->dst, !output->keep);
+ free(output->dst_directory);
+ free(output);
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+rnp_op_add_signature(rnp_ffi_t ffi,
+ rnp_op_sign_signatures_t &signatures,
+ rnp_key_handle_t key,
+ rnp_ctx_t & ctx,
+ rnp_op_sign_signature_t * sig)
+{
+ if (!key) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *signkey =
+ find_suitable_key(PGP_OP_SIGN, get_key_require_secret(key), &key->ffi->key_provider);
+ if (!signkey) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+
+ try {
+ signatures.emplace_back();
+ } catch (const std::exception &e) {
+ FFI_LOG(ffi, "%s", e.what());
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_op_sign_signature_t newsig = &signatures.back();
+ newsig->signer.key = signkey;
+ /* set default create/expire times */
+ newsig->signer.sigcreate = ctx.sigcreate;
+ newsig->signer.sigexpire = ctx.sigexpire;
+ newsig->ffi = ffi;
+
+ if (sig) {
+ *sig = newsig;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_armor(rnp_ctx_t &ctx, bool armored)
+{
+ ctx.armor = armored;
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_compression(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *compression, int level)
+{
+ if (!compression) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_compression_type_t zalg = PGP_C_UNKNOWN;
+ if (!str_to_compression_alg(compression, &zalg)) {
+ FFI_LOG(ffi, "Invalid compression: %s", compression);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ ctx.zalg = (int) zalg;
+ ctx.zlevel = level;
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_hash(rnp_ffi_t ffi, rnp_ctx_t &ctx, const char *hash)
+{
+ if (!hash) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (!str_to_hash_alg(hash, &ctx.halg)) {
+ FFI_LOG(ffi, "Invalid hash: %s", hash);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_creation_time(rnp_ctx_t &ctx, uint32_t create)
+{
+ ctx.sigcreate = create;
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_expiration_time(rnp_ctx_t &ctx, uint32_t expire)
+{
+ ctx.sigexpire = expire;
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_flags(rnp_ffi_t ffi, rnp_ctx_t &ctx, uint32_t flags)
+{
+ ctx.no_wrap = extract_flag(flags, RNP_ENCRYPT_NOWRAP);
+ if (flags) {
+ FFI_LOG(ffi, "Unknown operation flags: %x", flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_file_name(rnp_ctx_t &ctx, const char *filename)
+{
+ ctx.filename = filename ? filename : "";
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+rnp_op_set_file_mtime(rnp_ctx_t &ctx, uint32_t mtime)
+{
+ ctx.filemtime = mtime;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_op_encrypt_create(rnp_op_encrypt_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t output)
+try {
+ // checks
+ if (!op || !ffi || !input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *op = new rnp_op_encrypt_st();
+ rnp_ctx_init_ffi((*op)->rnpctx, ffi);
+ (*op)->ffi = ffi;
+ (*op)->input = input;
+ (*op)->output = output;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_add_recipient(rnp_op_encrypt_t op, rnp_key_handle_t handle)
+try {
+ // checks
+ if (!op || !handle) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = find_suitable_key(
+ PGP_OP_ENCRYPT, get_key_prefer_public(handle), &handle->ffi->key_provider);
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ op->rnpctx.recipients.push_back(key);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_add_signature(rnp_op_encrypt_t op,
+ rnp_key_handle_t key,
+ rnp_op_sign_signature_t *sig)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_hash(rnp_op_encrypt_t op, const char *hash)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_hash(op->ffi, op->rnpctx, hash);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_creation_time(rnp_op_encrypt_t op, uint32_t create)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_creation_time(op->rnpctx, create);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_expiration_time(rnp_op_encrypt_t op, uint32_t expire)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_expiration_time(op->rnpctx, expire);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_add_password(rnp_op_encrypt_t op,
+ const char * password,
+ const char * s2k_hash,
+ size_t iterations,
+ const char * s2k_cipher)
+try {
+ // checks
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (password && !*password) {
+ // no blank passwords
+ FFI_LOG(op->ffi, "Blank password");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // set some defaults
+ if (!s2k_hash) {
+ s2k_hash = DEFAULT_HASH_ALG;
+ }
+ if (!s2k_cipher) {
+ s2k_cipher = DEFAULT_SYMM_ALG;
+ }
+ // parse
+ pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN;
+ if (!str_to_hash_alg(s2k_hash, &hash_alg)) {
+ FFI_LOG(op->ffi, "Invalid hash: %s", s2k_hash);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN;
+ if (!str_to_cipher(s2k_cipher, &symm_alg)) {
+ FFI_LOG(op->ffi, "Invalid cipher: %s", s2k_cipher);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp::secure_vector<char> ask_pass(MAX_PASSWORD_LENGTH, '\0');
+ if (!password) {
+ pgp_password_ctx_t pswdctx(PGP_OP_ENCRYPT_SYM);
+ if (!pgp_request_password(
+ &op->ffi->pass_provider, &pswdctx, ask_pass.data(), ask_pass.size())) {
+ return RNP_ERROR_BAD_PASSWORD;
+ }
+ password = ask_pass.data();
+ }
+ return op->rnpctx.add_encryption_password(password, hash_alg, symm_alg, iterations);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_armor(rnp_op_encrypt_t op, bool armored)
+try {
+ // checks
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_armor(op->rnpctx, armored);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_cipher(rnp_op_encrypt_t op, const char *cipher)
+try {
+ // checks
+ if (!op || !cipher) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!str_to_cipher(cipher, &op->rnpctx.ealg)) {
+ FFI_LOG(op->ffi, "Invalid cipher: %s", cipher);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_aead(rnp_op_encrypt_t op, const char *alg)
+try {
+ // checks
+ if (!op || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!str_to_aead_alg(alg, &op->rnpctx.aalg)) {
+ FFI_LOG(op->ffi, "Invalid AEAD algorithm: %s", alg);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_aead_bits(rnp_op_encrypt_t op, int bits)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if ((bits < 0) || (bits > 16)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->rnpctx.abits = bits;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_compression(rnp_op_encrypt_t op, const char *compression, int level)
+try {
+ // checks
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_flags(rnp_op_encrypt_t op, uint32_t flags)
+try {
+ // checks
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_flags(op->ffi, op->rnpctx, flags);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_file_name(rnp_op_encrypt_t op, const char *filename)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_file_name(op->rnpctx, filename);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_set_file_mtime(rnp_op_encrypt_t op, uint32_t mtime)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_file_mtime(op->rnpctx, mtime);
+}
+FFI_GUARD
+
+static pgp_write_handler_t
+pgp_write_handler(pgp_password_provider_t *pass_provider,
+ rnp_ctx_t * rnpctx,
+ void * param,
+ pgp_key_provider_t * key_provider)
+{
+ pgp_write_handler_t handler;
+ memset(&handler, 0, sizeof(handler));
+ handler.password_provider = pass_provider;
+ handler.ctx = rnpctx;
+ handler.param = param;
+ handler.key_provider = key_provider;
+ return handler;
+}
+
+static rnp_result_t
+rnp_op_add_signatures(rnp_op_sign_signatures_t &opsigs, rnp_ctx_t &ctx)
+{
+ for (auto &sig : opsigs) {
+ if (!sig.signer.key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+
+ rnp_signer_info_t sinfo = sig.signer;
+ if (!sig.hash_set) {
+ sinfo.halg = ctx.halg;
+ }
+ if (!sig.expiry_set) {
+ sinfo.sigexpire = ctx.sigexpire;
+ }
+ if (!sig.create_set) {
+ sinfo.sigcreate = ctx.sigcreate;
+ }
+ ctx.signers.push_back(sinfo);
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_op_encrypt_execute(rnp_op_encrypt_t op)
+try {
+ // checks
+ if (!op || !op->input || !op->output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // set the default hash alg if none was specified
+ if (!op->rnpctx.halg) {
+ op->rnpctx.halg = DEFAULT_PGP_HASH_ALG;
+ }
+ pgp_write_handler_t handler =
+ pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider);
+
+ rnp_result_t ret;
+ if (!op->signatures.empty() && (ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) {
+ return ret;
+ }
+ ret = rnp_encrypt_sign_src(&handler, &op->input->src, &op->output->dst);
+
+ dst_flush(&op->output->dst);
+ op->output->keep = ret == RNP_SUCCESS;
+ op->input = NULL;
+ op->output = NULL;
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_encrypt_destroy(rnp_op_encrypt_t op)
+try {
+ delete op;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_create(rnp_op_sign_t *op, rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output)
+try {
+ // checks
+ if (!op || !ffi || !input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *op = new rnp_op_sign_st();
+ rnp_ctx_init_ffi((*op)->rnpctx, ffi);
+ (*op)->ffi = ffi;
+ (*op)->input = input;
+ (*op)->output = output;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_cleartext_create(rnp_op_sign_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t output)
+try {
+ rnp_result_t res = rnp_op_sign_create(op, ffi, input, output);
+ if (!res) {
+ (*op)->rnpctx.clearsign = true;
+ }
+ return res;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_detached_create(rnp_op_sign_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t signature)
+try {
+ rnp_result_t res = rnp_op_sign_create(op, ffi, input, signature);
+ if (!res) {
+ (*op)->rnpctx.detached = true;
+ }
+ return res;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_add_signature(rnp_op_sign_t op, rnp_key_handle_t key, rnp_op_sign_signature_t *sig)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_add_signature(op->ffi, op->signatures, key, op->rnpctx, sig);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_signature_set_hash(rnp_op_sign_signature_t sig, const char *hash)
+try {
+ if (!sig || !hash) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!str_to_hash_alg(hash, &sig->signer.halg)) {
+ FFI_LOG(sig->ffi, "Invalid hash: %s", hash);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ sig->hash_set = true;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_signature_set_creation_time(rnp_op_sign_signature_t sig, uint32_t create)
+try {
+ if (!sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ sig->signer.sigcreate = create;
+ sig->create_set = true;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_signature_set_expiration_time(rnp_op_sign_signature_t sig, uint32_t expires)
+try {
+ if (!sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ sig->signer.sigexpire = expires;
+ sig->expiry_set = true;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_set_armor(rnp_op_sign_t op, bool armored)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_armor(op->rnpctx, armored);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_set_compression(rnp_op_sign_t op, const char *compression, int level)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_compression(op->ffi, op->rnpctx, compression, level);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_set_hash(rnp_op_sign_t op, const char *hash)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_hash(op->ffi, op->rnpctx, hash);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_set_creation_time(rnp_op_sign_t op, uint32_t create)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_creation_time(op->rnpctx, create);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_set_expiration_time(rnp_op_sign_t op, uint32_t expire)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_expiration_time(op->rnpctx, expire);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_set_file_name(rnp_op_sign_t op, const char *filename)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_file_name(op->rnpctx, filename);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_set_file_mtime(rnp_op_sign_t op, uint32_t mtime)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return rnp_op_set_file_mtime(op->rnpctx, mtime);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_execute(rnp_op_sign_t op)
+try {
+ // checks
+ if (!op || !op->input || !op->output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // set the default hash alg if none was specified
+ if (!op->rnpctx.halg) {
+ op->rnpctx.halg = DEFAULT_PGP_HASH_ALG;
+ }
+ pgp_write_handler_t handler =
+ pgp_write_handler(&op->ffi->pass_provider, &op->rnpctx, NULL, &op->ffi->key_provider);
+
+ rnp_result_t ret;
+ if ((ret = rnp_op_add_signatures(op->signatures, op->rnpctx))) {
+ return ret;
+ }
+ ret = rnp_sign_src(&handler, &op->input->src, &op->output->dst);
+
+ dst_flush(&op->output->dst);
+ op->output->keep = ret == RNP_SUCCESS;
+ op->input = NULL;
+ op->output = NULL;
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_sign_destroy(rnp_op_sign_t op)
+try {
+ delete op;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static void
+rnp_op_verify_on_signatures(const std::vector<pgp_signature_info_t> &sigs, void *param)
+{
+ rnp_op_verify_t op = (rnp_op_verify_t) param;
+
+ try {
+ /* in case we have multiple signed layers */
+ delete[] op->signatures;
+ op->signatures = new rnp_op_verify_signature_st[sigs.size()];
+ } catch (const std::exception &e) {
+ FFI_LOG(op->ffi, "%s", e.what());
+ return;
+ }
+ op->signature_count = sigs.size();
+
+ size_t i = 0;
+ for (const auto &sinfo : sigs) {
+ rnp_op_verify_signature_t res = &op->signatures[i++];
+ /* sinfo.sig may be NULL */
+ if (sinfo.sig) {
+ try {
+ res->sig_pkt = *sinfo.sig;
+ } catch (const std::exception &e) {
+ FFI_LOG(op->ffi, "%s", e.what());
+ }
+ }
+
+ if (sinfo.unknown) {
+ res->verify_status = RNP_ERROR_SIGNATURE_UNKNOWN;
+ } else if (sinfo.valid) {
+ res->verify_status = sinfo.expired ? RNP_ERROR_SIGNATURE_EXPIRED : RNP_SUCCESS;
+ } else {
+ res->verify_status =
+ sinfo.no_signer ? RNP_ERROR_KEY_NOT_FOUND : RNP_ERROR_SIGNATURE_INVALID;
+ }
+ res->ffi = op->ffi;
+ }
+}
+
+static bool
+rnp_verify_src_provider(pgp_parse_handler_t *handler, pgp_source_t *src)
+{
+ /* this one is called only when input for detached signature is needed */
+ rnp_op_verify_t op = (rnp_op_verify_t) handler->param;
+ if (!op->detached_input) {
+ return false;
+ }
+ *src = op->detached_input->src;
+ /* we should give ownership on src to caller */
+ memset(&op->detached_input->src, 0, sizeof(op->detached_input->src));
+ return true;
+};
+
+static bool
+rnp_verify_dest_provider(pgp_parse_handler_t *handler,
+ pgp_dest_t ** dst,
+ bool * closedst,
+ const char * filename,
+ uint32_t mtime)
+{
+ rnp_op_verify_t op = (rnp_op_verify_t) handler->param;
+ if (!op->output) {
+ return false;
+ }
+ *dst = &(op->output->dst);
+ *closedst = false;
+ op->filename = filename ? strdup(filename) : NULL;
+ op->file_mtime = mtime;
+ return true;
+}
+
+static void
+recipient_handle_from_pk_sesskey(rnp_recipient_handle_t handle,
+ const pgp_pk_sesskey_t &sesskey)
+{
+ static_assert(sizeof(handle->keyid) == PGP_KEY_ID_SIZE, "Keyid size mismatch");
+ memcpy(handle->keyid, sesskey.key_id.data(), PGP_KEY_ID_SIZE);
+ handle->palg = sesskey.alg;
+}
+
+static void
+symenc_handle_from_sk_sesskey(rnp_symenc_handle_t handle, const pgp_sk_sesskey_t &sesskey)
+{
+ handle->alg = sesskey.alg;
+ handle->halg = sesskey.s2k.hash_alg;
+ handle->s2k_type = sesskey.s2k.specifier;
+ if (sesskey.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) {
+ handle->iterations = pgp_s2k_decode_iterations(sesskey.s2k.iterations);
+ } else {
+ handle->iterations = 1;
+ }
+ handle->aalg = sesskey.aalg;
+}
+
+static void
+rnp_verify_on_recipients(const std::vector<pgp_pk_sesskey_t> &recipients,
+ const std::vector<pgp_sk_sesskey_t> &passwords,
+ void * param)
+{
+ rnp_op_verify_t op = (rnp_op_verify_t) param;
+ /* store only top-level encrypted stream recipients info for now */
+ if (op->encrypted_layers++) {
+ return;
+ }
+ if (!recipients.empty()) {
+ op->recipients =
+ (rnp_recipient_handle_t) calloc(recipients.size(), sizeof(*op->recipients));
+ if (!op->recipients) {
+ FFI_LOG(op->ffi, "allocation failed");
+ return;
+ }
+ for (size_t i = 0; i < recipients.size(); i++) {
+ recipient_handle_from_pk_sesskey(&op->recipients[i], recipients[i]);
+ }
+ }
+ op->recipient_count = recipients.size();
+ if (!passwords.empty()) {
+ op->symencs = (rnp_symenc_handle_t) calloc(passwords.size(), sizeof(*op->symencs));
+ if (!op->symencs) {
+ FFI_LOG(op->ffi, "allocation failed");
+ return;
+ }
+ for (size_t i = 0; i < passwords.size(); i++) {
+ symenc_handle_from_sk_sesskey(&op->symencs[i], passwords[i]);
+ }
+ }
+ op->symenc_count = passwords.size();
+}
+
+static void
+rnp_verify_on_decryption_start(pgp_pk_sesskey_t *pubenc, pgp_sk_sesskey_t *symenc, void *param)
+{
+ rnp_op_verify_t op = (rnp_op_verify_t) param;
+ /* store only top-level encrypted stream info */
+ if (op->encrypted_layers > 1) {
+ return;
+ }
+ if (pubenc) {
+ op->used_recipient = (rnp_recipient_handle_t) calloc(1, sizeof(*op->used_recipient));
+ if (!op->used_recipient) {
+ FFI_LOG(op->ffi, "allocation failed");
+ return;
+ }
+ recipient_handle_from_pk_sesskey(op->used_recipient, *pubenc);
+ return;
+ }
+ if (symenc) {
+ op->used_symenc = (rnp_symenc_handle_t) calloc(1, sizeof(*op->used_symenc));
+ if (!op->used_symenc) {
+ FFI_LOG(op->ffi, "allocation failed");
+ return;
+ }
+ symenc_handle_from_sk_sesskey(op->used_symenc, *symenc);
+ return;
+ }
+ FFI_LOG(op->ffi, "Warning! Both pubenc and symenc are NULL.");
+}
+
+static void
+rnp_verify_on_decryption_info(bool mdc, pgp_aead_alg_t aead, pgp_symm_alg_t salg, void *param)
+{
+ rnp_op_verify_t op = (rnp_op_verify_t) param;
+ /* store only top-level encrypted stream info for now */
+ if (op->encrypted_layers > 1) {
+ return;
+ }
+ op->mdc = mdc;
+ op->aead = aead;
+ op->salg = salg;
+ op->encrypted = true;
+}
+
+static void
+rnp_verify_on_decryption_done(bool validated, void *param)
+{
+ rnp_op_verify_t op = (rnp_op_verify_t) param;
+ if (op->encrypted_layers > 1) {
+ return;
+ }
+ op->validated = validated;
+}
+
+rnp_result_t
+rnp_op_verify_create(rnp_op_verify_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_output_t output)
+try {
+ if (!op || !ffi || !input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *op = new rnp_op_verify_st();
+ rnp_ctx_init_ffi((*op)->rnpctx, ffi);
+ (*op)->ffi = ffi;
+ (*op)->input = input;
+ (*op)->output = output;
+
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_detached_create(rnp_op_verify_t *op,
+ rnp_ffi_t ffi,
+ rnp_input_t input,
+ rnp_input_t signature)
+try {
+ if (!op || !ffi || !input || !signature) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *op = new rnp_op_verify_st();
+ rnp_ctx_init_ffi((*op)->rnpctx, ffi);
+ (*op)->rnpctx.detached = true;
+ (*op)->ffi = ffi;
+ (*op)->input = signature;
+ (*op)->detached_input = input;
+
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static pgp_key_t *
+ffi_decrypt_key_provider(const pgp_key_request_ctx_t *ctx, void *userdata)
+{
+ rnp_decryption_kp_param_t *kparam = (rnp_decryption_kp_param_t *) userdata;
+
+ auto ffi = kparam->op->ffi;
+ bool hidden = ctx->secret && (ctx->search.type == PGP_KEY_SEARCH_KEYID) &&
+ (ctx->search.by.keyid == pgp_key_id_t({}));
+ /* default to the FFI key provider if not hidden keyid request */
+ if (!hidden) {
+ return ffi->key_provider.callback(ctx, ffi->key_provider.userdata);
+ }
+ /* if we had hidden request and last key is NULL then key search was exhausted */
+ if (!kparam->op->allow_hidden || (kparam->has_hidden && !kparam->last)) {
+ return NULL;
+ }
+ /* inform user about the hidden recipient before searching through the loaded keys */
+ if (!kparam->has_hidden) {
+ call_key_callback(ffi, ctx->search, ctx->secret);
+ }
+ kparam->has_hidden = true;
+ kparam->last = find_key(ffi, ctx->search, true, true, kparam->last);
+ return kparam->last;
+}
+
+rnp_result_t
+rnp_op_verify_set_flags(rnp_op_verify_t op, uint32_t flags)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ /* Allow to decrypt without valid signatures */
+ op->ignore_sigs = extract_flag(flags, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT);
+ /* Strict mode: require all signatures to be valid */
+ op->require_all_sigs = extract_flag(flags, RNP_VERIFY_REQUIRE_ALL_SIGS);
+ /* Allow hidden recipients if any */
+ op->allow_hidden = extract_flag(flags, RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT);
+
+ if (flags) {
+ FFI_LOG(op->ffi, "Unknown operation flags: %x", flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_execute(rnp_op_verify_t op)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_parse_handler_t handler;
+
+ handler.password_provider = &op->ffi->pass_provider;
+
+ rnp_decryption_kp_param_t kparam(op);
+ pgp_key_provider_t kprov = {ffi_decrypt_key_provider, &kparam};
+
+ handler.key_provider = &kprov;
+ handler.on_signatures = rnp_op_verify_on_signatures;
+ handler.src_provider = rnp_verify_src_provider;
+ handler.dest_provider = rnp_verify_dest_provider;
+ handler.on_recipients = rnp_verify_on_recipients;
+ handler.on_decryption_start = rnp_verify_on_decryption_start;
+ handler.on_decryption_info = rnp_verify_on_decryption_info;
+ handler.on_decryption_done = rnp_verify_on_decryption_done;
+ handler.param = op;
+ handler.ctx = &op->rnpctx;
+
+ rnp_result_t ret = process_pgp_source(&handler, op->input->src);
+ /* Allow to decrypt data ignoring the signatures check if requested */
+ if (op->ignore_sigs && op->validated && (ret == RNP_ERROR_SIGNATURE_INVALID)) {
+ ret = RNP_SUCCESS;
+ }
+ /* Allow to require all signatures be valid */
+ if (op->require_all_sigs && !ret) {
+ for (size_t i = 0; i < op->signature_count; i++) {
+ if (op->signatures[i].verify_status) {
+ ret = RNP_ERROR_SIGNATURE_INVALID;
+ break;
+ }
+ }
+ }
+ if (op->output) {
+ dst_flush(&op->output->dst);
+ op->output->keep = ret == RNP_SUCCESS;
+ }
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_signature_count(rnp_op_verify_t op, size_t *count)
+try {
+ if (!op || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *count = op->signature_count;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_signature_at(rnp_op_verify_t op, size_t idx, rnp_op_verify_signature_t *sig)
+try {
+ if (!op || !sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (idx >= op->signature_count) {
+ FFI_LOG(op->ffi, "Invalid signature index: %zu", idx);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *sig = &op->signatures[idx];
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_file_info(rnp_op_verify_t op, char **filename, uint32_t *mtime)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (mtime) {
+ *mtime = op->file_mtime;
+ }
+ if (filename) {
+ if (op->filename) {
+ *filename = strdup(op->filename);
+ } else {
+ *filename = NULL;
+ }
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static const char *
+get_protection_mode(rnp_op_verify_t op)
+{
+ if (!op->encrypted) {
+ return "none";
+ }
+ if (op->mdc) {
+ return "cfb-mdc";
+ }
+ if (op->aead == PGP_AEAD_NONE) {
+ return "cfb";
+ }
+ switch (op->aead) {
+ case PGP_AEAD_EAX:
+ return "aead-eax";
+ case PGP_AEAD_OCB:
+ return "aead-ocb";
+ default:
+ return "aead-unknown";
+ }
+}
+
+static const char *
+get_protection_cipher(rnp_op_verify_t op)
+{
+ if (!op->encrypted) {
+ return "none";
+ }
+ return id_str_pair::lookup(symm_alg_map, op->salg);
+}
+
+rnp_result_t
+rnp_op_verify_get_protection_info(rnp_op_verify_t op, char **mode, char **cipher, bool *valid)
+try {
+ if (!op || (!mode && !cipher && !valid)) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (mode) {
+ *mode = strdup(get_protection_mode(op));
+ if (!*mode) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ if (cipher) {
+ *cipher = strdup(get_protection_cipher(op));
+ if (!*cipher) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ if (valid) {
+ *valid = op->validated;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_recipient_count(rnp_op_verify_t op, size_t *count)
+try {
+ if (!op || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *count = op->recipient_count;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_used_recipient(rnp_op_verify_t op, rnp_recipient_handle_t *recipient)
+try {
+ if (!op || !recipient) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *recipient = op->used_recipient;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_recipient_at(rnp_op_verify_t op,
+ size_t idx,
+ rnp_recipient_handle_t *recipient)
+try {
+ if (!op || !recipient) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (idx >= op->recipient_count) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *recipient = &op->recipients[idx];
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_recipient_get_keyid(rnp_recipient_handle_t recipient, char **keyid)
+try {
+ if (!recipient || !keyid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ static_assert(sizeof(recipient->keyid) == PGP_KEY_ID_SIZE,
+ "rnp_recipient_handle_t.keyid size mismatch");
+ return hex_encode_value(recipient->keyid, PGP_KEY_ID_SIZE, keyid);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_recipient_get_alg(rnp_recipient_handle_t recipient, char **alg)
+try {
+ if (!recipient || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return get_map_value(pubkey_alg_map, recipient->palg, alg);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_symenc_count(rnp_op_verify_t op, size_t *count)
+try {
+ if (!op || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *count = op->symenc_count;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_used_symenc(rnp_op_verify_t op, rnp_symenc_handle_t *symenc)
+try {
+ if (!op || !symenc) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *symenc = op->used_symenc;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_get_symenc_at(rnp_op_verify_t op, size_t idx, rnp_symenc_handle_t *symenc)
+try {
+ if (!op || !symenc) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (idx >= op->symenc_count) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *symenc = &op->symencs[idx];
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_symenc_get_cipher(rnp_symenc_handle_t symenc, char **cipher)
+try {
+ if (!symenc || !cipher) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return get_map_value(symm_alg_map, symenc->alg, cipher);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_symenc_get_aead_alg(rnp_symenc_handle_t symenc, char **alg)
+try {
+ if (!symenc || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return get_map_value(aead_alg_map, symenc->aalg, alg);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_symenc_get_hash_alg(rnp_symenc_handle_t symenc, char **alg)
+try {
+ if (!symenc || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return get_map_value(hash_alg_map, symenc->halg, alg);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_symenc_get_s2k_type(rnp_symenc_handle_t symenc, char **type)
+try {
+ if (!symenc || !type) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return get_map_value(s2k_type_map, symenc->s2k_type, type);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_symenc_get_s2k_iterations(rnp_symenc_handle_t symenc, uint32_t *iterations)
+try {
+ if (!symenc || !iterations) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *iterations = symenc->iterations;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_destroy(rnp_op_verify_t op)
+try {
+ delete op;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_op_verify_st::~rnp_op_verify_st()
+{
+ delete[] signatures;
+ free(filename);
+ free(recipients);
+ free(used_recipient);
+ free(symencs);
+ free(used_symenc);
+}
+
+rnp_result_t
+rnp_op_verify_signature_get_status(rnp_op_verify_signature_t sig)
+try {
+ if (!sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return sig->verify_status;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_signature_get_handle(rnp_op_verify_signature_t sig,
+ rnp_signature_handle_t * handle)
+try {
+ if (!sig || !handle) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *handle = (rnp_signature_handle_t) calloc(1, sizeof(**handle));
+ if (!*handle) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ try {
+ (*handle)->sig = new pgp_subsig_t(sig->sig_pkt);
+ } catch (const std::exception &e) {
+ FFI_LOG(sig->ffi, "%s", e.what());
+ free(*handle);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ (*handle)->ffi = sig->ffi;
+ (*handle)->key = NULL;
+ (*handle)->own_sig = true;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_signature_get_hash(rnp_op_verify_signature_t sig, char **hash)
+try {
+ if (!sig || !hash) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ return get_map_value(hash_alg_map, sig->sig_pkt.halg, hash);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_signature_get_key(rnp_op_verify_signature_t sig, rnp_key_handle_t *key)
+try {
+ if (!sig->sig_pkt.has_keyid()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_ffi_t ffi = sig->ffi;
+ // create a search (since we'll use this later anyways)
+ pgp_key_search_t search(PGP_KEY_SEARCH_KEYID);
+ search.by.keyid = sig->sig_pkt.keyid();
+
+ // search the stores
+ pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &search, NULL);
+ pgp_key_t *sec = rnp_key_store_search(ffi->secring, &search, NULL);
+ if (!pub && !sec) {
+ return RNP_ERROR_KEY_NOT_FOUND;
+ }
+
+ struct rnp_key_handle_st *handle = (rnp_key_handle_st *) calloc(1, sizeof(*handle));
+ if (!handle) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ handle->ffi = ffi;
+ handle->pub = pub;
+ handle->sec = sec;
+ handle->locator = search;
+ *key = handle;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_verify_signature_get_times(rnp_op_verify_signature_t sig,
+ uint32_t * create,
+ uint32_t * expires)
+try {
+ if (create) {
+ *create = sig->sig_pkt.creation();
+ }
+ if (expires) {
+ *expires = sig->sig_pkt.expiration();
+ }
+
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_decrypt(rnp_ffi_t ffi, rnp_input_t input, rnp_output_t output)
+try {
+ // checks
+ if (!ffi || !input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ rnp_op_verify_t op = NULL;
+ rnp_result_t ret = rnp_op_verify_create(&op, ffi, input, output);
+ if (ret) {
+ return ret;
+ }
+ ret = rnp_op_verify_set_flags(op, RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT);
+ if (!ret) {
+ ret = rnp_op_verify_execute(op);
+ }
+ rnp_op_verify_destroy(op);
+ return ret;
+}
+FFI_GUARD
+
+static rnp_result_t
+str_to_locator(rnp_ffi_t ffi,
+ pgp_key_search_t *locator,
+ const char * identifier_type,
+ const char * identifier)
+{
+ // parse the identifier type
+ locator->type = static_cast<pgp_key_search_type_t>(
+ id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN));
+ if (locator->type == PGP_KEY_SEARCH_UNKNOWN) {
+ FFI_LOG(ffi, "Invalid identifier type: %s", identifier_type);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ // see what type we have
+ switch (locator->type) {
+ case PGP_KEY_SEARCH_USERID:
+ if (snprintf(locator->by.userid, sizeof(locator->by.userid), "%s", identifier) >=
+ (int) sizeof(locator->by.userid)) {
+ FFI_LOG(ffi, "UserID too long");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ break;
+ case PGP_KEY_SEARCH_KEYID: {
+ if (strlen(identifier) != (PGP_KEY_ID_SIZE * 2) ||
+ !rnp::hex_decode(identifier, locator->by.keyid.data(), locator->by.keyid.size())) {
+ FFI_LOG(ffi, "Invalid keyid: %s", identifier);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ } break;
+ case PGP_KEY_SEARCH_FINGERPRINT: {
+ // TODO: support v5 fingerprints
+ // Note: v2/v3 fingerprint are 16 bytes (32 chars) long.
+ if ((strlen(identifier) != (PGP_FINGERPRINT_SIZE * 2)) && (strlen(identifier) != 32)) {
+ FFI_LOG(ffi, "Invalid fingerprint: %s", identifier);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ locator->by.fingerprint.length = rnp::hex_decode(
+ identifier, locator->by.fingerprint.fingerprint, PGP_FINGERPRINT_SIZE);
+ if (!locator->by.fingerprint.length) {
+ FFI_LOG(ffi, "Invalid fingerprint: %s", identifier);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ } break;
+ case PGP_KEY_SEARCH_GRIP: {
+ if (strlen(identifier) != (PGP_KEY_GRIP_SIZE * 2) ||
+ !rnp::hex_decode(identifier, locator->by.grip.data(), locator->by.grip.size())) {
+ FFI_LOG(ffi, "Invalid grip: %s", identifier);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ } break;
+ default:
+ // should never happen
+ assert(false);
+ return RNP_ERROR_BAD_STATE;
+ }
+ return RNP_SUCCESS;
+}
+
+static bool
+locator_to_str(const pgp_key_search_t &locator,
+ const char ** identifier_type,
+ char * identifier,
+ size_t identifier_size)
+{
+ // find the identifier type string with the map
+ *identifier_type = id_str_pair::lookup(identifier_type_map, locator.type, NULL);
+ if (!*identifier_type) {
+ return false;
+ }
+ // fill in the actual identifier
+ switch (locator.type) {
+ case PGP_KEY_SEARCH_USERID:
+ if (snprintf(identifier, identifier_size, "%s", locator.by.userid) >=
+ (int) identifier_size) {
+ return false;
+ }
+ break;
+ case PGP_KEY_SEARCH_KEYID:
+ if (!rnp::hex_encode(
+ locator.by.keyid.data(), locator.by.keyid.size(), identifier, identifier_size)) {
+ return false;
+ }
+ break;
+ case PGP_KEY_SEARCH_FINGERPRINT:
+ if (!rnp::hex_encode(locator.by.fingerprint.fingerprint,
+ locator.by.fingerprint.length,
+ identifier,
+ identifier_size)) {
+ return false;
+ }
+ break;
+ case PGP_KEY_SEARCH_GRIP:
+ if (!rnp::hex_encode(
+ locator.by.grip.data(), locator.by.grip.size(), identifier, identifier_size)) {
+ return false;
+ }
+ break;
+ default:
+ assert(false);
+ return false;
+ }
+ return true;
+}
+
+static rnp_result_t
+rnp_locate_key_int(rnp_ffi_t ffi,
+ const pgp_key_search_t &locator,
+ rnp_key_handle_t * handle,
+ bool require_secret = false)
+{
+ // search pubring
+ pgp_key_t *pub = rnp_key_store_search(ffi->pubring, &locator, NULL);
+ // search secring
+ pgp_key_t *sec = rnp_key_store_search(ffi->secring, &locator, NULL);
+
+ if (require_secret && !sec) {
+ *handle = NULL;
+ return RNP_SUCCESS;
+ }
+
+ if (pub || sec) {
+ *handle = (rnp_key_handle_t) malloc(sizeof(**handle));
+ if (!*handle) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ (*handle)->ffi = ffi;
+ (*handle)->pub = pub;
+ (*handle)->sec = sec;
+ (*handle)->locator = locator;
+ } else {
+ *handle = NULL;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_locate_key(rnp_ffi_t ffi,
+ const char * identifier_type,
+ const char * identifier,
+ rnp_key_handle_t *handle)
+try {
+ // checks
+ if (!ffi || !identifier_type || !identifier || !handle) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // figure out the identifier type
+ pgp_key_search_t locator;
+ rnp_result_t ret = str_to_locator(ffi, &locator, identifier_type, identifier);
+ if (ret) {
+ return ret;
+ }
+
+ return rnp_locate_key_int(ffi, locator, handle);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_export(rnp_key_handle_t handle, rnp_output_t output, uint32_t flags)
+try {
+ pgp_dest_t *dst = NULL;
+ pgp_dest_t armordst = {};
+
+ // checks
+ if (!handle || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ dst = &output->dst;
+ if ((flags & RNP_KEY_EXPORT_PUBLIC) && (flags & RNP_KEY_EXPORT_SECRET)) {
+ FFI_LOG(handle->ffi, "Invalid export flags, select only public or secret, not both.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // handle flags
+ bool armored = extract_flag(flags, RNP_KEY_EXPORT_ARMORED);
+ pgp_key_t * key = NULL;
+ rnp_key_store_t *store = NULL;
+ if (flags & RNP_KEY_EXPORT_PUBLIC) {
+ extract_flag(flags, RNP_KEY_EXPORT_PUBLIC);
+ key = get_key_require_public(handle);
+ store = handle->ffi->pubring;
+ } else if (flags & RNP_KEY_EXPORT_SECRET) {
+ extract_flag(flags, RNP_KEY_EXPORT_SECRET);
+ key = get_key_require_secret(handle);
+ store = handle->ffi->secring;
+ } else {
+ FFI_LOG(handle->ffi, "must specify public or secret key for export");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ bool export_subs = extract_flag(flags, RNP_KEY_EXPORT_SUBKEYS);
+ // check for any unrecognized flags
+ if (flags) {
+ FFI_LOG(handle->ffi, "unrecognized flags remaining: 0x%X", flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ // make sure we found our key
+ if (!key) {
+ FFI_LOG(handle->ffi, "no suitable key found");
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ // only PGP packets supported for now
+ if (key->format != PGP_KEY_STORE_GPG && key->format != PGP_KEY_STORE_KBX) {
+ return RNP_ERROR_NOT_IMPLEMENTED;
+ }
+ if (armored) {
+ auto msgtype = key->is_secret() ? PGP_ARMORED_SECRET_KEY : PGP_ARMORED_PUBLIC_KEY;
+ rnp_result_t res = init_armored_dst(&armordst, &output->dst, msgtype);
+ if (res) {
+ return res;
+ }
+ dst = &armordst;
+ }
+ // write
+ if (key->is_primary()) {
+ // primary key, write just the primary or primary and all subkeys
+ key->write_xfer(*dst, export_subs ? store : NULL);
+ if (dst->werr) {
+ return RNP_ERROR_WRITE;
+ }
+ } else {
+ // subkeys flag is only valid for primary
+ if (export_subs) {
+ FFI_LOG(handle->ffi, "export with subkeys requested but key is not primary");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ // subkey, write the primary + this subkey only
+ pgp_key_t *primary = rnp_key_store_get_primary_key(store, key);
+ if (!primary) {
+ // shouldn't happen
+ return RNP_ERROR_GENERIC;
+ }
+ primary->write_xfer(*dst);
+ if (dst->werr) {
+ return RNP_ERROR_WRITE;
+ }
+ key->write_xfer(*dst);
+ if (dst->werr) {
+ return RNP_ERROR_WRITE;
+ }
+ }
+ if (armored) {
+ dst_finish(&armordst);
+ dst_close(&armordst, false);
+ }
+ output->keep = true;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_export_autocrypt(rnp_key_handle_t key,
+ rnp_key_handle_t subkey,
+ const char * uid,
+ rnp_output_t output,
+ uint32_t flags)
+try {
+ if (!key || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ bool base64 = extract_flag(flags, RNP_KEY_EXPORT_BASE64);
+ if (flags) {
+ FFI_LOG(key->ffi, "Unknown flags remaining: 0x%X", flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* Get the primary key */
+ pgp_key_t *primary = get_key_prefer_public(key);
+ if (!primary || !primary->is_primary() || !primary->usable_for(PGP_OP_VERIFY)) {
+ FFI_LOG(key->ffi, "No valid signing primary key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* Get encrypting subkey */
+ pgp_key_t *sub =
+ subkey ? get_key_prefer_public(subkey) :
+ find_suitable_key(PGP_OP_ENCRYPT, primary, &key->ffi->key_provider, true);
+ if (!sub || sub->is_primary() || !sub->usable_for(PGP_OP_ENCRYPT)) {
+ FFI_LOG(key->ffi, "No encrypting subkey");
+ return RNP_ERROR_KEY_NOT_FOUND;
+ }
+ /* Get userid */
+ size_t uididx = primary->uid_count();
+ if (uid) {
+ for (size_t idx = 0; idx < primary->uid_count(); idx++) {
+ if (primary->get_uid(idx).str == uid) {
+ uididx = idx;
+ break;
+ }
+ }
+ } else {
+ if (primary->uid_count() > 1) {
+ FFI_LOG(key->ffi, "Ambiguous userid");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ uididx = 0;
+ }
+ if (uididx >= primary->uid_count()) {
+ FFI_LOG(key->ffi, "Userid not found");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* Check whether base64 is requested */
+ bool res = false;
+ if (base64) {
+ rnp::ArmoredDest armor(output->dst, PGP_ARMORED_BASE64);
+ res = primary->write_autocrypt(armor.dst(), *sub, uididx);
+ } else {
+ res = primary->write_autocrypt(output->dst, *sub, uididx);
+ }
+ return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS;
+}
+FFI_GUARD
+
+static pgp_key_t *
+rnp_key_get_revoker(rnp_key_handle_t key)
+{
+ pgp_key_t *exkey = get_key_prefer_public(key);
+ if (!exkey) {
+ return NULL;
+ }
+ if (exkey->is_subkey()) {
+ return rnp_key_store_get_primary_key(key->ffi->secring, exkey);
+ }
+ // TODO: search through revocation key subpackets as well
+ return get_key_require_secret(key);
+}
+
+static rnp_result_t
+rnp_key_get_revocation(rnp_ffi_t ffi,
+ pgp_key_t * key,
+ pgp_key_t * revoker,
+ const char * hash,
+ const char * code,
+ const char * reason,
+ pgp_signature_t &sig)
+{
+ if (!hash) {
+ hash = DEFAULT_HASH_ALG;
+ }
+ pgp_hash_alg_t halg = PGP_HASH_UNKNOWN;
+ if (!str_to_hash_alg(hash, &halg)) {
+ FFI_LOG(ffi, "Unknown hash algorithm: %s", hash);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_revoke_t revinfo = {};
+ if (code && !str_to_revocation_type(code, &revinfo.code)) {
+ FFI_LOG(ffi, "Wrong revocation code: %s", code);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (revinfo.code > PGP_REVOCATION_RETIRED) {
+ FFI_LOG(ffi, "Wrong key revocation code: %d", (int) revinfo.code);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (reason) {
+ try {
+ revinfo.reason = reason;
+ } catch (const std::exception &e) {
+ FFI_LOG(ffi, "%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ /* unlock the secret key if needed */
+ rnp::KeyLocker revlock(*revoker);
+ if (revoker->is_locked() && !revoker->unlock(ffi->pass_provider)) {
+ FFI_LOG(ffi, "Failed to unlock secret key");
+ return RNP_ERROR_BAD_PASSWORD;
+ }
+ try {
+ revoker->gen_revocation(revinfo, halg, key->pkt(), sig, ffi->context);
+ } catch (const std::exception &e) {
+ FFI_LOG(ffi, "Failed to generate revocation signature: %s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_key_export_revocation(rnp_key_handle_t key,
+ rnp_output_t output,
+ uint32_t flags,
+ const char * hash,
+ const char * code,
+ const char * reason)
+try {
+ if (!key || !key->ffi || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED);
+ if (flags) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_key_t *exkey = get_key_prefer_public(key);
+ if (!exkey || !exkey->is_primary()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_t *revoker = rnp_key_get_revoker(key);
+ if (!revoker) {
+ FFI_LOG(key->ffi, "Revoker secret key not found");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_signature_t sig;
+ rnp_result_t ret =
+ rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig);
+ if (ret) {
+ return ret;
+ }
+
+ if (need_armor) {
+ rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY);
+ sig.write(armor.dst());
+ ret = armor.werr();
+ dst_flush(&armor.dst());
+ } else {
+ sig.write(output->dst);
+ ret = output->dst.werr;
+ dst_flush(&output->dst);
+ }
+ output->keep = !ret;
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_revoke(
+ rnp_key_handle_t key, uint32_t flags, const char *hash, const char *code, const char *reason)
+try {
+ if (!key || !key->ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (flags) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_key_t *exkey = get_key_prefer_public(key);
+ if (!exkey) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_t *revoker = rnp_key_get_revoker(key);
+ if (!revoker) {
+ FFI_LOG(key->ffi, "Revoker secret key not found");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_signature_t sig;
+ rnp_result_t ret =
+ rnp_key_get_revocation(key->ffi, exkey, revoker, hash, code, reason, sig);
+ if (ret) {
+ return ret;
+ }
+ pgp_sig_import_status_t pub_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY;
+ pgp_sig_import_status_t sec_status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY;
+ if (key->pub) {
+ pub_status = rnp_key_store_import_key_signature(key->ffi->pubring, key->pub, &sig);
+ }
+ if (key->sec) {
+ sec_status = rnp_key_store_import_key_signature(key->ffi->secring, key->sec, &sig);
+ }
+
+ if ((pub_status == PGP_SIG_IMPORT_STATUS_UNKNOWN) ||
+ (sec_status == PGP_SIG_IMPORT_STATUS_UNKNOWN)) {
+ return RNP_ERROR_GENERIC;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_25519_bits_tweaked(rnp_key_handle_t key, bool *result)
+try {
+ if (!key || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *seckey = get_key_require_secret(key);
+ if (!seckey || seckey->is_locked() || (seckey->alg() != PGP_PKA_ECDH) ||
+ (seckey->curve() != PGP_CURVE_25519)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *result = x25519_bits_tweaked(seckey->material().ec);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_25519_bits_tweak(rnp_key_handle_t key)
+try {
+ if (!key) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *seckey = get_key_require_secret(key);
+ if (!seckey || seckey->is_protected() || (seckey->alg() != PGP_PKA_ECDH) ||
+ (seckey->curve() != PGP_CURVE_25519)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!x25519_tweak_bits(seckey->pkt().material.ec)) {
+ FFI_LOG(key->ffi, "Failed to tweak 25519 key bits.");
+ return RNP_ERROR_BAD_STATE;
+ }
+ if (!seckey->write_sec_rawpkt(seckey->pkt(), "", key->ffi->context)) {
+ FFI_LOG(key->ffi, "Failed to update rawpkt.");
+ return RNP_ERROR_BAD_STATE;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_remove(rnp_key_handle_t key, uint32_t flags)
+try {
+ if (!key || !key->ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ bool pub = extract_flag(flags, RNP_KEY_REMOVE_PUBLIC);
+ bool sec = extract_flag(flags, RNP_KEY_REMOVE_SECRET);
+ bool sub = extract_flag(flags, RNP_KEY_REMOVE_SUBKEYS);
+ if (flags) {
+ FFI_LOG(key->ffi, "Unknown flags: %" PRIu32, flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!pub && !sec) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (sub && get_key_prefer_public(key)->is_subkey()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (pub) {
+ if (!key->ffi->pubring || !key->pub) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!rnp_key_store_remove_key(key->ffi->pubring, key->pub, sub)) {
+ return RNP_ERROR_KEY_NOT_FOUND;
+ }
+ key->pub = NULL;
+ }
+ if (sec) {
+ if (!key->ffi->secring || !key->sec) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!rnp_key_store_remove_key(key->ffi->secring, key->sec, sub)) {
+ return RNP_ERROR_KEY_NOT_FOUND;
+ }
+ key->sec = NULL;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static void
+report_signature_removal(rnp_ffi_t ffi,
+ const pgp_key_t & key,
+ rnp_key_signatures_cb sigcb,
+ void * app_ctx,
+ pgp_subsig_t & keysig,
+ bool & remove)
+{
+ if (!sigcb) {
+ return;
+ }
+ rnp_signature_handle_t sig = (rnp_signature_handle_t) calloc(1, sizeof(*sig));
+ if (!sig) {
+ FFI_LOG(ffi, "Signature handle allocation failed.");
+ return;
+ }
+ sig->ffi = ffi;
+ sig->key = &key;
+ sig->sig = &keysig;
+ sig->own_sig = false;
+ uint32_t action = remove ? RNP_KEY_SIGNATURE_REMOVE : RNP_KEY_SIGNATURE_KEEP;
+ sigcb(ffi, app_ctx, sig, &action);
+ switch (action) {
+ case RNP_KEY_SIGNATURE_REMOVE:
+ remove = true;
+ break;
+ case RNP_KEY_SIGNATURE_KEEP:
+ remove = false;
+ break;
+ default:
+ FFI_LOG(ffi, "Invalid signature removal action: %" PRIu32, action);
+ break;
+ }
+ rnp_signature_handle_destroy(sig);
+}
+
+static bool
+signature_needs_removal(rnp_ffi_t ffi, const pgp_key_t &key, pgp_subsig_t &sig, uint32_t flags)
+{
+ /* quick check for non-self signatures */
+ bool nonself = flags & RNP_KEY_SIGNATURE_NON_SELF_SIG;
+ if (nonself && key.is_primary() && !key.is_signer(sig)) {
+ return true;
+ }
+ if (nonself && key.is_subkey()) {
+ pgp_key_t *primary = rnp_key_store_get_primary_key(ffi->pubring, &key);
+ if (primary && !primary->is_signer(sig)) {
+ return true;
+ }
+ }
+ /* unknown signer */
+ pgp_key_t *signer = pgp_sig_get_signer(sig, ffi->pubring, &ffi->key_provider);
+ if (!signer && (flags & RNP_KEY_SIGNATURE_UNKNOWN_KEY)) {
+ return true;
+ }
+ /* validate signature if didn't */
+ if (signer && !sig.validated()) {
+ signer->validate_sig(key, sig, ffi->context);
+ }
+ /* we cannot check for invalid/expired if sig was not validated */
+ if (!sig.validated()) {
+ return false;
+ }
+ if ((flags & RNP_KEY_SIGNATURE_INVALID) && !sig.validity.valid) {
+ return true;
+ }
+ return false;
+}
+
+static void
+remove_key_signatures(rnp_ffi_t ffi,
+ pgp_key_t & pub,
+ pgp_key_t * sec,
+ uint32_t flags,
+ rnp_key_signatures_cb sigcb,
+ void * app_ctx)
+{
+ std::vector<pgp_sig_id_t> sigs;
+
+ for (size_t idx = 0; idx < pub.sig_count(); idx++) {
+ pgp_subsig_t &sig = pub.get_sig(idx);
+ bool remove = signature_needs_removal(ffi, pub, sig, flags);
+ report_signature_removal(ffi, pub, sigcb, app_ctx, sig, remove);
+ if (remove) {
+ sigs.push_back(sig.sigid);
+ }
+ }
+ size_t deleted = pub.del_sigs(sigs);
+ if (deleted != sigs.size()) {
+ FFI_LOG(ffi, "Invalid deleted sigs count: %zu instead of %zu.", deleted, sigs.size());
+ }
+ /* delete from the secret key if any */
+ if (sec && (sec != &pub)) {
+ sec->del_sigs(sigs);
+ }
+}
+
+rnp_result_t
+rnp_key_remove_signatures(rnp_key_handle_t handle,
+ uint32_t flags,
+ rnp_key_signatures_cb sigcb,
+ void * app_ctx)
+try {
+ if (!handle) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!flags && !sigcb) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ uint32_t origflags = flags;
+ extract_flag(flags,
+ RNP_KEY_SIGNATURE_INVALID | RNP_KEY_SIGNATURE_NON_SELF_SIG |
+ RNP_KEY_SIGNATURE_UNKNOWN_KEY);
+ if (flags) {
+ FFI_LOG(handle->ffi, "Invalid flags: %" PRIu32, flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ flags = origflags;
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* process key itself */
+ pgp_key_t *sec = get_key_require_secret(handle);
+ remove_key_signatures(handle->ffi, *key, sec, flags, sigcb, app_ctx);
+
+ /* process subkeys */
+ for (size_t idx = 0; key->is_primary() && (idx < key->subkey_count()); idx++) {
+ pgp_key_t *sub = pgp_key_get_subkey(key, handle->ffi->pubring, idx);
+ if (!sub) {
+ FFI_LOG(handle->ffi, "Failed to get subkey at idx %zu.", idx);
+ continue;
+ }
+ pgp_key_t *subsec = rnp_key_store_get_key_by_fpr(handle->ffi->secring, sub->fp());
+ remove_key_signatures(handle->ffi, *sub, subsec, flags, sigcb, app_ctx);
+ }
+ /* revalidate key/subkey */
+ key->revalidate(*handle->ffi->pubring);
+ if (sec) {
+ sec->revalidate(*handle->ffi->secring);
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static bool
+pk_alg_allows_custom_curve(pgp_pubkey_alg_t pkalg)
+{
+ switch (pkalg) {
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+parse_preferences(json_object *jso, pgp_user_prefs_t &prefs)
+{
+ static const struct {
+ const char * key;
+ enum json_type type;
+ } properties[] = {{"hashes", json_type_array},
+ {"ciphers", json_type_array},
+ {"compression", json_type_array},
+ {"key server", json_type_string}};
+
+ for (size_t iprop = 0; iprop < ARRAY_SIZE(properties); iprop++) {
+ json_object *value = NULL;
+ const char * key = properties[iprop].key;
+
+ if (!json_object_object_get_ex(jso, key, &value)) {
+ continue;
+ }
+
+ if (!json_object_is_type(value, properties[iprop].type)) {
+ return false;
+ }
+ try {
+ if (rnp::str_case_eq(key, "hashes")) {
+ int length = json_object_array_length(value);
+ for (int i = 0; i < length; i++) {
+ json_object *item = json_object_array_get_idx(value, i);
+ if (!json_object_is_type(item, json_type_string)) {
+ return false;
+ }
+ pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN;
+ if (!str_to_hash_alg(json_object_get_string(item), &hash_alg)) {
+ return false;
+ }
+ prefs.add_hash_alg(hash_alg);
+ }
+ } else if (rnp::str_case_eq(key, "ciphers")) {
+ int length = json_object_array_length(value);
+ for (int i = 0; i < length; i++) {
+ json_object *item = json_object_array_get_idx(value, i);
+ if (!json_object_is_type(item, json_type_string)) {
+ return false;
+ }
+ pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN;
+ if (!str_to_cipher(json_object_get_string(item), &symm_alg)) {
+ return false;
+ }
+ prefs.add_symm_alg(symm_alg);
+ }
+ } else if (rnp::str_case_eq(key, "compression")) {
+ int length = json_object_array_length(value);
+ for (int i = 0; i < length; i++) {
+ json_object *item = json_object_array_get_idx(value, i);
+ if (!json_object_is_type(item, json_type_string)) {
+ return false;
+ }
+ pgp_compression_type_t z_alg = PGP_C_UNKNOWN;
+ if (!str_to_compression_alg(json_object_get_string(item), &z_alg)) {
+ return false;
+ }
+ prefs.add_z_alg(z_alg);
+ }
+ } else if (rnp::str_case_eq(key, "key server")) {
+ prefs.key_server = json_object_get_string(value);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ // delete this field since it has been handled
+ json_object_object_del(jso, key);
+ }
+ return true;
+}
+
+static bool
+parse_keygen_crypto(json_object *jso, rnp_keygen_crypto_params_t &crypto)
+{
+ static const struct {
+ const char * key;
+ enum json_type type;
+ } properties[] = {{"type", json_type_string},
+ {"curve", json_type_string},
+ {"length", json_type_int},
+ {"hash", json_type_string}};
+
+ for (size_t i = 0; i < ARRAY_SIZE(properties); i++) {
+ json_object *value = NULL;
+ const char * key = properties[i].key;
+
+ if (!json_object_object_get_ex(jso, key, &value)) {
+ continue;
+ }
+
+ if (!json_object_is_type(value, properties[i].type)) {
+ return false;
+ }
+ // TODO: make sure there are no duplicate keys in the JSON
+ if (rnp::str_case_eq(key, "type")) {
+ if (!str_to_pubkey_alg(json_object_get_string(value), &crypto.key_alg)) {
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "length")) {
+ int length = json_object_get_int(value);
+ switch (crypto.key_alg) {
+ case PGP_PKA_RSA:
+ crypto.rsa.modulus_bit_len = length;
+ break;
+ case PGP_PKA_DSA:
+ crypto.dsa.p_bitlen = length;
+ break;
+ case PGP_PKA_ELGAMAL:
+ crypto.elgamal.key_bitlen = length;
+ break;
+ default:
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "curve")) {
+ if (!pk_alg_allows_custom_curve(crypto.key_alg)) {
+ return false;
+ }
+ if (!curve_str_to_type(json_object_get_string(value), &crypto.ecc.curve)) {
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "hash")) {
+ if (!str_to_hash_alg(json_object_get_string(value), &crypto.hash_alg)) {
+ return false;
+ }
+ } else {
+ // shouldn't happen
+ return false;
+ }
+ // delete this field since it has been handled
+ json_object_object_del(jso, key);
+ }
+ return true;
+}
+
+static bool
+parse_protection(json_object *jso, rnp_key_protection_params_t &protection)
+{
+ static const struct {
+ const char * key;
+ enum json_type type;
+ } properties[] = {{"cipher", json_type_string},
+ {"mode", json_type_string},
+ {"iterations", json_type_int},
+ {"hash", json_type_string}};
+
+ for (size_t i = 0; i < ARRAY_SIZE(properties); i++) {
+ json_object *value = NULL;
+ const char * key = properties[i].key;
+
+ if (!json_object_object_get_ex(jso, key, &value)) {
+ continue;
+ }
+
+ if (!json_object_is_type(value, properties[i].type)) {
+ return false;
+ }
+ // TODO: make sure there are no duplicate keys in the JSON
+ if (rnp::str_case_eq(key, "cipher")) {
+ if (!str_to_cipher(json_object_get_string(value), &protection.symm_alg)) {
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "mode")) {
+ if (!str_to_cipher_mode(json_object_get_string(value), &protection.cipher_mode)) {
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "iterations")) {
+ protection.iterations = json_object_get_int(value);
+ } else if (rnp::str_case_eq(key, "hash")) {
+ if (!str_to_hash_alg(json_object_get_string(value), &protection.hash_alg)) {
+ return false;
+ }
+ } else {
+ // shouldn't happen
+ return false;
+ }
+ // delete this field since it has been handled
+ json_object_object_del(jso, key);
+ }
+ return true;
+}
+
+static bool
+parse_keygen_primary(json_object * jso,
+ rnp_keygen_primary_desc_t & desc,
+ rnp_key_protection_params_t &prot)
+{
+ static const char *properties[] = {
+ "userid", "usage", "expiration", "preferences", "protection"};
+ auto &cert = desc.cert;
+
+ if (!parse_keygen_crypto(jso, desc.crypto)) {
+ return false;
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(properties); i++) {
+ json_object *value = NULL;
+ const char * key = properties[i];
+
+ if (!json_object_object_get_ex(jso, key, &value)) {
+ continue;
+ }
+ if (rnp::str_case_eq(key, "userid")) {
+ if (!json_object_is_type(value, json_type_string)) {
+ return false;
+ }
+ auto uid = json_object_get_string(value);
+ if (strlen(uid) > MAX_ID_LENGTH) {
+ return false;
+ }
+ cert.userid = json_object_get_string(value);
+ } else if (rnp::str_case_eq(key, "usage")) {
+ switch (json_object_get_type(value)) {
+ case json_type_array: {
+ int length = json_object_array_length(value);
+ for (int j = 0; j < length; j++) {
+ json_object *item = json_object_array_get_idx(value, j);
+ if (!json_object_is_type(item, json_type_string)) {
+ return false;
+ }
+ uint8_t flag = 0;
+ if (!str_to_key_flag(json_object_get_string(item), &flag)) {
+ return false;
+ }
+ // check for duplicate
+ if (cert.key_flags & flag) {
+ return false;
+ }
+ cert.key_flags |= flag;
+ }
+ } break;
+ case json_type_string: {
+ if (!str_to_key_flag(json_object_get_string(value), &cert.key_flags)) {
+ return false;
+ }
+ } break;
+ default:
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "expiration")) {
+ if (!json_object_is_type(value, json_type_int)) {
+ return false;
+ }
+ cert.key_expiration = json_object_get_int(value);
+ } else if (rnp::str_case_eq(key, "preferences")) {
+ if (!json_object_is_type(value, json_type_object)) {
+ return false;
+ }
+ if (!parse_preferences(value, cert.prefs)) {
+ return false;
+ }
+ if (json_object_object_length(value)) {
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "protection")) {
+ if (!json_object_is_type(value, json_type_object)) {
+ return false;
+ }
+ if (!parse_protection(value, prot)) {
+ return false;
+ }
+ if (json_object_object_length(value)) {
+ return false;
+ }
+ }
+ // delete this field since it has been handled
+ json_object_object_del(jso, key);
+ }
+ return !json_object_object_length(jso);
+}
+
+static bool
+parse_keygen_sub(json_object * jso,
+ rnp_keygen_subkey_desc_t & desc,
+ rnp_key_protection_params_t &prot)
+{
+ static const char *properties[] = {"usage", "expiration", "protection"};
+ auto & binding = desc.binding;
+
+ if (!parse_keygen_crypto(jso, desc.crypto)) {
+ return false;
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(properties); i++) {
+ json_object *value = NULL;
+ const char * key = properties[i];
+
+ if (!json_object_object_get_ex(jso, key, &value)) {
+ continue;
+ }
+ if (rnp::str_case_eq(key, "usage")) {
+ switch (json_object_get_type(value)) {
+ case json_type_array: {
+ int length = json_object_array_length(value);
+ for (int j = 0; j < length; j++) {
+ json_object *item = json_object_array_get_idx(value, j);
+ if (!json_object_is_type(item, json_type_string)) {
+ return false;
+ }
+ uint8_t flag = 0;
+ if (!str_to_key_flag(json_object_get_string(item), &flag)) {
+ return false;
+ }
+ if (binding.key_flags & flag) {
+ return false;
+ }
+ binding.key_flags |= flag;
+ }
+ } break;
+ case json_type_string: {
+ if (!str_to_key_flag(json_object_get_string(value), &binding.key_flags)) {
+ return false;
+ }
+ } break;
+ default:
+ return false;
+ }
+ } else if (rnp::str_case_eq(key, "expiration")) {
+ if (!json_object_is_type(value, json_type_int)) {
+ return false;
+ }
+ binding.key_expiration = json_object_get_int(value);
+ } else if (rnp::str_case_eq(key, "protection")) {
+ if (!json_object_is_type(value, json_type_object)) {
+ return false;
+ }
+ if (!parse_protection(value, prot)) {
+ return false;
+ }
+ if (json_object_object_length(value)) {
+ return false;
+ }
+ }
+ // delete this field since it has been handled
+ json_object_object_del(jso, key);
+ }
+ return !json_object_object_length(jso);
+}
+
+static bool
+gen_json_grips(char **result, const pgp_key_t *primary, const pgp_key_t *sub)
+{
+ if (!result) {
+ return true;
+ }
+
+ json_object *jso = json_object_new_object();
+ if (!jso) {
+ return false;
+ }
+ rnp::JSONObject jsowrap(jso);
+
+ char grip[PGP_KEY_GRIP_SIZE * 2 + 1];
+ if (primary) {
+ json_object *jsoprimary = json_object_new_object();
+ if (!jsoprimary) {
+ return false;
+ }
+ json_object_object_add(jso, "primary", jsoprimary);
+ if (!rnp::hex_encode(
+ primary->grip().data(), primary->grip().size(), grip, sizeof(grip))) {
+ return false;
+ }
+ json_object *jsogrip = json_object_new_string(grip);
+ if (!jsogrip) {
+ return false;
+ }
+ json_object_object_add(jsoprimary, "grip", jsogrip);
+ }
+ if (sub) {
+ json_object *jsosub = json_object_new_object();
+ if (!jsosub) {
+ return false;
+ }
+ json_object_object_add(jso, "sub", jsosub);
+ if (!rnp::hex_encode(sub->grip().data(), sub->grip().size(), grip, sizeof(grip))) {
+ return false;
+ }
+ json_object *jsogrip = json_object_new_string(grip);
+ if (!jsogrip) {
+ return false;
+ }
+ json_object_object_add(jsosub, "grip", jsogrip);
+ }
+ *result = strdup(json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY));
+ return *result;
+}
+
+static rnp_result_t
+gen_json_primary_key(rnp_ffi_t ffi,
+ json_object * jsoparams,
+ rnp_key_protection_params_t &prot,
+ pgp_fingerprint_t & fp,
+ bool protect)
+{
+ rnp_keygen_primary_desc_t desc = {};
+ // desc.crypto is a union
+ // so at least Clang 12 on Windows zero-initializes the first union member only
+ // keeping the "larger" member partially unintialized
+ desc.crypto.dsa.q_bitlen = 0;
+
+ desc.cert.key_expiration = DEFAULT_KEY_EXPIRATION;
+ if (!parse_keygen_primary(jsoparams, desc, prot)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_key_t pub;
+ pgp_key_t sec;
+ desc.crypto.ctx = &ffi->context;
+ if (!pgp_generate_primary_key(desc, true, sec, pub, ffi->secring->format)) {
+ return RNP_ERROR_GENERIC;
+ }
+ if (!rnp_key_store_add_key(ffi->pubring, &pub)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ /* encrypt secret key if specified */
+ if (protect && prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!rnp_key_store_add_key(ffi->secring, &sec)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ fp = pub.fp();
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+gen_json_subkey(rnp_ffi_t ffi,
+ json_object * jsoparams,
+ pgp_key_t & prim_pub,
+ pgp_key_t & prim_sec,
+ pgp_fingerprint_t &fp)
+{
+ rnp_keygen_subkey_desc_t desc = {};
+ rnp_key_protection_params_t prot = {};
+
+ desc.binding.key_expiration = DEFAULT_KEY_EXPIRATION;
+ if (!parse_keygen_sub(jsoparams, desc, prot)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!desc.binding.key_flags) {
+ /* Generate encrypt-only subkeys by default */
+ desc.binding.key_flags = PGP_KF_ENCRYPT;
+ }
+ pgp_key_t pub;
+ pgp_key_t sec;
+ desc.crypto.ctx = &ffi->context;
+ if (!pgp_generate_subkey(desc,
+ true,
+ prim_sec,
+ prim_pub,
+ sec,
+ pub,
+ ffi->pass_provider,
+ ffi->secring->format)) {
+ return RNP_ERROR_GENERIC;
+ }
+ if (!rnp_key_store_add_key(ffi->pubring, &pub)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ /* encrypt subkey if specified */
+ if (prot.symm_alg && !sec.protect(prot, ffi->pass_provider, ffi->context)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!rnp_key_store_add_key(ffi->secring, &sec)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ fp = pub.fp();
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_generate_key_json(rnp_ffi_t ffi, const char *json, char **results)
+try {
+ // checks
+ if (!ffi || !ffi->secring || !json) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // parse the JSON
+ json_tokener_error error;
+ json_object * jso = json_tokener_parse_verbose(json, &error);
+ if (!jso) {
+ // syntax error or some other issue
+ FFI_LOG(ffi, "Invalid JSON: %s", json_tokener_error_desc(error));
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ rnp::JSONObject jsowrap(jso);
+
+ // locate the appropriate sections
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ json_object *jsoprimary = NULL;
+ json_object *jsosub = NULL;
+ {
+ json_object_object_foreach(jso, key, value)
+ {
+ json_object **dest = NULL;
+
+ if (rnp::str_case_eq(key, "primary")) {
+ dest = &jsoprimary;
+ } else if (rnp::str_case_eq(key, "sub")) {
+ dest = &jsosub;
+ } else {
+ // unrecognized key in the object
+ FFI_LOG(ffi, "Unexpected key in JSON: %s", key);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // duplicate "primary"/"sub"
+ if (*dest) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *dest = value;
+ }
+ }
+
+ if (!jsoprimary && !jsosub) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // generate primary key
+ pgp_key_t * prim_pub = NULL;
+ pgp_key_t * prim_sec = NULL;
+ rnp_key_protection_params_t prim_prot = {};
+ pgp_fingerprint_t fp;
+ if (jsoprimary) {
+ ret = gen_json_primary_key(ffi, jsoprimary, prim_prot, fp, !jsosub);
+ if (ret) {
+ return ret;
+ }
+ prim_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp);
+ if (!jsosub) {
+ if (!gen_json_grips(results, prim_pub, NULL)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+ }
+ prim_sec = rnp_key_store_get_key_by_fpr(ffi->secring, fp);
+ } else {
+ /* generate subkey only - find primary key via JSON params */
+ json_object *jsoparent = NULL;
+ if (!json_object_object_get_ex(jsosub, "primary", &jsoparent) ||
+ json_object_object_length(jsoparent) != 1) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ const char *identifier_type = NULL;
+ const char *identifier = NULL;
+ json_object_object_foreach(jsoparent, key, value)
+ {
+ if (!json_object_is_type(value, json_type_string)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ identifier_type = key;
+ identifier = json_object_get_string(value);
+ }
+ if (!identifier_type || !identifier) {
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ pgp_key_search_t locator;
+ rnp_result_t tmpret = str_to_locator(ffi, &locator, identifier_type, identifier);
+ if (tmpret) {
+ return tmpret;
+ }
+
+ prim_pub = rnp_key_store_search(ffi->pubring, &locator, NULL);
+ prim_sec = rnp_key_store_search(ffi->secring, &locator, NULL);
+ if (!prim_sec || !prim_pub) {
+ return RNP_ERROR_KEY_NOT_FOUND;
+ }
+ json_object_object_del(jsosub, "primary");
+ }
+
+ /* Generate subkey */
+ ret = gen_json_subkey(ffi, jsosub, *prim_pub, *prim_sec, fp);
+ if (ret) {
+ if (jsoprimary) {
+ /* do not leave generated primary key in keyring */
+ rnp_key_store_remove_key(ffi->pubring, prim_pub, false);
+ rnp_key_store_remove_key(ffi->secring, prim_sec, false);
+ }
+ return ret;
+ }
+ /* Protect the primary key now */
+ if (prim_prot.symm_alg &&
+ !prim_sec->protect(prim_prot, ffi->pass_provider, ffi->context)) {
+ rnp_key_store_remove_key(ffi->pubring, prim_pub, true);
+ rnp_key_store_remove_key(ffi->secring, prim_sec, true);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_key_t *sub_pub = rnp_key_store_get_key_by_fpr(ffi->pubring, fp);
+ bool res = gen_json_grips(results, jsoprimary ? prim_pub : NULL, sub_pub);
+ return res ? RNP_SUCCESS : RNP_ERROR_OUT_OF_MEMORY;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_generate_key_ex(rnp_ffi_t ffi,
+ const char * key_alg,
+ const char * sub_alg,
+ uint32_t key_bits,
+ uint32_t sub_bits,
+ const char * key_curve,
+ const char * sub_curve,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key)
+try {
+ rnp_op_generate_t op = NULL;
+ rnp_op_generate_t subop = NULL;
+ rnp_key_handle_t primary = NULL;
+ rnp_key_handle_t subkey = NULL;
+ rnp_result_t ret = RNP_ERROR_KEY_GENERATION;
+
+ /* generate primary key */
+ if ((ret = rnp_op_generate_create(&op, ffi, key_alg))) {
+ return ret;
+ }
+ if (key_bits && (ret = rnp_op_generate_set_bits(op, key_bits))) {
+ goto done;
+ }
+ if (key_curve && (ret = rnp_op_generate_set_curve(op, key_curve))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_set_userid(op, userid))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_add_usage(op, "sign"))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_add_usage(op, "certify"))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_execute(op))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_get_key(op, &primary))) {
+ goto done;
+ }
+ /* generate subkey if requested */
+ if (!sub_alg) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_subkey_create(&subop, ffi, primary, sub_alg))) {
+ goto done;
+ }
+ if (sub_bits && (ret = rnp_op_generate_set_bits(subop, sub_bits))) {
+ goto done;
+ }
+ if (sub_curve && (ret = rnp_op_generate_set_curve(subop, sub_curve))) {
+ goto done;
+ }
+ if (password && (ret = rnp_op_generate_set_protection_password(subop, password))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_add_usage(subop, "encrypt"))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_execute(subop))) {
+ goto done;
+ }
+ if ((ret = rnp_op_generate_get_key(subop, &subkey))) {
+ goto done;
+ }
+done:
+ /* only now will protect the primary key - to not spend time on unlocking to sign
+ * subkey */
+ if (!ret && password) {
+ ret = rnp_key_protect(primary, password, NULL, NULL, NULL, 0);
+ }
+ if (ret && primary) {
+ rnp_key_remove(primary, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET);
+ }
+ if (ret && subkey) {
+ rnp_key_remove(subkey, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET);
+ }
+ if (!ret && key) {
+ *key = primary;
+ } else {
+ rnp_key_handle_destroy(primary);
+ }
+ rnp_key_handle_destroy(subkey);
+ rnp_op_generate_destroy(op);
+ rnp_op_generate_destroy(subop);
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_generate_key_rsa(rnp_ffi_t ffi,
+ uint32_t bits,
+ uint32_t subbits,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key)
+try {
+ return rnp_generate_key_ex(ffi,
+ RNP_ALGNAME_RSA,
+ subbits ? RNP_ALGNAME_RSA : NULL,
+ bits,
+ subbits,
+ NULL,
+ NULL,
+ userid,
+ password,
+ key);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_generate_key_dsa_eg(rnp_ffi_t ffi,
+ uint32_t bits,
+ uint32_t subbits,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key)
+try {
+ return rnp_generate_key_ex(ffi,
+ RNP_ALGNAME_DSA,
+ subbits ? RNP_ALGNAME_ELGAMAL : NULL,
+ bits,
+ subbits,
+ NULL,
+ NULL,
+ userid,
+ password,
+ key);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_generate_key_ec(rnp_ffi_t ffi,
+ const char * curve,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key)
+try {
+ return rnp_generate_key_ex(
+ ffi, RNP_ALGNAME_ECDSA, RNP_ALGNAME_ECDH, 0, 0, curve, curve, userid, password, key);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_generate_key_25519(rnp_ffi_t ffi,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key)
+try {
+ return rnp_generate_key_ex(ffi,
+ RNP_ALGNAME_EDDSA,
+ RNP_ALGNAME_ECDH,
+ 0,
+ 0,
+ NULL,
+ "Curve25519",
+ userid,
+ password,
+ key);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_generate_key_sm2(rnp_ffi_t ffi,
+ const char * userid,
+ const char * password,
+ rnp_key_handle_t *key)
+try {
+ return rnp_generate_key_ex(
+ ffi, RNP_ALGNAME_SM2, RNP_ALGNAME_SM2, 0, 0, NULL, NULL, userid, password, key);
+}
+FFI_GUARD
+
+static pgp_key_flags_t
+default_key_flags(pgp_pubkey_alg_t alg, bool subkey)
+{
+ switch (alg) {
+ case PGP_PKA_RSA:
+ return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY);
+ case PGP_PKA_DSA:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ return subkey ? PGP_KF_SIGN : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY);
+ case PGP_PKA_SM2:
+ return subkey ? PGP_KF_ENCRYPT : pgp_key_flags_t(PGP_KF_SIGN | PGP_KF_CERTIFY);
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ELGAMAL:
+ return PGP_KF_ENCRYPT;
+ default:
+ return PGP_KF_NONE;
+ }
+}
+
+rnp_result_t
+rnp_op_generate_create(rnp_op_generate_t *op, rnp_ffi_t ffi, const char *alg)
+try {
+ pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING;
+
+ if (!op || !ffi || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (!ffi->pubring || !ffi->secring) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (!str_to_pubkey_alg(alg, &key_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (!(pgp_pk_alg_capabilities(key_alg) & PGP_KF_SIGN)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *op = new rnp_op_generate_st();
+ (*op)->ffi = ffi;
+ (*op)->primary = true;
+ (*op)->crypto.key_alg = key_alg;
+ (*op)->crypto.ctx = &ffi->context;
+ (*op)->cert.key_flags = default_key_flags(key_alg, false);
+ (*op)->cert.key_expiration = DEFAULT_KEY_EXPIRATION;
+
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_subkey_create(rnp_op_generate_t *op,
+ rnp_ffi_t ffi,
+ rnp_key_handle_t primary,
+ const char * alg)
+try {
+ if (!op || !ffi || !alg || !primary) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (!ffi->pubring || !ffi->secring) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* TODO: should we do these checks here or may leave it up till generate call? */
+ if (!primary->sec || !primary->sec->usable_for(PGP_OP_ADD_SUBKEY)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_pubkey_alg_t key_alg = PGP_PKA_NOTHING;
+ if (!str_to_pubkey_alg(alg, &key_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *op = new rnp_op_generate_st();
+ (*op)->ffi = ffi;
+ (*op)->primary = false;
+ (*op)->crypto.key_alg = key_alg;
+ (*op)->crypto.ctx = &ffi->context;
+ (*op)->binding.key_flags = default_key_flags(key_alg, true);
+ (*op)->binding.key_expiration = DEFAULT_KEY_EXPIRATION;
+ (*op)->primary_sec = primary->sec;
+ (*op)->primary_pub = primary->pub;
+
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_bits(rnp_op_generate_t op, uint32_t bits)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ switch (op->crypto.key_alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ op->crypto.rsa.modulus_bit_len = bits;
+ break;
+ case PGP_PKA_ELGAMAL:
+ op->crypto.elgamal.key_bitlen = bits;
+ break;
+ case PGP_PKA_DSA:
+ op->crypto.dsa.p_bitlen = bits;
+ break;
+ default:
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_hash(rnp_op_generate_t op, const char *hash)
+try {
+ if (!op || !hash) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!str_to_hash_alg(hash, &op->crypto.hash_alg)) {
+ FFI_LOG(op->ffi, "Invalid hash: %s", hash);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_dsa_qbits(rnp_op_generate_t op, uint32_t qbits)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (op->crypto.key_alg != PGP_PKA_DSA) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->crypto.dsa.q_bitlen = qbits;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_curve(rnp_op_generate_t op, const char *curve)
+try {
+ if (!op || !curve) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!pk_alg_allows_custom_curve(op->crypto.key_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!curve_str_to_type(curve, &op->crypto.ecc.curve)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_protection_password(rnp_op_generate_t op, const char *password)
+try {
+ if (!op || !password) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ op->password.assign(password, password + strlen(password) + 1);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_request_password(rnp_op_generate_t op, bool request)
+try {
+ if (!op || !request) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ op->request_password = request;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_protection_cipher(rnp_op_generate_t op, const char *cipher)
+try {
+ if (!op || !cipher) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!str_to_cipher(cipher, &op->protection.symm_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_protection_hash(rnp_op_generate_t op, const char *hash)
+try {
+ if (!op || !hash) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!str_to_hash_alg(hash, &op->protection.hash_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_protection_mode(rnp_op_generate_t op, const char *mode)
+try {
+ if (!op || !mode) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!str_to_cipher_mode(mode, &op->protection.cipher_mode)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_protection_iterations(rnp_op_generate_t op, uint32_t iterations)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ op->protection.iterations = iterations;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_add_usage(rnp_op_generate_t op, const char *usage)
+try {
+ if (!op || !usage) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ uint8_t flag = 0;
+ if (!str_to_key_flag(usage, &flag)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!(pgp_pk_alg_capabilities(op->crypto.key_alg) & flag)) {
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ if (op->primary) {
+ op->cert.key_flags |= flag;
+ } else {
+ op->binding.key_flags |= flag;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_clear_usage(rnp_op_generate_t op)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (op->primary) {
+ op->cert.key_flags = 0;
+ } else {
+ op->binding.key_flags = 0;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_userid(rnp_op_generate_t op, const char *userid)
+try {
+ if (!op || !userid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (strlen(userid) > MAX_ID_LENGTH) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.userid = userid;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_expiration(rnp_op_generate_t op, uint32_t expiration)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (op->primary) {
+ op->cert.key_expiration = expiration;
+ } else {
+ op->binding.key_expiration = expiration;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_clear_pref_hashes(rnp_op_generate_t op)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.prefs.set_hash_algs({});
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_add_pref_hash(rnp_op_generate_t op, const char *hash)
+try {
+ if (!op || !hash) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN;
+ if (!str_to_hash_alg(hash, &hash_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.prefs.add_hash_alg(hash_alg);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_clear_pref_compression(rnp_op_generate_t op)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.prefs.set_z_algs({});
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_add_pref_compression(rnp_op_generate_t op, const char *compression)
+try {
+ if (!op || !compression) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_compression_type_t z_alg = PGP_C_UNKNOWN;
+ if (!str_to_compression_alg(compression, &z_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.prefs.add_z_alg(z_alg);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_clear_pref_ciphers(rnp_op_generate_t op)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.prefs.set_symm_algs({});
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_add_pref_cipher(rnp_op_generate_t op, const char *cipher)
+try {
+ if (!op || !cipher) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_symm_alg_t symm_alg = PGP_SA_UNKNOWN;
+ if (!str_to_cipher(cipher, &symm_alg)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.prefs.add_symm_alg(symm_alg);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_set_pref_keyserver(rnp_op_generate_t op, const char *keyserver)
+try {
+ if (!op) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->primary) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ op->cert.prefs.key_server = keyserver ? keyserver : "";
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_execute(rnp_op_generate_t op)
+try {
+ if (!op || !op->ffi) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ pgp_key_t pub;
+ pgp_key_t sec;
+ pgp_password_provider_t prov;
+
+ if (op->primary) {
+ rnp_keygen_primary_desc_t keygen = {};
+ keygen.crypto = op->crypto;
+ keygen.cert = op->cert;
+ op->cert.prefs = {}; /* generate call will free prefs */
+
+ if (!pgp_generate_primary_key(keygen, true, sec, pub, op->ffi->secring->format)) {
+ return RNP_ERROR_KEY_GENERATION;
+ }
+ } else {
+ /* subkey generation */
+ rnp_keygen_subkey_desc_t keygen = {};
+ keygen.crypto = op->crypto;
+ keygen.binding = op->binding;
+ if (!pgp_generate_subkey(keygen,
+ true,
+ *op->primary_sec,
+ *op->primary_pub,
+ sec,
+ pub,
+ op->ffi->pass_provider,
+ op->ffi->secring->format)) {
+ return RNP_ERROR_KEY_GENERATION;
+ }
+ }
+
+ /* add public key part to the keyring */
+ if (!(op->gen_pub = rnp_key_store_add_key(op->ffi->pubring, &pub))) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ /* encrypt secret key if requested */
+ if (!op->password.empty()) {
+ prov = {rnp_password_provider_string, (void *) op->password.data()};
+ } else if (op->request_password) {
+ prov = {rnp_password_cb_bounce, op->ffi};
+ }
+ if (prov.callback && !sec.protect(op->protection, prov, op->ffi->context)) {
+ FFI_LOG(op->ffi, "failed to encrypt the key");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto done;
+ }
+
+ /* add secret key to the keyring */
+ if (!(op->gen_sec = rnp_key_store_add_key(op->ffi->secring, &sec))) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ op->password.clear();
+ if (ret && op->gen_pub) {
+ rnp_key_store_remove_key(op->ffi->pubring, op->gen_pub, false);
+ op->gen_pub = NULL;
+ }
+ if (ret && op->gen_sec) {
+ rnp_key_store_remove_key(op->ffi->secring, op->gen_sec, false);
+ op->gen_sec = NULL;
+ }
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_get_key(rnp_op_generate_t op, rnp_key_handle_t *handle)
+try {
+ if (!op || !handle) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!op->gen_sec || !op->gen_pub) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *handle = (rnp_key_handle_t) malloc(sizeof(**handle));
+ if (!*handle) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ (*handle)->ffi = op->ffi;
+ (*handle)->pub = op->gen_pub;
+ (*handle)->sec = op->gen_sec;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_op_generate_destroy(rnp_op_generate_t op)
+try {
+ delete op;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_handle_destroy(rnp_key_handle_t key)
+try {
+ // This does not free key->key which is owned by the keyring
+ free(key);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+void
+rnp_buffer_destroy(void *ptr)
+{
+ free(ptr);
+}
+
+void
+rnp_buffer_clear(void *ptr, size_t size)
+{
+ if (ptr) {
+ secure_clear(ptr, size);
+ }
+}
+
+static pgp_key_t *
+get_key_require_public(rnp_key_handle_t handle)
+{
+ if (!handle->pub && handle->sec) {
+ pgp_key_request_ctx_t request;
+ request.secret = false;
+
+ // try fingerprint
+ request.search.type = PGP_KEY_SEARCH_FINGERPRINT;
+ request.search.by.fingerprint = handle->sec->fp();
+ handle->pub = pgp_request_key(&handle->ffi->key_provider, &request);
+ if (handle->pub) {
+ return handle->pub;
+ }
+
+ // try keyid
+ request.search.type = PGP_KEY_SEARCH_KEYID;
+ request.search.by.keyid = handle->sec->keyid();
+ handle->pub = pgp_request_key(&handle->ffi->key_provider, &request);
+ }
+ return handle->pub;
+}
+
+static pgp_key_t *
+get_key_prefer_public(rnp_key_handle_t handle)
+{
+ pgp_key_t *pub = get_key_require_public(handle);
+ return pub ? pub : get_key_require_secret(handle);
+}
+
+static pgp_key_t *
+get_key_require_secret(rnp_key_handle_t handle)
+{
+ if (!handle->sec && handle->pub) {
+ pgp_key_request_ctx_t request;
+ request.secret = true;
+
+ // try fingerprint
+ request.search.type = PGP_KEY_SEARCH_FINGERPRINT;
+ request.search.by.fingerprint = handle->pub->fp();
+ handle->sec = pgp_request_key(&handle->ffi->key_provider, &request);
+ if (handle->sec) {
+ return handle->sec;
+ }
+
+ // try keyid
+ request.search.type = PGP_KEY_SEARCH_KEYID;
+ request.search.by.keyid = handle->pub->keyid();
+ handle->sec = pgp_request_key(&handle->ffi->key_provider, &request);
+ }
+ return handle->sec;
+}
+
+static rnp_result_t
+key_get_uid_at(pgp_key_t *key, size_t idx, char **uid)
+{
+ if (!key || !uid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (idx >= key->uid_count()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *uid = strdup(key->get_uid(idx).str.c_str());
+ if (!*uid) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_key_add_uid(rnp_key_handle_t handle,
+ const char * uid,
+ const char * hash,
+ uint32_t expiration,
+ uint8_t key_flags,
+ bool primary)
+try {
+ if (!handle || !uid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ /* setup parameters */
+ if (!hash) {
+ hash = DEFAULT_HASH_ALG;
+ }
+ pgp_hash_alg_t hash_alg = PGP_HASH_UNKNOWN;
+ if (!str_to_hash_alg(hash, &hash_alg)) {
+ FFI_LOG(handle->ffi, "Invalid hash: %s", hash);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (strlen(uid) > MAX_ID_LENGTH) {
+ FFI_LOG(handle->ffi, "UserID too long");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_selfsig_cert_info_t info;
+ info.userid = uid;
+ info.key_flags = key_flags;
+ info.key_expiration = expiration;
+ info.primary = primary;
+
+ /* obtain and unlok secret key */
+ pgp_key_t *secret_key = get_key_require_secret(handle);
+ if (!secret_key || !secret_key->usable_for(PGP_OP_ADD_USERID)) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ pgp_key_t *public_key = get_key_prefer_public(handle);
+ if (!public_key && secret_key->format == PGP_KEY_STORE_G10) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ rnp::KeyLocker seclock(*secret_key);
+ if (secret_key->is_locked() &&
+ !secret_key->unlock(handle->ffi->pass_provider, PGP_OP_ADD_USERID)) {
+ return RNP_ERROR_BAD_PASSWORD;
+ }
+ /* add and certify userid */
+ secret_key->add_uid_cert(info, hash_alg, handle->ffi->context, public_key);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_primary_uid(rnp_key_handle_t handle, char **uid)
+try {
+ if (!handle || !uid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (key->has_primary_uid()) {
+ return key_get_uid_at(key, key->get_primary_uid(), uid);
+ }
+ for (size_t i = 0; i < key->uid_count(); i++) {
+ if (!key->get_uid(i).valid) {
+ continue;
+ }
+ return key_get_uid_at(key, i, uid);
+ }
+ return RNP_ERROR_BAD_PARAMETERS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_uid_count(rnp_key_handle_t handle, size_t *count)
+try {
+ if (!handle || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ *count = get_key_prefer_public(handle)->uid_count();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_uid_at(rnp_key_handle_t handle, size_t idx, char **uid)
+try {
+ if (handle == NULL || uid == NULL)
+ return RNP_ERROR_NULL_POINTER;
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ return key_get_uid_at(key, idx, uid);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_uid_handle_at(rnp_key_handle_t key, size_t idx, rnp_uid_handle_t *uid)
+try {
+ if (!key || !uid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *akey = get_key_prefer_public(key);
+ if (!akey) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (idx >= akey->uid_count()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *uid = (rnp_uid_handle_t) malloc(sizeof(**uid));
+ if (!*uid) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ (*uid)->ffi = key->ffi;
+ (*uid)->key = akey;
+ (*uid)->idx = idx;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static pgp_userid_t *
+rnp_uid_handle_get_uid(rnp_uid_handle_t uid)
+{
+ if (!uid || !uid->key) {
+ return NULL;
+ }
+ return &uid->key->get_uid(uid->idx);
+}
+
+rnp_result_t
+rnp_uid_get_type(rnp_uid_handle_t uid, uint32_t *type)
+try {
+ if (!type) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_userid_t *id = rnp_uid_handle_get_uid(uid);
+ if (!id) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ switch (id->pkt.tag) {
+ case PGP_PKT_USER_ID:
+ *type = RNP_USER_ID;
+ return RNP_SUCCESS;
+ case PGP_PKT_USER_ATTR:
+ *type = RNP_USER_ATTR;
+ return RNP_SUCCESS;
+ default:
+ return RNP_ERROR_BAD_STATE;
+ }
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_get_data(rnp_uid_handle_t uid, void **data, size_t *size)
+try {
+ if (!data || !size) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_userid_t *id = rnp_uid_handle_get_uid(uid);
+ if (!id) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *data = malloc(id->pkt.uid_len);
+ if (id->pkt.uid_len && !*data) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(*data, id->pkt.uid, id->pkt.uid_len);
+ *size = id->pkt.uid_len;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_is_primary(rnp_uid_handle_t uid, bool *primary)
+try {
+ if (!primary) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_userid_t *id = rnp_uid_handle_get_uid(uid);
+ if (!id) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *primary = uid->key->has_primary_uid() && (uid->key->get_primary_uid() == uid->idx);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_is_valid(rnp_uid_handle_t uid, bool *valid)
+try {
+ if (!valid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_userid_t *id = rnp_uid_handle_get_uid(uid);
+ if (!id) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ *valid = id->valid;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+rnp_key_return_signature(rnp_ffi_t ffi,
+ pgp_key_t * key,
+ pgp_subsig_t * subsig,
+ rnp_signature_handle_t *sig)
+{
+ *sig = (rnp_signature_handle_t) calloc(1, sizeof(**sig));
+ if (!*sig) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ (*sig)->ffi = ffi;
+ (*sig)->key = key;
+ (*sig)->sig = subsig;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_key_get_signature_count(rnp_key_handle_t handle, size_t *count)
+try {
+ if (!handle || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *count = key->keysig_count();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_signature_at(rnp_key_handle_t handle, size_t idx, rnp_signature_handle_t *sig)
+try {
+ if (!handle || !sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key || (idx >= key->keysig_count())) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return rnp_key_return_signature(handle->ffi, key, &key->get_keysig(idx), sig);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_revocation_signature(rnp_key_handle_t handle, rnp_signature_handle_t *sig)
+try {
+ if (!handle || !sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!key->revoked()) {
+ *sig = NULL;
+ return RNP_SUCCESS;
+ }
+ if (!key->has_sig(key->revocation().sigid)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ return rnp_key_return_signature(
+ handle->ffi, key, &key->get_sig(key->revocation().sigid), sig);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_get_signature_count(rnp_uid_handle_t handle, size_t *count)
+try {
+ if (!handle || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *count = handle->key->get_uid(handle->idx).sig_count();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_get_signature_at(rnp_uid_handle_t handle, size_t idx, rnp_signature_handle_t *sig)
+try {
+ if (!handle || !sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_userid_t &uid = handle->key->get_uid(handle->idx);
+ if (idx >= uid.sig_count()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ const pgp_sig_id_t &sigid = uid.get_sig(idx);
+ if (!handle->key->has_sig(sigid)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ return rnp_key_return_signature(
+ handle->ffi, handle->key, &handle->key->get_sig(sigid), sig);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_type(rnp_signature_handle_t handle, char **type)
+try {
+ if (!handle || !type) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ auto sigtype = id_str_pair::lookup(sig_type_map, handle->sig->sig.type());
+ return ret_str_value(sigtype, type);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_alg(rnp_signature_handle_t handle, char **alg)
+try {
+ if (!handle || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return get_map_value(pubkey_alg_map, handle->sig->sig.palg, alg);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_hash_alg(rnp_signature_handle_t handle, char **alg)
+try {
+ if (!handle || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return get_map_value(hash_alg_map, handle->sig->sig.halg, alg);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_creation(rnp_signature_handle_t handle, uint32_t *create)
+try {
+ if (!handle || !create) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *create = handle->sig->sig.creation();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_expiration(rnp_signature_handle_t handle, uint32_t *expires)
+try {
+ if (!handle || !expires) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *expires = handle->sig->sig.expiration();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_keyid(rnp_signature_handle_t handle, char **result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!handle->sig->sig.has_keyid()) {
+ *result = NULL;
+ return RNP_SUCCESS;
+ }
+ pgp_key_id_t keyid = handle->sig->sig.keyid();
+ return hex_encode_value(keyid.data(), keyid.size(), result);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_key_fprint(rnp_signature_handle_t handle, char **result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!handle->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!handle->sig->sig.has_keyfp()) {
+ *result = NULL;
+ return RNP_SUCCESS;
+ }
+ pgp_fingerprint_t keyfp = handle->sig->sig.keyfp();
+ return hex_encode_value(keyfp.fingerprint, keyfp.length, result);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_get_signer(rnp_signature_handle_t sig, rnp_key_handle_t *key)
+try {
+ if (!sig || !sig->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!sig->sig->sig.has_keyid()) {
+ *key = NULL;
+ return RNP_SUCCESS;
+ }
+ pgp_key_search_t locator(PGP_KEY_SEARCH_KEYID);
+ locator.by.keyid = sig->sig->sig.keyid();
+ return rnp_locate_key_int(sig->ffi, locator, key);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_is_valid(rnp_signature_handle_t sig, uint32_t flags)
+try {
+ if (!sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!sig->sig || sig->own_sig || flags) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (!sig->sig->validity.validated) {
+ pgp_key_t *signer =
+ pgp_sig_get_signer(*sig->sig, sig->ffi->pubring, &sig->ffi->key_provider);
+ if (!signer) {
+ return RNP_ERROR_KEY_NOT_FOUND;
+ }
+ signer->validate_sig(*sig->key, *sig->sig, sig->ffi->context);
+ }
+
+ if (!sig->sig->validity.validated) {
+ return RNP_ERROR_VERIFICATION_FAILED;
+ }
+ if (sig->sig->validity.expired) {
+ return RNP_ERROR_SIGNATURE_EXPIRED;
+ }
+ return sig->sig->valid() ? RNP_SUCCESS : RNP_ERROR_SIGNATURE_INVALID;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_packet_to_json(rnp_signature_handle_t sig, uint32_t flags, char **json)
+try {
+ if (!sig || !json) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ rnp::MemoryDest memdst;
+ sig->sig->sig.write(memdst.dst());
+ auto vec = memdst.to_vector();
+ rnp::MemorySource memsrc(vec);
+ return rnp_dump_src_to_json(&memsrc.src(), flags, json);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_remove(rnp_key_handle_t key, rnp_signature_handle_t sig)
+try {
+ if (!key || !sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (sig->own_sig || !sig->sig) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_t *pkey = get_key_require_public(key);
+ pgp_key_t *skey = get_key_require_secret(key);
+ if (!pkey && !skey) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ const pgp_sig_id_t sigid = sig->sig->sigid;
+ bool ok = false;
+ if (pkey) {
+ ok = pkey->del_sig(sigid);
+ pkey->revalidate(*key->ffi->pubring);
+ }
+ if (skey) {
+ /* secret key may not have signature, but we still need to delete it at least once to
+ * succeed */
+ ok = skey->del_sig(sigid) || ok;
+ skey->revalidate(*key->ffi->secring);
+ }
+ return ok ? RNP_SUCCESS : RNP_ERROR_NO_SIGNATURES_FOUND;
+}
+FFI_GUARD
+
+static rnp_result_t
+write_signature(rnp_signature_handle_t sig, pgp_dest_t &dst)
+{
+ sig->sig->rawpkt.write(dst);
+ dst_flush(&dst);
+ return dst.werr;
+}
+
+rnp_result_t
+rnp_signature_export(rnp_signature_handle_t sig, rnp_output_t output, uint32_t flags)
+try {
+ if (!sig || !sig->sig || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ bool need_armor = extract_flag(flags, RNP_KEY_EXPORT_ARMORED);
+ if (flags) {
+ FFI_LOG(sig->ffi, "Invalid flags: %" PRIu32, flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ rnp_result_t ret;
+ if (need_armor) {
+ rnp::ArmoredDest armor(output->dst, PGP_ARMORED_PUBLIC_KEY);
+ ret = write_signature(sig, armor.dst());
+ } else {
+ ret = write_signature(sig, output->dst);
+ }
+ output->keep = !ret;
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_signature_handle_destroy(rnp_signature_handle_t sig)
+try {
+ if (sig && sig->own_sig) {
+ delete sig->sig;
+ }
+ free(sig);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_is_revoked(rnp_uid_handle_t uid, bool *result)
+try {
+ if (!uid || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (!uid->key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *result = uid->key->get_uid(uid->idx).revoked;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_get_revocation_signature(rnp_uid_handle_t uid, rnp_signature_handle_t *sig)
+try {
+ if (!uid || !sig) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!uid->key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (uid->idx >= uid->key->uid_count()) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ const pgp_userid_t &userid = uid->key->get_uid(uid->idx);
+ if (!userid.revoked) {
+ *sig = NULL;
+ return RNP_SUCCESS;
+ }
+ if (!uid->key->has_sig(userid.revocation.sigid)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ return rnp_key_return_signature(
+ uid->ffi, uid->key, &uid->key->get_sig(userid.revocation.sigid), sig);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_remove(rnp_key_handle_t key, rnp_uid_handle_t uid)
+try {
+ if (!key || !uid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *pkey = get_key_require_public(key);
+ pgp_key_t *skey = get_key_require_secret(key);
+ if (!pkey && !skey) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if ((uid->key != pkey) && (uid->key != skey)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ bool ok = false;
+ if (pkey && (pkey->uid_count() > uid->idx)) {
+ pkey->del_uid(uid->idx);
+ pkey->revalidate(*key->ffi->pubring);
+ ok = true;
+ }
+ if (skey && (skey->uid_count() > uid->idx)) {
+ skey->del_uid(uid->idx);
+ skey->revalidate(*key->ffi->secring);
+ ok = true;
+ }
+ return ok ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_uid_handle_destroy(rnp_uid_handle_t uid)
+try {
+ free(uid);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_subkey_count(rnp_key_handle_t handle, size_t *count)
+try {
+ if (!handle || !count) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ *count = key->subkey_count();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_subkey_at(rnp_key_handle_t handle, size_t idx, rnp_key_handle_t *subkey)
+try {
+ if (!handle || !subkey) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (idx >= key->subkey_count()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_search_t locator(PGP_KEY_SEARCH_FINGERPRINT);
+ locator.by.fingerprint = key->get_subkey_fp(idx);
+ return rnp_locate_key_int(handle->ffi, locator, subkey);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_default_key(rnp_key_handle_t primary_key,
+ const char * usage,
+ uint32_t flags,
+ rnp_key_handle_t *default_key)
+try {
+ if (!primary_key || !usage || !default_key) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ uint8_t keyflag = 0;
+ if (!str_to_key_flag(usage, &keyflag)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ bool no_primary = extract_flag(flags, RNP_KEY_SUBKEYS_ONLY);
+ if (flags) {
+ FFI_LOG(primary_key->ffi, "Invalid flags: %" PRIu32, flags);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_op_t op = PGP_OP_UNKNOWN;
+ bool secret = false;
+ switch (keyflag) {
+ case PGP_KF_SIGN:
+ op = PGP_OP_SIGN;
+ secret = true;
+ break;
+ case PGP_KF_CERTIFY:
+ op = PGP_OP_CERTIFY;
+ secret = true;
+ break;
+ case PGP_KF_ENCRYPT:
+ op = PGP_OP_ENCRYPT;
+ break;
+ default:
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_t *key = get_key_prefer_public(primary_key);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_t *defkey =
+ find_suitable_key(op, key, &primary_key->ffi->key_provider, no_primary);
+ if (!defkey) {
+ *default_key = NULL;
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+
+ pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT);
+ search.by.fingerprint = defkey->fp();
+
+ rnp_result_t ret = rnp_locate_key_int(primary_key->ffi, search, default_key, secret);
+
+ if (!*default_key && !ret) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_alg(rnp_key_handle_t handle, char **alg)
+try {
+ if (!handle || !alg) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ return get_map_value(pubkey_alg_map, key->alg(), alg);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_bits(rnp_key_handle_t handle, uint32_t *bits)
+try {
+ if (!handle || !bits) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ size_t _bits = key->material().bits();
+ if (!_bits) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *bits = _bits;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_dsa_qbits(rnp_key_handle_t handle, uint32_t *qbits)
+try {
+ if (!handle || !qbits) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ size_t _qbits = key->material().qbits();
+ if (!_qbits) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *qbits = _qbits;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_curve(rnp_key_handle_t handle, char **curve)
+try {
+ if (!handle || !curve) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t * key = get_key_prefer_public(handle);
+ pgp_curve_t _curve = key->curve();
+ if (_curve == PGP_CURVE_UNKNOWN) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ const char *curvename = NULL;
+ if (!curve_type_to_str(_curve, &curvename)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ char *curvenamecp = strdup(curvename);
+ if (!curvenamecp) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ *curve = curvenamecp;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_fprint(rnp_key_handle_t handle, char **fprint)
+try {
+ if (!handle || !fprint) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ const pgp_fingerprint_t &fp = get_key_prefer_public(handle)->fp();
+ return hex_encode_value(fp.fingerprint, fp.length, fprint);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_keyid(rnp_key_handle_t handle, char **keyid)
+try {
+ if (!handle || !keyid) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ return hex_encode_value(key->keyid().data(), key->keyid().size(), keyid);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_grip(rnp_key_handle_t handle, char **grip)
+try {
+ if (!handle || !grip) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ const pgp_key_grip_t &kgrip = get_key_prefer_public(handle)->grip();
+ return hex_encode_value(kgrip.data(), kgrip.size(), grip);
+}
+FFI_GUARD
+
+static const pgp_key_grip_t *
+rnp_get_grip_by_fp(rnp_ffi_t ffi, const pgp_fingerprint_t &fp)
+{
+ pgp_key_t *key = NULL;
+ if (ffi->pubring) {
+ key = rnp_key_store_get_key_by_fpr(ffi->pubring, fp);
+ }
+ if (!key && ffi->secring) {
+ key = rnp_key_store_get_key_by_fpr(ffi->secring, fp);
+ }
+ return key ? &key->grip() : NULL;
+}
+
+rnp_result_t
+rnp_key_get_primary_grip(rnp_key_handle_t handle, char **grip)
+try {
+ if (!handle || !grip) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key->is_subkey()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!key->has_primary_fp()) {
+ *grip = NULL;
+ return RNP_SUCCESS;
+ }
+ const pgp_key_grip_t *pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp());
+ if (!pgrip) {
+ *grip = NULL;
+ return RNP_SUCCESS;
+ }
+ return hex_encode_value(pgrip->data(), pgrip->size(), grip);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_primary_fprint(rnp_key_handle_t handle, char **fprint)
+try {
+ if (!handle || !fprint) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key->is_subkey()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!key->has_primary_fp()) {
+ *fprint = NULL;
+ return RNP_SUCCESS;
+ }
+ const pgp_fingerprint_t &fp = key->primary_fp();
+ return hex_encode_value(fp.fingerprint, fp.length, fprint);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_allows_usage(rnp_key_handle_t handle, const char *usage, bool *result)
+try {
+ if (!handle || !usage || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ uint8_t flag = 0;
+ if (!str_to_key_flag(usage, &flag)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *result = key->flags() & flag;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_creation(rnp_key_handle_t handle, uint32_t *result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *result = key->creation();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_revoked(rnp_key_handle_t handle, bool *result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *result = key->revoked();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_valid(rnp_key_handle_t handle, bool *result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_require_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!key->validated()) {
+ key->validate(*handle->ffi->pubring);
+ }
+ if (!key->validated()) {
+ return RNP_ERROR_VERIFICATION_FAILED;
+ }
+ *result = key->valid();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_valid_till(rnp_key_handle_t handle, uint32_t *result)
+try {
+ if (!result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ uint64_t res = 0;
+ rnp_result_t ret = rnp_key_valid_till64(handle, &res);
+ if (ret) {
+ return ret;
+ }
+ if (res == UINT64_MAX) {
+ *result = UINT32_MAX;
+ } else if (res >= UINT32_MAX) {
+ *result = UINT32_MAX - 1;
+ } else {
+ *result = (uint32_t) res;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_valid_till64(rnp_key_handle_t handle, uint64_t *result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_require_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (!key->validated()) {
+ key->validate(*handle->ffi->pubring);
+ }
+ if (!key->validated()) {
+ return RNP_ERROR_VERIFICATION_FAILED;
+ }
+
+ if (key->is_subkey()) {
+ /* check validity time of the primary key as well */
+ pgp_key_t *primary = rnp_key_store_get_primary_key(handle->ffi->pubring, key);
+ if (!primary) {
+ /* no primary key - subkey considered as never valid */
+ *result = 0;
+ return RNP_SUCCESS;
+ }
+ if (!primary->validated()) {
+ primary->validate(*handle->ffi->pubring);
+ }
+ if (!primary->validated()) {
+ return RNP_ERROR_VERIFICATION_FAILED;
+ }
+ *result = key->valid_till();
+ } else {
+ *result = key->valid_till();
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_expiration(rnp_key_handle_t handle, uint32_t *result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *result = key->expiration();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_set_expiration(rnp_key_handle_t key, uint32_t expiry)
+try {
+ if (!key) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *pkey = get_key_prefer_public(key);
+ if (!pkey) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_key_t *skey = get_key_require_secret(key);
+ if (!skey) {
+ FFI_LOG(key->ffi, "Secret key required.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (pkey->is_primary()) {
+ if (!pgp_key_set_expiration(
+ pkey, skey, expiry, key->ffi->pass_provider, key->ffi->context)) {
+ return RNP_ERROR_GENERIC;
+ }
+ pkey->revalidate(*key->ffi->pubring);
+ if (pkey != skey) {
+ skey->revalidate(*key->ffi->secring);
+ }
+ return RNP_SUCCESS;
+ }
+
+ /* for subkey we need primary key */
+ if (!pkey->has_primary_fp()) {
+ FFI_LOG(key->ffi, "Primary key fp not available.");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_key_search_t search(PGP_KEY_SEARCH_FINGERPRINT);
+ search.by.fingerprint = pkey->primary_fp();
+ pgp_key_t *prim_sec = find_key(key->ffi, search, true, true);
+ if (!prim_sec) {
+ FFI_LOG(key->ffi, "Primary secret key not found.");
+ return RNP_ERROR_KEY_NOT_FOUND;
+ }
+ if (!pgp_subkey_set_expiration(
+ pkey, prim_sec, skey, expiry, key->ffi->pass_provider, key->ffi->context)) {
+ return RNP_ERROR_GENERIC;
+ }
+ prim_sec->revalidate(*key->ffi->secring);
+ pgp_key_t *prim_pub = find_key(key->ffi, search, false, true);
+ if (prim_pub) {
+ prim_pub->revalidate(*key->ffi->pubring);
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_revocation_reason(rnp_key_handle_t handle, char **result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key || !key->revoked()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *result = strdup(key->revocation().reason.c_str());
+ if (!*result) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+rnp_key_is_revoked_with_code(rnp_key_handle_t handle, bool *result, int code)
+{
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key || !key->revoked()) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ *result = key->revocation().code == code;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_key_is_superseded(rnp_key_handle_t handle, bool *result)
+try {
+ return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_SUPERSEDED);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_compromised(rnp_key_handle_t handle, bool *result)
+try {
+ return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_COMPROMISED);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_retired(rnp_key_handle_t handle, bool *result)
+try {
+ return rnp_key_is_revoked_with_code(handle, result, PGP_REVOCATION_RETIRED);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_expired(rnp_key_handle_t handle, bool *result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (!key) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ *result = key->expired();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_protection_type(rnp_key_handle_t key, char **type)
+try {
+ if (!key || !type) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!key->sec) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ const pgp_s2k_t &s2k = key->sec->pkt().sec_protection.s2k;
+ const char * res = "Unknown";
+ if (s2k.usage == PGP_S2KU_NONE) {
+ res = "None";
+ }
+ if ((s2k.usage == PGP_S2KU_ENCRYPTED) && (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) {
+ res = "Encrypted";
+ }
+ if ((s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED) &&
+ (s2k.specifier != PGP_S2KS_EXPERIMENTAL)) {
+ res = "Encrypted-Hashed";
+ }
+ if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) &&
+ (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET)) {
+ res = "GPG-None";
+ }
+ if ((s2k.specifier == PGP_S2KS_EXPERIMENTAL) &&
+ (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD)) {
+ res = "GPG-Smartcard";
+ }
+
+ return ret_str_value(res, type);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_protection_mode(rnp_key_handle_t key, char **mode)
+try {
+ if (!key || !mode) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!key->sec) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (key->sec->pkt().sec_protection.s2k.usage == PGP_S2KU_NONE) {
+ return ret_str_value("None", mode);
+ }
+ if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_EXPERIMENTAL) {
+ return ret_str_value("Unknown", mode);
+ }
+
+ return get_map_value(cipher_mode_map, key->sec->pkt().sec_protection.cipher_mode, mode);
+}
+FFI_GUARD
+
+static bool
+pgp_key_has_encryption_info(const pgp_key_t *key)
+{
+ return (key->pkt().sec_protection.s2k.usage != PGP_S2KU_NONE) &&
+ (key->pkt().sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL);
+}
+
+rnp_result_t
+rnp_key_get_protection_cipher(rnp_key_handle_t key, char **cipher)
+try {
+ if (!key || !cipher) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!key->sec) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!pgp_key_has_encryption_info(key->sec)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ return get_map_value(symm_alg_map, key->sec->pkt().sec_protection.symm_alg, cipher);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_protection_hash(rnp_key_handle_t key, char **hash)
+try {
+ if (!key || !hash) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!key->sec) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!pgp_key_has_encryption_info(key->sec)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ return get_map_value(hash_alg_map, key->sec->pkt().sec_protection.s2k.hash_alg, hash);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_get_protection_iterations(rnp_key_handle_t key, size_t *iterations)
+try {
+ if (!key || !iterations) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!key->sec) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!pgp_key_has_encryption_info(key->sec)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (key->sec->pkt().sec_protection.s2k.specifier == PGP_S2KS_ITERATED_AND_SALTED) {
+ *iterations = pgp_s2k_decode_iterations(key->sec->pkt().sec_protection.s2k.iterations);
+ } else {
+ *iterations = 1;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_locked(rnp_key_handle_t handle, bool *result)
+try {
+ if (handle == NULL || result == NULL)
+ return RNP_ERROR_NULL_POINTER;
+
+ pgp_key_t *key = get_key_require_secret(handle);
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ *result = key->is_locked();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_lock(rnp_key_handle_t handle)
+try {
+ if (handle == NULL)
+ return RNP_ERROR_NULL_POINTER;
+
+ pgp_key_t *key = get_key_require_secret(handle);
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ if (!key->lock()) {
+ return RNP_ERROR_GENERIC;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_unlock(rnp_key_handle_t handle, const char *password)
+try {
+ if (!handle) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ pgp_key_t *key = get_key_require_secret(handle);
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ bool ok = false;
+ if (password) {
+ pgp_password_provider_t prov(rnp_password_provider_string,
+ reinterpret_cast<void *>(const_cast<char *>(password)));
+ ok = key->unlock(prov);
+ } else {
+ ok = key->unlock(handle->ffi->pass_provider);
+ }
+ if (!ok) {
+ // likely a bad password
+ return RNP_ERROR_BAD_PASSWORD;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_protected(rnp_key_handle_t handle, bool *result)
+try {
+ if (handle == NULL || result == NULL)
+ return RNP_ERROR_NULL_POINTER;
+
+ pgp_key_t *key = get_key_require_secret(handle);
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ *result = key->is_protected();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_protect(rnp_key_handle_t handle,
+ const char * password,
+ const char * cipher,
+ const char * cipher_mode,
+ const char * hash,
+ size_t iterations)
+try {
+ rnp_key_protection_params_t protection = {};
+
+ // checks
+ if (!handle || !password) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (cipher && !str_to_cipher(cipher, &protection.symm_alg)) {
+ FFI_LOG(handle->ffi, "Invalid cipher: %s", cipher);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (cipher_mode && !str_to_cipher_mode(cipher_mode, &protection.cipher_mode)) {
+ FFI_LOG(handle->ffi, "Invalid cipher mode: %s", cipher_mode);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (hash && !str_to_hash_alg(hash, &protection.hash_alg)) {
+ FFI_LOG(handle->ffi, "Invalid hash: %s", hash);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ protection.iterations = iterations;
+
+ // get the key
+ pgp_key_t *key = get_key_require_secret(handle);
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ pgp_key_pkt_t * decrypted_key = NULL;
+ const std::string pass = password;
+ if (key->encrypted()) {
+ pgp_password_ctx_t ctx(PGP_OP_PROTECT, key);
+ decrypted_key = pgp_decrypt_seckey(*key, handle->ffi->pass_provider, ctx);
+ if (!decrypted_key) {
+ return RNP_ERROR_GENERIC;
+ }
+ }
+ bool res = key->protect(
+ decrypted_key ? *decrypted_key : key->pkt(), protection, pass, handle->ffi->context);
+ delete decrypted_key;
+ return res ? RNP_SUCCESS : RNP_ERROR_GENERIC;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_unprotect(rnp_key_handle_t handle, const char *password)
+try {
+ // checks
+ if (!handle) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ // get the key
+ pgp_key_t *key = get_key_require_secret(handle);
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ bool ok = false;
+ if (password) {
+ pgp_password_provider_t prov(rnp_password_provider_string,
+ reinterpret_cast<void *>(const_cast<char *>(password)));
+ ok = key->unprotect(prov, handle->ffi->context);
+ } else {
+ ok = key->unprotect(handle->ffi->pass_provider, handle->ffi->context);
+ }
+ if (!ok) {
+ // likely a bad password
+ return RNP_ERROR_BAD_PASSWORD;
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_primary(rnp_key_handle_t handle, bool *result)
+try {
+ if (handle == NULL || result == NULL)
+ return RNP_ERROR_NULL_POINTER;
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (key->format == PGP_KEY_STORE_G10) {
+ // we can't currently determine this for a G10 secret key
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ *result = key->is_primary();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_is_sub(rnp_key_handle_t handle, bool *result)
+try {
+ if (handle == NULL || result == NULL)
+ return RNP_ERROR_NULL_POINTER;
+
+ pgp_key_t *key = get_key_prefer_public(handle);
+ if (key->format == PGP_KEY_STORE_G10) {
+ // we can't currently determine this for a G10 secret key
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ *result = key->is_subkey();
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_have_secret(rnp_key_handle_t handle, bool *result)
+try {
+ if (handle == NULL || result == NULL)
+ return RNP_ERROR_NULL_POINTER;
+
+ *result = handle->sec != NULL;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_key_have_public(rnp_key_handle_t handle, bool *result)
+try {
+ if (handle == NULL || result == NULL)
+ return RNP_ERROR_NULL_POINTER;
+ *result = handle->pub != NULL;
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+static rnp_result_t
+key_to_bytes(pgp_key_t *key, uint8_t **buf, size_t *buf_len)
+{
+ auto vec = rnp_key_to_vec(*key);
+ *buf = (uint8_t *) calloc(1, vec.size());
+ if (!*buf) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(*buf, vec.data(), vec.size());
+ *buf_len = vec.size();
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_get_public_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len)
+try {
+ // checks
+ if (!handle || !buf || !buf_len) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = handle->pub;
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ return key_to_bytes(key, buf, buf_len);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_get_secret_key_data(rnp_key_handle_t handle, uint8_t **buf, size_t *buf_len)
+try {
+ // checks
+ if (!handle || !buf || !buf_len) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = handle->sec;
+ if (!key) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ return key_to_bytes(key, buf, buf_len);
+}
+FFI_GUARD
+
+static bool
+add_json_string_field(json_object *jso, const char *key, const char *value)
+{
+ json_object *jsostr = json_object_new_string(value);
+ if (!jsostr) {
+ return false;
+ }
+ json_object_object_add(jso, key, jsostr);
+ return true;
+}
+
+static bool
+add_json_int_field(json_object *jso, const char *key, int32_t value)
+{
+ json_object *jsoval = json_object_new_int(value);
+ if (!jsoval) {
+ return false;
+ }
+ json_object_object_add(jso, key, jsoval);
+ return true;
+}
+
+static bool
+add_json_key_usage(json_object *jso, uint8_t key_flags)
+{
+ json_object *jsoarr = json_object_new_array();
+ if (!jsoarr) {
+ return false;
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(key_usage_map); i++) {
+ if (key_usage_map[i].id & key_flags) {
+ json_object *jsostr = json_object_new_string(key_usage_map[i].str);
+ if (!jsostr || json_object_array_add(jsoarr, jsostr)) {
+ json_object_put(jsoarr);
+ return false;
+ }
+ }
+ }
+ if (json_object_array_length(jsoarr)) {
+ json_object_object_add(jso, "usage", jsoarr);
+ } else {
+ json_object_put(jsoarr);
+ }
+ return true;
+}
+
+static bool
+add_json_key_flags(json_object *jso, uint8_t key_flags)
+{
+ json_object *jsoarr = json_object_new_array();
+ if (!jsoarr) {
+ return false;
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(key_flags_map); i++) {
+ if (key_flags_map[i].id & key_flags) {
+ json_object *jsostr = json_object_new_string(key_flags_map[i].str);
+ if (!jsostr || json_object_array_add(jsoarr, jsostr)) {
+ json_object_put(jsoarr);
+ return false;
+ }
+ }
+ }
+ if (json_object_array_length(jsoarr)) {
+ json_object_object_add(jso, "flags", jsoarr);
+ } else {
+ json_object_put(jsoarr);
+ }
+ return true;
+}
+
+static rnp_result_t
+add_json_mpis(json_object *jso, ...)
+{
+ va_list ap;
+ const char * name;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ va_start(ap, jso);
+ while ((name = va_arg(ap, const char *))) {
+ pgp_mpi_t *val = va_arg(ap, pgp_mpi_t *);
+ if (!val) {
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto done;
+ }
+ char *hex = mpi2hex(val);
+ if (!hex) {
+ // this could probably be other things
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ json_object *jsostr = json_object_new_string(hex);
+ free(hex);
+ if (!jsostr) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ json_object_object_add(jso, name, jsostr);
+ }
+ ret = RNP_SUCCESS;
+
+done:
+ va_end(ap);
+ return ret;
+}
+
+static rnp_result_t
+add_json_public_mpis(json_object *jso, pgp_key_t *key)
+{
+ const pgp_key_material_t &km = key->material();
+ switch (km.alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return add_json_mpis(jso, "n", &km.rsa.n, "e", &km.rsa.e, NULL);
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return add_json_mpis(jso, "p", &km.eg.p, "g", &km.eg.g, "y", &km.eg.y, NULL);
+ case PGP_PKA_DSA:
+ return add_json_mpis(
+ jso, "p", &km.dsa.p, "q", &km.dsa.q, "g", &km.dsa.g, "y", &km.dsa.y, NULL);
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ return add_json_mpis(jso, "point", &km.ec.p, NULL);
+ default:
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+add_json_secret_mpis(json_object *jso, pgp_key_t *key)
+{
+ const pgp_key_material_t &km = key->material();
+ switch (key->alg()) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return add_json_mpis(
+ jso, "d", &km.rsa.d, "p", &km.rsa.p, "q", &km.rsa.q, "u", &km.rsa.u, NULL);
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return add_json_mpis(jso, "x", &km.eg.x, NULL);
+ case PGP_PKA_DSA:
+ return add_json_mpis(jso, "x", &km.dsa.x, NULL);
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ return add_json_mpis(jso, "x", &km.ec.x, NULL);
+ default:
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+add_json_sig_mpis(json_object *jso, const pgp_signature_t *sig)
+{
+ pgp_signature_material_t material = {};
+ try {
+ if (!sig->parse_material(material)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ switch (sig->palg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return add_json_mpis(jso, "sig", &material.rsa.s, NULL);
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ return add_json_mpis(jso, "r", &material.eg.r, "s", &material.eg.s, NULL);
+ case PGP_PKA_DSA:
+ return add_json_mpis(jso, "r", &material.dsa.r, "s", &material.dsa.s, NULL);
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ return add_json_mpis(jso, "r", &material.ecc.r, "s", &material.ecc.s, NULL);
+ default:
+ // TODO: we could use info->unknown and add a hex string of raw data here
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ return RNP_SUCCESS;
+}
+
+static bool
+add_json_user_prefs(json_object *jso, const pgp_user_prefs_t &prefs)
+{
+ // TODO: instead of using a string "Unknown" as a fallback for these,
+ // we could add a string of hex/dec (or even an int)
+ if (!prefs.symm_algs.empty()) {
+ json_object *jsoarr = json_object_new_array();
+ if (!jsoarr) {
+ return false;
+ }
+ json_object_object_add(jso, "ciphers", jsoarr);
+ for (auto alg : prefs.symm_algs) {
+ const char * name = id_str_pair::lookup(symm_alg_map, alg, "Unknown");
+ json_object *jsoname = json_object_new_string(name);
+ if (!jsoname || json_object_array_add(jsoarr, jsoname)) {
+ return false;
+ }
+ }
+ }
+ if (!prefs.hash_algs.empty()) {
+ json_object *jsoarr = json_object_new_array();
+ if (!jsoarr) {
+ return false;
+ }
+ json_object_object_add(jso, "hashes", jsoarr);
+ for (auto alg : prefs.hash_algs) {
+ const char * name = id_str_pair::lookup(hash_alg_map, alg, "Unknown");
+ json_object *jsoname = json_object_new_string(name);
+ if (!jsoname || json_object_array_add(jsoarr, jsoname)) {
+ return false;
+ }
+ }
+ }
+ if (!prefs.z_algs.empty()) {
+ json_object *jsoarr = json_object_new_array();
+ if (!jsoarr) {
+ return false;
+ }
+ json_object_object_add(jso, "compression", jsoarr);
+ for (auto alg : prefs.z_algs) {
+ const char * name = id_str_pair::lookup(compress_alg_map, alg, "Unknown");
+ json_object *jsoname = json_object_new_string(name);
+ if (!jsoname || json_object_array_add(jsoarr, jsoname)) {
+ return false;
+ }
+ }
+ }
+ if (!prefs.ks_prefs.empty()) {
+ json_object *jsoarr = json_object_new_array();
+ if (!jsoarr) {
+ return false;
+ }
+ json_object_object_add(jso, "key server preferences", jsoarr);
+ for (auto flag : prefs.ks_prefs) {
+ const char * name = id_str_pair::lookup(key_server_prefs_map, flag, "Unknown");
+ json_object *jsoname = json_object_new_string(name);
+ if (!jsoname || json_object_array_add(jsoarr, jsoname)) {
+ return false;
+ }
+ }
+ }
+ if (!prefs.key_server.empty()) {
+ if (!add_json_string_field(jso, "key server", prefs.key_server.c_str())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static rnp_result_t
+add_json_subsig(json_object *jso, bool is_sub, uint32_t flags, const pgp_subsig_t *subsig)
+{
+ // userid (if applicable)
+ if (!is_sub) {
+ json_object *jsouid = json_object_new_int(subsig->uid);
+ if (!jsouid) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "userid", jsouid);
+ }
+ // trust
+ json_object *jsotrust = json_object_new_object();
+ if (!jsotrust) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "trust", jsotrust);
+ // trust (level)
+ json_object *jsotrust_level = json_object_new_int(subsig->trustlevel);
+ if (!jsotrust_level) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jsotrust, "level", jsotrust_level);
+ // trust (amount)
+ json_object *jsotrust_amount = json_object_new_int(subsig->trustamount);
+ if (!jsotrust_amount) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jsotrust, "amount", jsotrust_amount);
+ // key flags (usage)
+ if (!add_json_key_usage(jso, subsig->key_flags)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // key flags (other)
+ if (!add_json_key_flags(jso, subsig->key_flags)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // preferences
+ const pgp_user_prefs_t &prefs = subsig->prefs;
+ if (!prefs.symm_algs.empty() || !prefs.hash_algs.empty() || !prefs.z_algs.empty() ||
+ !prefs.ks_prefs.empty() || !prefs.key_server.empty()) {
+ json_object *jsoprefs = json_object_new_object();
+ if (!jsoprefs) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "preferences", jsoprefs);
+ if (!add_json_user_prefs(jsoprefs, prefs)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ const pgp_signature_t *sig = &subsig->sig;
+ // version
+ json_object *jsoversion = json_object_new_int(sig->version);
+ if (!jsoversion) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "version", jsoversion);
+ // signature type
+ auto type = id_str_pair::lookup(sig_type_map, sig->type());
+ if (!add_json_string_field(jso, "type", type)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // signer key type
+ const char *key_type = id_str_pair::lookup(pubkey_alg_map, sig->palg);
+ if (!add_json_string_field(jso, "key type", key_type)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // hash
+ const char *hash = id_str_pair::lookup(hash_alg_map, sig->halg);
+ if (!add_json_string_field(jso, "hash", hash)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // creation time
+ json_object *jsocreation_time = json_object_new_int64(sig->creation());
+ if (!jsocreation_time) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "creation time", jsocreation_time);
+ // expiration (seconds)
+ json_object *jsoexpiration = json_object_new_int64(sig->expiration());
+ if (!jsoexpiration) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "expiration", jsoexpiration);
+ // signer
+ json_object *jsosigner = NULL;
+ // TODO: add signer fingerprint as well (no support internally yet)
+ if (sig->has_keyid()) {
+ jsosigner = json_object_new_object();
+ if (!jsosigner) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ char keyid[PGP_KEY_ID_SIZE * 2 + 1];
+ pgp_key_id_t signer = sig->keyid();
+ if (!rnp::hex_encode(signer.data(), signer.size(), keyid, sizeof(keyid))) {
+ return RNP_ERROR_GENERIC;
+ }
+ if (!add_json_string_field(jsosigner, "keyid", keyid)) {
+ json_object_put(jsosigner);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ json_object_object_add(jso, "signer", jsosigner);
+ // mpis
+ json_object *jsompis = NULL;
+ if (flags & RNP_JSON_SIGNATURE_MPIS) {
+ jsompis = json_object_new_object();
+ if (!jsompis) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t tmpret;
+ if ((tmpret = add_json_sig_mpis(jsompis, sig))) {
+ json_object_put(jsompis);
+ return tmpret;
+ }
+ }
+ json_object_object_add(jso, "mpis", jsompis);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+key_to_json(json_object *jso, rnp_key_handle_t handle, uint32_t flags)
+{
+ pgp_key_t *key = get_key_prefer_public(handle);
+
+ // type
+ const char *str = id_str_pair::lookup(pubkey_alg_map, key->alg(), NULL);
+ if (!str) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (!add_json_string_field(jso, "type", str)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // length
+ if (!add_json_int_field(jso, "length", key->material().bits())) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // curve / alg-specific items
+ switch (key->alg()) {
+ case PGP_PKA_ECDH: {
+ const char *hash_name =
+ id_str_pair::lookup(hash_alg_map, key->material().ec.kdf_hash_alg, NULL);
+ if (!hash_name) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ const char *cipher_name =
+ id_str_pair::lookup(symm_alg_map, key->material().ec.key_wrap_alg, NULL);
+ if (!cipher_name) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ json_object *jsohash = json_object_new_string(hash_name);
+ if (!jsohash) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "kdf hash", jsohash);
+ json_object *jsocipher = json_object_new_string(cipher_name);
+ if (!jsocipher) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "key wrap cipher", jsocipher);
+ }
+ [[fallthrough]];
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2: {
+ const char *curve_name = NULL;
+ if (!curve_type_to_str(key->material().ec.curve, &curve_name)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ json_object *jsocurve = json_object_new_string(curve_name);
+ if (!jsocurve) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "curve", jsocurve);
+ } break;
+ default:
+ break;
+ }
+
+ // keyid
+ char keyid[PGP_KEY_ID_SIZE * 2 + 1];
+ if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), keyid, sizeof(keyid))) {
+ return RNP_ERROR_GENERIC;
+ }
+ if (!add_json_string_field(jso, "keyid", keyid)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // fingerprint
+ char fpr[PGP_FINGERPRINT_SIZE * 2 + 1];
+ if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, fpr, sizeof(fpr))) {
+ return RNP_ERROR_GENERIC;
+ }
+ if (!add_json_string_field(jso, "fingerprint", fpr)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // grip
+ char grip[PGP_KEY_GRIP_SIZE * 2 + 1];
+ if (!rnp::hex_encode(key->grip().data(), key->grip().size(), grip, sizeof(grip))) {
+ return RNP_ERROR_GENERIC;
+ }
+ if (!add_json_string_field(jso, "grip", grip)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // revoked
+ json_object *jsorevoked = json_object_new_boolean(key->revoked() ? true : false);
+ if (!jsorevoked) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "revoked", jsorevoked);
+ // creation time
+ json_object *jsocreation_time = json_object_new_int64(key->creation());
+ if (!jsocreation_time) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "creation time", jsocreation_time);
+ // expiration
+ json_object *jsoexpiration = json_object_new_int64(key->expiration());
+ if (!jsoexpiration) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "expiration", jsoexpiration);
+ // key flags (usage)
+ if (!add_json_key_usage(jso, key->flags())) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // key flags (other)
+ if (!add_json_key_flags(jso, key->flags())) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ // parent / subkeys
+ if (key->is_primary()) {
+ json_object *jsosubkeys_arr = json_object_new_array();
+ if (!jsosubkeys_arr) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "subkey grips", jsosubkeys_arr);
+ for (auto &subfp : key->subkey_fps()) {
+ const pgp_key_grip_t *subgrip = rnp_get_grip_by_fp(handle->ffi, subfp);
+ if (!subgrip) {
+ continue;
+ }
+ if (!rnp::hex_encode(subgrip->data(), subgrip->size(), grip, sizeof(grip))) {
+ return RNP_ERROR_GENERIC;
+ }
+ json_object *jsostr = json_object_new_string(grip);
+ if (!jsostr || json_object_array_add(jsosubkeys_arr, jsostr)) {
+ json_object_put(jsostr);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ } else if (key->has_primary_fp()) {
+ auto pgrip = rnp_get_grip_by_fp(handle->ffi, key->primary_fp());
+ if (pgrip) {
+ if (!rnp::hex_encode(pgrip->data(), pgrip->size(), grip, sizeof(grip))) {
+ return RNP_ERROR_GENERIC;
+ }
+ if (!add_json_string_field(jso, "primary key grip", grip)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ // public
+ json_object *jsopublic = json_object_new_object();
+ if (!jsopublic) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ bool have_sec = handle->sec != NULL;
+ bool have_pub = handle->pub != NULL;
+ json_object_object_add(jso, "public key", jsopublic);
+ json_object_object_add(
+ jsopublic, "present", json_object_new_boolean(have_pub ? true : false));
+ if (flags & RNP_JSON_PUBLIC_MPIS) {
+ json_object *jsompis = json_object_new_object();
+ if (!jsompis) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jsopublic, "mpis", jsompis);
+ rnp_result_t tmpret;
+ if ((tmpret = add_json_public_mpis(jsompis, key))) {
+ return tmpret;
+ }
+ }
+ // secret
+ json_object *jsosecret = json_object_new_object();
+ if (!jsosecret) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "secret key", jsosecret);
+ json_object_object_add(
+ jsosecret, "present", json_object_new_boolean(have_sec ? true : false));
+ if (have_sec) {
+ bool locked = handle->sec->is_locked();
+ if (flags & RNP_JSON_SECRET_MPIS) {
+ if (locked) {
+ json_object_object_add(jsosecret, "mpis", NULL);
+ } else {
+ json_object *jsompis = json_object_new_object();
+ if (!jsompis) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jsosecret, "mpis", jsompis);
+ rnp_result_t tmpret;
+ if ((tmpret = add_json_secret_mpis(jsompis, handle->sec))) {
+ return tmpret;
+ }
+ }
+ }
+ json_object *jsolocked = json_object_new_boolean(locked ? true : false);
+ if (!jsolocked) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jsosecret, "locked", jsolocked);
+ json_object *jsoprotected =
+ json_object_new_boolean(handle->sec->is_protected() ? true : false);
+ if (!jsoprotected) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jsosecret, "protected", jsoprotected);
+ }
+ // userids
+ if (key->is_primary()) {
+ json_object *jsouids_arr = json_object_new_array();
+ if (!jsouids_arr) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "userids", jsouids_arr);
+ for (size_t i = 0; i < key->uid_count(); i++) {
+ json_object *jsouid = json_object_new_string(key->get_uid(i).str.c_str());
+ if (!jsouid || json_object_array_add(jsouids_arr, jsouid)) {
+ json_object_put(jsouid);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ }
+ // signatures
+ if (flags & RNP_JSON_SIGNATURES) {
+ json_object *jsosigs_arr = json_object_new_array();
+ if (!jsosigs_arr) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ json_object_object_add(jso, "signatures", jsosigs_arr);
+ for (size_t i = 0; i < key->sig_count(); i++) {
+ json_object *jsosig = json_object_new_object();
+ if (!jsosig || json_object_array_add(jsosigs_arr, jsosig)) {
+ json_object_put(jsosig);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t tmpret;
+ if ((tmpret =
+ add_json_subsig(jsosig, key->is_subkey(), flags, &key->get_sig(i)))) {
+ return tmpret;
+ }
+ }
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_key_to_json(rnp_key_handle_t handle, uint32_t flags, char **result)
+try {
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ json_object *jso = NULL;
+
+ // checks
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ jso = json_object_new_object();
+ if (!jso) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ if ((ret = key_to_json(jso, handle, flags))) {
+ goto done;
+ }
+ *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY);
+ if (!*result) {
+ goto done;
+ }
+ *result = strdup(*result);
+ if (!*result) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ ret = RNP_SUCCESS;
+done:
+ json_object_put(jso);
+ return ret;
+}
+FFI_GUARD
+
+static rnp_result_t
+rnp_dump_src_to_json(pgp_source_t *src, uint32_t flags, char **result)
+{
+ rnp_dump_ctx_t dumpctx = {};
+
+ dumpctx.dump_mpi = extract_flag(flags, RNP_JSON_DUMP_MPI);
+ dumpctx.dump_packets = extract_flag(flags, RNP_JSON_DUMP_RAW);
+ dumpctx.dump_grips = extract_flag(flags, RNP_JSON_DUMP_GRIP);
+ if (flags) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ json_object *jso = NULL;
+ rnp_result_t ret = stream_dump_packets_json(&dumpctx, src, &jso);
+ if (ret) {
+ goto done;
+ }
+
+ *result = (char *) json_object_to_json_string_ext(jso, JSON_C_TO_STRING_PRETTY);
+ if (!*result) {
+ goto done;
+ }
+ *result = strdup(*result);
+ if (!*result) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ ret = RNP_SUCCESS;
+done:
+ json_object_put(jso);
+ return ret;
+}
+
+rnp_result_t
+rnp_key_packets_to_json(rnp_key_handle_t handle, bool secret, uint32_t flags, char **result)
+try {
+ if (!handle || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_key_t *key = secret ? handle->sec : handle->pub;
+ if (!key || (key->format == PGP_KEY_STORE_G10)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ auto vec = rnp_key_to_vec(*key);
+ rnp::MemorySource mem(vec);
+ return rnp_dump_src_to_json(&mem.src(), flags, result);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_dump_packets_to_json(rnp_input_t input, uint32_t flags, char **result)
+try {
+ if (!input || !result) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ return rnp_dump_src_to_json(&input->src, flags, result);
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_dump_packets_to_output(rnp_input_t input, rnp_output_t output, uint32_t flags)
+try {
+ if (!input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ rnp_dump_ctx_t dumpctx = {};
+ dumpctx.dump_mpi = extract_flag(flags, RNP_DUMP_MPI);
+ dumpctx.dump_packets = extract_flag(flags, RNP_DUMP_RAW);
+ dumpctx.dump_grips = extract_flag(flags, RNP_DUMP_GRIP);
+ if (flags) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp_result_t ret = stream_dump_packets(&dumpctx, &input->src, &output->dst);
+ output->keep = true;
+ return ret;
+}
+FFI_GUARD
+
+// move to next key
+static bool
+key_iter_next_key(rnp_identifier_iterator_t it)
+{
+ // check if we not reached the end of the ring
+ *it->keyp = std::next(*it->keyp);
+ if (*it->keyp != it->store->keys.end()) {
+ it->uididx = 0;
+ return true;
+ }
+ // if we are currently on pubring, switch to secring (if not empty)
+ if (it->store == it->ffi->pubring && !it->ffi->secring->keys.empty()) {
+ it->store = it->ffi->secring;
+ *it->keyp = it->store->keys.begin();
+ it->uididx = 0;
+ return true;
+ }
+ // we've gone through both rings
+ it->store = NULL;
+ return false;
+}
+
+// move to next item (key or userid)
+static bool
+key_iter_next_item(rnp_identifier_iterator_t it)
+{
+ switch (it->type) {
+ case PGP_KEY_SEARCH_KEYID:
+ case PGP_KEY_SEARCH_FINGERPRINT:
+ case PGP_KEY_SEARCH_GRIP:
+ return key_iter_next_key(it);
+ case PGP_KEY_SEARCH_USERID:
+ it->uididx++;
+ while (it->uididx >= (*it->keyp)->uid_count()) {
+ if (!key_iter_next_key(it)) {
+ return false;
+ }
+ it->uididx = 0;
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ return true;
+}
+
+static bool
+key_iter_first_key(rnp_identifier_iterator_t it)
+{
+ if (rnp_key_store_get_key_count(it->ffi->pubring)) {
+ it->store = it->ffi->pubring;
+ } else if (rnp_key_store_get_key_count(it->ffi->secring)) {
+ it->store = it->ffi->secring;
+ } else {
+ it->store = NULL;
+ return false;
+ }
+ *it->keyp = it->store->keys.begin();
+ it->uididx = 0;
+ return true;
+}
+
+static bool
+key_iter_first_item(rnp_identifier_iterator_t it)
+{
+ switch (it->type) {
+ case PGP_KEY_SEARCH_KEYID:
+ case PGP_KEY_SEARCH_FINGERPRINT:
+ case PGP_KEY_SEARCH_GRIP:
+ return key_iter_first_key(it);
+ case PGP_KEY_SEARCH_USERID:
+ if (!key_iter_first_key(it)) {
+ return false;
+ }
+ it->uididx = 0;
+ while (it->uididx >= (*it->keyp)->uid_count()) {
+ if (!key_iter_next_key(it)) {
+ return false;
+ }
+ }
+ break;
+ default:
+ assert(false);
+ break;
+ }
+ return true;
+}
+
+static bool
+key_iter_get_item(const rnp_identifier_iterator_t it, char *buf, size_t buf_len)
+{
+ const pgp_key_t *key = &**it->keyp;
+ switch (it->type) {
+ case PGP_KEY_SEARCH_KEYID: {
+ if (!rnp::hex_encode(key->keyid().data(), key->keyid().size(), buf, buf_len)) {
+ return false;
+ }
+ break;
+ }
+ case PGP_KEY_SEARCH_FINGERPRINT:
+ if (!rnp::hex_encode(key->fp().fingerprint, key->fp().length, buf, buf_len)) {
+ return false;
+ }
+ break;
+ case PGP_KEY_SEARCH_GRIP:
+ if (!rnp::hex_encode(key->grip().data(), key->grip().size(), buf, buf_len)) {
+ return false;
+ }
+ break;
+ case PGP_KEY_SEARCH_USERID: {
+ if (it->uididx >= key->uid_count()) {
+ return false;
+ }
+ const pgp_userid_t &uid = key->get_uid(it->uididx);
+ if (uid.str.size() >= buf_len) {
+ return false;
+ }
+ memcpy(buf, uid.str.c_str(), uid.str.size() + 1);
+ } break;
+ default:
+ assert(false);
+ break;
+ }
+ return true;
+}
+
+rnp_result_t
+rnp_identifier_iterator_create(rnp_ffi_t ffi,
+ rnp_identifier_iterator_t *it,
+ const char * identifier_type)
+try {
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ struct rnp_identifier_iterator_st *obj = NULL;
+
+ // checks
+ if (!ffi || !it || !identifier_type) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ // create iterator
+ obj = (struct rnp_identifier_iterator_st *) calloc(1, sizeof(*obj));
+ if (!obj) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ obj->ffi = ffi;
+ obj->keyp = new std::list<pgp_key_t>::iterator();
+ obj->uididx = 0;
+ // parse identifier type
+ obj->type = static_cast<pgp_key_search_type_t>(
+ id_str_pair::lookup(identifier_type_map, identifier_type, PGP_KEY_SEARCH_UNKNOWN));
+ if (obj->type == PGP_KEY_SEARCH_UNKNOWN) {
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto done;
+ }
+ obj->tbl = json_object_new_object();
+ if (!obj->tbl) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ // move to first item (if any)
+ key_iter_first_item(obj);
+ *it = obj;
+
+ ret = RNP_SUCCESS;
+done:
+ if (ret) {
+ rnp_identifier_iterator_destroy(obj);
+ }
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_identifier_iterator_next(rnp_identifier_iterator_t it, const char **identifier)
+try {
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ // checks
+ if (!it || !identifier) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ // initialize the result to NULL
+ *identifier = NULL;
+ // this means we reached the end of the rings
+ if (!it->store) {
+ return RNP_SUCCESS;
+ }
+ // get the item
+ if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) {
+ return RNP_ERROR_GENERIC;
+ }
+ bool exists;
+ bool iterator_valid = true;
+ while ((exists = json_object_object_get_ex(it->tbl, it->buf, NULL))) {
+ if (!((iterator_valid = key_iter_next_item(it)))) {
+ break;
+ }
+ if (!key_iter_get_item(it, it->buf, sizeof(it->buf))) {
+ return RNP_ERROR_GENERIC;
+ }
+ }
+ // see if we actually found a new entry
+ if (!exists) {
+ // TODO: Newer json-c has a useful return value for json_object_object_add,
+ // which doesn't require the json_object_object_get_ex check below.
+ json_object_object_add(it->tbl, it->buf, NULL);
+ if (!json_object_object_get_ex(it->tbl, it->buf, NULL)) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ *identifier = it->buf;
+ }
+ // prepare for the next one
+ if (iterator_valid) {
+ key_iter_next_item(it);
+ }
+ ret = RNP_SUCCESS;
+
+done:
+ if (ret) {
+ *identifier = NULL;
+ }
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_identifier_iterator_destroy(rnp_identifier_iterator_t it)
+try {
+ if (it) {
+ json_object_put(it->tbl);
+ if (it->keyp) {
+ delete it->keyp;
+ }
+ free(it);
+ }
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_guess_contents(rnp_input_t input, char **contents)
+try {
+ if (!input || !contents) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN;
+ if (is_armored_source(&input->src)) {
+ msgtype = rnp_armored_get_type(&input->src);
+ } else {
+ msgtype = rnp_armor_guess_type(&input->src);
+ }
+ const char *msg = id_str_pair::lookup(armor_type_map, msgtype);
+ size_t len = strlen(msg);
+ *contents = (char *) calloc(1, len + 1);
+ if (!*contents) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(*contents, msg, len);
+ return RNP_SUCCESS;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_enarmor(rnp_input_t input, rnp_output_t output, const char *type)
+try {
+ pgp_armored_msg_t msgtype = PGP_ARMORED_UNKNOWN;
+ if (!input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (type) {
+ msgtype = static_cast<pgp_armored_msg_t>(
+ id_str_pair::lookup(armor_type_map, type, PGP_ARMORED_UNKNOWN));
+ if (msgtype == PGP_ARMORED_UNKNOWN) {
+ RNP_LOG("Unsupported armor type: %s", type);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ } else {
+ msgtype = rnp_armor_guess_type(&input->src);
+ if (!msgtype) {
+ RNP_LOG("Unrecognized data to armor (try specifying a type)");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+ rnp_result_t ret = rnp_armor_source(&input->src, &output->dst, msgtype);
+ output->keep = !ret;
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_dearmor(rnp_input_t input, rnp_output_t output)
+try {
+ if (!input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ rnp_result_t ret = rnp_dearmor_source(&input->src, &output->dst);
+ output->keep = !ret;
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_pipe(rnp_input_t input, rnp_output_t output)
+try {
+ if (!input || !output) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ rnp_result_t ret = dst_write_src(&input->src, &output->dst);
+ output->keep = !ret;
+ return ret;
+}
+FFI_GUARD
+
+rnp_result_t
+rnp_output_armor_set_line_length(rnp_output_t output, size_t llen)
+try {
+ if (!output || !llen) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return armored_dst_set_line_length(&output->dst, llen);
+}
+FFI_GUARD
+
+const char *
+rnp_backend_string()
+{
+ return rnp::backend_string();
+}
+
+const char *
+rnp_backend_version()
+{
+ return rnp::backend_version();
+}
diff --git a/src/lib/sec_profile.cpp b/src/lib/sec_profile.cpp
new file mode 100644
index 0000000..f9d0de8
--- /dev/null
+++ b/src/lib/sec_profile.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2021 [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 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 "sec_profile.hpp"
+#include "types.h"
+#include "defaults.h"
+#include <ctime>
+#include <algorithm>
+
+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) && (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();
+}
+
+SecurityRule &
+SecurityProfile::add_rule(const SecurityRule &rule)
+{
+ rules_.push_back(rule);
+ return rules_.back();
+}
+
+SecurityRule &
+SecurityProfile::add_rule(SecurityRule &&rule)
+{
+ rules_.emplace_back(rule);
+ return rules_.back();
+}
+
+bool
+SecurityProfile::del_rule(const SecurityRule &rule)
+{
+ size_t old_size = rules_.size();
+ rules_.erase(std::remove_if(rules_.begin(),
+ rules_.end(),
+ [rule](const SecurityRule &item) { return item == rule; }),
+ rules_.end());
+ return old_size != rules_.size();
+}
+
+void
+SecurityProfile::clear_rules(FeatureType type, int feature)
+{
+ rules_.erase(std::remove_if(rules_.begin(),
+ rules_.end(),
+ [type, feature](const SecurityRule &item) {
+ return (item.type == type) && (item.feature == feature);
+ }),
+ rules_.end());
+}
+
+void
+SecurityProfile::clear_rules(FeatureType type)
+{
+ rules_.erase(
+ std::remove_if(rules_.begin(),
+ rules_.end(),
+ [type](const SecurityRule &item) { return item.type == type; }),
+ rules_.end());
+}
+
+void
+SecurityProfile::clear_rules()
+{
+ rules_.clear();
+}
+
+bool
+SecurityProfile::has_rule(FeatureType type,
+ int value,
+ uint64_t time,
+ SecurityAction action) const noexcept
+{
+ for (auto &rule : rules_) {
+ if (rule.matches(type, value, time, action)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+const SecurityRule &
+SecurityProfile::get_rule(FeatureType type,
+ int value,
+ uint64_t time,
+ SecurityAction action) const
+{
+ const SecurityRule *res = nullptr;
+ for (auto &rule : rules_) {
+ if (!rule.matches(type, value, time, action)) {
+ continue;
+ }
+ if (rule.override) {
+ return rule;
+ }
+ if (!res || (res->from < rule.from)) {
+ res = &rule;
+ }
+ }
+ if (!res) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ return *res;
+}
+
+SecurityLevel
+SecurityProfile::hash_level(pgp_hash_alg_t hash,
+ uint64_t time,
+ SecurityAction action) const noexcept
+{
+ if (!has_rule(FeatureType::Hash, hash, time, action)) {
+ 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() : time_(0), prov_state_(NULL), rng(RNG::Type::DRBG)
+{
+ /* 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({FeatureType::Hash, PGP_HASH_MD5, SecurityLevel::Insecure, 1325376000});
+}
+
+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/src/lib/sec_profile.hpp b/src/lib/sec_profile.hpp
new file mode 100644
index 0000000..a4d8456
--- /dev/null
+++ b/src/lib/sec_profile.hpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (c) 2021 [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 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 RNP_SEC_PROFILE_H_
+#define RNP_SEC_PROFILE_H_
+
+#include <cstdint>
+#include <vector>
+#include <unordered_map>
+#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;
+ SecurityAction action;
+
+ 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<SecurityRule> rules_;
+
+ public:
+ size_t size() const noexcept;
+ SecurityRule &add_rule(const SecurityRule &rule);
+ SecurityRule &add_rule(SecurityRule &&rule);
+ 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,
+ 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<int, size_t> 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/src/lib/types.h b/src/lib/types.h
new file mode 100644
index 0000000..5a67d42
--- /dev/null
+++ b/src/lib/types.h
@@ -0,0 +1,482 @@
+/*
+ * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef TYPES_H_
+#define TYPES_H_
+
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <array>
+#include <cstring>
+#include <type_traits>
+
+#include <rnp/rnp_def.h>
+#include "crypto/common.h"
+#include "sec_profile.hpp"
+
+/* SHA1 Hash Size */
+#define PGP_SHA1_HASH_SIZE 20
+
+/* Maximum length of the packet header */
+#define PGP_MAX_HEADER_SIZE 6
+
+/* Maximum supported userid length */
+#define MAX_ID_LENGTH 128
+
+/* Maximum supported password length */
+#define MAX_PASSWORD_LENGTH 256
+
+class id_str_pair {
+ public:
+ int id;
+ const char *str;
+
+ /**
+ * @brief Lookup constant pair array for the specified id or string value.
+ * Note: array must be finished with NULL string to stop the lookup.
+ *
+ * @param pair pointer to the const array with pairs.
+ * @param id identifier to search for
+ * @param notfound value to return if identifier is not found.
+ * @return string, representing the identifier.
+ */
+ static const char *lookup(const id_str_pair pair[],
+ int id,
+ const char * notfound = "unknown");
+ static int lookup(const id_str_pair pair[], const char *str, int notfound = 0);
+ static int lookup(const id_str_pair pair[],
+ const std::vector<uint8_t> &bytes,
+ int notfound = 0);
+ static int lookup(const id_str_pair pair[],
+ const std::basic_string<uint8_t> &bytes,
+ int notfound = 0);
+};
+
+/** pgp_fingerprint_t */
+typedef struct pgp_fingerprint_t {
+ uint8_t fingerprint[PGP_FINGERPRINT_SIZE];
+ unsigned length;
+ bool operator==(const pgp_fingerprint_t &src) const;
+ bool operator!=(const pgp_fingerprint_t &src) const;
+} pgp_fingerprint_t;
+
+typedef std::array<uint8_t, PGP_KEY_GRIP_SIZE> pgp_sig_id_t;
+
+namespace std {
+template <> struct hash<pgp_fingerprint_t> {
+ std::size_t
+ operator()(pgp_fingerprint_t const &fp) const noexcept
+ {
+ /* since fingerprint value is hash itself, we may use its low bytes */
+ size_t res = 0;
+ static_assert(sizeof(fp.fingerprint) == PGP_FINGERPRINT_SIZE,
+ "pgp_fingerprint_t size mismatch");
+ static_assert(PGP_FINGERPRINT_SIZE >= sizeof(res), "pgp_fingerprint_t size mismatch");
+ std::memcpy(&res, fp.fingerprint, sizeof(res));
+ return res;
+ }
+};
+
+template <> struct hash<pgp_sig_id_t> {
+ std::size_t
+ operator()(pgp_sig_id_t const &sigid) const noexcept
+ {
+ /* since signature id value is hash itself, we may use its low bytes */
+ size_t res = 0;
+ static_assert(std::tuple_size<pgp_sig_id_t>::value >= sizeof(res),
+ "pgp_sig_id_t size mismatch");
+ std::memcpy(&res, sigid.data(), sizeof(res));
+ return res;
+ }
+};
+}; // namespace std
+
+typedef std::array<uint8_t, PGP_KEY_GRIP_SIZE> pgp_key_grip_t;
+
+typedef std::array<uint8_t, PGP_KEY_ID_SIZE> pgp_key_id_t;
+
+namespace rnp {
+class rnp_exception : public std::exception {
+ rnp_result_t code_;
+
+ public:
+ rnp_exception(rnp_result_t code = RNP_ERROR_GENERIC) : code_(code){};
+ virtual const char *
+ what() const throw()
+ {
+ return "rnp_exception";
+ };
+ rnp_result_t
+ code() const
+ {
+ return code_;
+ };
+};
+} // namespace rnp
+
+/* validity information for the signature/key/userid */
+typedef struct pgp_validity_t {
+ bool validated{}; /* item was validated */
+ bool valid{}; /* item is valid by signature/key checks and calculations.
+ Still may be revoked or expired. */
+ bool expired{}; /* item is expired */
+
+ void mark_valid();
+ void reset();
+} pgp_validity_t;
+
+/**
+ * Type to keep public/secret key mpis without any openpgp-dependent data.
+ */
+typedef struct pgp_key_material_t {
+ pgp_pubkey_alg_t alg; /* algorithm of the key */
+ bool secret; /* secret part of the key material is populated */
+ pgp_validity_t validity; /* key material validation status */
+
+ union {
+ pgp_rsa_key_t rsa;
+ pgp_dsa_key_t dsa;
+ pgp_eg_key_t eg;
+ pgp_ec_key_t ec;
+ };
+
+ size_t bits() const;
+ size_t qbits() const;
+ void validate(rnp::SecurityContext &ctx, bool reset = true);
+ bool valid() const;
+} pgp_key_material_t;
+
+/**
+ * Type to keep signature without any openpgp-dependent data.
+ */
+typedef struct pgp_signature_material_t {
+ union {
+ pgp_rsa_signature_t rsa;
+ pgp_dsa_signature_t dsa;
+ pgp_ec_signature_t ecc;
+ pgp_eg_signature_t eg;
+ };
+} pgp_signature_material_t;
+
+/**
+ * Type to keep pk-encrypted data without any openpgp-dependent data.
+ */
+typedef struct pgp_encrypted_material_t {
+ union {
+ pgp_rsa_encrypted_t rsa;
+ pgp_eg_encrypted_t eg;
+ pgp_sm2_encrypted_t sm2;
+ pgp_ecdh_encrypted_t ecdh;
+ };
+} pgp_encrypted_material_t;
+
+typedef struct pgp_s2k_t {
+ pgp_s2k_usage_t usage{};
+
+ /* below fields may not all be valid, depending on the usage field above */
+ pgp_s2k_specifier_t specifier{};
+ pgp_hash_alg_t hash_alg{};
+ uint8_t salt[PGP_SALT_SIZE];
+ unsigned iterations{};
+ /* GnuPG custom s2k data */
+ pgp_s2k_gpg_extension_t gpg_ext_num{};
+ uint8_t gpg_serial_len{};
+ uint8_t gpg_serial[16];
+ /* Experimental s2k data */
+ std::vector<uint8_t> experimental{};
+} pgp_s2k_t;
+
+typedef struct pgp_key_protection_t {
+ pgp_s2k_t s2k{}; /* string-to-key kdf params */
+ pgp_symm_alg_t symm_alg{}; /* symmetric alg */
+ pgp_cipher_mode_t cipher_mode{}; /* block cipher mode */
+ uint8_t iv[PGP_MAX_BLOCK_SIZE];
+} pgp_key_protection_t;
+
+typedef struct pgp_key_pkt_t pgp_key_pkt_t;
+typedef struct pgp_userid_pkt_t pgp_userid_pkt_t;
+typedef struct pgp_signature_t pgp_signature_t;
+
+/* Signature subpacket, see 5.2.3.1 in RFC 4880 and RFC 4880 bis 02 */
+typedef struct pgp_sig_subpkt_t {
+ pgp_sig_subpacket_type_t type; /* type of the subpacket */
+ size_t len; /* length of the data */
+ uint8_t * data; /* raw subpacket data, excluding the header */
+ bool critical : 1; /* critical flag */
+ bool hashed : 1; /* whether subpacket is hashed or not */
+ bool parsed : 1; /* whether subpacket was successfully parsed */
+ union {
+ uint32_t create; /* 5.2.3.4. Signature Creation Time */
+ uint32_t expiry; /* 5.2.3.6. Key Expiration Time */
+ /* 5.2.3.10. Signature Expiration Time */
+ bool exportable; /* 5.2.3.11. Exportable Certification */
+ struct {
+ uint8_t level;
+ uint8_t amount;
+ } trust; /* 5.2.3.13. Trust Signature */
+ struct {
+ const char *str;
+ unsigned len;
+ } regexp; /* 5.2.3.14. Regular Expression */
+ bool revocable; /* 5.2.3.12. Revocable */
+ struct {
+ uint8_t *arr;
+ unsigned len;
+ } preferred; /* 5.2.3.7. Preferred Symmetric Algorithms */
+ /* 5.2.3.8. Preferred Hash Algorithms */
+ /* 5.2.3.9. Preferred Compression Algorithms */
+ struct {
+ uint8_t klass;
+ pgp_pubkey_alg_t pkalg;
+ uint8_t * fp;
+ } revocation_key; /* 5.2.3.15. Revocation Key */
+ uint8_t *issuer; /* 5.2.3.5. Issuer */
+ struct {
+ uint8_t flags[4];
+ unsigned nlen;
+ unsigned vlen;
+ bool human;
+ const uint8_t *name;
+ const uint8_t *value;
+ } notation; /* 5.2.3.16. Notation Data */
+ struct {
+ bool no_modify;
+ } ks_prefs; /* 5.2.3.17. Key Server Preferences */
+ struct {
+ const char *uri;
+ unsigned len;
+ } preferred_ks; /* 5.2.3.18. Preferred Key Server */
+ bool primary_uid; /* 5.2.3.19. Primary User ID */
+ struct {
+ const char *uri;
+ unsigned len;
+ } policy; /* 5.2.3.20. Policy URI */
+ uint8_t key_flags; /* 5.2.3.21. Key Flags */
+ struct {
+ const char *uid;
+ unsigned len;
+ } signer; /* 5.2.3.22. Signer's User ID */
+ struct {
+ pgp_revocation_type_t code;
+ const char * str;
+ unsigned len;
+ } revocation_reason; /* 5.2.3.23. Reason for Revocation */
+ uint8_t features; /* 5.2.3.24. Features */
+ struct {
+ pgp_pubkey_alg_t pkalg;
+ pgp_hash_alg_t halg;
+ uint8_t * hash;
+ unsigned hlen;
+ } sig_target; /* 5.2.3.25. Signature Target */
+ pgp_signature_t *sig; /* 5.2.3.27. Embedded Signature */
+ struct {
+ uint8_t version;
+ uint8_t *fp;
+ unsigned len;
+ } issuer_fp; /* 5.2.3.28. Issuer Fingerprint, RFC 4880 bis 04 */
+ } fields; /* parsed contents of the subpacket */
+
+ pgp_sig_subpkt_t()
+ : type(PGP_SIG_SUBPKT_UNKNOWN), len(0), data(NULL), critical(false), hashed(false),
+ parsed(false), fields({}){};
+ pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src);
+ pgp_sig_subpkt_t(pgp_sig_subpkt_t &&src);
+ pgp_sig_subpkt_t &operator=(pgp_sig_subpkt_t &&src);
+ pgp_sig_subpkt_t &operator=(const pgp_sig_subpkt_t &src);
+ ~pgp_sig_subpkt_t();
+ bool parse();
+} pgp_sig_subpkt_t;
+
+typedef struct pgp_one_pass_sig_t pgp_one_pass_sig_t;
+
+typedef enum {
+ /* first octet */
+ PGP_KEY_SERVER_NO_MODIFY = 0x80
+} pgp_key_server_prefs_t;
+
+typedef struct pgp_literal_hdr_t {
+ uint8_t format;
+ char fname[256];
+ uint8_t fname_len;
+ uint32_t timestamp;
+} pgp_literal_hdr_t;
+
+typedef struct pgp_aead_hdr_t {
+ int version{}; /* version of the AEAD packet */
+ pgp_symm_alg_t ealg; /* underlying symmetric algorithm */
+ pgp_aead_alg_t aalg; /* AEAD algorithm, i.e. EAX, OCB, etc */
+ int csize{}; /* chunk size bits */
+ uint8_t iv[PGP_AEAD_MAX_NONCE_LEN]; /* initial vector for the message */
+ size_t ivlen{}; /* iv length */
+
+ pgp_aead_hdr_t() : ealg(PGP_SA_UNKNOWN), aalg(PGP_AEAD_NONE)
+ {
+ }
+} pgp_aead_hdr_t;
+
+/** litdata_type_t */
+typedef enum {
+ PGP_LDT_BINARY = 'b',
+ PGP_LDT_TEXT = 't',
+ PGP_LDT_UTF8 = 'u',
+ PGP_LDT_LOCAL = 'l',
+ PGP_LDT_LOCAL2 = '1'
+} pgp_litdata_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 */
+
+ pgp_revoke_t() = default;
+ pgp_revoke_t(pgp_subsig_t &sig);
+} pgp_revoke_t;
+
+typedef struct pgp_user_prefs_t {
+ // preferred symmetric algs (pgp_symm_alg_t)
+ std::vector<uint8_t> symm_algs{};
+ // preferred hash algs (pgp_hash_alg_t)
+ std::vector<uint8_t> hash_algs{};
+ // preferred compression algs (pgp_compression_type_t)
+ std::vector<uint8_t> z_algs{};
+ // key server preferences (pgp_key_server_prefs_t)
+ std::vector<uint8_t> ks_prefs{};
+ // preferred key server
+ std::string key_server{};
+
+ void set_symm_algs(const std::vector<uint8_t> &algs);
+ void add_symm_alg(pgp_symm_alg_t alg);
+ void set_hash_algs(const std::vector<uint8_t> &algs);
+ void add_hash_alg(pgp_hash_alg_t alg);
+ void set_z_algs(const std::vector<uint8_t> &algs);
+ void add_z_alg(pgp_compression_type_t alg);
+ void set_ks_prefs(const std::vector<uint8_t> &prefs);
+ void add_ks_pref(pgp_key_server_prefs_t pref);
+} pgp_user_prefs_t;
+
+struct rnp_keygen_ecc_params_t {
+ pgp_curve_t curve;
+};
+
+struct rnp_keygen_rsa_params_t {
+ uint32_t modulus_bit_len;
+};
+
+struct rnp_keygen_dsa_params_t {
+ size_t p_bitlen;
+ size_t q_bitlen;
+};
+
+struct rnp_keygen_elgamal_params_t {
+ size_t key_bitlen;
+};
+
+/* structure used to hold context of key generation */
+namespace rnp {
+class SecurityContext;
+}
+
+typedef struct rnp_keygen_crypto_params_t {
+ // Asymmteric algorithm that user requesed key for
+ pgp_pubkey_alg_t key_alg;
+ // Hash to be used for key signature
+ pgp_hash_alg_t hash_alg;
+ // Pointer to security context
+ rnp::SecurityContext *ctx;
+ union {
+ struct rnp_keygen_ecc_params_t ecc;
+ struct rnp_keygen_rsa_params_t rsa;
+ struct rnp_keygen_dsa_params_t dsa;
+ struct rnp_keygen_elgamal_params_t elgamal;
+ };
+} rnp_keygen_crypto_params_t;
+
+typedef struct rnp_selfsig_cert_info_t {
+ 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; /* 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.
+ */
+ void populate(pgp_userid_pkt_t &uid, pgp_signature_t &sig);
+} rnp_selfsig_cert_info_t;
+
+typedef struct rnp_selfsig_binding_info_t {
+ uint8_t key_flags;
+ uint32_t key_expiration;
+} rnp_selfsig_binding_info_t;
+
+typedef struct rnp_keygen_primary_desc_t {
+ rnp_keygen_crypto_params_t crypto{};
+ rnp_selfsig_cert_info_t cert{};
+} rnp_keygen_primary_desc_t;
+
+typedef struct rnp_keygen_subkey_desc_t {
+ rnp_keygen_crypto_params_t crypto;
+ rnp_selfsig_binding_info_t binding;
+} rnp_keygen_subkey_desc_t;
+
+typedef struct rnp_key_protection_params_t {
+ pgp_symm_alg_t symm_alg;
+ pgp_cipher_mode_t cipher_mode;
+ unsigned iterations;
+ pgp_hash_alg_t hash_alg;
+} rnp_key_protection_params_t;
+
+#endif /* TYPES_H_ */
diff --git a/src/lib/utils.cpp b/src/lib/utils.cpp
new file mode 100644
index 0000000..3c6216c
--- /dev/null
+++ b/src/lib/utils.cpp
@@ -0,0 +1,80 @@
+/*
+ * Copyright (c) 2021, [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 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 "types.h"
+#include "str-utils.h"
+
+const char *
+id_str_pair::lookup(const id_str_pair pair[], int id, const char *notfound)
+{
+ while (pair && pair->str) {
+ if (pair->id == id) {
+ return pair->str;
+ }
+ pair++;
+ }
+ return notfound;
+}
+
+int
+id_str_pair::lookup(const id_str_pair pair[], const char *str, int notfound)
+{
+ while (pair && pair->str) {
+ if (rnp::str_case_eq(str, pair->str)) {
+ return pair->id;
+ }
+ pair++;
+ }
+ return notfound;
+}
+
+int
+id_str_pair::lookup(const id_str_pair pair[], const std::vector<uint8_t> &bytes, int notfound)
+{
+ while (pair && pair->str) {
+ if ((strlen(pair->str) == bytes.size()) &&
+ !memcmp(pair->str, bytes.data(), bytes.size())) {
+ return pair->id;
+ }
+ pair++;
+ }
+ return notfound;
+}
+
+int
+id_str_pair::lookup(const id_str_pair pair[],
+ const std::basic_string<uint8_t> &bytes,
+ int notfound)
+{
+ while (pair && pair->str) {
+ if ((strlen(pair->str) == bytes.size()) &&
+ !memcmp(pair->str, bytes.data(), bytes.size())) {
+ return pair->id;
+ }
+ pair++;
+ }
+ return notfound;
+}
diff --git a/src/lib/utils.h b/src/lib/utils.h
new file mode 100644
index 0000000..3035ee5
--- /dev/null
+++ b/src/lib/utils.h
@@ -0,0 +1,101 @@
+/*
+ * Copyright (c) 2017-2021 [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 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 RNP_UTILS_H_
+#define RNP_UTILS_H_
+
+#include <stdio.h>
+#include <limits.h>
+#include "logging.h"
+
+/* number of elements in an array */
+#define ARRAY_SIZE(a) (sizeof(a) / sizeof(*(a)))
+
+/*
+ * @params
+ * array: array of the structures to lookup
+ * id_field name of the field to compare against
+ * ret_field filed to return
+ * lookup_value lookup value
+ * ret return value
+ */
+#define ARRAY_LOOKUP_BY_ID(array, id_field, ret_field, lookup_value, ret) \
+ do { \
+ for (size_t i__ = 0; i__ < ARRAY_SIZE(array); i__++) { \
+ if ((array)[i__].id_field == (lookup_value)) { \
+ (ret) = (array)[i__].ret_field; \
+ break; \
+ } \
+ } \
+ } while (0)
+
+/* Portable way to convert bits to bytes */
+
+#define BITS_TO_BYTES(b) (((b) + (CHAR_BIT - 1)) / CHAR_BIT)
+
+/* Load little-endian 32-bit from y to x in portable fashion */
+
+inline void
+LOAD32LE(uint32_t &x, const uint8_t y[4])
+{
+ x = (static_cast<uint32_t>(y[3]) << 24) | (static_cast<uint32_t>(y[2]) << 16) |
+ (static_cast<uint32_t>(y[1]) << 8) | (static_cast<uint32_t>(y[0]) << 0);
+}
+
+/* Store big-endian 32-bit value x in y */
+inline void
+STORE32BE(uint8_t x[4], uint32_t y)
+{
+ x[0] = (uint8_t)(y >> 24) & 0xff;
+ x[1] = (uint8_t)(y >> 16) & 0xff;
+ x[2] = (uint8_t)(y >> 8) & 0xff;
+ x[3] = (uint8_t)(y >> 0) & 0xff;
+}
+
+/* Store big-endian 64-bit value x in y */
+inline void
+STORE64BE(uint8_t x[8], uint64_t y)
+{
+ x[0] = (uint8_t)(y >> 56) & 0xff;
+ x[1] = (uint8_t)(y >> 48) & 0xff;
+ x[2] = (uint8_t)(y >> 40) & 0xff;
+ x[3] = (uint8_t)(y >> 32) & 0xff;
+ x[4] = (uint8_t)(y >> 24) & 0xff;
+ x[5] = (uint8_t)(y >> 16) & 0xff;
+ x[6] = (uint8_t)(y >> 8) & 0xff;
+ x[7] = (uint8_t)(y >> 0) & 0xff;
+}
+
+inline char *
+getenv_logname(void)
+{
+ char *name = getenv("LOGNAME");
+ if (!name) {
+ name = getenv("USER");
+ }
+ return name;
+}
+
+#endif
diff --git a/src/lib/version.h.in b/src/lib/version.h.in
new file mode 100644
index 0000000..2382cfd
--- /dev/null
+++ b/src/lib/version.h.in
@@ -0,0 +1,52 @@
+/* Copyright (c) 2018 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.
+ */
+
+#define RNP_VERSION_MAJOR @RNP_VERSION_MAJOR@
+#define RNP_VERSION_MINOR @RNP_VERSION_MINOR@
+#define RNP_VERSION_PATCH @RNP_VERSION_PATCH@
+
+#define RNP_VERSION_STRING "@RNP_VERSION@"
+#define RNP_VERSION_STRING_FULL "@RNP_VERSION_FULL@"
+
+#define RNP_VERSION_COMMIT_TIMESTAMP @RNP_VERSION_COMMIT_TIMESTAMP@
+
+// using a 32-bit version with 10 bits per component
+#define RNP_VERSION_COMPONENT_MASK 0x3ff
+#define RNP_VERSION_MAJOR_SHIFT 20
+#define RNP_VERSION_MINOR_SHIFT 10
+#define RNP_VERSION_PATCH_SHIFT 0
+#define RNP_VERSION_CODE_FOR(major, minor, patch) \
+ (((major & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MAJOR_SHIFT) | \
+ ((minor & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_MINOR_SHIFT) | \
+ ((patch & RNP_VERSION_COMPONENT_MASK) << RNP_VERSION_PATCH_SHIFT))
+
+#define RNP_VERSION_CODE \
+ RNP_VERSION_CODE_FOR(RNP_VERSION_MAJOR, RNP_VERSION_MINOR, RNP_VERSION_PATCH)
+
+static_assert(RNP_VERSION_MAJOR <= RNP_VERSION_COMPONENT_MASK &&
+ RNP_VERSION_MINOR <= RNP_VERSION_COMPONENT_MASK &&
+ RNP_VERSION_PATCH <= RNP_VERSION_COMPONENT_MASK,
+ "version components must be within range");
+
diff --git a/src/librekey/g23_sexp.hpp b/src/librekey/g23_sexp.hpp
new file mode 100644
index 0000000..b888680
--- /dev/null
+++ b/src/librekey/g23_sexp.hpp
@@ -0,0 +1,71 @@
+/*
+ * Copyright (c) 2021, [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 RIBOSE, INC. AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * 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 RNP_G23_SEXP_HPP
+#define RNP_G23_SEXP_HPP
+
+#include "sexp/sexp.h"
+#include "sexp/ext-key-format.h"
+
+#define SXP_MAX_DEPTH 30
+
+class gnupg_sexp_t;
+typedef std::shared_ptr<gnupg_sexp_t> p_gnupg_sexp;
+
+class gnupg_sexp_t : public sexp::sexp_list_t {
+ /* write gnupg_sexp_t contents, adding padding, for the further encryption */
+ rnp::secure_vector<uint8_t> write_padded(size_t padblock) const;
+
+ public:
+ void
+ add(const std::string &str)
+ {
+ push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(str)));
+ };
+ void
+ add(const uint8_t *data, size_t size)
+ {
+ push_back(std::shared_ptr<sexp::sexp_string_t>(new sexp::sexp_string_t(data, size)));
+ };
+ void add(unsigned u);
+ p_gnupg_sexp add_sub();
+ 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::SecurityContext &ctx);
+ bool parse(const char *r_bytes, size_t r_length, size_t depth = 1);
+ bool write(pgp_dest_t &dst) const noexcept;
+};
+
+class gnupg_extended_private_key_t : public ext_key_format::extended_private_key_t {
+ public:
+ bool parse(const char *r_bytes, size_t r_length, size_t depth = 1);
+};
+
+#endif
diff --git a/src/librekey/kbx_blob.hpp b/src/librekey/kbx_blob.hpp
new file mode 100644
index 0000000..274413c
--- /dev/null
+++ b/src/librekey/kbx_blob.hpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2021, [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 RIBOSE, INC. AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * 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 RNP_KBX_BLOB_HPP
+#define RNP_KBX_BLOB_HPP
+
+typedef enum : uint8_t {
+ KBX_EMPTY_BLOB = 0,
+ KBX_HEADER_BLOB = 1,
+ KBX_PGP_BLOB = 2,
+ KBX_X509_BLOB = 3
+} kbx_blob_type_t;
+
+class kbx_blob_t {
+ protected:
+ kbx_blob_type_t type_;
+ std::vector<uint8_t> image_;
+
+ uint8_t ru8(size_t idx);
+ uint16_t ru16(size_t idx);
+ uint32_t ru32(size_t idx);
+
+ public:
+ virtual ~kbx_blob_t() = default;
+ kbx_blob_t(std::vector<uint8_t> &data);
+ virtual bool
+ parse()
+ {
+ return true;
+ };
+
+ kbx_blob_type_t
+ type()
+ {
+ return type_;
+ }
+
+ std::vector<uint8_t> &
+ image()
+ {
+ return image_;
+ }
+
+ uint32_t
+ length() const noexcept
+ {
+ return image_.size();
+ }
+};
+
+class kbx_header_blob_t : public kbx_blob_t {
+ protected:
+ uint8_t version_{};
+ uint16_t flags_{};
+ uint32_t file_created_at_{};
+ uint32_t last_maintenance_run_{};
+
+ public:
+ kbx_header_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){};
+ bool parse();
+
+ uint32_t
+ file_created_at()
+ {
+ return file_created_at_;
+ }
+};
+
+typedef struct {
+ uint8_t fp[PGP_FINGERPRINT_SIZE];
+ uint32_t keyid_offset;
+ uint16_t flags;
+} kbx_pgp_key_t;
+
+typedef struct {
+ uint32_t offset;
+ uint32_t length;
+ uint16_t flags;
+ uint8_t validity;
+} kbx_pgp_uid_t;
+
+typedef struct {
+ uint32_t expired;
+} kbx_pgp_sig_t;
+
+class kbx_pgp_blob_t : public kbx_blob_t {
+ protected:
+ uint8_t version_{};
+ uint16_t flags_{};
+ uint32_t keyblock_offset_{};
+ uint32_t keyblock_length_{};
+
+ std::vector<uint8_t> sn_{};
+ std::vector<kbx_pgp_key_t> keys_{};
+ std::vector<kbx_pgp_uid_t> uids_{};
+ std::vector<kbx_pgp_sig_t> sigs_{};
+
+ uint8_t ownertrust_{};
+ uint8_t all_validity_{};
+
+ uint32_t recheck_after_{};
+ uint32_t latest_timestamp_{};
+ uint32_t blob_created_at_{};
+
+ public:
+ kbx_pgp_blob_t(std::vector<uint8_t> &data) : kbx_blob_t(data){};
+
+ uint32_t
+ keyblock_offset()
+ {
+ return keyblock_offset_;
+ }
+
+ uint32_t
+ keyblock_length()
+ {
+ return keyblock_length_;
+ }
+
+ size_t
+ nkeys()
+ {
+ return keys_.size();
+ }
+ size_t
+ nuids()
+ {
+ return uids_.size();
+ }
+ size_t
+ nsigs()
+ {
+ return sigs_.size();
+ }
+
+ bool parse();
+};
+
+#endif
diff --git a/src/librekey/key_store_g10.cpp b/src/librekey/key_store_g10.cpp
new file mode 100644
index 0000000..dcf3fe1
--- /dev/null
+++ b/src/librekey/key_store_g10.cpp
@@ -0,0 +1,1243 @@
+/*
+ * 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:
+ * 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 <memory>
+#include <sstream>
+#include <string.h>
+#include <stdlib.h>
+#include <limits.h>
+#include <time.h>
+#include "config.h"
+
+#include <librepgp/stream-packet.h>
+#include "key_store_pgp.h"
+#include "key_store_g10.h"
+
+#include "crypto/common.h"
+#include "crypto/mem.h"
+#include "crypto/cipher.hpp"
+#include "pgp-key.h"
+#include "time-utils.h"
+
+#include "g23_sexp.hpp"
+using namespace ext_key_format;
+using namespace sexp;
+
+#define G10_CBC_IV_SIZE 16
+
+#define G10_OCB_NONCE_SIZE 12
+
+#define G10_SHA1_HASH_SIZE 20
+
+#define G10_PROTECTED_AT_SIZE 15
+
+typedef struct format_info {
+ pgp_symm_alg_t cipher;
+ pgp_cipher_mode_t cipher_mode;
+ pgp_hash_alg_t hash_alg;
+ size_t cipher_block_size;
+ const char * g10_type;
+ size_t iv_size;
+ size_t tag_length;
+ bool with_associated_data;
+ bool disable_padding;
+} format_info;
+
+static bool g10_calculated_hash(const pgp_key_pkt_t &key,
+ const char * protected_at,
+ uint8_t * checksum);
+
+static const format_info formats[] = {{PGP_SA_AES_128,
+ PGP_CIPHER_MODE_CBC,
+ PGP_HASH_SHA1,
+ 16,
+ "openpgp-s2k3-sha1-aes-cbc",
+ G10_CBC_IV_SIZE,
+ 0,
+ false,
+ true},
+ {PGP_SA_AES_256,
+ PGP_CIPHER_MODE_CBC,
+ PGP_HASH_SHA1,
+ 16,
+ "openpgp-s2k3-sha1-aes256-cbc",
+ G10_CBC_IV_SIZE,
+ 0,
+ false,
+ true},
+ {PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ PGP_HASH_SHA1,
+ 16,
+ "openpgp-s2k3-ocb-aes",
+ G10_OCB_NONCE_SIZE,
+ 16,
+ true,
+ true}};
+
+static const id_str_pair g10_alg_aliases[] = {
+ {PGP_PKA_RSA, "rsa"},
+ {PGP_PKA_RSA, "openpgp-rsa"},
+ {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"},
+ {PGP_PKA_RSA, "oid.1.2.840.113549.1.1.1"},
+ {PGP_PKA_ELGAMAL, "elg"},
+ {PGP_PKA_ELGAMAL, "elgamal"},
+ {PGP_PKA_ELGAMAL, "openpgp-elg"},
+ {PGP_PKA_ELGAMAL, "openpgp-elg-sig"},
+ {PGP_PKA_DSA, "dsa"},
+ {PGP_PKA_DSA, "openpgp-dsa"},
+ {PGP_PKA_ECDSA, "ecc"},
+ {PGP_PKA_ECDSA, "ecdsa"},
+ {PGP_PKA_ECDH, "ecdh"},
+ {PGP_PKA_EDDSA, "eddsa"},
+ {0, NULL},
+};
+
+static const id_str_pair g10_curve_aliases[] = {
+ {PGP_CURVE_NIST_P_256, "NIST P-256"},
+ {PGP_CURVE_NIST_P_256, "1.2.840.10045.3.1.7"},
+ {PGP_CURVE_NIST_P_256, "prime256v1"},
+ {PGP_CURVE_NIST_P_256, "secp256r1"},
+ {PGP_CURVE_NIST_P_256, "nistp256"},
+ {PGP_CURVE_NIST_P_384, "NIST P-384"},
+ {PGP_CURVE_NIST_P_384, "secp384r1"},
+ {PGP_CURVE_NIST_P_384, "1.3.132.0.34"},
+ {PGP_CURVE_NIST_P_384, "nistp384"},
+ {PGP_CURVE_NIST_P_521, "NIST P-521"},
+ {PGP_CURVE_NIST_P_521, "secp521r1"},
+ {PGP_CURVE_NIST_P_521, "1.3.132.0.35"},
+ {PGP_CURVE_NIST_P_521, "nistp521"},
+ {PGP_CURVE_25519, "Curve25519"},
+ {PGP_CURVE_25519, "1.3.6.1.4.1.3029.1.5.1"},
+ {PGP_CURVE_ED25519, "Ed25519"},
+ {PGP_CURVE_ED25519, "1.3.6.1.4.1.11591.15.1"},
+ {PGP_CURVE_BP256, "brainpoolP256r1"},
+ {PGP_CURVE_BP256, "1.3.36.3.3.2.8.1.1.7"},
+ {PGP_CURVE_BP384, "brainpoolP384r1"},
+ {PGP_CURVE_BP384, "1.3.36.3.3.2.8.1.1.11"},
+ {PGP_CURVE_BP512, "brainpoolP512r1"},
+ {PGP_CURVE_BP512, "1.3.36.3.3.2.8.1.1.13"},
+ {PGP_CURVE_P256K1, "secp256k1"},
+ {PGP_CURVE_P256K1, "1.3.132.0.10"},
+ {0, NULL},
+};
+
+static const id_str_pair g10_curve_names[] = {
+ {PGP_CURVE_NIST_P_256, "NIST P-256"},
+ {PGP_CURVE_NIST_P_384, "NIST P-384"},
+ {PGP_CURVE_NIST_P_521, "NIST P-521"},
+ {PGP_CURVE_ED25519, "Ed25519"},
+ {PGP_CURVE_25519, "Curve25519"},
+ {PGP_CURVE_BP256, "brainpoolP256r1"},
+ {PGP_CURVE_BP384, "brainpoolP384r1"},
+ {PGP_CURVE_BP512, "brainpoolP512r1"},
+ {PGP_CURVE_P256K1, "secp256k1"},
+ {0, NULL},
+};
+
+static const format_info *
+find_format(pgp_symm_alg_t cipher, pgp_cipher_mode_t mode, pgp_hash_alg_t hash_alg)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (formats[i].cipher == cipher && formats[i].cipher_mode == mode &&
+ formats[i].hash_alg == hash_alg) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+static const format_info *
+parse_format(const char *format, size_t format_len)
+{
+ for (size_t i = 0; i < ARRAY_SIZE(formats); i++) {
+ if (strlen(formats[i].g10_type) == format_len &&
+ !strncmp(formats[i].g10_type, format, format_len)) {
+ return &formats[i];
+ }
+ }
+ return NULL;
+}
+
+#define STR_HELPER(x) #x
+#define STR(x) STR_HELPER(x)
+
+void
+gnupg_sexp_t::add(unsigned u)
+{
+ char s[sizeof(STR(UINT_MAX)) + 1];
+ snprintf(s, sizeof(s), "%u", u);
+ push_back(std::make_shared<sexp_string_t>(s));
+}
+
+std::shared_ptr<gnupg_sexp_t>
+gnupg_sexp_t::add_sub()
+{
+ auto res = std::make_shared<gnupg_sexp_t>();
+ push_back(res);
+ return res;
+}
+
+/*
+ * Parse S-expression
+ * https://people.csail.mit.edu/rivest/Sexp.txt
+ * sexp library supports canonical and advanced transport formats
+ * as well as base64 encoding of canonical
+ */
+
+bool
+gnupg_sexp_t::parse(const char *r_bytes, size_t r_length, size_t depth)
+{
+ bool res = false;
+ std::istringstream iss(std::string(r_bytes, r_length));
+ try {
+ sexp_input_stream_t sis(&iss, depth);
+ sexp_list_t::parse(sis.set_byte_size(8)->get_char());
+ res = true;
+ } catch (sexp_exception_t &e) {
+ RNP_LOG("%s", e.what());
+ }
+ return res;
+}
+
+/*
+ * Parse gnupg extended private key file ("G23")
+ * https://github.com/gpg/gnupg/blob/main/agent/keyformat.txt
+ */
+
+bool
+gnupg_extended_private_key_t::parse(const char *r_bytes, size_t r_length, size_t depth)
+{
+ bool res = false;
+ std::istringstream iss(std::string(r_bytes, r_length));
+ try {
+ ext_key_input_stream_t g23_is(&iss, depth);
+ g23_is.scan(*this);
+ res = true;
+ } catch (sexp_exception_t &e) {
+ RNP_LOG("%s", e.what());
+ }
+ return res;
+}
+
+static const sexp_list_t *
+lookup_var(const sexp_list_t *list, const std::string &name) noexcept
+{
+ const sexp_list_t *res = nullptr;
+ // We are looking for a list element (condition 1)
+ // that:
+ // -- has at least two SEXP elements (condition 2)
+ // -- has a SEXP string at 0 postion (condition 3)
+ // matching given name (condition 4)
+ auto match = [name](const std::shared_ptr<sexp_object_t> &ptr) {
+ bool r = false;
+ auto r1 = ptr->sexp_list_view();
+ if (r1 && r1->size() >= 2) { // conditions (1) and (2)
+ auto r2 = r1->sexp_string_at(0);
+ if (r2 && r2 == name) // conditions (3) and (4)
+ r = true;
+ }
+ return r;
+ };
+ auto r3 = std::find_if(list->begin(), list->end(), match);
+ if (r3 == list->end())
+ RNP_LOG("Haven't got variable '%s'", name.c_str());
+ else
+ res = (*r3)->sexp_list_view();
+ return res;
+}
+
+static const sexp_string_t *
+lookup_var_data(const sexp_list_t *list, const std::string &name) noexcept
+{
+ const sexp_list_t *var = lookup_var(list, name);
+ if (!var) {
+ return NULL;
+ }
+
+ if (!var->at(1)->is_sexp_string()) {
+ RNP_LOG("Expected block value");
+ return NULL;
+ }
+
+ return var->sexp_string_at(1);
+}
+
+static bool
+read_mpi(const sexp_list_t *list, const std::string &name, pgp_mpi_t &val) noexcept
+{
+ const sexp_string_t *data = lookup_var_data(list, name);
+ if (!data) {
+ return false;
+ }
+
+ /* strip leading zero */
+ const auto &bytes = data->get_string();
+ if ((bytes.size() > 1) && !bytes[0] && (bytes[1] & 0x80)) {
+ return mem2mpi(&val, bytes.data() + 1, bytes.size() - 1);
+ }
+ return mem2mpi(&val, bytes.data(), bytes.size());
+}
+
+static bool
+read_curve(const sexp_list_t *list, const std::string &name, pgp_ec_key_t &key) noexcept
+{
+ const sexp_string_t *data = lookup_var_data(list, name);
+ if (!data) {
+ return false;
+ }
+
+ const auto &bytes = data->get_string();
+ pgp_curve_t curve = static_cast<pgp_curve_t>(
+ id_str_pair::lookup(g10_curve_aliases, data->get_string(), PGP_CURVE_UNKNOWN));
+ if (curve != PGP_CURVE_UNKNOWN) {
+ key.curve = curve;
+ return true;
+ }
+ RNP_LOG("Unknown curve: %.*s", (int) bytes.size(), (char *) bytes.data());
+ return false;
+}
+
+void
+gnupg_sexp_t::add_mpi(const std::string &name, const pgp_mpi_t &mpi)
+{
+ auto sub_s_exp = add_sub();
+ sub_s_exp->push_back(std::make_shared<sexp_string_t>(name));
+ auto value_block = std::make_shared<sexp_string_t>();
+ sub_s_exp->push_back(value_block);
+
+ sexp_simple_string_t data;
+ size_t len = mpi_bytes(&mpi);
+ size_t idx;
+
+ for (idx = 0; (idx < len) && !mpi.mpi[idx]; idx++)
+ ;
+
+ if (idx < len) {
+ if (mpi.mpi[idx] & 0x80) {
+ data.append(0);
+ data.std::basic_string<uint8_t>::append(mpi.mpi + idx, len - idx);
+ } else {
+ data.assign(mpi.mpi + idx, mpi.mpi + len);
+ }
+ value_block->set_string(data);
+ }
+}
+
+void
+gnupg_sexp_t::add_curve(const std::string &name, const pgp_ec_key_t &key)
+{
+ const char *curve = id_str_pair::lookup(g10_curve_names, key.curve, NULL);
+ if (!curve) {
+ RNP_LOG("unknown curve");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ auto psub_s_exp = add_sub();
+ psub_s_exp->add(name);
+ psub_s_exp->add(curve);
+
+ if ((key.curve != PGP_CURVE_ED25519) && (key.curve != PGP_CURVE_25519)) {
+ return;
+ }
+
+ psub_s_exp = add_sub();
+ psub_s_exp->add("flags");
+ psub_s_exp->add((key.curve == PGP_CURVE_ED25519) ? "eddsa" : "djb-tweak");
+}
+
+static bool
+parse_pubkey(pgp_key_pkt_t &pubkey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg)
+{
+ pubkey.version = PGP_V4;
+ pubkey.alg = alg;
+ pubkey.material.alg = alg;
+ switch (alg) {
+ case PGP_PKA_DSA:
+ if (!read_mpi(s_exp, "p", pubkey.material.dsa.p) ||
+ !read_mpi(s_exp, "q", pubkey.material.dsa.q) ||
+ !read_mpi(s_exp, "g", pubkey.material.dsa.g) ||
+ !read_mpi(s_exp, "y", pubkey.material.dsa.y)) {
+ return false;
+ }
+ break;
+
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!read_mpi(s_exp, "n", pubkey.material.rsa.n) ||
+ !read_mpi(s_exp, "e", pubkey.material.rsa.e)) {
+ return false;
+ }
+ break;
+
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!read_mpi(s_exp, "p", pubkey.material.eg.p) ||
+ !read_mpi(s_exp, "g", pubkey.material.eg.g) ||
+ !read_mpi(s_exp, "y", pubkey.material.eg.y)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ if (!read_curve(s_exp, "curve", pubkey.material.ec) ||
+ !read_mpi(s_exp, "q", pubkey.material.ec.p)) {
+ return false;
+ }
+ if (pubkey.material.ec.curve == PGP_CURVE_ED25519) {
+ /* need to adjust it here since 'ecc' key type defaults to ECDSA */
+ pubkey.alg = PGP_PKA_EDDSA;
+ pubkey.material.alg = PGP_PKA_EDDSA;
+ }
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) alg);
+ return false;
+ }
+
+ return true;
+}
+
+static bool
+parse_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *s_exp, pgp_pubkey_alg_t alg)
+{
+ switch (alg) {
+ case PGP_PKA_DSA:
+ if (!read_mpi(s_exp, "x", seckey.material.dsa.x)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!read_mpi(s_exp, "d", seckey.material.rsa.d) ||
+ !read_mpi(s_exp, "p", seckey.material.rsa.p) ||
+ !read_mpi(s_exp, "q", seckey.material.rsa.q) ||
+ !read_mpi(s_exp, "u", seckey.material.rsa.u)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!read_mpi(s_exp, "x", seckey.material.eg.x)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ if (!read_mpi(s_exp, "d", seckey.material.ec.x)) {
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) alg);
+ return false;
+ }
+
+ seckey.material.secret = true;
+ return true;
+}
+
+static bool
+decrypt_protected_section(const sexp_simple_string_t &encrypted_data,
+ const pgp_key_pkt_t & seckey,
+ const std::string & password,
+ gnupg_sexp_t & r_s_exp,
+ uint8_t * associated_data,
+ size_t associated_data_len)
+{
+ const format_info * info = NULL;
+ unsigned keysize = 0;
+ uint8_t derived_key[PGP_MAX_KEY_SIZE];
+ uint8_t * decrypted_data = NULL;
+ size_t decrypted_data_len = 0;
+ size_t output_written = 0;
+ size_t input_consumed = 0;
+ std::unique_ptr<Cipher> dec;
+ bool ret = false;
+
+ const char *decrypted_bytes;
+ size_t s_exp_len;
+
+ // sanity checks
+ const pgp_key_protection_t &prot = seckey.sec_protection;
+ keysize = pgp_key_size(prot.symm_alg);
+ if (!keysize) {
+ RNP_LOG("parse_seckey: unknown symmetric algo");
+ goto done;
+ }
+ // find the protection format in our table
+ info = find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg);
+ if (!info) {
+ RNP_LOG("Unsupported format, alg: %d, chiper_mode: %d, hash: %d",
+ prot.symm_alg,
+ prot.cipher_mode,
+ prot.s2k.hash_alg);
+ goto done;
+ }
+
+ // derive the key
+ if (pgp_s2k_iterated(prot.s2k.hash_alg,
+ derived_key,
+ keysize,
+ password.c_str(),
+ prot.s2k.salt,
+ prot.s2k.iterations)) {
+ RNP_LOG("pgp_s2k_iterated failed");
+ goto done;
+ }
+
+ // decrypt
+ decrypted_data = (uint8_t *) malloc(encrypted_data.size());
+ if (decrypted_data == NULL) {
+ RNP_LOG("can't allocate memory");
+ goto done;
+ }
+ dec = Cipher::decryption(
+ info->cipher, info->cipher_mode, info->tag_length, info->disable_padding);
+ if (!dec || !dec->set_key(derived_key, keysize)) {
+ goto done;
+ }
+ if (associated_data != nullptr && associated_data_len != 0) {
+ if (!dec->set_ad(associated_data, associated_data_len)) {
+ goto done;
+ }
+ }
+ // Nonce shall be the last chunk of associated data
+ if (!dec->set_iv(prot.iv, info->iv_size)) {
+ goto done;
+ }
+ if (!dec->finish(decrypted_data,
+ encrypted_data.size(),
+ &output_written,
+ encrypted_data.data(),
+ encrypted_data.size(),
+ &input_consumed)) {
+ goto done;
+ }
+ decrypted_data_len = output_written;
+ s_exp_len = decrypted_data_len;
+ decrypted_bytes = (const char *) decrypted_data;
+
+ // parse and validate the decrypted s-exp
+
+ if (!r_s_exp.parse(decrypted_bytes, s_exp_len, SXP_MAX_DEPTH)) {
+ goto done;
+ }
+ if (!r_s_exp.size() || r_s_exp.at(0)->is_sexp_string()) {
+ RNP_LOG("Hasn't got sub s-exp with key data.");
+ goto done;
+ }
+ ret = true;
+done:
+ if (!ret) {
+ r_s_exp.clear();
+ }
+ secure_clear(decrypted_data, decrypted_data_len);
+ free(decrypted_data);
+ return ret;
+}
+
+static bool
+parse_protected_seckey(pgp_key_pkt_t &seckey, const sexp_list_t *list, const char *password)
+{
+ // find and validate the protected section
+ const sexp_list_t *protected_key = lookup_var(list, "protected");
+ if (!protected_key) {
+ RNP_LOG("missing protected section");
+ return false;
+ }
+ if (protected_key->size() != 4 || !protected_key->at(1)->is_sexp_string() ||
+ protected_key->at(2)->is_sexp_string() || !protected_key->at(3)->is_sexp_string()) {
+ RNP_LOG("Wrong protected format, expected: (protected mode (params) "
+ "encrypted_octet_string)\n");
+ return false;
+ }
+
+ // lookup the protection format
+ auto & fmt_bt = protected_key->sexp_string_at(1)->get_string();
+ const format_info *format = parse_format((const char *) fmt_bt.data(), fmt_bt.size());
+ if (!format) {
+ RNP_LOG("Unsupported protected mode: '%.*s'\n",
+ (int) fmt_bt.size(),
+ (const char *) fmt_bt.data());
+ return false;
+ }
+
+ // fill in some fields based on the lookup above
+ pgp_key_protection_t &prot = seckey.sec_protection;
+ prot.symm_alg = format->cipher;
+ prot.cipher_mode = format->cipher_mode;
+ prot.s2k.hash_alg = format->hash_alg;
+
+ // locate and validate the protection parameters
+ auto params = protected_key->sexp_list_at(2);
+ if (params->size() != 2 || params->at(0)->is_sexp_string() ||
+ !params->at(1)->is_sexp_string()) {
+ RNP_LOG("Wrong params format, expected: ((hash salt no_of_iterations) iv)\n");
+ return false;
+ }
+
+ // locate and validate the (hash salt no_of_iterations) exp
+ auto alg = params->sexp_list_at(0);
+ if (alg->size() != 3 || !alg->at(0)->is_sexp_string() || !alg->at(1)->is_sexp_string() ||
+ !alg->at(2)->is_sexp_string()) {
+ RNP_LOG("Wrong params sub-level format, expected: (hash salt no_of_iterations)\n");
+ return false;
+ }
+ auto &hash_bt = alg->sexp_string_at(0)->get_string();
+ if (hash_bt != "sha1") {
+ RNP_LOG("Wrong hashing algorithm, should be sha1 but %.*s\n",
+ (int) hash_bt.size(),
+ (const char *) hash_bt.data());
+ return false;
+ }
+
+ // fill in some constant values
+ prot.s2k.hash_alg = PGP_HASH_SHA1;
+ prot.s2k.usage = PGP_S2KU_ENCRYPTED_AND_HASHED;
+ prot.s2k.specifier = PGP_S2KS_ITERATED_AND_SALTED;
+
+ // check salt size
+ auto &salt_bt = alg->sexp_string_at(1)->get_string();
+ if (salt_bt.size() != PGP_SALT_SIZE) {
+ RNP_LOG("Wrong salt size, should be %d but %d\n", PGP_SALT_SIZE, (int) salt_bt.size());
+ return false;
+ }
+
+ // salt
+ memcpy(prot.s2k.salt, salt_bt.data(), salt_bt.size());
+ // s2k iterations
+ auto iter = alg->sexp_string_at(2);
+ prot.s2k.iterations = iter->as_unsigned();
+ if (prot.s2k.iterations == UINT_MAX) {
+ RNP_LOG("Wrong numbers of iteration, %.*s\n",
+ (int) iter->get_string().size(),
+ (const char *) iter->get_string().data());
+ return false;
+ }
+
+ // iv
+ auto &iv_bt = params->sexp_string_at(1)->get_string();
+ if (iv_bt.size() != format->iv_size) {
+ RNP_LOG("Wrong nonce size, should be %zu but %zu\n", format->iv_size, iv_bt.size());
+ return false;
+ }
+ memcpy(prot.iv, iv_bt.data(), iv_bt.size());
+
+ // we're all done if no password was provided (decryption not requested)
+ if (!password) {
+ seckey.material.secret = false;
+ return true;
+ }
+
+ // password was provided, so decrypt
+ auto & enc_bt = protected_key->sexp_string_at(3)->get_string();
+ gnupg_sexp_t decrypted_s_exp;
+
+ // Build associated data (AD) that is not included in the ciphertext but that should be
+ // authenticated. gnupg builds AD as follows (file 'protect.c' do_encryption/do_decryption
+ // functions)
+ // -- "protected-private-key" section content
+ // -- less "protected" subsection
+ // -- serialized in canonical format
+ std::string associated_data;
+ if (format->with_associated_data) {
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ os.var_put_char('(');
+ for_each(list->begin(), list->end(), [&](const std::shared_ptr<sexp_object_t> &obj) {
+ if (obj->sexp_list_view() != protected_key)
+ obj->print_canonical(&os);
+ });
+ os.var_put_char(')');
+ associated_data = oss.str();
+ }
+
+ if (!decrypt_protected_section(
+ enc_bt,
+ seckey,
+ password,
+ decrypted_s_exp,
+ format->with_associated_data ? (uint8_t *) associated_data.data() : nullptr,
+ format->with_associated_data ? associated_data.length() : 0)) {
+ return false;
+ }
+ // see if we have a protected-at section
+ char protected_at[G10_PROTECTED_AT_SIZE] = {0};
+ auto protected_at_data = lookup_var_data(list, "protected-at");
+ if (protected_at_data) {
+ if (protected_at_data->get_string().size() != G10_PROTECTED_AT_SIZE) {
+ RNP_LOG("protected-at has wrong length: %zu, expected, %d\n",
+ protected_at_data->get_string().size(),
+ G10_PROTECTED_AT_SIZE);
+ return false;
+ }
+ memcpy(protected_at,
+ protected_at_data->get_string().data(),
+ protected_at_data->get_string().size());
+ }
+ // parse MPIs
+ if (!parse_seckey(seckey, decrypted_s_exp.sexp_list_at(0), seckey.alg)) {
+ RNP_LOG("failed to parse seckey");
+ return false;
+ }
+ // check hash, if present
+ if (decrypted_s_exp.size() > 1) {
+ if (decrypted_s_exp.at(1)->is_sexp_string()) {
+ RNP_LOG("Wrong hash block type.");
+ return false;
+ }
+ auto sub_el = decrypted_s_exp.sexp_list_at(1);
+ if (sub_el->size() < 3 || !sub_el->at(0)->is_sexp_string() ||
+ !sub_el->at(1)->is_sexp_string() || !sub_el->at(2)->is_sexp_string()) {
+ RNP_LOG("Wrong hash block structure.");
+ return false;
+ }
+
+ auto &hkey = sub_el->sexp_string_at(0)->get_string();
+ if (hkey != "hash") {
+ RNP_LOG("Has got wrong hash block at encrypted key data.");
+ return false;
+ }
+ auto &halg = sub_el->sexp_string_at(1)->get_string();
+ if (halg != "sha1") {
+ RNP_LOG("Supported only sha1 hash at encrypted private key.");
+ return false;
+ }
+ uint8_t checkhash[G10_SHA1_HASH_SIZE];
+ if (!g10_calculated_hash(seckey, protected_at, checkhash)) {
+ RNP_LOG("failed to calculate hash");
+ return false;
+ }
+ auto &hval = sub_el->sexp_string_at(2)->get_string();
+ if (hval.size() != G10_SHA1_HASH_SIZE ||
+ memcmp(checkhash, hval.data(), G10_SHA1_HASH_SIZE)) {
+ RNP_LOG("Incorrect hash at encrypted private key.");
+ return false;
+ }
+ }
+ seckey.material.secret = true;
+ return true;
+}
+
+static bool
+g23_parse_seckey(pgp_key_pkt_t &seckey,
+ const uint8_t *data,
+ size_t data_len,
+ const char * password)
+{
+ gnupg_extended_private_key_t g23_extended_key;
+
+ const char *bytes = (const char *) data;
+ if (!g23_extended_key.parse(bytes, data_len, SXP_MAX_DEPTH)) {
+ RNP_LOG("Failed to parse s-exp.");
+ return false;
+ }
+ // Although the library parses full g23 extended key
+ // we extract and use g10 part only
+ const sexp_list_t &g10_key = g23_extended_key.key;
+
+ /* expected format:
+ * (<type>
+ * (<algo>
+ * (x <mpi>)
+ * (y <mpi>)
+ * )
+ * )
+ */
+
+ if (g10_key.size() != 2 || !g10_key.at(0)->is_sexp_string() ||
+ !g10_key.at(1)->is_sexp_list()) {
+ RNP_LOG("Wrong format, expected: (<type> (...))");
+ return false;
+ }
+
+ bool is_protected = false;
+
+ auto &name = g10_key.sexp_string_at(0)->get_string();
+ if (name == "private-key") {
+ is_protected = false;
+ } else if (name == "protected-private-key") {
+ is_protected = true;
+ } else {
+ RNP_LOG("Unsupported top-level block: '%.*s'",
+ (int) name.size(),
+ (const char *) name.data());
+ return false;
+ }
+
+ auto alg_s_exp = g10_key.sexp_list_at(1);
+ if (alg_s_exp->size() < 2) {
+ RNP_LOG("Wrong count of algorithm-level elements: %zu", alg_s_exp->size());
+ return false;
+ }
+
+ if (!alg_s_exp->at(0)->is_sexp_string()) {
+ RNP_LOG("Expected block with algorithm name, but has s-exp");
+ return false;
+ }
+
+ auto & alg_bt = alg_s_exp->sexp_string_at(0)->get_string();
+ pgp_pubkey_alg_t alg = static_cast<pgp_pubkey_alg_t>(
+ id_str_pair::lookup(g10_alg_aliases, alg_bt.c_str(), PGP_PKA_NOTHING));
+ if (alg == PGP_PKA_NOTHING) {
+ RNP_LOG(
+ "Unsupported algorithm: '%.*s'", (int) alg_bt.size(), (const char *) alg_bt.data());
+ return false;
+ }
+
+ bool ret = false;
+ if (!parse_pubkey(seckey, alg_s_exp, alg)) {
+ RNP_LOG("failed to parse pubkey");
+ goto done;
+ }
+
+ if (is_protected) {
+ if (!parse_protected_seckey(seckey, alg_s_exp, password)) {
+ goto done;
+ }
+ } else {
+ seckey.sec_protection.s2k.usage = PGP_S2KU_NONE;
+ seckey.sec_protection.symm_alg = PGP_SA_PLAINTEXT;
+ seckey.sec_protection.s2k.hash_alg = PGP_HASH_UNKNOWN;
+ if (!parse_seckey(seckey, alg_s_exp, alg)) {
+ RNP_LOG("failed to parse seckey");
+ goto done;
+ }
+ }
+ ret = true;
+done:
+ if (!ret) {
+ seckey = pgp_key_pkt_t();
+ }
+ return ret;
+}
+
+pgp_key_pkt_t *
+g10_decrypt_seckey(const pgp_rawpacket_t &raw,
+ const pgp_key_pkt_t & pubkey,
+ const char * password)
+{
+ if (!password) {
+ return NULL;
+ }
+ auto seckey = std::unique_ptr<pgp_key_pkt_t>(new pgp_key_pkt_t(pubkey, false));
+ if (!g23_parse_seckey(*seckey, raw.raw.data(), raw.raw.size(), password)) {
+ return NULL;
+ }
+ /* g10 has the same 'ecc' algo for ECDSA/ECDH/EDDSA. Probably should be better place to fix
+ * this. */
+ seckey->alg = pubkey.alg;
+ seckey->material.alg = pubkey.material.alg;
+ return seckey.release();
+}
+
+static bool
+copy_secret_fields(pgp_key_pkt_t &dst, const pgp_key_pkt_t &src)
+{
+ switch (src.alg) {
+ case PGP_PKA_DSA:
+ dst.material.dsa.x = src.material.dsa.x;
+ break;
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ dst.material.rsa.d = src.material.rsa.d;
+ dst.material.rsa.p = src.material.rsa.p;
+ dst.material.rsa.q = src.material.rsa.q;
+ dst.material.rsa.u = src.material.rsa.u;
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ dst.material.eg.x = src.material.eg.x;
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ dst.material.ec.x = src.material.ec.x;
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) src.alg);
+ return false;
+ }
+
+ dst.material.secret = src.material.secret;
+ dst.sec_protection = src.sec_protection;
+ dst.tag = is_subkey_pkt(dst.tag) ? PGP_PKT_SECRET_SUBKEY : PGP_PKT_SECRET_KEY;
+ return true;
+}
+
+bool
+rnp_key_store_g10_from_src(rnp_key_store_t * key_store,
+ pgp_source_t * src,
+ const pgp_key_provider_t *key_provider)
+{
+ 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 (!g23_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;
+ }
+
+ const pgp_key_t *pubkey = pgp_request_key(key_provider, &req_ctx);
+ if (!pubkey) {
+ return false;
+ }
+
+ /* 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("%s", e.what());
+ return false;
+ }
+}
+
+/*
+ * Write G10 S-exp to buffer
+ *
+ * Supported format: (1:a2:ab(3:asd1:a))
+ */
+bool
+gnupg_sexp_t::write(pgp_dest_t &dst) const noexcept
+{
+ bool res = false;
+ try {
+ std::ostringstream oss(std::ios_base::binary);
+ sexp_output_stream_t os(&oss);
+ print_canonical(&os);
+ const std::string &s = oss.str();
+ const char * ss = s.c_str();
+ dst_write(&dst, ss, s.size());
+ res = (dst.werr == RNP_SUCCESS);
+
+ } catch (...) {
+ }
+
+ return res;
+}
+
+void
+gnupg_sexp_t::add_pubkey(const pgp_key_pkt_t &key)
+{
+ switch (key.alg) {
+ case PGP_PKA_DSA:
+ add("dsa");
+ add_mpi("p", key.material.dsa.p);
+ add_mpi("q", key.material.dsa.q);
+ add_mpi("g", key.material.dsa.g);
+ add_mpi("y", key.material.dsa.y);
+ break;
+ case PGP_PKA_RSA_SIGN_ONLY:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA:
+ add("rsa");
+ add_mpi("n", key.material.rsa.n);
+ add_mpi("e", key.material.rsa.e);
+ break;
+ case PGP_PKA_ELGAMAL:
+ add("elg");
+ add_mpi("p", key.material.eg.p);
+ add_mpi("g", key.material.eg.g);
+ add_mpi("y", key.material.eg.y);
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA:
+ add("ecc");
+ add_curve("curve", key.material.ec);
+ add_mpi("q", key.material.ec.p);
+ break;
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+}
+
+void
+gnupg_sexp_t::add_seckey(const pgp_key_pkt_t &key)
+{
+ switch (key.alg) {
+ case PGP_PKA_DSA:
+ add_mpi("x", key.material.dsa.x);
+ break;
+ case PGP_PKA_RSA_SIGN_ONLY:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA:
+ add_mpi("d", key.material.rsa.d);
+ add_mpi("p", key.material.rsa.p);
+ add_mpi("q", key.material.rsa.q);
+ add_mpi("u", key.material.rsa.u);
+ break;
+ case PGP_PKA_ELGAMAL:
+ add_mpi("x", key.material.eg.x);
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_ECDH:
+ case PGP_PKA_EDDSA: {
+ add_mpi("d", key.material.ec.x);
+ break;
+ }
+ default:
+ RNP_LOG("Unsupported public key algorithm: %d", (int) key.alg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+}
+
+rnp::secure_vector<uint8_t>
+gnupg_sexp_t::write_padded(size_t padblock) const
+{
+ 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);
+ }
+
+ // 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<uint8_t>(mem, mem + raw.writeb());
+}
+
+void
+gnupg_sexp_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);
+ }
+ const format_info *format =
+ find_format(prot.symm_alg, prot.cipher_mode, prot.s2k.hash_alg);
+ if (!format) {
+ RNP_LOG("Unknown protection format.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ // randomize IV and salt
+ ctx.rng.get(prot.iv, sizeof(prot.iv));
+ ctx.rng.get(prot.s2k.salt, sizeof(prot.s2k.salt));
+
+ // write seckey
+ gnupg_sexp_t raw_s_exp;
+ auto psub_s_exp = raw_s_exp.add_sub();
+ psub_s_exp->add_seckey(seckey);
+
+ // calculate hash
+ 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?
+ struct tm tm = {};
+ rnp_gmtime(ctx.time(), tm);
+ strftime(protected_at, sizeof(protected_at), "%Y%m%dT%H%M%S", &tm);
+ if (!g10_calculated_hash(seckey, protected_at, checksum)) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ psub_s_exp = raw_s_exp.add_sub();
+ psub_s_exp->add("hash");
+ psub_s_exp->add("sha1");
+ psub_s_exp->add(checksum, sizeof(checksum));
+
+ /* write raw secret key to the memory */
+ rnp::secure_vector<uint8_t> rawkey = raw_s_exp.write_padded(format->cipher_block_size);
+
+ /* derive encrypting key */
+ unsigned keysize = pgp_key_size(prot.symm_alg);
+ if (!keysize) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> derived_key;
+ if (pgp_s2k_iterated(format->hash_alg,
+ derived_key.data(),
+ keysize,
+ password.c_str(),
+ prot.s2k.salt,
+ prot.s2k.iterations)) {
+ RNP_LOG("s2k key derivation failed");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ /* encrypt raw key */
+ std::unique_ptr<Cipher> enc(
+ Cipher::encryption(format->cipher, format->cipher_mode, 0, true));
+ if (!enc || !enc->set_key(derived_key.data(), keysize) ||
+ !enc->set_iv(prot.iv, format->iv_size)) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ size_t output_written, input_consumed;
+ std::vector<uint8_t> enckey(rawkey.size());
+
+ if (!enc->finish(enckey.data(),
+ enckey.size(),
+ &output_written,
+ rawkey.data(),
+ rawkey.size(),
+ &input_consumed)) {
+ RNP_LOG("Encryption failed");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ /* build s_exp with encrypted key */
+ psub_s_exp = add_sub();
+ psub_s_exp->add("protected");
+ psub_s_exp->add(format->g10_type);
+ /* protection params: s2k, iv */
+ auto psub_sub_s_exp = psub_s_exp->add_sub();
+ /* s2k params: hash, salt, iterations */
+ auto psub_sub_sub_s_exp = psub_sub_s_exp->add_sub();
+ psub_sub_sub_s_exp->add("sha1");
+ psub_sub_sub_s_exp->add(prot.s2k.salt, PGP_SALT_SIZE);
+ psub_sub_sub_s_exp->add(prot.s2k.iterations);
+ psub_sub_s_exp->add(prot.iv, format->iv_size);
+ /* encrypted key data itself */
+ psub_s_exp->add(enckey.data(), enckey.size());
+ /* protected-at */
+ psub_s_exp = add_sub();
+ 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::SecurityContext &ctx)
+{
+ bool is_protected = true;
+
+ switch (seckey->sec_protection.s2k.usage) {
+ case PGP_S2KU_NONE:
+ is_protected = false;
+ break;
+ case PGP_S2KU_ENCRYPTED_AND_HASHED:
+ is_protected = true;
+ // TODO: these are forced for now, until openpgp-native is implemented
+ seckey->sec_protection.symm_alg = PGP_SA_AES_128;
+ seckey->sec_protection.cipher_mode = PGP_CIPHER_MODE_CBC;
+ seckey->sec_protection.s2k.hash_alg = PGP_HASH_SHA1;
+ break;
+ default:
+ RNP_LOG("unsupported s2k usage");
+ return false;
+ }
+
+ try {
+ gnupg_sexp_t s_exp;
+ s_exp.add(is_protected ? "protected-private-key" : "private-key");
+ auto pkey = s_exp.add_sub();
+ pkey->add_pubkey(*seckey);
+
+ if (is_protected) {
+ pkey->add_protected_seckey(*seckey, password, ctx);
+ } else {
+ pkey->add_seckey(*seckey);
+ }
+ return s_exp.write(*dst) && !dst->werr;
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to write g10 key: %s", e.what());
+ return false;
+ }
+}
+
+static bool
+g10_calculated_hash(const pgp_key_pkt_t &key, const char *protected_at, uint8_t *checksum)
+{
+ try {
+ /* populate s_exp */
+ gnupg_sexp_t s_exp;
+ s_exp.add_pubkey(key);
+ s_exp.add_seckey(key);
+ auto 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 */
+ rnp::MemoryDest memdst;
+ memdst.set_secure(true);
+ if (!s_exp.write(memdst.dst())) {
+ RNP_LOG("Failed to write s_exp");
+ return false;
+ }
+ 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());
+ return false;
+ }
+}
+
+bool
+rnp_key_store_gnupg_sexp_to_dst(pgp_key_t *key, pgp_dest_t *dest)
+{
+ if (key->format != PGP_KEY_STORE_G10) {
+ RNP_LOG("incorrect format: %d", key->format);
+ return false;
+ }
+ pgp_rawpacket_t &packet = key->rawpkt();
+ dst_write(dest, packet.raw.data(), packet.raw.size());
+ return dest->werr == RNP_SUCCESS;
+}
diff --git a/src/librekey/key_store_g10.h b/src/librekey/key_store_g10.h
new file mode 100644
index 0000000..f770628
--- /dev/null
+++ b/src/librekey/key_store_g10.h
@@ -0,0 +1,42 @@
+/*
+ * Copyright (c) 2017, [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 RIBOSE, INC. AND CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * 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 RNP_KEY_STORE_G10_H
+#define RNP_KEY_STORE_G10_H
+
+#include <rekey/rnp_key_store.h>
+
+bool rnp_key_store_g10_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *);
+bool rnp_key_store_gnupg_sexp_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::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/src/librekey/key_store_kbx.cpp b/src/librekey/key_store_kbx.cpp
new file mode 100644
index 0000000..bc504f6
--- /dev/null
+++ b/src/librekey/key_store_kbx.cpp
@@ -0,0 +1,706 @@
+/*
+ * 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:
+ * 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 <sys/types.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <stdint.h>
+#include <time.h>
+#include <inttypes.h>
+#include <cassert>
+
+#include "key_store_pgp.h"
+#include "key_store_kbx.h"
+#include "pgp-key.h"
+#include <librepgp/stream-sig.h>
+
+/* same limit with GnuPG 2.1 */
+#define BLOB_SIZE_LIMIT (5 * 1024 * 1024)
+/* limit the number of keys/sigs/uids in the blob */
+#define BLOB_OBJ_LIMIT 0x8000
+
+#define BLOB_HEADER_SIZE 0x5
+#define BLOB_FIRST_SIZE 0x20
+#define BLOB_KEY_SIZE 0x1C
+#define BLOB_UID_SIZE 0x0C
+#define BLOB_SIG_SIZE 0x04
+#define BLOB_VALIDITY_SIZE 0x10
+
+uint8_t
+kbx_blob_t::ru8(size_t idx)
+{
+ return image_[idx];
+}
+
+uint16_t
+kbx_blob_t::ru16(size_t idx)
+{
+ return read_uint16(image_.data() + idx);
+}
+
+uint32_t
+kbx_blob_t::ru32(size_t idx)
+{
+ return read_uint32(image_.data() + idx);
+}
+
+kbx_blob_t::kbx_blob_t(std::vector<uint8_t> &data)
+{
+ if (data.size() < BLOB_HEADER_SIZE) {
+ RNP_LOG("Too small KBX blob.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ uint32_t len = read_uint32(data.data());
+ if (len > BLOB_SIZE_LIMIT) {
+ RNP_LOG("Too large KBX blob.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ if (len != data.size()) {
+ RNP_LOG("KBX blob size mismatch.");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ image_ = data;
+ type_ = (kbx_blob_type_t) ru8(4);
+}
+
+bool
+kbx_header_blob_t::parse()
+{
+ if (length() != BLOB_FIRST_SIZE) {
+ RNP_LOG("The first blob has wrong length: %" PRIu32 " but expected %d",
+ length(),
+ (int) BLOB_FIRST_SIZE);
+ return false;
+ }
+
+ size_t idx = BLOB_HEADER_SIZE;
+ version_ = ru8(idx++);
+ if (version_ != 1) {
+ RNP_LOG("Wrong version, expect 1 but has %" PRIu8, version_);
+ return false;
+ }
+
+ flags_ = ru16(idx);
+ idx += 2;
+
+ // blob should contains a magic KBXf
+ if (memcmp(image_.data() + idx, "KBXf", 4)) {
+ RNP_LOG("The first blob hasn't got a KBXf magic string");
+ return false;
+ }
+ idx += 4;
+ // RFU
+ idx += 4;
+ // File creation time
+ file_created_at_ = ru32(idx);
+ idx += 4;
+ // Duplicated?
+ file_created_at_ = ru32(idx);
+ // RFU +4 bytes
+ // RFU +4 bytes
+ return true;
+}
+
+bool
+kbx_pgp_blob_t::parse()
+{
+ if (image_.size() < 15 + BLOB_HEADER_SIZE) {
+ RNP_LOG("Too few data in the blob.");
+ return false;
+ }
+
+ size_t idx = BLOB_HEADER_SIZE;
+ /* version */
+ version_ = ru8(idx++);
+ if (version_ != 1) {
+ RNP_LOG("Wrong version: %" PRIu8, version_);
+ return false;
+ }
+ /* flags */
+ flags_ = ru16(idx);
+ idx += 2;
+ /* keyblock offset */
+ keyblock_offset_ = ru32(idx);
+ idx += 4;
+ /* keyblock length */
+ keyblock_length_ = ru32(idx);
+ idx += 4;
+
+ if ((keyblock_offset_ > image_.size()) ||
+ (keyblock_offset_ > (UINT32_MAX - keyblock_length_)) ||
+ (image_.size() < (keyblock_offset_ + keyblock_length_))) {
+ RNP_LOG("Wrong keyblock offset/length, blob size: %zu"
+ ", keyblock offset: %" PRIu32 ", length: %" PRIu32,
+ image_.size(),
+ keyblock_offset_,
+ keyblock_length_);
+ return false;
+ }
+ /* number of key blocks */
+ size_t nkeys = ru16(idx);
+ idx += 2;
+ if (nkeys < 1) {
+ RNP_LOG("PGP blob should contains at least 1 key");
+ return false;
+ }
+ if (nkeys > BLOB_OBJ_LIMIT) {
+ RNP_LOG("Too many keys in the PGP blob");
+ return false;
+ }
+
+ /* Size of the single key record */
+ size_t keys_len = ru16(idx);
+ idx += 2;
+ if (keys_len < BLOB_KEY_SIZE) {
+ RNP_LOG(
+ "PGP blob needs %d bytes, but contains: %zu bytes", (int) BLOB_KEY_SIZE, keys_len);
+ return false;
+ }
+
+ for (size_t i = 0; i < nkeys; i++) {
+ if (image_.size() - idx < keys_len) {
+ RNP_LOG("Too few bytes left for key blob");
+ return false;
+ }
+
+ kbx_pgp_key_t nkey = {};
+ /* copy fingerprint */
+ memcpy(nkey.fp, &image_[idx], 20);
+ idx += 20;
+ /* keyid offset */
+ nkey.keyid_offset = ru32(idx);
+ idx += 4;
+ /* flags */
+ nkey.flags = ru16(idx);
+ idx += 2;
+ /* RFU */
+ idx += 2;
+ /* skip padding bytes if it existed */
+ idx += keys_len - BLOB_KEY_SIZE;
+ keys_.push_back(std::move(nkey));
+ }
+
+ if (image_.size() - idx < 2) {
+ RNP_LOG("No data for sn_size");
+ return false;
+ }
+ size_t sn_size = ru16(idx);
+ idx += 2;
+
+ if (image_.size() - idx < sn_size) {
+ RNP_LOG("SN is %zu, while bytes left are %zu", sn_size, image_.size() - idx);
+ return false;
+ }
+
+ if (sn_size) {
+ sn_ = {image_.begin() + idx, image_.begin() + idx + sn_size};
+ idx += sn_size;
+ }
+
+ if (image_.size() - idx < 4) {
+ RNP_LOG("Too few data for uids");
+ return false;
+ }
+ size_t nuids = ru16(idx);
+ if (nuids > BLOB_OBJ_LIMIT) {
+ RNP_LOG("Too many uids in the PGP blob");
+ return false;
+ }
+
+ size_t uids_len = ru16(idx + 2);
+ idx += 4;
+
+ if (uids_len < BLOB_UID_SIZE) {
+ RNP_LOG("Too few bytes for uid struct: %zu", uids_len);
+ return false;
+ }
+
+ for (size_t i = 0; i < nuids; i++) {
+ if (image_.size() - idx < uids_len) {
+ RNP_LOG("Too few bytes to read uid struct.");
+ return false;
+ }
+ kbx_pgp_uid_t nuid = {};
+ /* offset */
+ nuid.offset = ru32(idx);
+ idx += 4;
+ /* length */
+ nuid.length = ru32(idx);
+ idx += 4;
+ /* flags */
+ nuid.flags = ru16(idx);
+ idx += 2;
+ /* validity */
+ nuid.validity = ru8(idx);
+ idx++;
+ /* RFU */
+ idx++;
+ // skip padding bytes if it existed
+ idx += uids_len - BLOB_UID_SIZE;
+
+ uids_.push_back(std::move(nuid));
+ }
+
+ if (image_.size() - idx < 4) {
+ RNP_LOG("No data left for sigs");
+ return false;
+ }
+
+ size_t nsigs = ru16(idx);
+ if (nsigs > BLOB_OBJ_LIMIT) {
+ RNP_LOG("Too many sigs in the PGP blob");
+ return false;
+ }
+
+ size_t sigs_len = ru16(idx + 2);
+ idx += 4;
+
+ if (sigs_len < BLOB_SIG_SIZE) {
+ RNP_LOG("Too small SIGN structure: %zu", uids_len);
+ return false;
+ }
+
+ for (size_t i = 0; i < nsigs; i++) {
+ if (image_.size() - idx < sigs_len) {
+ RNP_LOG("Too few data for sig");
+ return false;
+ }
+
+ kbx_pgp_sig_t nsig = {};
+ nsig.expired = ru32(idx);
+ idx += 4;
+
+ // skip padding bytes if it existed
+ idx += (sigs_len - BLOB_SIG_SIZE);
+
+ sigs_.push_back(nsig);
+ }
+
+ if (image_.size() - idx < BLOB_VALIDITY_SIZE) {
+ RNP_LOG("Too few data for trust/validities");
+ return false;
+ }
+
+ ownertrust_ = ru8(idx);
+ idx++;
+ all_validity_ = ru8(idx);
+ idx++;
+ // RFU
+ idx += 2;
+ recheck_after_ = ru32(idx);
+ idx += 4;
+ latest_timestamp_ = ru32(idx);
+ idx += 4;
+ blob_created_at_ = ru32(idx);
+ // do not forget to idx += 4 on further expansion
+
+ // here starts keyblock, UID and reserved space for future usage
+
+ // Maybe we should add checksum verify but GnuPG never checked it
+ // Checksum is last 20 bytes of blob and it is SHA-1, if it invalid MD5 and starts from 4
+ // zero it is MD5.
+
+ return true;
+}
+
+static std::unique_ptr<kbx_blob_t>
+rnp_key_store_kbx_parse_blob(const uint8_t *image, size_t image_len)
+{
+ std::unique_ptr<kbx_blob_t> blob;
+ // a blob shouldn't be less of length + type
+ if (image_len < BLOB_HEADER_SIZE) {
+ RNP_LOG("Blob size is %zu but it shouldn't be less of header", image_len);
+ return blob;
+ }
+
+ try {
+ std::vector<uint8_t> data(image, image + image_len);
+ kbx_blob_type_t type = (kbx_blob_type_t) image[4];
+
+ switch (type) {
+ case KBX_EMPTY_BLOB:
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data));
+ break;
+ case KBX_HEADER_BLOB:
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_header_blob_t(data));
+ break;
+ case KBX_PGP_BLOB:
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_pgp_blob_t(data));
+ break;
+ case KBX_X509_BLOB:
+ // current we doesn't parse X509 blob, so, keep it as is
+ blob = std::unique_ptr<kbx_blob_t>(new kbx_blob_t(data));
+ break;
+ // unsupported blob type
+ default:
+ RNP_LOG("Unsupported blob type: %d", (int) type);
+ return blob;
+ }
+
+ if (!blob->parse()) {
+ return NULL;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return NULL;
+ }
+ return blob;
+}
+
+bool
+rnp_key_store_kbx_from_src(rnp_key_store_t * key_store,
+ pgp_source_t * src,
+ const pgp_key_provider_t *key_provider)
+{
+ 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<kbx_pgp_blob_t &>(*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;
+ }
+}
+
+static bool
+pbuf(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ dst_write(dst, buf, len);
+ return dst->werr == RNP_SUCCESS;
+}
+
+static bool
+pu8(pgp_dest_t *dst, uint8_t p)
+{
+ return pbuf(dst, &p, 1);
+}
+
+static bool
+pu16(pgp_dest_t *dst, uint16_t f)
+{
+ uint8_t p[2];
+ p[0] = (uint8_t)(f >> 8);
+ p[1] = (uint8_t) f;
+ return pbuf(dst, p, 2);
+}
+
+static bool
+pu32(pgp_dest_t *dst, uint32_t f)
+{
+ uint8_t p[4];
+ STORE32BE(p, f);
+ return pbuf(dst, p, 4);
+}
+
+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 = key_store->secctx.time();
+
+ if (!key_store->blobs.empty() && (key_store->blobs[0]->type() == KBX_HEADER_BLOB)) {
+ kbx_header_blob_t &blob = dynamic_cast<kbx_header_blob_t &>(*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, 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)
+{
+ 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 (!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 (!pu16(&mem.dst(), 1 + key->subkey_count())) { // number of keys in keyblock
+ return false;
+ }
+ if (!pu16(&mem.dst(), 28)) { // size of key info structure)
+ return false;
+ }
+
+ 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<uint32_t> subkey_sig_expirations;
+ for (auto &sfp : key->subkey_fps()) {
+ pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp);
+ if (!subkey || !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 (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(&mem.dst(), 0)) { // Zero size of serial number
+ return false;
+ }
+
+ // skip serial number
+ if (!pu16(&mem.dst(), key->uid_count()) || !pu16(&mem.dst(), 12)) {
+ return false;
+ }
+
+ 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(&mem.dst(), 0)) { // flags, (not yet used)
+ return false;
+ }
+
+ if (!pu8(&mem.dst(), 0) || !pu8(&mem.dst(), 0)) { // Validity & RFU
+ return false;
+ }
+ }
+
+ if (!pu16(&mem.dst(), key->sig_count() + subkey_sig_expirations.size()) ||
+ !pu16(&mem.dst(), 4)) {
+ return false;
+ }
+
+ 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(&mem.dst(), expiration)) {
+ return false;
+ }
+ }
+
+ if (!pu8(&mem.dst(), 0) ||
+ !pu8(&mem.dst(), 0)) { // Assigned ownertrust & All_Validity (not yet used)
+ return false;
+ }
+
+ if (!pu16(&mem.dst(), 0) || !pu32(&mem.dst(), 0)) { // RFU & Recheck_after
+ return false;
+ }
+
+ if (!pu32(&mem.dst(), key_store->secctx.time()) ||
+ !pu32(&mem.dst(), key_store->secctx.time())) { // Latest timestamp && created
+ return false;
+ }
+
+ 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 (size_t i = 0; i < key->uid_count(); i++) {
+ const pgp_userid_t &uid = key->get_uid(i);
+ uint8_t * p = (uint8_t *) mem.memory() + uid_start + (12 * i);
+ /* store absolute uid offset in the output stream */
+ uint32_t pt = mem.writeb() + dst->writeb;
+ STORE32BE(p, pt);
+ /* and uid length */
+ pt = uid.str.size();
+ STORE32BE(p + 4, pt);
+ /* uid data itself */
+ if (!pbuf(&mem.dst(), uid.str.c_str(), pt)) {
+ return false;
+ }
+ }
+
+ /* write keyblock and fix the offset/length */
+ size_t key_start = mem.writeb();
+ uint32_t pt = key_start;
+ uint8_t *p = (uint8_t *) mem.memory() + 8;
+ STORE32BE(p, pt);
+
+ 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);
+ if (!subkey) {
+ return false;
+ }
+ subkey->write(mem.dst());
+ if (mem.werr()) {
+ return false;
+ }
+ }
+
+ /* key blob length */
+ pt = mem.writeb() - key_start;
+ p = (uint8_t *) mem.memory() + 12;
+ STORE32BE(p, pt);
+
+ // fix the length of blob
+ pt = mem.writeb() + 20;
+ p = (uint8_t *) mem.memory();
+ STORE32BE(p, pt);
+
+ // checksum
+ 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(&mem.dst(), checksum, PGP_SHA1_HASH_SIZE))) {
+ return false;
+ }
+
+ /* finally write to the output */
+ 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)
+{
+ for (auto &blob : key_store->blobs) {
+ if (blob->type() != KBX_X509_BLOB) {
+ continue;
+ }
+ if (!pbuf(dst, blob->image().data(), blob->length())) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool
+rnp_key_store_kbx_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ 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;
+ }
+}
diff --git a/src/librekey/key_store_kbx.h b/src/librekey/key_store_kbx.h
new file mode 100644
index 0000000..68d725d
--- /dev/null
+++ b/src/librekey/key_store_kbx.h
@@ -0,0 +1,36 @@
+/*
+ * Copyright (c) 2017, [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 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 RNP_KEY_STORE_KBX_H
+#define RNP_KEY_STORE_KBX_H
+
+#include <rekey/rnp_key_store.h>
+#include "sec_profile.hpp"
+
+bool rnp_key_store_kbx_from_src(rnp_key_store_t *, pgp_source_t *, const pgp_key_provider_t *);
+bool rnp_key_store_kbx_to_dst(rnp_key_store_t *, pgp_dest_t *);
+
+#endif // RNP_KEY_STORE_KBX_H
diff --git a/src/librekey/key_store_pgp.cpp b/src/librekey/key_store_pgp.cpp
new file mode 100644
index 0000000..6edc099
--- /dev/null
+++ b/src/librekey/key_store_pgp.cpp
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#if defined(__NetBSD__)
+__COPYRIGHT("@(#) Copyright (c) 2009 The NetBSD Foundation, Inc. All rights reserved.");
+__RCSID("$NetBSD: keyring.c,v 1.50 2011/06/25 00:37:44 agc Exp $");
+#endif
+
+#include <stdlib.h>
+#include <string.h>
+
+#include <librepgp/stream-common.h>
+#include <librepgp/stream-sig.h>
+#include <librepgp/stream-packet.h>
+#include <librepgp/stream-key.h>
+#include "crypto/mem.h"
+
+#include "types.h"
+#include "key_store_pgp.h"
+#include "pgp-key.h"
+
+bool
+rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring,
+ pgp_transferable_subkey_t *tskey,
+ pgp_key_t * pkey)
+{
+ try {
+ /* create subkey */
+ pgp_key_t skey(*tskey, pkey);
+ /* add it to the storage */
+ return rnp_key_store_add_key(keyring, &skey);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ RNP_LOG_KEY_PKT("failed to create subkey %s", tskey->subkey);
+ RNP_LOG_KEY("primary key is %s", pkey);
+ return false;
+ }
+}
+
+bool
+rnp_key_store_add_transferable_key(rnp_key_store_t *keyring, pgp_transferable_key_t *tkey)
+{
+ pgp_key_t *addkey = NULL;
+
+ /* create key from transferable key */
+ try {
+ pgp_key_t key(*tkey);
+ /* temporary disable key validation */
+ keyring->disable_validation = true;
+ /* add key to the storage before subkeys */
+ addkey = rnp_key_store_add_key(keyring, &key);
+ } catch (const std::exception &e) {
+ keyring->disable_validation = false;
+ RNP_LOG_KEY_PKT("failed to add key %s", tkey->key);
+ return false;
+ }
+
+ if (!addkey) {
+ keyring->disable_validation = false;
+ RNP_LOG("Failed to add key to key store.");
+ return false;
+ }
+
+ /* add subkeys */
+ for (auto &subkey : tkey->subkeys) {
+ if (!rnp_key_store_add_transferable_subkey(keyring, &subkey, addkey)) {
+ RNP_LOG("Failed to add subkey to key store.");
+ keyring->disable_validation = false;
+ goto error;
+ }
+ }
+
+ /* now validate/refresh the whole key with subkeys */
+ keyring->disable_validation = false;
+ addkey->revalidate(*keyring);
+ return true;
+error:
+ /* during key addition all fields are copied so will be cleaned below */
+ rnp_key_store_remove_key(keyring, addkey, false);
+ return false;
+}
+
+rnp_result_t
+rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring,
+ pgp_source_t & src,
+ bool skiperrors)
+{
+ pgp_transferable_key_t key;
+ rnp_result_t ret = process_pgp_key_auto(src, key, true, skiperrors);
+
+ if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) {
+ return ret;
+ }
+
+ /* check whether we have primary key */
+ if (key.key.tag != PGP_PKT_RESERVED) {
+ return rnp_key_store_add_transferable_key(&keyring, &key) ? RNP_SUCCESS :
+ RNP_ERROR_BAD_STATE;
+ }
+
+ /* we just skipped some unexpected packets and read nothing */
+ if (key.subkeys.empty()) {
+ return RNP_SUCCESS;
+ }
+
+ return rnp_key_store_add_transferable_subkey(&keyring, &key.subkeys.front(), NULL) ?
+ RNP_SUCCESS :
+ RNP_ERROR_BAD_STATE;
+}
+
+rnp_result_t
+rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring, pgp_source_t *src, bool skiperrors)
+{
+ /* check whether we have transferable subkey in source */
+ if (is_subkey_pkt(stream_pkt_type(*src))) {
+ pgp_transferable_subkey_t tskey;
+ 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) */
+ 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;
+ }
+}
+
+std::vector<uint8_t>
+rnp_key_to_vec(const pgp_key_t &key)
+{
+ 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)
+{
+ for (auto &key : key_store->keys) {
+ if (key.is_secret() != secret) {
+ continue;
+ }
+ // skip subkeys, they are written below (orphans are ignored)
+ if (!key.is_primary()) {
+ continue;
+ }
+
+ if (key.format != PGP_KEY_STORE_GPG) {
+ RNP_LOG("incorrect format (conversions not supported): %d", key.format);
+ return false;
+ }
+ key.write(*dst);
+ if (dst->werr) {
+ return false;
+ }
+ for (auto &sfp : key.subkey_fps()) {
+ pgp_key_t *subkey = rnp_key_store_get_key_by_fpr(key_store, sfp);
+ if (!subkey) {
+ RNP_LOG("Missing subkey");
+ continue;
+ }
+ subkey->write(*dst);
+ if (dst->werr) {
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+bool
+rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ // two separate passes (public keys, then secret keys)
+ return do_write(key_store, dst, false) && do_write(key_store, dst, true);
+}
diff --git a/src/librekey/key_store_pgp.h b/src/librekey/key_store_pgp.h
new file mode 100644
index 0000000..d3dcd06
--- /dev/null
+++ b/src/librekey/key_store_pgp.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/*
+ * Copyright (c) 2005-2008 Nominet UK (www.nic.uk)
+ * All rights reserved.
+ * Contributors: Ben Laurie, Rachel Willmer. The Contributors have asserted
+ * their moral rights under the UK Copyright Design and Patents Act 1988 to
+ * be recorded as the authors of this copyright work.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not
+ * use this file except in compliance with the License.
+ *
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/** \file
+ */
+
+#ifndef KEY_STORE_PGP_H_
+#define KEY_STORE_PGP_H_
+
+#include <rekey/rnp_key_store.h>
+#include <librepgp/stream-common.h>
+#include <librepgp/stream-key.h>
+
+/* Read the whole keyring from the src, processing all available keys or subkeys */
+rnp_result_t rnp_key_store_pgp_read_from_src(rnp_key_store_t *keyring,
+ pgp_source_t * src,
+ bool skiperrors = false);
+
+/* Read the first key or subkey from the src */
+rnp_result_t rnp_key_store_pgp_read_key_from_src(rnp_key_store_t &keyring,
+ pgp_source_t & src,
+ bool skiperrors = false);
+
+bool rnp_key_store_pgp_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst);
+
+bool rnp_key_store_add_transferable_subkey(rnp_key_store_t * keyring,
+ pgp_transferable_subkey_t *tskey,
+ pgp_key_t * pkey);
+
+bool rnp_key_store_add_transferable_key(rnp_key_store_t * keyring,
+ pgp_transferable_key_t *tkey);
+
+std::vector<uint8_t> rnp_key_to_vec(const pgp_key_t &key);
+
+#endif /* KEY_STORE_PGP_H_ */
diff --git a/src/librekey/rnp_key_store.cpp b/src/librekey/rnp_key_store.cpp
new file mode 100644
index 0000000..002a51e
--- /dev/null
+++ b/src/librekey/rnp_key_store.cpp
@@ -0,0 +1,803 @@
+/*
+ * 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).
+ *
+ * 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 "config.h"
+#include <sys/stat.h>
+#include <sys/types.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#else
+#include "uniwin.h"
+#endif
+
+#include <assert.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <dirent.h>
+#include <errno.h>
+#include <algorithm>
+#include <stdexcept>
+
+#include <rekey/rnp_key_store.h>
+#include <librepgp/stream-packet.h>
+
+#include "key_store_pgp.h"
+#include "key_store_kbx.h"
+#include "key_store_g10.h"
+#include "kbx_blob.hpp"
+
+#include "pgp-key.h"
+#include "fingerprint.h"
+#include "crypto/hash.hpp"
+#include "crypto/mem.h"
+#include "file-utils.h"
+#ifdef _WIN32
+#include "str-utils.h"
+#endif
+
+bool
+rnp_key_store_load_from_path(rnp_key_store_t * key_store,
+ const pgp_key_provider_t *key_provider)
+{
+ pgp_source_t src = {};
+
+ if (key_store->format == PGP_KEY_STORE_G10) {
+ auto dir = rnp_opendir(key_store->path.c_str());
+ if (!dir) {
+ RNP_LOG(
+ "Can't open G10 directory %s: %s", key_store->path.c_str(), strerror(errno));
+ return false;
+ }
+
+ std::string dirname;
+ while (!((dirname = rnp_readdir_name(dir)).empty())) {
+ std::string path = rnp::path::append(key_store->path, dirname);
+
+ if (init_file_src(&src, path.c_str())) {
+ RNP_LOG("failed to read file %s", path.c_str());
+ continue;
+ }
+ // G10 may fail to read one file, so ignore it!
+ if (!rnp_key_store_g10_from_src(key_store, &src, key_provider)) {
+ RNP_LOG("Can't parse file: %s", path.c_str()); // TODO: %S ?
+ }
+ src_close(&src);
+ }
+ rnp_closedir(dir);
+ return true;
+ }
+
+ /* init file source and load from it */
+ if (init_file_src(&src, key_store->path.c_str())) {
+ RNP_LOG("failed to read file %s", key_store->path.c_str());
+ return false;
+ }
+
+ bool rc = rnp_key_store_load_from_src(key_store, &src, key_provider);
+ src_close(&src);
+ return rc;
+}
+
+bool
+rnp_key_store_load_from_src(rnp_key_store_t * key_store,
+ pgp_source_t * src,
+ const pgp_key_provider_t *key_provider)
+{
+ switch (key_store->format) {
+ case PGP_KEY_STORE_GPG:
+ return rnp_key_store_pgp_read_from_src(key_store, src) == RNP_SUCCESS;
+ case PGP_KEY_STORE_KBX:
+ return rnp_key_store_kbx_from_src(key_store, src, key_provider);
+ case PGP_KEY_STORE_G10:
+ return rnp_key_store_g10_from_src(key_store, src, key_provider);
+ default:
+ RNP_LOG("Unsupported load from memory for key-store format: %d", key_store->format);
+ }
+
+ return false;
+}
+
+bool
+rnp_key_store_write_to_path(rnp_key_store_t *key_store)
+{
+ bool rc;
+ pgp_dest_t keydst = {};
+
+ /* write g10 key store to the directory */
+ if (key_store->format == PGP_KEY_STORE_G10) {
+ char path[MAXPATHLEN];
+
+ struct stat path_stat;
+ if (rnp_stat(key_store->path.c_str(), &path_stat) != -1) {
+ if (!S_ISDIR(path_stat.st_mode)) {
+ RNP_LOG("G10 keystore should be a directory: %s", key_store->path.c_str());
+ return false;
+ }
+ } else {
+ if (errno != ENOENT) {
+ RNP_LOG("stat(%s): %s", key_store->path.c_str(), strerror(errno));
+ return false;
+ }
+ if (RNP_MKDIR(key_store->path.c_str(), S_IRWXU) != 0) {
+ RNP_LOG("mkdir(%s, S_IRWXU): %s", key_store->path.c_str(), strerror(errno));
+ return false;
+ }
+ }
+
+ for (auto &key : key_store->keys) {
+ char grip[PGP_FINGERPRINT_HEX_SIZE] = {0};
+ rnp::hex_encode(key.grip().data(), key.grip().size(), grip, sizeof(grip));
+ snprintf(path, sizeof(path), "%s/%s.key", key_store->path.c_str(), grip);
+
+ if (init_tmpfile_dest(&keydst, path, true)) {
+ RNP_LOG("failed to create file");
+ return false;
+ }
+
+ if (!rnp_key_store_gnupg_sexp_to_dst(&key, &keydst)) {
+ RNP_LOG("failed to write key to file");
+ dst_close(&keydst, true);
+ return false;
+ }
+
+ rc = dst_finish(&keydst) == RNP_SUCCESS;
+ dst_close(&keydst, !rc);
+
+ if (!rc) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ /* write kbx/gpg store to the single file */
+ if (init_tmpfile_dest(&keydst, key_store->path.c_str(), true)) {
+ RNP_LOG("failed to create keystore file");
+ return false;
+ }
+
+ if (!rnp_key_store_write_to_dst(key_store, &keydst)) {
+ RNP_LOG("failed to write keys to file");
+ dst_close(&keydst, true);
+ return false;
+ }
+
+ rc = dst_finish(&keydst) == RNP_SUCCESS;
+ dst_close(&keydst, !rc);
+ return rc;
+}
+
+bool
+rnp_key_store_write_to_dst(rnp_key_store_t *key_store, pgp_dest_t *dst)
+{
+ switch (key_store->format) {
+ case PGP_KEY_STORE_GPG:
+ return rnp_key_store_pgp_write_to_dst(key_store, dst);
+ case PGP_KEY_STORE_KBX:
+ return rnp_key_store_kbx_to_dst(key_store, dst);
+ default:
+ RNP_LOG("Unsupported write to memory for key-store format: %d", key_store->format);
+ }
+
+ return false;
+}
+
+void
+rnp_key_store_clear(rnp_key_store_t *keyring)
+{
+ keyring->keybyfp.clear();
+ keyring->keys.clear();
+ keyring->blobs.clear();
+}
+
+size_t
+rnp_key_store_get_key_count(const rnp_key_store_t *keyring)
+{
+ return keyring->keys.size();
+}
+
+static bool
+rnp_key_store_refresh_subkey_grips(rnp_key_store_t *keyring, pgp_key_t *key)
+{
+ if (key->is_subkey()) {
+ RNP_LOG("wrong argument");
+ return false;
+ }
+
+ for (auto &skey : keyring->keys) {
+ bool found = false;
+
+ /* if we have primary_grip then we also added to subkey_grips */
+ if (!skey.is_subkey() || skey.has_primary_fp()) {
+ continue;
+ }
+
+ for (size_t i = 0; i < skey.sig_count(); i++) {
+ const pgp_subsig_t &subsig = skey.get_sig(i);
+
+ if (subsig.sig.type() != PGP_SIG_SUBKEY) {
+ continue;
+ }
+ if (subsig.sig.has_keyfp() && (key->fp() == subsig.sig.keyfp())) {
+ found = true;
+ break;
+ }
+ if (subsig.sig.has_keyid() && (key->keyid() == subsig.sig.keyid())) {
+ found = true;
+ break;
+ }
+ }
+
+ if (found) {
+ try {
+ key->link_subkey_fp(skey);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+static pgp_key_t *
+rnp_key_store_add_subkey(rnp_key_store_t *keyring, pgp_key_t *srckey, pgp_key_t *oldkey)
+{
+ pgp_key_t *primary = NULL;
+ if (oldkey) {
+ primary = rnp_key_store_get_primary_key(keyring, oldkey);
+ }
+ if (!primary) {
+ primary = rnp_key_store_get_primary_key(keyring, srckey);
+ }
+
+ if (oldkey) {
+ /* check for the weird case when same subkey has different primary keys */
+ if (srckey->has_primary_fp() && oldkey->has_primary_fp() &&
+ (srckey->primary_fp() != oldkey->primary_fp())) {
+ RNP_LOG_KEY("Warning: different primary keys for subkey %s", srckey);
+ pgp_key_t *srcprim = rnp_key_store_get_key_by_fpr(keyring, srckey->primary_fp());
+ if (srcprim && (srcprim != primary)) {
+ srcprim->remove_subkey_fp(srckey->fp());
+ }
+ }
+ /* in case we already have key let's merge it in */
+ if (!oldkey->merge(*srckey, primary)) {
+ RNP_LOG_KEY("failed to merge subkey %s", srckey);
+ RNP_LOG_KEY("primary key is %s", primary);
+ return NULL;
+ }
+ } else {
+ try {
+ keyring->keys.emplace_back();
+ oldkey = &keyring->keys.back();
+ keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end());
+ *oldkey = pgp_key_t(*srckey);
+ if (primary) {
+ primary->link_subkey_fp(*oldkey);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG_KEY("key %s copying failed", srckey);
+ RNP_LOG_KEY("primary key is %s", primary);
+ RNP_LOG("%s", e.what());
+ if (oldkey) {
+ keyring->keys.pop_back();
+ keyring->keybyfp.erase(srckey->fp());
+ }
+ return NULL;
+ }
+ }
+
+ /* validate all added keys if not disabled */
+ if (!keyring->disable_validation && !oldkey->validated()) {
+ oldkey->validate_subkey(primary, keyring->secctx);
+ }
+ if (!oldkey->refresh_data(primary, keyring->secctx)) {
+ RNP_LOG_KEY("Failed to refresh subkey %s data", srckey);
+ RNP_LOG_KEY("primary key is %s", primary);
+ }
+ return oldkey;
+}
+
+/* add a key to keyring */
+pgp_key_t *
+rnp_key_store_add_key(rnp_key_store_t *keyring, pgp_key_t *srckey)
+{
+ assert(srckey->type() && srckey->version());
+ pgp_key_t *added_key = rnp_key_store_get_key_by_fpr(keyring, srckey->fp());
+ /* we cannot merge G10 keys - so just return it */
+ if (added_key && (srckey->format == PGP_KEY_STORE_G10)) {
+ return added_key;
+ }
+ /* different processing for subkeys */
+ if (srckey->is_subkey()) {
+ return rnp_key_store_add_subkey(keyring, srckey, added_key);
+ }
+
+ if (added_key) {
+ if (!added_key->merge(*srckey)) {
+ RNP_LOG_KEY("failed to merge key %s", srckey);
+ return NULL;
+ }
+ } else {
+ try {
+ keyring->keys.emplace_back();
+ added_key = &keyring->keys.back();
+ keyring->keybyfp[srckey->fp()] = std::prev(keyring->keys.end());
+ *added_key = pgp_key_t(*srckey);
+ /* primary key may be added after subkeys, so let's handle this case correctly */
+ if (!rnp_key_store_refresh_subkey_grips(keyring, added_key)) {
+ RNP_LOG_KEY("failed to refresh subkey grips for %s", added_key);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG_KEY("key %s copying failed", srckey);
+ RNP_LOG("%s", e.what());
+ if (added_key) {
+ keyring->keys.pop_back();
+ keyring->keybyfp.erase(srckey->fp());
+ }
+ return NULL;
+ }
+ }
+
+ /* validate all added keys if not disabled or already validated */
+ if (!keyring->disable_validation && !added_key->validated()) {
+ added_key->revalidate(*keyring);
+ } else if (!added_key->refresh_data(keyring->secctx)) {
+ RNP_LOG_KEY("Failed to refresh key %s data", srckey);
+ }
+ return added_key;
+}
+
+pgp_key_t *
+rnp_key_store_import_key(rnp_key_store_t * keyring,
+ pgp_key_t * srckey,
+ bool pubkey,
+ pgp_key_import_status_t *status)
+{
+ /* add public key */
+ pgp_key_t *exkey = rnp_key_store_get_key_by_fpr(keyring, srckey->fp());
+ size_t expackets = exkey ? exkey->rawpkt_count() : 0;
+ try {
+ pgp_key_t keycp(*srckey, pubkey);
+ keyring->disable_validation = true;
+ exkey = rnp_key_store_add_key(keyring, &keycp);
+ keyring->disable_validation = false;
+ if (!exkey) {
+ RNP_LOG("failed to add key to the keyring");
+ return NULL;
+ }
+ bool changed = exkey->rawpkt_count() > expackets;
+ if (changed || !exkey->validated()) {
+ /* this will revalidated primary key with all subkeys */
+ exkey->revalidate(*keyring);
+ }
+ if (status) {
+ *status = changed ? (expackets ? PGP_KEY_IMPORT_STATUS_UPDATED :
+ PGP_KEY_IMPORT_STATUS_NEW) :
+ PGP_KEY_IMPORT_STATUS_UNCHANGED;
+ }
+ return exkey;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ keyring->disable_validation = false;
+ return NULL;
+ }
+}
+
+pgp_key_t *
+rnp_key_store_get_signer_key(rnp_key_store_t *store, const pgp_signature_t *sig)
+{
+ 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);
+ }
+ // fall back to key id search
+ if (sig->has_keyid()) {
+ search.by.keyid = sig->keyid();
+ search.type = PGP_KEY_SEARCH_KEYID;
+ return rnp_key_store_search(store, &search, NULL);
+ }
+ return NULL;
+}
+
+static pgp_sig_import_status_t
+rnp_key_store_import_subkey_signature(rnp_key_store_t * keyring,
+ pgp_key_t * key,
+ const pgp_signature_t *sig)
+{
+ if ((sig->type() != PGP_SIG_SUBKEY) && (sig->type() != PGP_SIG_REV_SUBKEY)) {
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+ pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, sig);
+ if (!primary || !key->has_primary_fp()) {
+ RNP_LOG("No primary grip or primary key");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY;
+ }
+ if (primary->fp() != key->primary_fp()) {
+ RNP_LOG("Wrong subkey signature's signer.");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ try {
+ pgp_key_t tmpkey(key->pkt());
+ tmpkey.add_sig(*sig);
+ if (!tmpkey.refresh_data(primary, keyring->secctx)) {
+ RNP_LOG("Failed to add signature to the key.");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ size_t expackets = key->rawpkt_count();
+ key = rnp_key_store_add_key(keyring, &tmpkey);
+ if (!key) {
+ RNP_LOG("Failed to add key with imported sig to the keyring");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+ return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW :
+ PGP_SIG_IMPORT_STATUS_UNCHANGED;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+}
+
+pgp_sig_import_status_t
+rnp_key_store_import_key_signature(rnp_key_store_t * keyring,
+ pgp_key_t * key,
+ const pgp_signature_t *sig)
+{
+ if (key->is_subkey()) {
+ return rnp_key_store_import_subkey_signature(keyring, key, sig);
+ }
+ if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) {
+ RNP_LOG("Wrong signature type: %d", (int) sig->type());
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ try {
+ pgp_key_t tmpkey(key->pkt());
+ tmpkey.add_sig(*sig);
+ if (!tmpkey.refresh_data(keyring->secctx)) {
+ RNP_LOG("Failed to add signature to the key.");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+
+ size_t expackets = key->rawpkt_count();
+ key = rnp_key_store_add_key(keyring, &tmpkey);
+ if (!key) {
+ RNP_LOG("Failed to add key with imported sig to the keyring");
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+ return (key->rawpkt_count() > expackets) ? PGP_SIG_IMPORT_STATUS_NEW :
+ PGP_SIG_IMPORT_STATUS_UNCHANGED;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ }
+}
+
+pgp_key_t *
+rnp_key_store_import_signature(rnp_key_store_t * keyring,
+ const pgp_signature_t * sig,
+ pgp_sig_import_status_t *status)
+{
+ pgp_sig_import_status_t tmp_status = PGP_SIG_IMPORT_STATUS_UNKNOWN;
+ if (!status) {
+ status = &tmp_status;
+ }
+ *status = PGP_SIG_IMPORT_STATUS_UNKNOWN;
+
+ /* we support only direct-key and key revocation signatures here */
+ if ((sig->type() != PGP_SIG_DIRECT) && (sig->type() != PGP_SIG_REV_KEY)) {
+ return NULL;
+ }
+
+ pgp_key_t *res_key = rnp_key_store_get_signer_key(keyring, sig);
+ if (!res_key || !res_key->is_primary()) {
+ *status = PGP_SIG_IMPORT_STATUS_UNKNOWN_KEY;
+ return NULL;
+ }
+ *status = rnp_key_store_import_key_signature(keyring, res_key, sig);
+ return res_key;
+}
+
+bool
+rnp_key_store_remove_key(rnp_key_store_t *keyring, const pgp_key_t *key, bool subkeys)
+{
+ auto it = keyring->keybyfp.find(key->fp());
+ if (it == keyring->keybyfp.end()) {
+ return false;
+ }
+
+ /* cleanup primary_grip (or subkey)/subkey_grips */
+ if (key->is_primary() && key->subkey_count()) {
+ for (size_t i = 0; i < key->subkey_count(); i++) {
+ auto it = keyring->keybyfp.find(key->get_subkey_fp(i));
+ if (it == keyring->keybyfp.end()) {
+ continue;
+ }
+ /* if subkeys are deleted then no need to update grips */
+ if (subkeys) {
+ keyring->keys.erase(it->second);
+ keyring->keybyfp.erase(it);
+ continue;
+ }
+ it->second->unset_primary_fp();
+ }
+ }
+ if (key->is_subkey() && key->has_primary_fp()) {
+ pgp_key_t *primary = rnp_key_store_get_primary_key(keyring, key);
+ if (primary) {
+ primary->remove_subkey_fp(key->fp());
+ }
+ }
+
+ keyring->keys.erase(it->second);
+ keyring->keybyfp.erase(it);
+ return true;
+}
+
+const pgp_key_t *
+rnp_key_store_get_key_by_fpr(const rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr)
+{
+ auto it = keyring->keybyfp.find(fpr);
+ if (it == keyring->keybyfp.end()) {
+ return NULL;
+ }
+ return &*it->second;
+}
+
+pgp_key_t *
+rnp_key_store_get_key_by_fpr(rnp_key_store_t *keyring, const pgp_fingerprint_t &fpr)
+{
+ auto it = keyring->keybyfp.find(fpr);
+ if (it == keyring->keybyfp.end()) {
+ return NULL;
+ }
+ return &*it->second;
+}
+
+pgp_key_t *
+rnp_key_store_get_primary_key(rnp_key_store_t *keyring, const pgp_key_t *subkey)
+{
+ if (!subkey->is_subkey()) {
+ return NULL;
+ }
+
+ if (subkey->has_primary_fp()) {
+ pgp_key_t *primary = rnp_key_store_get_key_by_fpr(keyring, subkey->primary_fp());
+ return primary && primary->is_primary() ? primary : NULL;
+ }
+
+ for (size_t i = 0; i < subkey->sig_count(); i++) {
+ const pgp_subsig_t &subsig = subkey->get_sig(i);
+ if (subsig.sig.type() != PGP_SIG_SUBKEY) {
+ continue;
+ }
+
+ pgp_key_t *primary = rnp_key_store_get_signer_key(keyring, &subsig.sig);
+ if (primary && primary->is_primary()) {
+ return primary;
+ }
+ }
+ return NULL;
+}
+
+static void
+grip_hash_mpi(rnp::Hash &hash, const pgp_mpi_t &val, const char name, bool lzero = true)
+{
+ size_t len = mpi_bytes(&val);
+ size_t idx = 0;
+ for (idx = 0; (idx < len) && !val.mpi[idx]; idx++)
+ ;
+
+ if (name) {
+ size_t hlen = idx >= len ? 0 : len - idx;
+ if ((len > idx) && lzero && (val.mpi[idx] & 0x80)) {
+ hlen++;
+ }
+
+ char buf[20] = {0};
+ snprintf(buf, sizeof(buf), "(1:%c%zu:", name, hlen);
+ hash.add(buf, strlen(buf));
+ }
+
+ if (idx < len) {
+ /* gcrypt prepends mpis with zero if higher bit is set */
+ if (lzero && (val.mpi[idx] & 0x80)) {
+ uint8_t zero = 0;
+ hash.add(&zero, 1);
+ }
+ hash.add(val.mpi + idx, len - idx);
+ }
+ if (name) {
+ hash.add(")", 1);
+ }
+}
+
+static void
+grip_hash_ecc_hex(rnp::Hash &hash, const char *hex, char name)
+{
+ pgp_mpi_t mpi = {};
+ mpi.len = rnp::hex_decode(hex, mpi.mpi, sizeof(mpi.mpi));
+ if (!mpi.len) {
+ RNP_LOG("wrong hex mpi");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ /* libgcrypt doesn't add leading zero when hashes ecc mpis */
+ return grip_hash_mpi(hash, mpi, name, false);
+}
+
+static void
+grip_hash_ec(rnp::Hash &hash, const pgp_ec_key_t &key)
+{
+ const ec_curve_desc_t *desc = get_curve_desc(key.curve);
+ if (!desc) {
+ RNP_LOG("unknown curve %d", (int) key.curve);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ /* build uncompressed point from gx and gy */
+ pgp_mpi_t g = {};
+ g.mpi[0] = 0x04;
+ g.len = 1;
+ size_t len = rnp::hex_decode(desc->gx, g.mpi + g.len, sizeof(g.mpi) - g.len);
+ if (!len) {
+ RNP_LOG("wrong x mpi");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ g.len += len;
+ len = rnp::hex_decode(desc->gy, g.mpi + g.len, sizeof(g.mpi) - g.len);
+ if (!len) {
+ RNP_LOG("wrong y mpi");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ g.len += len;
+
+ /* p, a, b, g, n, q */
+ grip_hash_ecc_hex(hash, desc->p, 'p');
+ grip_hash_ecc_hex(hash, desc->a, 'a');
+ grip_hash_ecc_hex(hash, desc->b, 'b');
+ grip_hash_mpi(hash, g, 'g', false);
+ grip_hash_ecc_hex(hash, desc->n, 'n');
+
+ if ((key.curve == PGP_CURVE_ED25519) || (key.curve == PGP_CURVE_25519)) {
+ if (g.len < 1) {
+ RNP_LOG("wrong 25519 p");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ g.len = key.p.len - 1;
+ memcpy(g.mpi, key.p.mpi + 1, g.len);
+ grip_hash_mpi(hash, g, 'q', false);
+ } else {
+ grip_hash_mpi(hash, key.p, 'q', false);
+ }
+}
+
+/* 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 {
+ 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');
+ 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');
+ 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');
+ break;
+ case PGP_PKA_ECDH:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ 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();
+ } catch (const std::exception &e) {
+ RNP_LOG("Grip calculation failed: %s", e.what());
+ return false;
+ }
+}
+
+pgp_key_t *
+rnp_key_store_search(rnp_key_store_t * keyring,
+ const pgp_key_search_t *search,
+ pgp_key_t * after)
+{
+ // since keys are distinguished by fingerprint then just do map lookup
+ if (search->type == PGP_KEY_SEARCH_FINGERPRINT) {
+ pgp_key_t *key = rnp_key_store_get_key_by_fpr(keyring, search->by.fingerprint);
+ if (after && (after != key)) {
+ RNP_LOG("searching with invalid after param");
+ return NULL;
+ }
+ // return NULL if after is specified
+ return after ? NULL : key;
+ }
+
+ // if after is provided, make sure it is a member of the appropriate list
+ auto it =
+ std::find_if(keyring->keys.begin(), keyring->keys.end(), [after](const pgp_key_t &key) {
+ return !after || (after == &key);
+ });
+ if (after && (it == keyring->keys.end())) {
+ RNP_LOG("searching with non-keyrings after param");
+ return NULL;
+ }
+ if (after) {
+ it = std::next(it);
+ }
+ it = std::find_if(it, keyring->keys.end(), [search](const pgp_key_t &key) {
+ return rnp_key_matches_search(&key, search);
+ });
+ return (it == keyring->keys.end()) ? NULL : &(*it);
+}
+
+rnp_key_store_t::rnp_key_store_t(pgp_key_store_format_t _format,
+ const std::string & _path,
+ rnp::SecurityContext & ctx)
+ : secctx(ctx)
+{
+ if (_format == PGP_KEY_STORE_UNKNOWN) {
+ RNP_LOG("Invalid key store format");
+ throw std::invalid_argument("format");
+ }
+ format = _format;
+ path = _path;
+}
+
+rnp_key_store_t::~rnp_key_store_t()
+{
+ rnp_key_store_clear(this);
+}
diff --git a/src/librepgp/stream-armor.cpp b/src/librepgp/stream-armor.cpp
new file mode 100644
index 0000000..669c305
--- /dev/null
+++ b/src/librepgp/stream-armor.cpp
@@ -0,0 +1,1287 @@
+/*
+ * 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:
+ *
+ * 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 "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <algorithm>
+#include "stream-def.h"
+#include "stream-armor.h"
+#include "stream-packet.h"
+#include "str-utils.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)
+#define ARMORED_MAX_LINE_LENGTH (76)
+
+typedef struct pgp_source_armored_param_t {
+ pgp_source_t * readsrc; /* source to read from */
+ pgp_armored_msg_t type; /* type of the message */
+ char * armorhdr; /* armor header */
+ char * version; /* Version: header if any */
+ char * comment; /* Comment: header if any */
+ char * hash; /* Hash: header if any */
+ char * charset; /* Charset: header if any */
+ uint8_t rest[ARMORED_BLOCK_SIZE]; /* unread decoded bytes, makes implementation easier */
+ unsigned restlen; /* number of bytes in rest */
+ unsigned restpos; /* index of first unread byte in rest, restpos <= restlen */
+ 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 */
+ std::unique_ptr<rnp::CRC24> 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 */
+ 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 */
+ std::unique_ptr<rnp::CRC24> crc_ctx; /* CTX used to calculate CRC */
+} pgp_dest_armored_param_t;
+
+/*
+ Table for base64 lookups:
+ 0xff - wrong character,
+ 0xfe - '='
+ 0xfd - eol/whitespace,
+ 0..0x3f - represented 6-bit number
+*/
+static const uint8_t B64DEC[256] = {
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xfd, 0xfd, 0xff, 0xff, 0xfd, 0xff,
+ 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xfd, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x3e, 0xff,
+ 0xff, 0xff, 0x3f, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x3b, 0x3c, 0x3d, 0xff, 0xff,
+ 0xff, 0xfe, 0xff, 0xff, 0xff, 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09,
+ 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18,
+ 0x19, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20, 0x21,
+ 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2a, 0x2b, 0x2c, 0x2d, 0x2e, 0x2f, 0x30,
+ 0x31, 0x32, 0x33, 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, 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, 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, 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, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
+ 0xff};
+
+static bool
+armor_read_padding(pgp_source_armored_param_t *param, size_t *read)
+{
+ char st[64];
+ size_t stlen = 0;
+
+ 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(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;
+ pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param;
+
+ if (!src_peek_line(param->readsrc, crc, sizeof(crc), &clen)) {
+ return false;
+ }
+
+ if ((clen != 5) || (crc[0] != CH_EQ)) {
+ return false;
+ }
+
+ for (int i = 0; i < 4; i++) {
+ if ((dec[i] = B64DEC[(uint8_t) crc[i + 1]]) >= 64) {
+ return false;
+ }
+ }
+
+ param->readcrc[0] = (dec[0] << 2) | ((dec[1] >> 4) & 0x0F);
+ param->readcrc[1] = (dec[1] << 4) | ((dec[2] >> 2) & 0x0F);
+ param->readcrc[2] = (dec[2] << 6) | dec[3];
+
+ param->has_crc = true;
+
+ src_skip(param->readsrc, 5);
+ return src_skip_eol(param->readsrc);
+}
+
+static bool
+armor_skip_chars(pgp_source_t *src, const char *chars)
+{
+ uint8_t ch;
+ size_t read;
+
+ do {
+ bool found = false;
+ if (!src_peek(src, &ch, 1, &read)) {
+ return false;
+ }
+ if (!read) {
+ /* return true only if there is no underlying read error */
+ return true;
+ }
+ for (const char *chptr = chars; *chptr; chptr++) {
+ if (ch == *chptr) {
+ src_skip(src, 1);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ break;
+ }
+ } while (1);
+
+ return true;
+}
+
+static bool
+armor_read_trailer(pgp_source_t *src)
+{
+ char st[64];
+ char str[64];
+ size_t stlen;
+ pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param;
+
+ if (!armor_skip_chars(param->readsrc, "\r\n")) {
+ return false;
+ }
+
+ stlen = strlen(param->armorhdr);
+ if ((stlen > 5) && (stlen + 8 + 1 <= sizeof(st))) {
+ memcpy(st, ST_ARMOR_END, 8); /* 8 here is mandatory */
+ memcpy(st + 8, param->armorhdr + 5, stlen - 5);
+ memcpy(st + stlen + 3, ST_DASHES, 5);
+ stlen += 8;
+ } else {
+ RNP_LOG("Internal error");
+ return false;
+ }
+ if (!src_peek_eq(param->readsrc, str, stlen) || strncmp(str, st, stlen)) {
+ return false;
+ }
+ src_skip(param->readsrc, stlen);
+ (void) armor_skip_chars(param->readsrc, "\t ");
+ (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 */
+ uint8_t *bufptr = (uint8_t *) buf; /* for better readability below */
+ uint8_t *bptr, *bend; /* pointer to input data in b64buf */
+ uint8_t *dptr, *dend, *pend; /* pointers to decoded data in decbuf: working pointer, last
+ available byte, last byte to process */
+ uint8_t bval;
+ uint32_t b24;
+ size_t read = 0;
+ size_t left = len;
+ size_t eqcount = 0; /* number of '=' at the end of base64 stream */
+
+ if (!param) {
+ return false;
+ }
+
+ /* checking whether there are some decoded bytes */
+ if (param->restpos < param->restlen) {
+ if (param->restlen - param->restpos >= len) {
+ memcpy(bufptr, &param->rest[param->restpos], len);
+ param->restpos += len;
+ try {
+ param->crc_ctx->add(bufptr, len);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ *readres = len;
+ return true;
+ } else {
+ left = len - (param->restlen - param->restpos);
+ memcpy(bufptr, &param->rest[param->restpos], len - left);
+ param->restpos = param->restlen = 0;
+ bufptr += len - left;
+ }
+ }
+
+ if (param->eofb64) {
+ *readres = len - left;
+ return true;
+ }
+
+ memcpy(decbuf, param->brest, param->brestlen);
+ dend = decbuf + param->brestlen;
+
+ do {
+ if (!src_peek(param->readsrc, b64buf, sizeof(b64buf), &read)) {
+ return false;
+ }
+ if (!read) {
+ RNP_LOG("premature end of armored input");
+ return false;
+ }
+
+ dptr = dend;
+ bptr = b64buf;
+ bend = b64buf + read;
+ /* checking input data, stripping away whitespaces, checking for end of the b64 data */
+ while (bptr < bend) {
+ if ((bval = B64DEC[*(bptr++)]) < 64) {
+ *(dptr++) = bval;
+ } else if (bval == 0xfe) {
+ /* '=' means the base64 padding or the beginning of checksum */
+ param->eofb64 = true;
+ break;
+ } else if (bval == 0xff) {
+ 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;
+ dptr = decbuf;
+ /* Processing full 4s which will go directly to the buf.
+ After this left < 3 or decbuf has < 4 bytes */
+ if ((size_t)(dend - dptr) / 4 * 3 < left) {
+ pend = decbuf + (dend - dptr) / 4 * 4;
+ left -= (dend - dptr) / 4 * 3;
+ } else {
+ pend = decbuf + (left / 3) * 4;
+ left -= left / 3 * 3;
+ }
+
+ /* this one would the most performance-consuming part for large chunks */
+ while (dptr < pend) {
+ b24 = *dptr++ << 18;
+ b24 |= *dptr++ << 12;
+ b24 |= *dptr++ << 6;
+ b24 |= *dptr++;
+ *bufptr++ = b24 >> 16;
+ *bufptr++ = b24 >> 8;
+ *bufptr++ = b24 & 0xff;
+ }
+
+ /* moving rest to the beginning of decbuf */
+ memmove(decbuf, dptr, dend - dptr);
+ dend = decbuf + (dend - dptr);
+
+ /* 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);
+ }
+
+ /* end of base64 data */
+ if (param->noheaders) {
+ if (!base64_read_padding(param, &eqcount)) {
+ return false;
+ }
+ break;
+ }
+ /* 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;
+ pend = decbuf + (dend - decbuf) / 4 * 4;
+ bptr = param->rest;
+ while (dptr < pend) {
+ b24 = *dptr++ << 18;
+ b24 |= *dptr++ << 12;
+ b24 |= *dptr++ << 6;
+ b24 |= *dptr++;
+ *bptr++ = b24 >> 16;
+ *bptr++ = b24 >> 8;
+ *bptr++ = b24 & 0xff;
+ }
+
+ if (!armored_update_crc(param, buf, bufptr - (uint8_t *) buf)) {
+ return false;
+ }
+
+ if (param->eofb64) {
+ if ((dend - dptr + eqcount) % 4 != 0) {
+ RNP_LOG("wrong b64 padding");
+ return false;
+ }
+
+ if (eqcount == 1) {
+ b24 = (*dptr << 10) | (*(dptr + 1) << 4) | (*(dptr + 2) >> 2);
+ *bptr++ = b24 >> 8;
+ *bptr++ = b24 & 0xff;
+ } else if (eqcount == 2) {
+ *bptr++ = (*dptr << 2) | (*(dptr + 1) >> 4);
+ }
+
+ /* Calculate CRC after reading whole input stream */
+ if (!armored_update_crc(param, param->rest, bptr - param->rest, true)) {
+ return false;
+ }
+ } else {
+ /* few bytes which do not fit to 4 boundary */
+ for (int i = 0; i < dend - dptr; i++) {
+ param->brest[i] = *(dptr + i);
+ }
+ param->brestlen = dend - dptr;
+ }
+
+ param->restlen = bptr - param->rest;
+
+ /* 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 && !armored_update_crc(param, bufptr, read)) {
+ return false;
+ }
+ left -= read;
+ param->restpos += read;
+ }
+
+ *readres = len - left;
+ return true;
+}
+
+static void
+armored_src_close(pgp_source_t *src)
+{
+ pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param;
+
+ if (param) {
+ free(param->armorhdr);
+ free(param->version);
+ free(param->comment);
+ free(param->hash);
+ free(param->charset);
+ delete param;
+ src->param = NULL;
+ }
+}
+
+/** @brief finds armor header position in the buffer, returning beginning of header or NULL.
+ * hdrlen will contain the length of the header
+ **/
+static const char *
+find_armor_header(const char *buf, size_t len, size_t *hdrlen)
+{
+ int st = -1;
+
+ for (unsigned i = 0; i < len - 10; i++) {
+ if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) {
+ st = i;
+ break;
+ }
+ }
+
+ if (st < 0) {
+ return NULL;
+ }
+
+ for (unsigned i = st + 5; i <= len - 5; i++) {
+ if ((buf[i] == CH_DASH) && !strncmp(&buf[i + 1], ST_DASHES, 4)) {
+ *hdrlen = i + 5 - st;
+ return &buf[st];
+ }
+ }
+
+ return NULL;
+}
+
+static bool
+str_equals(const char *str, size_t len, const char *another)
+{
+ size_t alen = strlen(another);
+ return (len == alen) && !memcmp(str, another, alen);
+}
+
+static pgp_armored_msg_t
+armor_str_to_data_type(const char *str, size_t len)
+{
+ if (!str) {
+ return PGP_ARMORED_UNKNOWN;
+ }
+ if (str_equals(str, len, "BEGIN PGP MESSAGE")) {
+ return PGP_ARMORED_MESSAGE;
+ }
+ if (str_equals(str, len, "BEGIN PGP PUBLIC KEY BLOCK") ||
+ str_equals(str, len, "BEGIN PGP PUBLIC KEY")) {
+ return PGP_ARMORED_PUBLIC_KEY;
+ }
+ if (str_equals(str, len, "BEGIN PGP SECRET KEY BLOCK") ||
+ str_equals(str, len, "BEGIN PGP SECRET KEY") ||
+ str_equals(str, len, "BEGIN PGP PRIVATE KEY BLOCK") ||
+ str_equals(str, len, "BEGIN PGP PRIVATE KEY")) {
+ return PGP_ARMORED_SECRET_KEY;
+ }
+ if (str_equals(str, len, "BEGIN PGP SIGNATURE")) {
+ return PGP_ARMORED_SIGNATURE;
+ }
+ if (str_equals(str, len, "BEGIN PGP SIGNED MESSAGE")) {
+ return PGP_ARMORED_CLEARTEXT;
+ }
+ return PGP_ARMORED_UNKNOWN;
+}
+
+pgp_armored_msg_t
+rnp_armor_guess_type(pgp_source_t *src)
+{
+ uint8_t ptag;
+
+ if (!src_peek_eq(src, &ptag, 1)) {
+ return PGP_ARMORED_UNKNOWN;
+ }
+
+ switch (get_packet_type(ptag)) {
+ case PGP_PKT_PK_SESSION_KEY:
+ case PGP_PKT_SK_SESSION_KEY:
+ case PGP_PKT_ONE_PASS_SIG:
+ case PGP_PKT_SE_DATA:
+ case PGP_PKT_SE_IP_DATA:
+ case PGP_PKT_COMPRESSED:
+ case PGP_PKT_LITDATA:
+ case PGP_PKT_MARKER:
+ return PGP_ARMORED_MESSAGE;
+ case PGP_PKT_PUBLIC_KEY:
+ case PGP_PKT_PUBLIC_SUBKEY:
+ return PGP_ARMORED_PUBLIC_KEY;
+ case PGP_PKT_SECRET_KEY:
+ case PGP_PKT_SECRET_SUBKEY:
+ return PGP_ARMORED_SECRET_KEY;
+ case PGP_PKT_SIGNATURE:
+ return PGP_ARMORED_SIGNATURE;
+ default:
+ return PGP_ARMORED_UNKNOWN;
+ }
+}
+
+static pgp_armored_msg_t
+rnp_armored_guess_type_by_readahead(pgp_source_t *src)
+{
+ if (!src->cache) {
+ return PGP_ARMORED_UNKNOWN;
+ }
+
+ pgp_source_t armorsrc = {0};
+ pgp_source_t memsrc = {0};
+ size_t read;
+ // peek as much as the cache can take
+ bool cache_res = src_peek(src, NULL, sizeof(src->cache->buf), &read);
+ if (!cache_res || !read ||
+ init_mem_src(&memsrc,
+ src->cache->buf + src->cache->pos,
+ src->cache->len - src->cache->pos,
+ false)) {
+ return PGP_ARMORED_UNKNOWN;
+ }
+ rnp_result_t res = init_armored_src(&armorsrc, &memsrc);
+ if (res) {
+ src_close(&memsrc);
+ RNP_LOG("failed to parse armored data");
+ return PGP_ARMORED_UNKNOWN;
+ }
+ pgp_armored_msg_t guessed = rnp_armor_guess_type(&armorsrc);
+ src_close(&armorsrc);
+ src_close(&memsrc);
+ return guessed;
+}
+
+pgp_armored_msg_t
+rnp_armored_get_type(pgp_source_t *src)
+{
+ pgp_armored_msg_t guessed = rnp_armored_guess_type_by_readahead(src);
+ if (guessed != PGP_ARMORED_UNKNOWN) {
+ return guessed;
+ }
+
+ char hdr[ARMORED_PEEK_BUF_SIZE];
+ const char *armhdr;
+ size_t armhdrlen;
+ size_t read;
+
+ if (!src_peek(src, hdr, sizeof(hdr), &read) || (read < 20)) {
+ return PGP_ARMORED_UNKNOWN;
+ }
+ if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) {
+ return PGP_ARMORED_UNKNOWN;
+ }
+
+ return armor_str_to_data_type(armhdr + 5, armhdrlen - 10);
+}
+
+static bool
+armor_parse_header(pgp_source_t *src)
+{
+ char hdr[ARMORED_PEEK_BUF_SIZE];
+ const char * armhdr;
+ size_t armhdrlen;
+ size_t read;
+ pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param;
+
+ if (!src_peek(param->readsrc, hdr, sizeof(hdr), &read) || (read < 20)) {
+ return false;
+ }
+
+ if (!(armhdr = find_armor_header(hdr, read, &armhdrlen))) {
+ RNP_LOG("no armor header");
+ return false;
+ }
+
+ /* if there are non-whitespaces before the armor header then issue warning */
+ for (char *ch = hdr; ch < armhdr; ch++) {
+ if (B64DEC[(uint8_t) *ch] != 0xfd) {
+ RNP_LOG("extra data before the header line");
+ break;
+ }
+ }
+
+ param->type = armor_str_to_data_type(armhdr + 5, armhdrlen - 10);
+ if (param->type == PGP_ARMORED_UNKNOWN) {
+ RNP_LOG("unknown armor header");
+ return false;
+ }
+
+ if ((param->armorhdr = (char *) malloc(armhdrlen - 9)) == NULL) {
+ RNP_LOG("allocation failed");
+ return false;
+ }
+
+ memcpy(param->armorhdr, armhdr + 5, armhdrlen - 10);
+ param->armorhdr[armhdrlen - 10] = '\0';
+ src_skip(param->readsrc, armhdr - hdr + armhdrlen);
+ armor_skip_chars(param->readsrc, "\t ");
+ return true;
+}
+
+static bool
+armor_skip_line(pgp_source_t *src)
+{
+ char header[ARMORED_PEEK_BUF_SIZE] = {0};
+ do {
+ size_t hdrlen = 0;
+ bool res = src_peek_line(src, header, sizeof(header), &hdrlen);
+ if (hdrlen) {
+ src_skip(src, hdrlen);
+ }
+ if (res || (hdrlen < sizeof(header) - 1)) {
+ return res;
+ }
+ } while (1);
+}
+
+static bool
+is_base64_line(const char *line, size_t len)
+{
+ for (size_t i = 0; i < len && line[i]; i++) {
+ if (B64DEC[(uint8_t) line[i]] == 0xff)
+ return false;
+ }
+ return true;
+}
+
+static bool
+armor_parse_headers(pgp_source_t *src)
+{
+ pgp_source_armored_param_t *param = (pgp_source_armored_param_t *) src->param;
+ char header[ARMORED_PEEK_BUF_SIZE] = {0};
+
+ do {
+ size_t hdrlen = 0;
+ if (!src_peek_line(param->readsrc, header, sizeof(header), &hdrlen)) {
+ /* if line is too long let's cut it to the reasonable size */
+ src_skip(param->readsrc, hdrlen);
+ if ((hdrlen != sizeof(header) - 1) || !armor_skip_line(param->readsrc)) {
+ RNP_LOG("failed to peek line: unexpected end of data");
+ return false;
+ }
+ RNP_LOG("Too long armor header - truncated.");
+ header[hdrlen] = '\0';
+ } else if (hdrlen) {
+ if (is_base64_line(header, hdrlen)) {
+ RNP_LOG("Warning: no empty line after the base64 headers");
+ return true;
+ }
+ src_skip(param->readsrc, hdrlen);
+ if (rnp::is_blank_line(header, hdrlen)) {
+ return src_skip_eol(param->readsrc);
+ }
+ } else {
+ /* empty line - end of the headers */
+ return src_skip_eol(param->readsrc);
+ }
+
+ char *hdrval = (char *) malloc(hdrlen + 1);
+ if (!hdrval) {
+ RNP_LOG("malloc failed");
+ return false;
+ }
+
+ if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_VERSION, 9)) {
+ memcpy(hdrval, header + 9, hdrlen - 8);
+ free(param->version);
+ param->version = hdrval;
+ } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_COMMENT, 9)) {
+ memcpy(hdrval, header + 9, hdrlen - 8);
+ free(param->comment);
+ param->comment = hdrval;
+ } else if ((hdrlen >= 5) && !strncmp(header, ST_HEADER_HASH, 6)) {
+ memcpy(hdrval, header + 6, hdrlen - 5);
+ free(param->hash);
+ param->hash = hdrval;
+ } else if ((hdrlen >= 9) && !strncmp(header, ST_HEADER_CHARSET, 9)) {
+ memcpy(hdrval, header + 9, hdrlen - 8);
+ free(param->charset);
+ param->charset = hdrval;
+ } else {
+ RNP_LOG("unknown header '%s'", header);
+ free(hdrval);
+ }
+
+ if (!src_skip_eol(param->readsrc)) {
+ return false;
+ }
+ } while (1);
+}
+
+rnp_result_t
+init_armored_src(pgp_source_t *src, pgp_source_t *readsrc, bool noheaders)
+{
+ if (!init_src_common(src, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ 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 */
+ if (!src_skip_eol(param->readsrc)) {
+ RNP_LOG("no eol after the armor header");
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ /* parsing headers */
+ if (!armor_parse_headers(src)) {
+ RNP_LOG("failed to parse headers");
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+
+ /* now we are good to go with base64-encoded data */
+ errcode = RNP_SUCCESS;
+finish:
+ if (errcode) {
+ src_close(src);
+ }
+ return errcode;
+}
+
+/** @brief Write message header to the dst. */
+static bool
+armor_write_message_header(pgp_dest_armored_param_t *param, bool finish)
+{
+ 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";
+ break;
+ case PGP_ARMORED_SECRET_KEY:
+ str = "PRIVATE KEY BLOCK";
+ break;
+ case PGP_ARMORED_SIGNATURE:
+ str = "SIGNATURE";
+ break;
+ case PGP_ARMORED_CLEARTEXT:
+ str = "SIGNED MESSAGE";
+ break;
+ default:
+ return false;
+ }
+ 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->eol[0]) {
+ dst_write(param->writedst, &param->eol[0], 1);
+ }
+ if (param->eol[1]) {
+ dst_write(param->writedst, &param->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] = {
+ 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R',
+ 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
+ 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1',
+ '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H',
+ 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z',
+ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r',
+ 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
+ '+', '/', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
+ 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h',
+ 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z',
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '+', '/', 'A', 'B', 'C', 'D', 'E', 'F',
+ 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
+ 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
+ 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', '+', '/'};
+
+static void
+armored_encode3(uint8_t *out, uint8_t *in)
+{
+ out[0] = B64ENC[in[0] >> 2];
+ out[1] = B64ENC[((in[0] << 4) | (in[1] >> 4)) & 0xff];
+ out[2] = B64ENC[((in[1] << 2) | (in[2] >> 6)) & 0xff];
+ out[3] = B64ENC[in[2] & 0xff];
+}
+
+static rnp_result_t
+armored_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ 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 */
+ 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(&param->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) {
+ 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 */
+ auto inllen = (adjusted_llen >> 2) + (adjusted_llen >> 1);
+ /* pointer to the last full line space in encbuf */
+ 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 */
+ 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 {
+ /* we have full line of input */
+ param->lout = 0;
+ }
+
+ /* processing one line */
+ while (bufptr < inlend) {
+ 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) {
+ armor_append_eol(param, encptr);
+ }
+ }
+
+ dst_write(param->writedst, encbuf, encptr - encbuf);
+
+ /* saving tail */
+ param->tailc = bufend - bufptr;
+ memcpy(param->tail, bufptr, param->tailc);
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+armored_dst_finish(pgp_dest_t *dst)
+{
+ 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;
+ dst_write(param->writedst, buf, 4);
+ } else if (param->tailc == 2) {
+ buf[0] = B64ENC[(param->tail[0] >> 2)];
+ 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;
+ try {
+ auto crc = param->crc_ctx->finish();
+ armored_encode3(&buf[1], crc.data());
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ }
+ dst_write(param->writedst, buf, 5);
+ armor_write_eol(param);
+
+ /* writing armor header */
+ 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)
+{
+ pgp_dest_armored_param_t *param = (pgp_dest_armored_param_t *) dst->param;
+
+ if (!param) {
+ return;
+ }
+ /* dst_close may be called without dst_finish on error */
+ delete param;
+ dst->param = NULL;
+}
+
+rnp_result_t
+init_armored_dst(pgp_dest_t *dst, pgp_dest_t *writedst, pgp_armored_msg_t msgtype)
+{
+ if (!init_dst_common(dst, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ 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;
+ dst->finish = armored_dst_finish;
+ dst->close = armored_dst_close;
+ dst->type = PGP_STREAM_ARMORED;
+ dst->writeb = 0;
+ dst->clen = 0;
+
+ param->writedst = writedst;
+ param->type = msgtype;
+ /* 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 */
+ /* armor header */
+ if (!armor_write_message_header(param, false)) {
+ RNP_LOG("unknown data type");
+ armored_dst_close(dst, true);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ armor_write_eol(param);
+ /* empty line */
+ armor_write_eol(param);
+ return RNP_SUCCESS;
+}
+
+bool
+is_armored_dest(pgp_dest_t *dst)
+{
+ return dst->type == PGP_STREAM_ARMORED;
+}
+
+rnp_result_t
+armored_dst_set_line_length(pgp_dest_t *dst, size_t llen)
+{
+ if (!dst || (llen < ARMORED_MIN_LINE_LENGTH) || (llen > ARMORED_MAX_LINE_LENGTH) ||
+ !dst->param || !is_armored_dest(dst)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ auto param = (pgp_dest_armored_param_t *) dst->param;
+ param->llen = llen;
+ return RNP_SUCCESS;
+}
+
+bool
+is_armored_source(pgp_source_t *src)
+{
+ uint8_t buf[ARMORED_PEEK_BUF_SIZE];
+ size_t read = 0;
+
+ if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_ARMOR_BEGIN) + 1)) {
+ return false;
+ }
+ buf[read - 1] = 0;
+ return !!strstr((char *) buf, ST_ARMOR_BEGIN);
+}
+
+bool
+is_cleartext_source(pgp_source_t *src)
+{
+ uint8_t buf[ARMORED_PEEK_BUF_SIZE];
+ size_t read = 0;
+
+ if (!src_peek(src, buf, sizeof(buf), &read) || (read < strlen(ST_CLEAR_BEGIN))) {
+ return false;
+ }
+ 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};
+
+ /* initializing armored message */
+ res = init_armored_src(&armorsrc, src);
+ if (res) {
+ return res;
+ }
+ /* Reading data from armored source and writing it to the output */
+ res = dst_write_src(&armorsrc, dst);
+ if (res) {
+ RNP_LOG("dearmoring failed");
+ }
+
+ src_close(&armorsrc);
+ return res;
+}
+
+rnp_result_t
+rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype)
+{
+ pgp_dest_t armordst = {0};
+ rnp_result_t res = init_armored_dst(&armordst, dst, msgtype);
+ if (res) {
+ return res;
+ }
+
+ res = dst_write_src(src, &armordst);
+ if (res) {
+ RNP_LOG("armoring failed");
+ }
+
+ 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/src/librepgp/stream-armor.h b/src/librepgp/stream-armor.h
new file mode 100644
index 0000000..4c91fd2
--- /dev/null
+++ b/src/librepgp/stream-armor.h
@@ -0,0 +1,174 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STREAM_ARMOUR_H_
+#define STREAM_ARMOUR_H_
+
+#include "stream-common.h"
+
+typedef enum {
+ PGP_ARMORED_UNKNOWN,
+ PGP_ARMORED_MESSAGE,
+ PGP_ARMORED_PUBLIC_KEY,
+ PGP_ARMORED_SECRET_KEY,
+ PGP_ARMORED_SIGNATURE,
+ 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,
+ 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)
+ * @return RNP_SUCCESS on success or error code otherwise
+ **/
+rnp_result_t init_armored_dst(pgp_dest_t * dst,
+ pgp_dest_t * writedst,
+ pgp_armored_msg_t msgtype);
+
+/* @brief Dearmor the source, outputting binary data
+ * @param src initialized source with armored data
+ * @param dst initialized dest to write binary data to
+ * @return RNP_SUCCESS on success or error code otherwise
+ **/
+rnp_result_t rnp_dearmor_source(pgp_source_t *src, pgp_dest_t *dst);
+
+/* @brief Armor the source, outputting base64-encoded data with headers
+ * @param src initialized source with binary data
+ * @param dst destination to write armored data
+ * @msgtype type of the message, to write correct armor headers
+ * @return RNP_SUCCESS on success or error code otherwise
+ **/
+rnp_result_t rnp_armor_source(pgp_source_t *src, pgp_dest_t *dst, pgp_armored_msg_t msgtype);
+
+/* @brief Guess the corresponding armored message type by first byte(s) of PGP message
+ * @param src initialized source with binary PGP message data
+ * @return corresponding enum element or PGP_ARMORED_UNKNOWN
+ **/
+pgp_armored_msg_t rnp_armor_guess_type(pgp_source_t *src);
+
+/* @brief Get type of the armored message by peeking header.
+ * @param src initialized source with armored message data.
+ * @return corresponding enum element or PGP_ARMORED_UNKNOWN
+ **/
+pgp_armored_msg_t rnp_armored_get_type(pgp_source_t *src);
+
+/* @brief Check whether source could be an armored source
+ * @param src initialized source with some data
+ * @return true if source could be an armored data or false otherwise
+ **/
+bool is_armored_source(pgp_source_t *src);
+
+/* @brief Check whether destination is armored
+ * @param dest initialized destination
+ * @return true if destination is armored or false otherwise
+ **/
+bool is_armored_dest(pgp_dest_t *dst);
+
+/* @brief Check whether source is cleartext signed
+ * @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/src/librepgp/stream-common.cpp b/src/librepgp/stream-common.cpp
new file mode 100644
index 0000000..334f93b
--- /dev/null
+++ b/src/librepgp/stream-common.cpp
@@ -0,0 +1,1212 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#include <string.h>
+#else
+#include "uniwin.h"
+#endif
+#include <sys/stat.h>
+#include <stdarg.h>
+#include <errno.h>
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#include <rnp/rnp_def.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "types.h"
+#include "file-utils.h"
+#include "crypto/mem.h"
+#include <algorithm>
+#include <memory>
+
+bool
+src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ size_t left = len;
+ size_t read;
+ pgp_source_cache_t *cache = src->cache;
+ bool readahead = cache ? cache->readahead : false;
+
+ if (src->error) {
+ return false;
+ }
+
+ if (src->eof || (len == 0)) {
+ *readres = 0;
+ return true;
+ }
+
+ // Do not read more then available if source size is known
+ if (src->knownsize && (src->readb + len > src->size)) {
+ len = src->size - src->readb;
+ left = len;
+ readahead = false;
+ }
+
+ // Check whether we have cache and there is data inside
+ if (cache && (cache->len > cache->pos)) {
+ read = cache->len - cache->pos;
+ if (read >= len) {
+ memcpy(buf, &cache->buf[cache->pos], len);
+ cache->pos += len;
+ goto finish;
+ } else {
+ memcpy(buf, &cache->buf[cache->pos], read);
+ cache->pos += read;
+ buf = (uint8_t *) buf + read;
+ left = len - read;
+ }
+ }
+
+ // If we got here then we have empty cache or no cache at all
+ while (left > 0) {
+ if (left > sizeof(cache->buf) || !readahead || !cache) {
+ // If there is no cache or chunk is larger then read directly
+ if (!src->read(src, buf, left, &read)) {
+ src->error = 1;
+ return false;
+ }
+ if (!read) {
+ src->eof = 1;
+ len = len - left;
+ goto finish;
+ }
+ left -= read;
+ buf = (uint8_t *) buf + read;
+ } else {
+ // Try to fill the cache to avoid small reads
+ if (!src->read(src, &cache->buf[0], sizeof(cache->buf), &read)) {
+ src->error = 1;
+ return false;
+ }
+ if (!read) {
+ src->eof = 1;
+ len = len - left;
+ goto finish;
+ } else if (read < left) {
+ memcpy(buf, &cache->buf[0], read);
+ left -= read;
+ buf = (uint8_t *) buf + read;
+ } else {
+ memcpy(buf, &cache->buf[0], left);
+ cache->pos = left;
+ cache->len = read;
+ goto finish;
+ }
+ }
+ }
+
+finish:
+ src->readb += len;
+ if (src->knownsize && (src->readb == src->size)) {
+ src->eof = 1;
+ }
+ *readres = len;
+ return true;
+}
+
+bool
+src_read_eq(pgp_source_t *src, void *buf, size_t len)
+{
+ size_t res = 0;
+ return src_read(src, buf, len, &res) && (res == len);
+}
+
+bool
+src_peek(pgp_source_t *src, void *buf, size_t len, size_t *peeked)
+{
+ pgp_source_cache_t *cache = src->cache;
+ if (src->error) {
+ return false;
+ }
+ if (!cache || (len > sizeof(cache->buf))) {
+ return false;
+ }
+ if (src->eof) {
+ *peeked = 0;
+ return true;
+ }
+
+ size_t read = 0;
+ bool readahead = cache->readahead;
+ // Do not read more then available if source size is known
+ if (src->knownsize && (src->readb + len > src->size)) {
+ len = src->size - src->readb;
+ readahead = false;
+ }
+
+ if (cache->len - cache->pos >= len) {
+ if (buf) {
+ memcpy(buf, &cache->buf[cache->pos], len);
+ }
+ *peeked = len;
+ return true;
+ }
+
+ if (cache->pos > 0) {
+ memmove(&cache->buf[0], &cache->buf[cache->pos], cache->len - cache->pos);
+ cache->len -= cache->pos;
+ cache->pos = 0;
+ }
+
+ while (cache->len < len) {
+ read = readahead ? sizeof(cache->buf) - cache->len : len - cache->len;
+ if (src->knownsize && (src->readb + read > src->size)) {
+ read = src->size - src->readb;
+ }
+ if (!src->read(src, &cache->buf[cache->len], read, &read)) {
+ src->error = 1;
+ return false;
+ }
+ if (!read) {
+ if (buf) {
+ memcpy(buf, &cache->buf[0], cache->len);
+ }
+ *peeked = cache->len;
+ return true;
+ }
+ cache->len += read;
+ if (cache->len >= len) {
+ if (buf) {
+ memcpy(buf, cache->buf, len);
+ }
+ *peeked = len;
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+src_peek_eq(pgp_source_t *src, void *buf, size_t len)
+{
+ size_t res = 0;
+ return src_peek(src, buf, len, &res) && (res == len);
+}
+
+void
+src_skip(pgp_source_t *src, size_t len)
+{
+ if (src->cache && (src->cache->len - src->cache->pos >= len)) {
+ src->readb += len;
+ src->cache->pos += len;
+ return;
+ }
+
+ size_t res = 0;
+ 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;
+ }
+
+ while (len && !src_eof(src)) {
+ if (!src_read(src, buf, std::min((size_t) PGP_INPUT_CACHE_SIZE, len), &res)) {
+ break;
+ }
+ len -= res;
+ }
+ free(buf);
+}
+
+rnp_result_t
+src_finish(pgp_source_t *src)
+{
+ rnp_result_t res = RNP_SUCCESS;
+ if (src->finish) {
+ res = src->finish(src);
+ }
+
+ return res;
+}
+
+bool
+src_error(const pgp_source_t *src)
+{
+ return src->error;
+}
+
+bool
+src_eof(pgp_source_t *src)
+{
+ if (src->eof) {
+ return true;
+ }
+ /* Error on stream read is NOT considered as eof. See src_error(). */
+ uint8_t check;
+ size_t read = 0;
+ return src_peek(src, &check, 1, &read) && (read == 0);
+}
+
+void
+src_close(pgp_source_t *src)
+{
+ if (src->close) {
+ src->close(src);
+ }
+
+ if (src->cache) {
+ free(src->cache);
+ src->cache = NULL;
+ }
+}
+
+bool
+src_skip_eol(pgp_source_t *src)
+{
+ uint8_t eol[2];
+ size_t read;
+
+ if (!src_peek(src, eol, 2, &read) || !read) {
+ return false;
+ }
+ if (eol[0] == '\n') {
+ src_skip(src, 1);
+ return true;
+ }
+ if ((read == 2) && (eol[0] == '\r') && (eol[1] == '\n')) {
+ src_skip(src, 2);
+ return true;
+ }
+ return false;
+}
+
+bool
+src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *readres)
+{
+ size_t scan_pos = 0;
+ size_t inc = 64;
+ len = len - 1;
+
+ do {
+ size_t to_peek = scan_pos + inc;
+ to_peek = to_peek > len ? len : to_peek;
+ inc = inc * 2;
+
+ /* inefficient, each time we again read from the beginning */
+ if (!src_peek(src, buf, to_peek, readres)) {
+ return false;
+ }
+
+ /* we continue scanning where we stopped previously */
+ for (; scan_pos < *readres; scan_pos++) {
+ if (buf[scan_pos] == '\n') {
+ if ((scan_pos > 0) && (buf[scan_pos - 1] == '\r')) {
+ scan_pos--;
+ }
+ buf[scan_pos] = '\0';
+ *readres = scan_pos;
+ return true;
+ }
+ }
+ if (*readres < to_peek) {
+ return false;
+ }
+ } while (scan_pos < len);
+ return false;
+}
+
+bool
+init_src_common(pgp_source_t *src, size_t paramsize)
+{
+ memset(src, 0, sizeof(*src));
+ src->cache = (pgp_source_cache_t *) calloc(1, sizeof(*src->cache));
+ if (!src->cache) {
+ RNP_LOG("cache allocation failed");
+ return false;
+ }
+ src->cache->readahead = true;
+ if (!paramsize) {
+ return true;
+ }
+ src->param = calloc(1, paramsize);
+ if (!src->param) {
+ RNP_LOG("param allocation failed");
+ free(src->cache);
+ src->cache = NULL;
+ return false;
+ }
+ return true;
+}
+
+typedef struct pgp_source_file_param_t {
+ int fd;
+} pgp_source_file_param_t;
+
+static bool
+file_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ int64_t rres = read(param->fd, buf, len);
+ if (rres < 0) {
+ return false;
+ }
+ *readres = rres;
+ return true;
+}
+
+static void
+file_src_close(pgp_source_t *src)
+{
+ pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param;
+ if (param) {
+ if (src->type == PGP_STREAM_FILE) {
+ close(param->fd);
+ }
+ free(src->param);
+ src->param = NULL;
+ }
+}
+
+static rnp_result_t
+init_fd_src(pgp_source_t *src, int fd, uint64_t *size)
+{
+ if (!init_src_common(src, sizeof(pgp_source_file_param_t))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ pgp_source_file_param_t *param = (pgp_source_file_param_t *) src->param;
+ param->fd = fd;
+ src->read = file_src_read;
+ src->close = file_src_close;
+ src->type = PGP_STREAM_FILE;
+ src->size = size ? *size : 0;
+ src->knownsize = !!size;
+
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+init_file_src(pgp_source_t *src, const char *path)
+{
+ int fd;
+ struct stat st;
+
+ if (rnp_stat(path, &st) != 0) {
+ RNP_LOG("can't stat '%s'", path);
+ return RNP_ERROR_READ;
+ }
+
+ /* read call may succeed on directory depending on OS type */
+ if (S_ISDIR(st.st_mode)) {
+ RNP_LOG("source is directory");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ int flags = O_RDONLY;
+#ifdef HAVE_O_BINARY
+ flags |= O_BINARY;
+#else
+#ifdef HAVE__O_BINARY
+ flags |= _O_BINARY;
+#endif
+#endif
+ fd = rnp_open(path, flags, 0);
+
+ if (fd < 0) {
+ RNP_LOG("can't open '%s'", path);
+ return RNP_ERROR_READ;
+ }
+ uint64_t size = st.st_size;
+ rnp_result_t ret = init_fd_src(src, fd, &size);
+ if (ret) {
+ close(fd);
+ }
+ return ret;
+}
+
+rnp_result_t
+init_stdin_src(pgp_source_t *src)
+{
+ pgp_source_file_param_t *param;
+
+ if (!init_src_common(src, sizeof(pgp_source_file_param_t))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_source_file_param_t *) src->param;
+ param->fd = 0;
+ src->read = file_src_read;
+ src->close = file_src_close;
+ src->type = PGP_STREAM_STDIN;
+
+ return RNP_SUCCESS;
+}
+
+typedef struct pgp_source_mem_param_t {
+ const void *memory;
+ bool free;
+ size_t len;
+ size_t pos;
+} pgp_source_mem_param_t;
+
+typedef struct pgp_dest_mem_param_t {
+ unsigned maxalloc;
+ unsigned allocated;
+ void * memory;
+ bool free;
+ bool discard_overflow;
+ bool secure;
+} pgp_dest_mem_param_t;
+
+static bool
+mem_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ if (len > param->len - param->pos) {
+ len = param->len - param->pos;
+ }
+ memcpy(buf, (uint8_t *) param->memory + param->pos, len);
+ param->pos += len;
+ *read = len;
+ return true;
+}
+
+static void
+mem_src_close(pgp_source_t *src)
+{
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ if (param) {
+ if (param->free) {
+ free((void *) param->memory);
+ }
+ free(src->param);
+ src->param = NULL;
+ }
+}
+
+rnp_result_t
+init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free)
+{
+ if (!mem && len) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ /* this is actually double buffering, but then src_peek will fail */
+ if (!init_src_common(src, sizeof(pgp_source_mem_param_t))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ param->memory = mem;
+ param->len = len;
+ param->pos = 0;
+ param->free = free;
+ src->read = mem_src_read;
+ src->close = mem_src_close;
+ src->finish = NULL;
+ src->size = len;
+ src->knownsize = 1;
+ src->type = PGP_STREAM_MEMORY;
+
+ return RNP_SUCCESS;
+}
+
+static bool
+null_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+ return false;
+}
+
+rnp_result_t
+init_null_src(pgp_source_t *src)
+{
+ memset(src, 0, sizeof(*src));
+ src->read = null_src_read;
+ src->type = PGP_STREAM_NULL;
+ src->error = true;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+read_mem_src(pgp_source_t *src, pgp_source_t *readsrc)
+{
+ pgp_dest_t dst;
+ rnp_result_t ret;
+
+ if ((ret = init_mem_dest(&dst, NULL, 0))) {
+ return ret;
+ }
+
+ if ((ret = dst_write_src(readsrc, &dst))) {
+ goto done;
+ }
+
+ if ((ret = init_mem_src(src, mem_dest_own_memory(&dst), dst.writeb, true))) {
+ goto done;
+ }
+
+ ret = RNP_SUCCESS;
+done:
+ dst_close(&dst, true);
+ return ret;
+}
+
+rnp_result_t
+file_to_mem_src(pgp_source_t *src, const char *filename)
+{
+ pgp_source_t fsrc = {};
+ rnp_result_t res = RNP_ERROR_GENERIC;
+
+ if ((res = init_file_src(&fsrc, filename))) {
+ return res;
+ }
+
+ res = read_mem_src(src, &fsrc);
+ src_close(&fsrc);
+
+ return res;
+}
+
+const void *
+mem_src_get_memory(pgp_source_t *src, bool own)
+{
+ if (src->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return NULL;
+ }
+
+ if (!src->param) {
+ return NULL;
+ }
+
+ pgp_source_mem_param_t *param = (pgp_source_mem_param_t *) src->param;
+ if (own) {
+ param->free = false;
+ }
+ return param->memory;
+}
+
+bool
+init_dst_common(pgp_dest_t *dst, size_t paramsize)
+{
+ memset(dst, 0, sizeof(*dst));
+ dst->werr = RNP_SUCCESS;
+ if (!paramsize) {
+ 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)
+{
+ /* we call write function only if all previous calls succeeded */
+ if ((len > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) {
+ /* if cache non-empty and len will overflow it then fill it and write out */
+ if ((dst->clen > 0) && (dst->clen + len > sizeof(dst->cache))) {
+ memcpy(dst->cache + dst->clen, buf, sizeof(dst->cache) - dst->clen);
+ buf = (uint8_t *) buf + sizeof(dst->cache) - dst->clen;
+ len -= sizeof(dst->cache) - dst->clen;
+ dst->werr = dst->write(dst, dst->cache, sizeof(dst->cache));
+ dst->writeb += sizeof(dst->cache);
+ dst->clen = 0;
+ if (dst->werr != RNP_SUCCESS) {
+ return;
+ }
+ }
+
+ /* here everything will fit into the cache or cache is empty */
+ if (dst->no_cache || (len > sizeof(dst->cache))) {
+ dst->werr = dst->write(dst, buf, len);
+ if (!dst->werr) {
+ dst->writeb += len;
+ }
+ } else {
+ memcpy(dst->cache + dst->clen, buf, len);
+ dst->clen += len;
+ }
+ }
+}
+
+void
+dst_printf(pgp_dest_t *dst, const char *format, ...)
+{
+ char buf[2048];
+ size_t len;
+ va_list ap;
+
+ va_start(ap, format);
+ len = vsnprintf(buf, sizeof(buf), format, ap);
+ va_end(ap);
+
+ if (len >= sizeof(buf)) {
+ RNP_LOG("too long dst_printf");
+ len = sizeof(buf) - 1;
+ }
+ dst_write(dst, buf, len);
+}
+
+void
+dst_flush(pgp_dest_t *dst)
+{
+ if ((dst->clen > 0) && (dst->write) && (dst->werr == RNP_SUCCESS)) {
+ dst->werr = dst->write(dst, dst->cache, dst->clen);
+ dst->writeb += dst->clen;
+ dst->clen = 0;
+ }
+}
+
+rnp_result_t
+dst_finish(pgp_dest_t *dst)
+{
+ rnp_result_t res = RNP_SUCCESS;
+
+ if (!dst->finished) {
+ /* flush write cache in the dst */
+ dst_flush(dst);
+ if (dst->finish) {
+ res = dst->finish(dst);
+ }
+ dst->finished = true;
+ }
+
+ return res;
+}
+
+void
+dst_close(pgp_dest_t *dst, bool discard)
+{
+ if (!discard && !dst->finished) {
+ dst_finish(dst);
+ }
+
+ if (dst->close) {
+ dst->close(dst, discard);
+ }
+}
+
+typedef struct pgp_dest_file_param_t {
+ int fd;
+ int errcode;
+ bool overwrite;
+ std::string path;
+} pgp_dest_file_param_t;
+
+static rnp_result_t
+file_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* we assyme that blocking I/O is used so everything is written or error received */
+ ssize_t ret = write(param->fd, buf, len);
+ if (ret < 0) {
+ param->errcode = errno;
+ RNP_LOG("write failed, error %d", param->errcode);
+ return RNP_ERROR_WRITE;
+ } else {
+ param->errcode = 0;
+ return RNP_SUCCESS;
+ }
+}
+
+static void
+file_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+
+ if (dst->type == PGP_STREAM_FILE) {
+ close(param->fd);
+ if (discard) {
+ rnp_unlink(param->path.c_str());
+ }
+ }
+
+ delete param;
+ dst->param = NULL;
+}
+
+static rnp_result_t
+init_fd_dest(pgp_dest_t *dst, int fd, const char *path)
+{
+ if (!init_dst_common(dst, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ try {
+ std::unique_ptr<pgp_dest_file_param_t> param(new pgp_dest_file_param_t());
+ param->path = path;
+ param->fd = fd;
+ dst->param = param.release();
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ dst->write = file_dst_write;
+ dst->close = file_dst_close;
+ dst->type = PGP_STREAM_FILE;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite)
+{
+ /* check whether file/dir already exists */
+ struct stat st;
+ if (!rnp_stat(path, &st)) {
+ if (!overwrite) {
+ RNP_LOG("file already exists: '%s'", path);
+ return RNP_ERROR_WRITE;
+ }
+
+ /* if we are overwriting empty directory then should first remove it */
+ if (S_ISDIR(st.st_mode)) {
+ if (rmdir(path) == -1) {
+ RNP_LOG("failed to remove directory: error %d", errno);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+ }
+
+ int flags = O_WRONLY | O_CREAT;
+ flags |= overwrite ? O_TRUNC : O_EXCL;
+#ifdef HAVE_O_BINARY
+ flags |= O_BINARY;
+#else
+#ifdef HAVE__O_BINARY
+ flags |= _O_BINARY;
+#endif
+#endif
+ int fd = rnp_open(path, flags, S_IRUSR | S_IWUSR);
+ if (fd < 0) {
+ RNP_LOG("failed to create file '%s'. Error %d.", path, errno);
+ return RNP_ERROR_WRITE;
+ }
+
+ rnp_result_t res = init_fd_dest(dst, fd, path);
+ if (res) {
+ close(fd);
+ }
+ return res;
+}
+
+#define TMPDST_SUFFIX ".rnp-tmp.XXXXXX"
+
+static rnp_result_t
+file_tmpdst_finish(pgp_dest_t *dst)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ if (!param) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* close the file */
+ close(param->fd);
+ param->fd = -1;
+
+ /* rename the temporary file */
+ if (param->path.size() < strlen(TMPDST_SUFFIX)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ try {
+ /* remove suffix so we have required path */
+ std::string origpath(param->path.begin(), param->path.end() - strlen(TMPDST_SUFFIX));
+ /* check if file already exists */
+ struct stat st;
+ if (!rnp_stat(origpath.c_str(), &st)) {
+ if (!param->overwrite) {
+ RNP_LOG("target path already exists");
+ return RNP_ERROR_BAD_STATE;
+ }
+#ifdef _WIN32
+ /* rename() call on Windows fails if destination exists */
+ else {
+ rnp_unlink(origpath.c_str());
+ }
+#endif
+
+ /* we should remove dir if overwriting, file will be unlinked in rename call */
+ if (S_ISDIR(st.st_mode) && rmdir(origpath.c_str())) {
+ RNP_LOG("failed to remove directory");
+ return RNP_ERROR_BAD_STATE;
+ }
+ }
+
+ if (rnp_rename(param->path.c_str(), origpath.c_str())) {
+ RNP_LOG("failed to rename temporary path to target file: %s", strerror(errno));
+ return RNP_ERROR_BAD_STATE;
+ }
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+}
+
+static void
+file_tmpdst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+
+ /* we close file in finish function, except the case when some error occurred */
+ if (!dst->finished && (dst->type == PGP_STREAM_FILE)) {
+ close(param->fd);
+ if (discard) {
+ rnp_unlink(param->path.c_str());
+ }
+ }
+
+ delete param;
+ dst->param = NULL;
+}
+
+rnp_result_t
+init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite)
+{
+ try {
+ std::string tmp = std::string(path) + std::string(TMPDST_SUFFIX);
+ /* make sure tmp.data() is zero-terminated */
+ tmp.push_back('\0');
+#if defined(HAVE_MKSTEMP) && !defined(_WIN32)
+ int fd = mkstemp(&tmp[0]);
+#else
+ int fd = rnp_mkstemp(&tmp[0]);
+#endif
+ if (fd < 0) {
+ RNP_LOG("failed to create temporary file with template '%s'. Error %d.",
+ tmp.c_str(),
+ errno);
+ return RNP_ERROR_WRITE;
+ }
+ rnp_result_t res = init_fd_dest(dst, fd, tmp.c_str());
+ if (res) {
+ close(fd);
+ return res;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* now let's change some parameters to handle temporary file correctly */
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ param->overwrite = overwrite;
+ dst->finish = file_tmpdst_finish;
+ dst->close = file_tmpdst_close;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+init_stdout_dest(pgp_dest_t *dst)
+{
+ rnp_result_t res = init_fd_dest(dst, STDOUT_FILENO, "");
+ if (res) {
+ return res;
+ }
+ dst->type = PGP_STREAM_STDOUT;
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+mem_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (!param) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* checking whether we need to realloc or discard extra bytes */
+ if (param->discard_overflow && (dst->writeb >= param->allocated)) {
+ return RNP_SUCCESS;
+ }
+ if (param->discard_overflow && (dst->writeb + len > param->allocated)) {
+ len = param->allocated - dst->writeb;
+ }
+
+ if (dst->writeb + len > param->allocated) {
+ if ((param->maxalloc > 0) && (dst->writeb + len > param->maxalloc)) {
+ RNP_LOG("attempt to alloc more then allowed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ /* round up to the page boundary and do it exponentially */
+ size_t alloc = ((dst->writeb + len) * 2 + 4095) / 4096 * 4096;
+ if ((param->maxalloc > 0) && (alloc > param->maxalloc)) {
+ alloc = param->maxalloc;
+ }
+
+ void *newalloc = param->secure ? calloc(1, alloc) : realloc(param->memory, alloc);
+ if (!newalloc) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (param->secure && param->memory) {
+ memcpy(newalloc, param->memory, dst->writeb);
+ secure_clear(param->memory, dst->writeb);
+ free(param->memory);
+ }
+ param->memory = newalloc;
+ param->allocated = alloc;
+ }
+
+ memcpy((uint8_t *) param->memory + dst->writeb, buf, len);
+ return RNP_SUCCESS;
+}
+
+static void
+mem_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+
+ if (param->free) {
+ if (param->secure) {
+ secure_clear(param->memory, param->allocated);
+ }
+ free(param->memory);
+ }
+ free(param);
+ dst->param = NULL;
+}
+
+rnp_result_t
+init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len)
+{
+ pgp_dest_mem_param_t *param;
+
+ if (!init_dst_common(dst, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_dest_mem_param_t *) dst->param;
+
+ param->maxalloc = len;
+ param->allocated = mem ? len : 0;
+ param->memory = mem;
+ param->free = !mem;
+ param->secure = false;
+
+ dst->write = mem_dst_write;
+ dst->close = mem_dst_close;
+ dst->type = PGP_STREAM_MEMORY;
+ dst->werr = RNP_SUCCESS;
+ dst->no_cache = true;
+
+ return RNP_SUCCESS;
+}
+
+void
+mem_dest_discard_overflow(pgp_dest_t *dst, bool discard)
+{
+ if (dst->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return;
+ }
+
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (param) {
+ param->discard_overflow = discard;
+ }
+}
+
+void *
+mem_dest_get_memory(pgp_dest_t *dst)
+{
+ if (dst->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return NULL;
+ }
+
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+
+ if (param) {
+ return param->memory;
+ }
+
+ return NULL;
+}
+
+void *
+mem_dest_own_memory(pgp_dest_t *dst)
+{
+ if (dst->type != PGP_STREAM_MEMORY) {
+ RNP_LOG("wrong function call");
+ return NULL;
+ }
+
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+
+ if (!param) {
+ RNP_LOG("null param");
+ return NULL;
+ }
+
+ dst_finish(dst);
+
+ if (param->free) {
+ if (!dst->writeb) {
+ free(param->memory);
+ param->memory = NULL;
+ return param->memory;
+ }
+ /* it may be larger then required - let's truncate */
+ void *newalloc = realloc(param->memory, dst->writeb);
+ if (!newalloc) {
+ return NULL;
+ }
+ param->memory = newalloc;
+ param->allocated = dst->writeb;
+ param->free = false;
+ return param->memory;
+ }
+
+ /* in this case we should copy the memory */
+ void *res = malloc(dst->writeb);
+ if (res) {
+ memcpy(res, param->memory, dst->writeb);
+ }
+ return res;
+}
+
+void
+mem_dest_secure_memory(pgp_dest_t *dst, bool secure)
+{
+ if (!dst || (dst->type != PGP_STREAM_MEMORY)) {
+ RNP_LOG("wrong function call");
+ return;
+ }
+ pgp_dest_mem_param_t *param = (pgp_dest_mem_param_t *) dst->param;
+ if (param) {
+ param->secure = secure;
+ }
+}
+
+static rnp_result_t
+null_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ return RNP_SUCCESS;
+}
+
+static void
+null_dst_close(pgp_dest_t *dst, bool discard)
+{
+ ;
+}
+
+rnp_result_t
+init_null_dest(pgp_dest_t *dst)
+{
+ dst->param = NULL;
+ dst->write = null_dst_write;
+ dst->close = null_dst_close;
+ dst->type = PGP_STREAM_NULL;
+ dst->writeb = 0;
+ dst->clen = 0;
+ dst->werr = RNP_SUCCESS;
+ dst->no_cache = true;
+
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+dst_write_src(pgp_source_t *src, pgp_dest_t *dst, uint64_t limit)
+{
+ const size_t bufsize = PGP_INPUT_CACHE_SIZE;
+ uint8_t * readbuf = (uint8_t *) malloc(bufsize);
+ if (!readbuf) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t res = RNP_SUCCESS;
+ try {
+ size_t read;
+ uint64_t totalread = 0;
+
+ while (!src->eof) {
+ if (!src_read(src, readbuf, bufsize, &read)) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (!read) {
+ continue;
+ }
+ totalread += read;
+ if (limit && totalread > limit) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (dst) {
+ dst_write(dst, readbuf, read);
+ if (dst->werr) {
+ RNP_LOG("failed to output data");
+ res = RNP_ERROR_WRITE;
+ break;
+ }
+ }
+ }
+ } catch (...) {
+ free(readbuf);
+ throw;
+ }
+ free(readbuf);
+ if (res || !dst) {
+ return res;
+ }
+ dst_flush(dst);
+ return dst->werr;
+}
diff --git a/src/librepgp/stream-common.h b/src/librepgp/stream-common.h
new file mode 100644
index 0000000..02279d3
--- /dev/null
+++ b/src/librepgp/stream-common.h
@@ -0,0 +1,556 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STREAM_COMMON_H_
+#define STREAM_COMMON_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "types.h"
+
+#define PGP_INPUT_CACHE_SIZE 32768
+#define PGP_OUTPUT_CACHE_SIZE 32768
+
+#define PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE 512
+
+typedef enum {
+ PGP_STREAM_NULL,
+ PGP_STREAM_FILE,
+ PGP_STREAM_MEMORY,
+ PGP_STREAM_STDIN,
+ PGP_STREAM_STDOUT,
+ PGP_STREAM_PACKET,
+ PGP_STREAM_PARLEN_PACKET,
+ PGP_STREAM_LITERAL,
+ PGP_STREAM_COMPRESSED,
+ PGP_STREAM_ENCRYPTED,
+ PGP_STREAM_SIGNED,
+ PGP_STREAM_ARMORED,
+ PGP_STREAM_CLEARTEXT
+} pgp_stream_type_t;
+
+typedef struct pgp_source_t pgp_source_t;
+typedef struct pgp_dest_t pgp_dest_t;
+
+typedef bool pgp_source_read_func_t(pgp_source_t *src, void *buf, size_t len, size_t *read);
+typedef rnp_result_t pgp_source_finish_func_t(pgp_source_t *src);
+typedef void pgp_source_close_func_t(pgp_source_t *src);
+
+typedef rnp_result_t pgp_dest_write_func_t(pgp_dest_t *dst, const void *buf, size_t len);
+typedef rnp_result_t pgp_dest_finish_func_t(pgp_dest_t *src);
+typedef void pgp_dest_close_func_t(pgp_dest_t *dst, bool discard);
+
+/* statically preallocated cache for sources */
+typedef struct pgp_source_cache_t {
+ uint8_t buf[PGP_INPUT_CACHE_SIZE];
+ unsigned pos; /* current position in cache */
+ unsigned len; /* number of bytes available in cache */
+ bool readahead; /* whether read-ahead with larger chunks allowed */
+} pgp_source_cache_t;
+
+typedef struct pgp_source_t {
+ pgp_source_read_func_t * read;
+ pgp_source_finish_func_t *finish;
+ pgp_source_close_func_t * close;
+ pgp_stream_type_t type;
+
+ uint64_t size; /* size of the data if available, see knownsize */
+ uint64_t readb; /* number of bytes read from the stream via src_read. Do not confuse with
+ number of bytes as returned via the read since data may be cached */
+ pgp_source_cache_t *cache; /* cache if used */
+ void * param; /* source-specific additional data */
+
+ unsigned eof : 1; /* end of data as reported by read and empty cache */
+ unsigned knownsize : 1; /* whether size of the data is known */
+ unsigned error : 1; /* there were reading error */
+} pgp_source_t;
+
+/** @brief helper function to allocate memory for source's cache and param
+ * Also fills src and param with zeroes
+ * @param src pointer to the source structure
+ * @param paramsize number of bytes required for src->param
+ * @return true on success or false if memory allocation failed.
+ **/
+bool init_src_common(pgp_source_t *src, size_t paramsize);
+
+/** @brief read up to len bytes from the source
+ * While this function tries to read as much bytes as possible however it may return
+ * less then len bytes. Then src->eof can be checked if it's end of data.
+ *
+ * @param src source structure
+ * @param buf preallocated buffer which can store up to len bytes
+ * @param len number of bytes to read
+ * @param read number of read bytes will be stored here. Cannot be NULL.
+ * @return true on success or false otherwise
+ **/
+bool src_read(pgp_source_t *src, void *buf, size_t len, size_t *read);
+
+/** @brief shortcut to read exactly len bytes from source. See src_read for parameters.
+ * @return true if len bytes were read or false otherwise (i.e. less then len were read or
+ * read error occurred) */
+bool src_read_eq(pgp_source_t *src, void *buf, size_t len);
+
+/** @brief read up to len bytes and keep them in the cache/do not process
+ * Works only for streams with cache
+ * @param src source structure
+ * @param buf preallocated buffer which can store up to len bytes, or NULL if data should be
+ * discarded, just making sure that needed input is available in source
+ * @param len number of bytes to read. Must be less then PGP_INPUT_CACHE_SIZE.
+ * @param read number of bytes read will be stored here. Cannot be NULL.
+ * @return true on success or false otherwise
+ **/
+bool src_peek(pgp_source_t *src, void *buf, size_t len, size_t *read);
+
+/** @brief shortcut to read exactly len bytes and keep them in the cache/do not process
+ * Works only for streams with cache
+ * @return true if len bytes were read or false otherwise (i.e. less then len were read or
+ * read error occurred) */
+bool src_peek_eq(pgp_source_t *src, void *buf, size_t len);
+
+/** @brief skip up to len bytes.
+ * Note: use src_read() if you want to check error condition/get number of bytes
+ *skipped.
+ * @param src source structure
+ * @param len number of bytes to skip
+ **/
+void src_skip(pgp_source_t *src, size_t len);
+
+/** @brief notify source that all reading is done, so final data processing may be started,
+ * i.e. signature reading and verification and so on. Do not misuse with src_close.
+ * @param src allocated and initialized source structure
+ * @return RNP_SUCCESS or error code. If source doesn't have finish handler then also
+ * RNP_SUCCESS is returned
+ */
+rnp_result_t src_finish(pgp_source_t *src);
+
+/** @brief check whether there were reading error on source
+ * @param allocated and initialized source structure
+ * @return true if there were reading error or false otherwise
+ */
+bool src_error(const pgp_source_t *src);
+
+/** @brief check whether there is no more input on source
+ * @param src allocated and initialized source structure
+ * @return true if there is no more input or false otherwise.
+ * On read error false will be returned.
+ */
+bool src_eof(pgp_source_t *src);
+
+/** @brief close the source and deallocate all internal resources if any
+ */
+void src_close(pgp_source_t *src);
+
+/** @brief skip end of line on the source (\r\n or \n, depending on input)
+ * @param src allocated and initialized source
+ * @return true if eol was found and skipped or false otherwise
+ */
+bool src_skip_eol(pgp_source_t *src);
+
+/** @brief peek the line on the source
+ * @param src allocated and initialized source with data
+ * @param buf preallocated buffer to store the result. Result include NULL character and
+ * doesn't include the end of line sequence.
+ * @param len maximum length of data to store in buf, including terminating NULL
+ * @param read on success here will be stored number of bytes in the string, without the NULL
+ * character.
+ * @return true on success
+ * false is returned if there were eof, read error or eol was not found within the
+ * len. Supported eol sequences are \r\n and \n
+ */
+bool src_peek_line(pgp_source_t *src, char *buf, size_t len, size_t *read);
+
+/** @brief init file source
+ * @param src pre-allocated source structure
+ * @param path path to the file
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_file_src(pgp_source_t *src, const char *path);
+
+/** @brief init stdin source
+ * @param src pre-allocated source structure
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_stdin_src(pgp_source_t *src);
+
+/** @brief init memory source
+ * @param src pre-allocated source structure
+ * @param mem memory to read from
+ * @param len number of bytes in input
+ * @param free free the memory pointer on stream close or not
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_mem_src(pgp_source_t *src, const void *mem, size_t len, bool free);
+
+/** @brief init NULL source, which doesn't allow to read anything and always returns an error.
+ * @param src pre-allocated source structure
+ * @return always RNP_SUCCESS
+ **/
+rnp_result_t init_null_src(pgp_source_t *src);
+
+/** @brief init memory source with contents of other source
+ * @param src pre-allocated source structure
+ * @param readsrc opened source with data
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t read_mem_src(pgp_source_t *src, pgp_source_t *readsrc);
+
+/** @brief init memory source with contents of the specified file
+ * @param src pre-allocated source structure
+ * @param filename name of the file
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t file_to_mem_src(pgp_source_t *src, const char *filename);
+
+/** @brief get memory from the memory source
+ * @param src initialized memory source
+ * @param own transfer ownership of the memory
+ * @return pointer to the memory or NULL if it is not a memory source
+ **/
+const void *mem_src_get_memory(pgp_source_t *src, bool own = false);
+
+typedef struct pgp_dest_t {
+ pgp_dest_write_func_t * write;
+ pgp_dest_finish_func_t *finish;
+ pgp_dest_close_func_t * close;
+ pgp_stream_type_t type;
+ rnp_result_t werr; /* write function may set this to some error code */
+
+ size_t writeb; /* number of bytes written */
+ void * param; /* source-specific additional data */
+ bool no_cache; /* disable write caching */
+ uint8_t cache[PGP_OUTPUT_CACHE_SIZE];
+ unsigned clen; /* number of bytes in cache */
+ bool finished; /* whether dst_finish was called on dest or not */
+} pgp_dest_t;
+
+/** @brief helper function to allocate memory for dest's param.
+ * Initializes dst and param with zeroes as well.
+ * @param dst dest structure
+ * @param paramsize number of bytes required for dst->param
+ * @return true on success, or false if memory allocation failed
+ **/
+bool init_dst_common(pgp_dest_t *dst, size_t paramsize);
+
+/** @brief write buffer to the destination
+ *
+ * @param dst destination structure
+ * @param buf buffer with data
+ * @param len number of bytes to write
+ * @return true on success or false otherwise
+ **/
+void dst_write(pgp_dest_t *dst, const void *buf, size_t len);
+
+/** @brief printf formatted string to the destination
+ *
+ * @param dst destination structure
+ * @param format format string, which is the same as printf() uses
+ * @param ... additional arguments
+ */
+void dst_printf(pgp_dest_t *dst, const char *format, ...);
+
+/** @brief do all finalization tasks after all writing is done, i.e. calculate and write
+ * mdc, signatures and so on. Do not misuse with dst_close. If was not called then will be
+ * called from the dst_close
+ *
+ * @param dst destination structure
+ * @return RNP_SUCCESS or error code if something went wrong
+ **/
+rnp_result_t dst_finish(pgp_dest_t *dst);
+
+/** @brief close the destination
+ *
+ * @param dst destination structure to be closed
+ * @param discard if this is true then all produced output should be discarded
+ * @return void
+ **/
+void dst_close(pgp_dest_t *dst, bool discard);
+
+/** @brief flush cached data if any. dst_write caches small writes, so data does not
+ * immediately go to stream write function.
+ *
+ * @param dst destination structure
+ * @return void
+ **/
+void dst_flush(pgp_dest_t *dst);
+
+/** @brief init file destination
+ * @param dst pre-allocated dest structure
+ * @param path path to the file
+ * @param overwrite overwrite existing file
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_file_dest(pgp_dest_t *dst, const char *path, bool overwrite);
+
+/** @brief init file destination, using the temporary file name, based on path.
+ * Once writing is over, dst_finish() will attempt to rename to the desired name.
+ * @param dst pre-allocated dest structure
+ * @param path path to the file
+ * @param overwrite overwrite existing file on rename
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_tmpfile_dest(pgp_dest_t *dst, const char *path, bool overwrite);
+
+/** @brief init stdout destination
+ * @param dst pre-allocated dest structure
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_stdout_dest(pgp_dest_t *dst);
+
+/** @brief init memory destination
+ * @param dst pre-allocated dest structure
+ * @param mem pointer to the pre-allocated memory buffer, or NULL if it should be allocated
+ * @param len number of bytes which mem can keep, or maximum amount of memory to allocate if
+ * mem is NULL. If len is zero in later case then allocation is not limited.
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_mem_dest(pgp_dest_t *dst, void *mem, unsigned len);
+
+/** @brief set whether to silently discard bytes which overflow memory of the dst.
+ * @param dst pre-allocated and initialized memory dest
+ * @param discard true to discard or false to return an error on overflow.
+ **/
+void mem_dest_discard_overflow(pgp_dest_t *dst, bool discard);
+
+/** @brief get the pointer to the memory where data is written.
+ * Do not retain the result, it may change between calls due to realloc
+ * @param dst pre-allocated and initialized memory dest
+ * @return pointer to the memory area or NULL if memory was not allocated
+ **/
+void *mem_dest_get_memory(pgp_dest_t *dst);
+
+/** @brief get ownership on the memory dest's contents. This must be called only before
+ * closing the dest
+ * @param dst pre-allocated and initialized memory dest
+ * @return pointer to the memory area or NULL if memory was not allocated (i.e. nothing was
+ * written to the destination). Also NULL will be returned on possible (re-)allocation
+ * failure, this case can be identified by non-zero dst->writeb.
+ **/
+void *mem_dest_own_memory(pgp_dest_t *dst);
+
+/** @brief mark memory dest as secure, so it will be deallocated securely
+ * @param dst pre-allocated and initialized memory dest
+ * @param secure whether memory should be considered as secure or not
+ * @return void
+ **/
+void mem_dest_secure_memory(pgp_dest_t *dst, bool secure);
+
+/** @brief init null destination which silently discards all the output
+ * @param dst pre-allocated dest structure
+ * @return RNP_SUCCESS or error code
+ **/
+rnp_result_t init_null_dest(pgp_dest_t *dst);
+
+/** @brief reads from source and writes to destination
+ * @param src initialized source
+ * @param dst initialized destination
+ * @param limit sets the maximum amount of bytes to be read,
+ * returning an error if the source hasn't come to eof after that amount
+ * 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<uint8_t> &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<uint8_t>
+ to_vector()
+ {
+ uint8_t *mem = (uint8_t *) memory();
+ return std::vector<uint8_t>(mem, mem + writeb());
+ }
+};
+} // namespace rnp
+
+#endif
diff --git a/src/librepgp/stream-ctx.cpp b/src/librepgp/stream-ctx.cpp
new file mode 100644
index 0000000..28b5444
--- /dev/null
+++ b/src/librepgp/stream-ctx.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <string.h>
+#include <assert.h>
+#include "defaults.h"
+#include "utils.h"
+#include "stream-ctx.h"
+
+rnp_result_t
+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;
+ 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);
+ info.s2k_cipher = ealg;
+ /* Note: we're relying on the fact that a longer-than-needed key length
+ * here does not change the entire derived key (it just generates unused
+ * extra bytes at the end). We derive a key of our maximum supported length,
+ * which is a bit wasteful.
+ *
+ * This is done because we do not yet know what cipher this key will actually
+ * 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.c_str(), info.key.data(), info.key.size())) {
+ return RNP_ERROR_GENERIC;
+ }
+ passwords.push_back(info);
+ return RNP_SUCCESS;
+}
diff --git a/src/librepgp/stream-ctx.h b/src/librepgp/stream-ctx.h
new file mode 100644
index 0000000..b9e0c10
--- /dev/null
+++ b/src/librepgp/stream-ctx.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2019-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STREAM_CTX_H_
+#define STREAM_CTX_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "types.h"
+#include <string>
+#include <list>
+#include "pgp-key.h"
+#include "crypto/mem.h"
+#include "sec_profile.hpp"
+
+/* signature info structure */
+typedef struct rnp_signer_info_t {
+ pgp_key_t * key{};
+ pgp_hash_alg_t halg{};
+ int64_t sigcreate{};
+ uint64_t sigexpire{};
+} rnp_signer_info_t;
+
+typedef struct rnp_symmetric_pass_info_t {
+ pgp_s2k_t s2k{};
+ pgp_symm_alg_t s2k_cipher{};
+
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> key;
+} rnp_symmetric_pass_info_t;
+
+/** rnp operation context : contains configuration data about the currently ongoing operation.
+ *
+ * Common fields which make sense for every operation:
+ * - overwrite : silently overwrite output file if exists
+ * - armor : except cleartext signing, which outputs text in clear and always armor signature,
+ * this controls whether output is armored (base64-encoded). For armor/dearmor operation it
+ * controls the direction of the conversion (true means enarmor, false - dearmor),
+ * - rng : random number generator
+ * - operation : current operation type
+ *
+ * For operations with OpenPGP embedded data (i.e. encrypted data and attached signatures):
+ * - filename, filemtime : to specify information about the contents of literal data packet
+ * - zalg, zlevel : compression algorithm and level, zlevel = 0 to disable compression
+ *
+ * For encryption operation (including encrypt-and-sign):
+ * - halg : hash algorithm used during key derivation for password-based encryption
+ * - ealg, aalg, abits : symmetric encryption algorithm and AEAD parameters if used
+ * - recipients : list of key ids used to encrypt data to
+ * - passwords : list of passwords used for password-based encryption
+ * - filename, filemtime, zalg, zlevel : see previous
+ *
+ * For signing of any kind (attached, detached, cleartext):
+ * - clearsign, detached : controls kind of the signed data. Both are mutually-exclusive.
+ * If both are false then attached signing is used.
+ * - halg : hash algorithm used to calculate signature(s)
+ * - signers : list of rnp_signer_info_t structures describing signing key and parameters
+ * - sigcreate, sigexpire : default signature(s) creation and expiration times
+ * - filename, filemtime, zalg, zlevel : only for attached signatures, see previous
+ *
+ * For data decryption and/or verification there is not much of fields:
+ * - discard: discard the output data (i.e. just decrypt and/or verify signatures)
+ *
+ */
+
+typedef struct rnp_ctx_t {
+ std::string filename{}; /* name of the input file to store in literal data packet */
+ int64_t filemtime{}; /* file modification time to store in literal data packet */
+ int64_t sigcreate{}; /* signature creation time */
+ uint64_t sigexpire{}; /* signature expiration time */
+ bool clearsign{}; /* cleartext signature */
+ bool detached{}; /* detached signature */
+ pgp_hash_alg_t halg{}; /* hash algorithm */
+ pgp_symm_alg_t ealg{}; /* encryption algorithm */
+ int zalg{}; /* compression algorithm used */
+ 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<pgp_key_t *> recipients{}; /* recipients of the encrypted message */
+ std::list<rnp_symmetric_pass_info_t> passwords{}; /* passwords to encrypt message */
+ std::list<rnp_signer_info_t> signers{}; /* keys to which sign message */
+ rnp::SecurityContext * ctx{}; /* pointer to rnp::RNG */
+
+ 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;
+
+#endif
diff --git a/src/librepgp/stream-def.h b/src/librepgp/stream-def.h
new file mode 100644
index 0000000..7a1108f
--- /dev/null
+++ b/src/librepgp/stream-def.h
@@ -0,0 +1,67 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STREAM_DEF_H_
+#define STREAM_DEF_H_
+
+#define CT_BUF_LEN 4096
+#define CH_CR ('\r')
+#define CH_LF ('\n')
+#define CH_EQ ('=')
+#define CH_DASH ('-')
+#define CH_SPACE (' ')
+#define CH_TAB ('\t')
+#define CH_COMMA (',')
+#define ST_CR ("\r")
+#define ST_LF ("\n")
+#define ST_CRLF ("\r\n")
+#define ST_CRLFCRLF ("\r\n\r\n")
+#define ST_DASHSP ("- ")
+#define ST_COMMA (",")
+
+#define ST_DASHES ("-----")
+#define ST_ARMOR_BEGIN ("-----BEGIN PGP ")
+#define ST_ARMOR_END ("-----END PGP ")
+#define ST_CLEAR_BEGIN ("-----BEGIN PGP SIGNED MESSAGE-----")
+#define ST_SIG_BEGIN ("\n-----BEGIN PGP SIGNATURE-----")
+#define ST_HEADER_VERSION ("Version: ")
+#define ST_HEADER_COMMENT ("Comment: ")
+#define ST_HEADER_HASH ("Hash: ")
+#define ST_HEADER_CHARSET ("Charset: ")
+#define ST_FROM ("From")
+
+/* Preallocated cache length for AEAD encryption/decryption */
+#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/src/librepgp/stream-dump.cpp b/src/librepgp/stream-dump.cpp
new file mode 100644
index 0000000..416f9ae
--- /dev/null
+++ b/src/librepgp/stream-dump.cpp
@@ -0,0 +1,2533 @@
+/*
+ * Copyright (c) 2018-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include "time-utils.h"
+#include "stream-def.h"
+#include "stream-dump.h"
+#include "stream-armor.h"
+#include "stream-packet.h"
+#include "stream-parse.h"
+#include "types.h"
+#include "ctype.h"
+#include "crypto/symmetric.h"
+#include "crypto/s2k.h"
+#include "fingerprint.h"
+#include "pgp-key.h"
+#include "crypto.h"
+#include "json-utils.h"
+#include <algorithm>
+
+static const id_str_pair packet_tag_map[] = {
+ {PGP_PKT_RESERVED, "Reserved"},
+ {PGP_PKT_PK_SESSION_KEY, "Public-Key Encrypted Session Key"},
+ {PGP_PKT_SIGNATURE, "Signature"},
+ {PGP_PKT_SK_SESSION_KEY, "Symmetric-Key Encrypted Session Key"},
+ {PGP_PKT_ONE_PASS_SIG, "One-Pass Signature"},
+ {PGP_PKT_SECRET_KEY, "Secret Key"},
+ {PGP_PKT_PUBLIC_KEY, "Public Key"},
+ {PGP_PKT_SECRET_SUBKEY, "Secret Subkey"},
+ {PGP_PKT_COMPRESSED, "Compressed Data"},
+ {PGP_PKT_SE_DATA, "Symmetrically Encrypted Data"},
+ {PGP_PKT_MARKER, "Marker"},
+ {PGP_PKT_LITDATA, "Literal Data"},
+ {PGP_PKT_TRUST, "Trust"},
+ {PGP_PKT_USER_ID, "User ID"},
+ {PGP_PKT_PUBLIC_SUBKEY, "Public Subkey"},
+ {PGP_PKT_RESERVED2, "reserved2"},
+ {PGP_PKT_RESERVED3, "reserved3"},
+ {PGP_PKT_USER_ATTR, "User Attribute"},
+ {PGP_PKT_SE_IP_DATA, "Symmetric Encrypted and Integrity Protected Data"},
+ {PGP_PKT_MDC, "Modification Detection Code"},
+ {PGP_PKT_AEAD_ENCRYPTED, "AEAD Encrypted Data Packet"},
+ {0x00, NULL},
+};
+
+static const id_str_pair sig_type_map[] = {
+ {PGP_SIG_BINARY, "Signature of a binary document"},
+ {PGP_SIG_TEXT, "Signature of a canonical text document"},
+ {PGP_SIG_STANDALONE, "Standalone signature"},
+ {PGP_CERT_GENERIC, "Generic User ID certification"},
+ {PGP_CERT_PERSONA, "Personal User ID certification"},
+ {PGP_CERT_CASUAL, "Casual User ID certification"},
+ {PGP_CERT_POSITIVE, "Positive User ID certification"},
+ {PGP_SIG_SUBKEY, "Subkey Binding Signature"},
+ {PGP_SIG_PRIMARY, "Primary Key Binding Signature"},
+ {PGP_SIG_DIRECT, "Direct-key signature"},
+ {PGP_SIG_REV_KEY, "Key revocation signature"},
+ {PGP_SIG_REV_SUBKEY, "Subkey revocation signature"},
+ {PGP_SIG_REV_CERT, "Certification revocation signature"},
+ {PGP_SIG_TIMESTAMP, "Timestamp signature"},
+ {PGP_SIG_3RD_PARTY, "Third-Party Confirmation signature"},
+ {0x00, NULL},
+};
+
+static const id_str_pair sig_subpkt_type_map[] = {
+ {PGP_SIG_SUBPKT_CREATION_TIME, "signature creation time"},
+ {PGP_SIG_SUBPKT_EXPIRATION_TIME, "signature expiration time"},
+ {PGP_SIG_SUBPKT_EXPORT_CERT, "exportable certification"},
+ {PGP_SIG_SUBPKT_TRUST, "trust signature"},
+ {PGP_SIG_SUBPKT_REGEXP, "regular expression"},
+ {PGP_SIG_SUBPKT_REVOCABLE, "revocable"},
+ {PGP_SIG_SUBPKT_KEY_EXPIRY, "key expiration time"},
+ {PGP_SIG_SUBPKT_PREFERRED_SKA, "preferred symmetric algorithms"},
+ {PGP_SIG_SUBPKT_REVOCATION_KEY, "revocation key"},
+ {PGP_SIG_SUBPKT_ISSUER_KEY_ID, "issuer key ID"},
+ {PGP_SIG_SUBPKT_NOTATION_DATA, "notation data"},
+ {PGP_SIG_SUBPKT_PREFERRED_HASH, "preferred hash algorithms"},
+ {PGP_SIG_SUBPKT_PREF_COMPRESS, "preferred compression algorithms"},
+ {PGP_SIG_SUBPKT_KEYSERV_PREFS, "key server preferences"},
+ {PGP_SIG_SUBPKT_PREF_KEYSERV, "preferred key server"},
+ {PGP_SIG_SUBPKT_PRIMARY_USER_ID, "primary user ID"},
+ {PGP_SIG_SUBPKT_POLICY_URI, "policy URI"},
+ {PGP_SIG_SUBPKT_KEY_FLAGS, "key flags"},
+ {PGP_SIG_SUBPKT_SIGNERS_USER_ID, "signer's user ID"},
+ {PGP_SIG_SUBPKT_REVOCATION_REASON, "reason for revocation"},
+ {PGP_SIG_SUBPKT_FEATURES, "features"},
+ {PGP_SIG_SUBPKT_SIGNATURE_TARGET, "signature target"},
+ {PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE, "embedded signature"},
+ {PGP_SIG_SUBPKT_ISSUER_FPR, "issuer fingerprint"},
+ {PGP_SIG_SUBPKT_PREFERRED_AEAD, "preferred AEAD algorithms"},
+ {0x00, NULL},
+};
+
+static const id_str_pair key_type_map[] = {
+ {PGP_PKT_SECRET_KEY, "Secret key"},
+ {PGP_PKT_PUBLIC_KEY, "Public key"},
+ {PGP_PKT_SECRET_SUBKEY, "Secret subkey"},
+ {PGP_PKT_PUBLIC_SUBKEY, "Public subkey"},
+ {0x00, NULL},
+};
+
+static const id_str_pair pubkey_alg_map[] = {
+ {PGP_PKA_RSA, "RSA (Encrypt or Sign)"},
+ {PGP_PKA_RSA_ENCRYPT_ONLY, "RSA (Encrypt-Only)"},
+ {PGP_PKA_RSA_SIGN_ONLY, "RSA (Sign-Only)"},
+ {PGP_PKA_ELGAMAL, "Elgamal (Encrypt-Only)"},
+ {PGP_PKA_DSA, "DSA"},
+ {PGP_PKA_ECDH, "ECDH"},
+ {PGP_PKA_ECDSA, "ECDSA"},
+ {PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN, "Elgamal"},
+ {PGP_PKA_RESERVED_DH, "Reserved for DH (X9.42)"},
+ {PGP_PKA_EDDSA, "EdDSA"},
+ {PGP_PKA_SM2, "SM2"},
+ {0x00, NULL},
+};
+
+static const id_str_pair symm_alg_map[] = {
+ {PGP_SA_PLAINTEXT, "Plaintext"},
+ {PGP_SA_IDEA, "IDEA"},
+ {PGP_SA_TRIPLEDES, "TripleDES"},
+ {PGP_SA_CAST5, "CAST5"},
+ {PGP_SA_BLOWFISH, "Blowfish"},
+ {PGP_SA_AES_128, "AES-128"},
+ {PGP_SA_AES_192, "AES-192"},
+ {PGP_SA_AES_256, "AES-256"},
+ {PGP_SA_TWOFISH, "Twofish"},
+ {PGP_SA_CAMELLIA_128, "Camellia-128"},
+ {PGP_SA_CAMELLIA_192, "Camellia-192"},
+ {PGP_SA_CAMELLIA_256, "Camellia-256"},
+ {PGP_SA_SM4, "SM4"},
+ {0x00, NULL},
+};
+
+static const id_str_pair hash_alg_map[] = {
+ {PGP_HASH_MD5, "MD5"},
+ {PGP_HASH_SHA1, "SHA1"},
+ {PGP_HASH_RIPEMD, "RIPEMD160"},
+ {PGP_HASH_SHA256, "SHA256"},
+ {PGP_HASH_SHA384, "SHA384"},
+ {PGP_HASH_SHA512, "SHA512"},
+ {PGP_HASH_SHA224, "SHA224"},
+ {PGP_HASH_SM3, "SM3"},
+ {PGP_HASH_SHA3_256, "SHA3-256"},
+ {PGP_HASH_SHA3_512, "SHA3-512"},
+ {0x00, NULL},
+};
+
+static const id_str_pair z_alg_map[] = {
+ {PGP_C_NONE, "Uncompressed"},
+ {PGP_C_ZIP, "ZIP"},
+ {PGP_C_ZLIB, "ZLib"},
+ {PGP_C_BZIP2, "BZip2"},
+ {0x00, NULL},
+};
+
+static const id_str_pair aead_alg_map[] = {
+ {PGP_AEAD_NONE, "None"},
+ {PGP_AEAD_EAX, "EAX"},
+ {PGP_AEAD_OCB, "OCB"},
+ {0x00, NULL},
+};
+
+static const id_str_pair revoc_reason_map[] = {
+ {PGP_REVOCATION_NO_REASON, "No reason"},
+ {PGP_REVOCATION_SUPERSEDED, "Superseded"},
+ {PGP_REVOCATION_COMPROMISED, "Compromised"},
+ {PGP_REVOCATION_RETIRED, "Retired"},
+ {PGP_REVOCATION_NO_LONGER_VALID, "No longer valid"},
+ {0x00, NULL},
+};
+
+typedef struct pgp_dest_indent_param_t {
+ int level;
+ bool lstart;
+ pgp_dest_t *writedst;
+} pgp_dest_indent_param_t;
+
+static rnp_result_t
+indent_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param;
+ const char * line = (const char *) buf;
+ char indent[4] = {' ', ' ', ' ', ' '};
+
+ if (!len) {
+ return RNP_SUCCESS;
+ }
+
+ do {
+ if (param->lstart) {
+ for (int i = 0; i < param->level; i++) {
+ dst_write(param->writedst, indent, sizeof(indent));
+ }
+ param->lstart = false;
+ }
+
+ for (size_t i = 0; i < len; i++) {
+ if ((line[i] == '\n') || (i == len - 1)) {
+ dst_write(param->writedst, line, i + 1);
+ param->lstart = line[i] == '\n';
+ line += i + 1;
+ len -= i + 1;
+ break;
+ }
+ }
+ } while (len > 0);
+
+ return RNP_SUCCESS;
+}
+
+static void
+indent_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+
+ free(param);
+}
+
+static rnp_result_t
+init_indent_dest(pgp_dest_t *dst, pgp_dest_t *origdst)
+{
+ pgp_dest_indent_param_t *param;
+
+ if (!init_dst_common(dst, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ dst->write = indent_dst_write;
+ dst->close = indent_dst_close;
+ dst->finish = NULL;
+ dst->no_cache = true;
+ param = (pgp_dest_indent_param_t *) dst->param;
+ param->writedst = origdst;
+ param->lstart = true;
+
+ return RNP_SUCCESS;
+}
+
+static void
+indent_dest_increase(pgp_dest_t *dst)
+{
+ pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param;
+ param->level++;
+}
+
+static void
+indent_dest_decrease(pgp_dest_t *dst)
+{
+ pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param;
+ if (param->level > 0) {
+ param->level--;
+ }
+}
+
+static void
+indent_dest_set(pgp_dest_t *dst, int level)
+{
+ pgp_dest_indent_param_t *param = (pgp_dest_indent_param_t *) dst->param;
+ param->level = level;
+}
+
+static size_t
+vsnprinthex(char *str, size_t slen, const uint8_t *buf, size_t buflen)
+{
+ static const char *hexes = "0123456789abcdef";
+ size_t idx = 0;
+
+ for (size_t i = 0; (i < buflen) && (i < (slen - 1) / 2); i++) {
+ str[idx++] = hexes[buf[i] >> 4];
+ str[idx++] = hexes[buf[i] & 0xf];
+ }
+ str[idx] = '\0';
+ return buflen * 2;
+}
+
+static void
+dst_print_mpi(pgp_dest_t *dst, const char *name, pgp_mpi_t *mpi, bool dumpbin)
+{
+ char hex[5000];
+ if (!dumpbin) {
+ dst_printf(dst, "%s: %d bits\n", name, (int) mpi_bits(mpi));
+ } else {
+ vsnprinthex(hex, sizeof(hex), mpi->mpi, mpi->len);
+ dst_printf(dst, "%s: %d bits, %s\n", name, (int) mpi_bits(mpi), hex);
+ }
+}
+
+static void
+dst_print_palg(pgp_dest_t *dst, const char *name, pgp_pubkey_alg_t palg)
+{
+ const char *palg_name = id_str_pair::lookup(pubkey_alg_map, palg, "Unknown");
+ if (!name) {
+ name = "public key algorithm";
+ }
+
+ dst_printf(dst, "%s: %d (%s)\n", name, (int) palg, palg_name);
+}
+
+static void
+dst_print_halg(pgp_dest_t *dst, const char *name, pgp_hash_alg_t halg)
+{
+ const char *halg_name = id_str_pair::lookup(hash_alg_map, halg, "Unknown");
+ if (!name) {
+ name = "hash algorithm";
+ }
+
+ dst_printf(dst, "%s: %d (%s)\n", name, (int) halg, halg_name);
+}
+
+static void
+dst_print_salg(pgp_dest_t *dst, const char *name, pgp_symm_alg_t salg)
+{
+ const char *salg_name = id_str_pair::lookup(symm_alg_map, salg, "Unknown");
+ if (!name) {
+ name = "symmetric algorithm";
+ }
+
+ dst_printf(dst, "%s: %d (%s)\n", name, (int) salg, salg_name);
+}
+
+static void
+dst_print_aalg(pgp_dest_t *dst, const char *name, pgp_aead_alg_t aalg)
+{
+ const char *aalg_name = id_str_pair::lookup(aead_alg_map, aalg, "Unknown");
+ if (!name) {
+ name = "aead algorithm";
+ }
+
+ dst_printf(dst, "%s: %d (%s)\n", name, (int) aalg, aalg_name);
+}
+
+static void
+dst_print_zalg(pgp_dest_t *dst, const char *name, pgp_compression_type_t zalg)
+{
+ const char *zalg_name = id_str_pair::lookup(z_alg_map, zalg, "Unknown");
+ if (!name) {
+ name = "compression algorithm";
+ }
+
+ dst_printf(dst, "%s: %d (%s)\n", name, (int) zalg, zalg_name);
+}
+
+static void
+dst_print_raw(pgp_dest_t *dst, const char *name, const void *data, size_t len)
+{
+ dst_printf(dst, "%s: ", name);
+ dst_write(dst, data, len);
+ dst_printf(dst, "\n");
+}
+
+static void
+dst_print_algs(
+ pgp_dest_t *dst, const char *name, uint8_t *algs, size_t algc, const id_str_pair map[])
+{
+ if (!name) {
+ name = "algorithms";
+ }
+
+ dst_printf(dst, "%s: ", name);
+ for (size_t i = 0; i < algc; i++) {
+ dst_printf(
+ dst, "%s%s", id_str_pair::lookup(map, algs[i], "Unknown"), i + 1 < algc ? ", " : "");
+ }
+ dst_printf(dst, " (");
+ for (size_t i = 0; i < algc; i++) {
+ dst_printf(dst, "%d%s", (int) algs[i], i + 1 < algc ? ", " : "");
+ }
+ dst_printf(dst, ")\n");
+}
+
+static void
+dst_print_sig_type(pgp_dest_t *dst, const char *name, pgp_sig_type_t sigtype)
+{
+ const char *sig_name = id_str_pair::lookup(sig_type_map, sigtype, "Unknown");
+ if (!name) {
+ name = "signature type";
+ }
+ dst_printf(dst, "%s: %d (%s)\n", name, (int) sigtype, sig_name);
+}
+
+static void
+dst_print_hex(pgp_dest_t *dst, const char *name, const uint8_t *data, size_t len, bool bytes)
+{
+ char hex[512];
+ vsnprinthex(hex, sizeof(hex), data, len);
+ if (bytes) {
+ dst_printf(dst, "%s: 0x%s (%d bytes)\n", name, hex, (int) len);
+ } else {
+ dst_printf(dst, "%s: 0x%s\n", name, hex);
+ }
+}
+
+static void
+dst_print_keyid(pgp_dest_t *dst, const char *name, const pgp_key_id_t &keyid)
+{
+ if (!name) {
+ name = "key id";
+ }
+ dst_print_hex(dst, name, keyid.data(), keyid.size(), false);
+}
+
+static void
+dst_print_s2k(pgp_dest_t *dst, pgp_s2k_t *s2k)
+{
+ dst_printf(dst, "s2k specifier: %d\n", (int) s2k->specifier);
+ if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) {
+ dst_printf(dst, "GPG extension num: %d\n", (int) s2k->gpg_ext_num);
+ if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) {
+ static_assert(sizeof(s2k->gpg_serial) == 16, "invalid s2k->gpg_serial size");
+ size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len;
+ dst_print_hex(dst, "card serial number", s2k->gpg_serial, slen, true);
+ }
+ return;
+ }
+ if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) {
+ dst_print_hex(dst,
+ "Unknown experimental s2k",
+ s2k->experimental.data(),
+ s2k->experimental.size(),
+ true);
+ return;
+ }
+ dst_print_halg(dst, "s2k hash algorithm", s2k->hash_alg);
+ if ((s2k->specifier == PGP_S2KS_SALTED) ||
+ (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) {
+ dst_print_hex(dst, "s2k salt", s2k->salt, PGP_SALT_SIZE, false);
+ }
+ if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) {
+ size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations);
+ dst_printf(dst, "s2k iterations: %zu (encoded as %u)\n", real_iter, s2k->iterations);
+ }
+}
+
+static void
+dst_print_time(pgp_dest_t *dst, const char *name, uint32_t time)
+{
+ if (!name) {
+ name = "time";
+ }
+ auto str = rnp_ctime(time).substr(0, 24);
+ dst_printf(dst,
+ "%s: %zu (%s%s)\n",
+ name,
+ (size_t) time,
+ rnp_y2k38_warning(time) ? ">=" : "",
+ str.c_str());
+}
+
+static void
+dst_print_expiration(pgp_dest_t *dst, const char *name, uint32_t seconds)
+{
+ if (!name) {
+ name = "expiration";
+ }
+ if (seconds) {
+ int days = seconds / (24 * 60 * 60);
+ dst_printf(dst, "%s: %zu seconds (%d days)\n", name, (size_t) seconds, days);
+ } else {
+ dst_printf(dst, "%s: 0 (never)\n", name);
+ }
+}
+
+#define LINELEN 16
+
+static void
+dst_hexdump(pgp_dest_t *dst, const uint8_t *src, size_t length)
+{
+ size_t i;
+ char line[LINELEN + 1];
+
+ for (i = 0; i < length; i++) {
+ if (i % LINELEN == 0) {
+ dst_printf(dst, "%.5zu | ", i);
+ }
+ dst_printf(dst, "%.02x ", (uint8_t) src[i]);
+ line[i % LINELEN] = (isprint(src[i])) ? src[i] : '.';
+ if (i % LINELEN == LINELEN - 1) {
+ line[LINELEN] = 0x0;
+ dst_printf(dst, " | %s\n", line);
+ }
+ }
+ if (i % LINELEN != 0) {
+ for (; i % LINELEN != 0; i++) {
+ dst_printf(dst, " ");
+ line[i % LINELEN] = ' ';
+ }
+ line[LINELEN] = 0x0;
+ dst_printf(dst, " | %s\n", line);
+ }
+}
+
+static rnp_result_t stream_dump_packets_raw(rnp_dump_ctx_t *ctx,
+ pgp_source_t * src,
+ pgp_dest_t * dst);
+static void stream_dump_signature_pkt(rnp_dump_ctx_t * ctx,
+ pgp_signature_t *sig,
+ pgp_dest_t * dst);
+
+static void
+signature_dump_subpacket(rnp_dump_ctx_t *ctx, pgp_dest_t *dst, const pgp_sig_subpkt_t &subpkt)
+{
+ const char *sname = id_str_pair::lookup(sig_subpkt_type_map, subpkt.type, "Unknown");
+
+ switch (subpkt.type) {
+ case PGP_SIG_SUBPKT_CREATION_TIME:
+ dst_print_time(dst, sname, subpkt.fields.create);
+ break;
+ case PGP_SIG_SUBPKT_EXPIRATION_TIME:
+ dst_print_expiration(dst, sname, subpkt.fields.expiry);
+ break;
+ case PGP_SIG_SUBPKT_EXPORT_CERT:
+ dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.exportable);
+ break;
+ case PGP_SIG_SUBPKT_TRUST:
+ dst_printf(dst,
+ "%s: amount %d, level %d\n",
+ sname,
+ (int) subpkt.fields.trust.amount,
+ (int) subpkt.fields.trust.level);
+ break;
+ case PGP_SIG_SUBPKT_REGEXP:
+ dst_print_raw(dst, sname, subpkt.fields.regexp.str, subpkt.fields.regexp.len);
+ break;
+ case PGP_SIG_SUBPKT_REVOCABLE:
+ dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.revocable);
+ break;
+ case PGP_SIG_SUBPKT_KEY_EXPIRY:
+ dst_print_expiration(dst, sname, subpkt.fields.expiry);
+ break;
+ case PGP_SIG_SUBPKT_PREFERRED_SKA:
+ dst_print_algs(dst,
+ "preferred symmetric algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ symm_alg_map);
+ break;
+ case PGP_SIG_SUBPKT_REVOCATION_KEY:
+ dst_printf(dst, "%s\n", sname);
+ dst_printf(dst, "class: %d\n", (int) subpkt.fields.revocation_key.klass);
+ dst_print_palg(dst, NULL, subpkt.fields.revocation_key.pkalg);
+ dst_print_hex(
+ dst, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE, true);
+ break;
+ case PGP_SIG_SUBPKT_ISSUER_KEY_ID:
+ dst_print_hex(dst, sname, subpkt.fields.issuer, PGP_KEY_ID_SIZE, false);
+ break;
+ case PGP_SIG_SUBPKT_NOTATION_DATA: {
+ std::string name(subpkt.fields.notation.name,
+ subpkt.fields.notation.name + subpkt.fields.notation.nlen);
+ std::vector<uint8_t> value(subpkt.fields.notation.value,
+ subpkt.fields.notation.value + subpkt.fields.notation.vlen);
+ if (subpkt.fields.notation.human) {
+ dst_printf(dst, "%s: %s = ", sname, name.c_str());
+ dst_printf(dst, "%.*s\n", (int) value.size(), (char *) value.data());
+ } else {
+ char hex[64];
+ vsnprinthex(hex, sizeof(hex), value.data(), value.size());
+ dst_printf(dst, "%s: %s = ", sname, name.c_str());
+ dst_printf(dst, "0x%s (%zu bytes)\n", hex, value.size());
+ }
+ break;
+ }
+ case PGP_SIG_SUBPKT_PREFERRED_HASH:
+ dst_print_algs(dst,
+ "preferred hash algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ hash_alg_map);
+ break;
+ case PGP_SIG_SUBPKT_PREF_COMPRESS:
+ dst_print_algs(dst,
+ "preferred compression algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ z_alg_map);
+ break;
+ case PGP_SIG_SUBPKT_KEYSERV_PREFS:
+ dst_printf(dst, "%s\n", sname);
+ dst_printf(dst, "no-modify: %d\n", (int) subpkt.fields.ks_prefs.no_modify);
+ break;
+ case PGP_SIG_SUBPKT_PREF_KEYSERV:
+ dst_print_raw(
+ dst, sname, subpkt.fields.preferred_ks.uri, subpkt.fields.preferred_ks.len);
+ break;
+ case PGP_SIG_SUBPKT_PRIMARY_USER_ID:
+ dst_printf(dst, "%s: %d\n", sname, (int) subpkt.fields.primary_uid);
+ break;
+ case PGP_SIG_SUBPKT_POLICY_URI:
+ dst_print_raw(dst, sname, subpkt.fields.policy.uri, subpkt.fields.policy.len);
+ break;
+ case PGP_SIG_SUBPKT_KEY_FLAGS: {
+ uint8_t flg = subpkt.fields.key_flags;
+ dst_printf(dst, "%s: 0x%02x ( ", sname, flg);
+ dst_printf(dst, "%s", flg ? "" : "none");
+ dst_printf(dst, "%s", flg & PGP_KF_CERTIFY ? "certify " : "");
+ dst_printf(dst, "%s", flg & PGP_KF_SIGN ? "sign " : "");
+ dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_COMMS ? "encrypt_comm " : "");
+ dst_printf(dst, "%s", flg & PGP_KF_ENCRYPT_STORAGE ? "encrypt_storage " : "");
+ dst_printf(dst, "%s", flg & PGP_KF_SPLIT ? "split " : "");
+ dst_printf(dst, "%s", flg & PGP_KF_AUTH ? "auth " : "");
+ dst_printf(dst, "%s", flg & PGP_KF_SHARED ? "shared " : "");
+ dst_printf(dst, ")\n");
+ break;
+ }
+ case PGP_SIG_SUBPKT_SIGNERS_USER_ID:
+ dst_print_raw(dst, sname, subpkt.fields.signer.uid, subpkt.fields.signer.len);
+ break;
+ case PGP_SIG_SUBPKT_REVOCATION_REASON: {
+ int code = subpkt.fields.revocation_reason.code;
+ const char *reason = id_str_pair::lookup(revoc_reason_map, code, "Unknown");
+ dst_printf(dst, "%s: %d (%s)\n", sname, code, reason);
+ dst_print_raw(dst,
+ "message",
+ subpkt.fields.revocation_reason.str,
+ subpkt.fields.revocation_reason.len);
+ break;
+ }
+ case PGP_SIG_SUBPKT_FEATURES:
+ dst_printf(dst, "%s: 0x%02x ( ", sname, subpkt.data[0]);
+ dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_MDC ? "mdc " : "");
+ dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_AEAD ? "aead " : "");
+ dst_printf(dst, "%s", subpkt.fields.features & PGP_KEY_FEATURE_V5 ? "v5 keys " : "");
+ dst_printf(dst, ")\n");
+ break;
+ case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE:
+ dst_printf(dst, "%s:\n", sname);
+ stream_dump_signature_pkt(ctx, subpkt.fields.sig, dst);
+ break;
+ case PGP_SIG_SUBPKT_ISSUER_FPR:
+ dst_print_hex(
+ dst, sname, subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len, true);
+ break;
+ case PGP_SIG_SUBPKT_PREFERRED_AEAD:
+ dst_print_algs(dst,
+ "preferred aead algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ aead_alg_map);
+ break;
+ default:
+ if (!ctx->dump_packets) {
+ indent_dest_increase(dst);
+ dst_hexdump(dst, subpkt.data, subpkt.len);
+ indent_dest_decrease(dst);
+ }
+ }
+}
+
+static void
+signature_dump_subpackets(rnp_dump_ctx_t * ctx,
+ pgp_dest_t * dst,
+ pgp_signature_t *sig,
+ bool hashed)
+{
+ bool empty = true;
+
+ for (auto &subpkt : sig->subpkts) {
+ if (subpkt.hashed != hashed) {
+ continue;
+ }
+ empty = false;
+ dst_printf(dst, ":type %d, len %d", (int) subpkt.type, (int) subpkt.len);
+ dst_printf(dst, "%s\n", subpkt.critical ? ", critical" : "");
+ if (ctx->dump_packets) {
+ dst_printf(dst, ":subpacket contents:\n");
+ indent_dest_increase(dst);
+ dst_hexdump(dst, subpkt.data, subpkt.len);
+ indent_dest_decrease(dst);
+ }
+ signature_dump_subpacket(ctx, dst, subpkt);
+ }
+
+ if (empty) {
+ dst_printf(dst, "none\n");
+ }
+}
+
+static void
+stream_dump_signature_pkt(rnp_dump_ctx_t *ctx, pgp_signature_t *sig, pgp_dest_t *dst)
+{
+ indent_dest_increase(dst);
+
+ dst_printf(dst, "version: %d\n", (int) sig->version);
+ dst_print_sig_type(dst, "type", sig->type());
+ if (sig->version < PGP_V4) {
+ dst_print_time(dst, "creation time", sig->creation_time);
+ dst_print_keyid(dst, "signing key id", sig->signer);
+ }
+ dst_print_palg(dst, NULL, sig->palg);
+ dst_print_halg(dst, NULL, sig->halg);
+
+ if (sig->version >= PGP_V4) {
+ dst_printf(dst, "hashed subpackets:\n");
+ indent_dest_increase(dst);
+ signature_dump_subpackets(ctx, dst, sig, true);
+ indent_dest_decrease(dst);
+
+ dst_printf(dst, "unhashed subpackets:\n");
+ indent_dest_increase(dst);
+ signature_dump_subpackets(ctx, dst, sig, false);
+ indent_dest_decrease(dst);
+ }
+
+ dst_print_hex(dst, "lbits", sig->lbits, sizeof(sig->lbits), false);
+ dst_printf(dst, "signature material:\n");
+ indent_dest_increase(dst);
+
+ pgp_signature_material_t material = {};
+ try {
+ sig->parse_material(material);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return;
+ }
+ switch (sig->palg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ dst_print_mpi(dst, "rsa s", &material.rsa.s, ctx->dump_mpi);
+ break;
+ case PGP_PKA_DSA:
+ dst_print_mpi(dst, "dsa r", &material.dsa.r, ctx->dump_mpi);
+ dst_print_mpi(dst, "dsa s", &material.dsa.s, ctx->dump_mpi);
+ break;
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ case PGP_PKA_ECDH:
+ dst_print_mpi(dst, "ecc r", &material.ecc.r, ctx->dump_mpi);
+ dst_print_mpi(dst, "ecc s", &material.ecc.s, ctx->dump_mpi);
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ dst_print_mpi(dst, "eg r", &material.eg.r, ctx->dump_mpi);
+ dst_print_mpi(dst, "eg s", &material.eg.s, ctx->dump_mpi);
+ break;
+ default:
+ dst_printf(dst, "unknown algorithm\n");
+ }
+ indent_dest_decrease(dst);
+ indent_dest_decrease(dst);
+}
+
+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;
+
+ dst_printf(dst, "Signature packet\n");
+ try {
+ ret = sig.parse(*src);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ indent_dest_increase(dst);
+ dst_printf(dst, "failed to parse\n");
+ indent_dest_decrease(dst);
+ 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)
+{
+ pgp_key_pkt_t key;
+ rnp_result_t ret;
+ pgp_fingerprint_t keyfp = {};
+
+ try {
+ ret = key.parse(*src);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ dst_printf(dst, "%s packet\n", id_str_pair::lookup(key_type_map, key.tag, "Unknown"));
+ indent_dest_increase(dst);
+
+ dst_printf(dst, "version: %d\n", (int) key.version);
+ dst_print_time(dst, "creation time", key.creation_time);
+ if (key.version < PGP_V4) {
+ dst_printf(dst, "v3 validity days: %d\n", (int) key.v3_days);
+ }
+ dst_print_palg(dst, NULL, key.alg);
+ dst_printf(dst, "public key material:\n");
+ indent_dest_increase(dst);
+
+ switch (key.alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ dst_print_mpi(dst, "rsa n", &key.material.rsa.n, ctx->dump_mpi);
+ dst_print_mpi(dst, "rsa e", &key.material.rsa.e, ctx->dump_mpi);
+ break;
+ case PGP_PKA_DSA:
+ dst_print_mpi(dst, "dsa p", &key.material.dsa.p, ctx->dump_mpi);
+ dst_print_mpi(dst, "dsa q", &key.material.dsa.q, ctx->dump_mpi);
+ dst_print_mpi(dst, "dsa g", &key.material.dsa.g, ctx->dump_mpi);
+ dst_print_mpi(dst, "dsa y", &key.material.dsa.y, ctx->dump_mpi);
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ dst_print_mpi(dst, "eg p", &key.material.eg.p, ctx->dump_mpi);
+ dst_print_mpi(dst, "eg g", &key.material.eg.g, ctx->dump_mpi);
+ dst_print_mpi(dst, "eg y", &key.material.eg.y, ctx->dump_mpi);
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2: {
+ const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve);
+ dst_print_mpi(dst, "ecc p", &key.material.ec.p, ctx->dump_mpi);
+ dst_printf(dst, "ecc curve: %s\n", cdesc ? cdesc->pgp_name : "unknown");
+ break;
+ }
+ case PGP_PKA_ECDH: {
+ const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve);
+ dst_print_mpi(dst, "ecdh p", &key.material.ec.p, ctx->dump_mpi);
+ dst_printf(dst, "ecdh curve: %s\n", cdesc ? cdesc->pgp_name : "unknown");
+ dst_print_halg(dst, "ecdh hash algorithm", key.material.ec.kdf_hash_alg);
+ dst_printf(dst, "ecdh key wrap algorithm: %d\n", (int) key.material.ec.key_wrap_alg);
+ break;
+ }
+ default:
+ dst_printf(dst, "unknown public key algorithm\n");
+ }
+ indent_dest_decrease(dst);
+
+ if (is_secret_key_pkt(key.tag)) {
+ dst_printf(dst, "secret key material:\n");
+ indent_dest_increase(dst);
+
+ dst_printf(dst, "s2k usage: %d\n", (int) key.sec_protection.s2k.usage);
+ if ((key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED) ||
+ (key.sec_protection.s2k.usage == PGP_S2KU_ENCRYPTED_AND_HASHED)) {
+ dst_print_salg(dst, NULL, key.sec_protection.symm_alg);
+ dst_print_s2k(dst, &key.sec_protection.s2k);
+ if (key.sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) {
+ size_t bl_size = pgp_block_size(key.sec_protection.symm_alg);
+ if (bl_size) {
+ dst_print_hex(dst, "cipher iv", key.sec_protection.iv, bl_size, true);
+ } else {
+ dst_printf(dst, "cipher iv: unknown algorithm\n");
+ }
+ }
+ dst_printf(dst, "encrypted secret key data: %d bytes\n", (int) key.sec_len);
+ }
+
+ if (!key.sec_protection.s2k.usage) {
+ dst_printf(dst, "cleartext secret key data: %d bytes\n", (int) key.sec_len);
+ }
+ indent_dest_decrease(dst);
+ }
+
+ pgp_key_id_t keyid = {};
+ if (!pgp_keyid(keyid, key)) {
+ dst_print_hex(dst, "keyid", keyid.data(), keyid.size(), false);
+ } else {
+ dst_printf(dst, "keyid: failed to calculate");
+ }
+
+ if ((key.version > PGP_V3) && (ctx->dump_grips)) {
+ if (!pgp_fingerprint(keyfp, key)) {
+ dst_print_hex(dst, "fingerprint", keyfp.fingerprint, keyfp.length, false);
+ } else {
+ dst_printf(dst, "fingerprint: failed to calculate");
+ }
+ }
+
+ if (ctx->dump_grips) {
+ pgp_key_grip_t grip;
+ if (rnp_key_store_get_key_grip(&key.material, grip)) {
+ dst_print_hex(dst, "grip", grip.data(), grip.size(), false);
+ } else {
+ dst_printf(dst, "grip: failed to calculate");
+ }
+ }
+
+ indent_dest_decrease(dst);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_userid(pgp_source_t *src, pgp_dest_t *dst)
+{
+ pgp_userid_pkt_t uid;
+ rnp_result_t ret;
+ const char * utype;
+
+ try {
+ ret = uid.parse(*src);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ switch (uid.tag) {
+ case PGP_PKT_USER_ID:
+ utype = "UserID";
+ break;
+ case PGP_PKT_USER_ATTR:
+ utype = "UserAttr";
+ break;
+ default:
+ utype = "Unknown user id";
+ }
+
+ dst_printf(dst, "%s packet\n", utype);
+ indent_dest_increase(dst);
+
+ switch (uid.tag) {
+ case PGP_PKT_USER_ID:
+ dst_printf(dst, "id: ");
+ dst_write(dst, uid.uid, uid.uid_len);
+ dst_printf(dst, "\n");
+ break;
+ case PGP_PKT_USER_ATTR:
+ dst_printf(dst, "id: (%d bytes of data)\n", (int) uid.uid_len);
+ break;
+ default:;
+ }
+
+ indent_dest_decrease(dst);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_pk_session_key(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst)
+{
+ pgp_pk_sesskey_t pkey;
+ pgp_encrypted_material_t material;
+ rnp_result_t ret;
+
+ try {
+ ret = pkey.parse(*src);
+ if (!pkey.parse_material(material)) {
+ ret = RNP_ERROR_BAD_FORMAT;
+ }
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ dst_printf(dst, "Public-key encrypted session key packet\n");
+ indent_dest_increase(dst);
+
+ dst_printf(dst, "version: %d\n", (int) pkey.version);
+ dst_print_keyid(dst, NULL, pkey.key_id);
+ dst_print_palg(dst, NULL, pkey.alg);
+ dst_printf(dst, "encrypted material:\n");
+ indent_dest_increase(dst);
+
+ switch (pkey.alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ dst_print_mpi(dst, "rsa m", &material.rsa.m, ctx->dump_mpi);
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ dst_print_mpi(dst, "eg g", &material.eg.g, ctx->dump_mpi);
+ dst_print_mpi(dst, "eg m", &material.eg.m, ctx->dump_mpi);
+ break;
+ case PGP_PKA_SM2:
+ dst_print_mpi(dst, "sm2 m", &material.sm2.m, ctx->dump_mpi);
+ break;
+ case PGP_PKA_ECDH:
+ dst_print_mpi(dst, "ecdh p", &material.ecdh.p, ctx->dump_mpi);
+ if (ctx->dump_mpi) {
+ dst_print_hex(dst, "ecdh m", material.ecdh.m, material.ecdh.mlen, true);
+ } else {
+ dst_printf(dst, "ecdh m: %d bytes\n", (int) material.ecdh.mlen);
+ }
+ break;
+ default:
+ dst_printf(dst, "unknown public key algorithm\n");
+ }
+
+ indent_dest_decrease(dst);
+ indent_dest_decrease(dst);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_sk_session_key(pgp_source_t *src, pgp_dest_t *dst)
+{
+ pgp_sk_sesskey_t skey;
+ rnp_result_t ret;
+
+ try {
+ ret = skey.parse(*src);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ dst_printf(dst, "Symmetric-key encrypted session key packet\n");
+ indent_dest_increase(dst);
+ dst_printf(dst, "version: %d\n", (int) skey.version);
+ dst_print_salg(dst, NULL, skey.alg);
+ if (skey.version == PGP_SKSK_V5) {
+ dst_print_aalg(dst, NULL, skey.aalg);
+ }
+ dst_print_s2k(dst, &skey.s2k);
+ if (skey.version == PGP_SKSK_V5) {
+ dst_print_hex(dst, "aead iv", skey.iv, skey.ivlen, true);
+ }
+ dst_print_hex(dst, "encrypted key", skey.enckey, skey.enckeylen, true);
+ indent_dest_decrease(dst);
+
+ return RNP_SUCCESS;
+}
+
+static bool
+stream_dump_get_aead_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr)
+{
+ pgp_dest_t encdst = {};
+ uint8_t encpkt[64] = {};
+
+ if (init_mem_dest(&encdst, &encpkt, sizeof(encpkt))) {
+ return false;
+ }
+ mem_dest_discard_overflow(&encdst, true);
+
+ if (stream_read_packet(src, &encdst)) {
+ dst_close(&encdst, false);
+ return false;
+ }
+ size_t len = std::min(encdst.writeb, sizeof(encpkt));
+ dst_close(&encdst, false);
+
+ pgp_source_t memsrc = {};
+ if (init_mem_src(&memsrc, encpkt, len, false)) {
+ return false;
+ }
+ bool res = get_aead_src_hdr(&memsrc, hdr);
+ src_close(&memsrc);
+ return res;
+}
+
+static rnp_result_t
+stream_dump_aead_encrypted(pgp_source_t *src, pgp_dest_t *dst)
+{
+ dst_printf(dst, "AEAD-encrypted data packet\n");
+
+ pgp_aead_hdr_t aead = {};
+ if (!stream_dump_get_aead_hdr(src, &aead)) {
+ dst_printf(dst, "ERROR: failed to read AEAD header\n");
+ return RNP_ERROR_READ;
+ }
+
+ indent_dest_increase(dst);
+
+ dst_printf(dst, "version: %d\n", (int) aead.version);
+ dst_print_salg(dst, NULL, aead.ealg);
+ dst_print_aalg(dst, NULL, aead.aalg);
+ dst_printf(dst, "chunk size: %d\n", (int) aead.csize);
+ dst_print_hex(dst, "initialization vector", aead.iv, aead.ivlen, true);
+
+ indent_dest_decrease(dst);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_encrypted(pgp_source_t *src, pgp_dest_t *dst, int tag)
+{
+ switch (tag) {
+ case PGP_PKT_SE_DATA:
+ dst_printf(dst, "Symmetrically-encrypted data packet\n\n");
+ break;
+ case PGP_PKT_SE_IP_DATA:
+ dst_printf(dst, "Symmetrically-encrypted integrity protected data packet\n\n");
+ break;
+ case PGP_PKT_AEAD_ENCRYPTED:
+ return stream_dump_aead_encrypted(src, dst);
+ default:
+ dst_printf(dst, "Unknown encrypted data packet\n\n");
+ break;
+ }
+
+ return stream_skip_packet(src);
+}
+
+static rnp_result_t
+stream_dump_one_pass(pgp_source_t *src, pgp_dest_t *dst)
+{
+ pgp_one_pass_sig_t onepass;
+ rnp_result_t ret;
+
+ try {
+ ret = onepass.parse(*src);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ dst_printf(dst, "One-pass signature packet\n");
+ indent_dest_increase(dst);
+
+ dst_printf(dst, "version: %d\n", (int) onepass.version);
+ dst_print_sig_type(dst, NULL, onepass.type);
+ dst_print_halg(dst, NULL, onepass.halg);
+ dst_print_palg(dst, NULL, onepass.palg);
+ dst_print_keyid(dst, "signing key id", onepass.keyid);
+ dst_printf(dst, "nested: %d\n", (int) onepass.nested);
+
+ indent_dest_decrease(dst);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_compressed(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst)
+{
+ pgp_source_t zsrc = {0};
+ uint8_t zalg;
+ rnp_result_t ret;
+
+ if ((ret = init_compressed_src(&zsrc, src))) {
+ return ret;
+ }
+
+ dst_printf(dst, "Compressed data packet\n");
+ indent_dest_increase(dst);
+
+ get_compressed_src_alg(&zsrc, &zalg);
+ dst_print_zalg(dst, NULL, (pgp_compression_type_t) zalg);
+ dst_printf(dst, "Decompressed contents:\n");
+ ret = stream_dump_packets_raw(ctx, &zsrc, dst);
+
+ src_close(&zsrc);
+ indent_dest_decrease(dst);
+ return ret;
+}
+
+static rnp_result_t
+stream_dump_literal(pgp_source_t *src, pgp_dest_t *dst)
+{
+ pgp_source_t lsrc = {0};
+ pgp_literal_hdr_t lhdr = {0};
+ rnp_result_t ret;
+ uint8_t readbuf[16384];
+
+ if ((ret = init_literal_src(&lsrc, src))) {
+ return ret;
+ }
+
+ dst_printf(dst, "Literal data packet\n");
+ indent_dest_increase(dst);
+
+ get_literal_src_hdr(&lsrc, &lhdr);
+ dst_printf(dst, "data format: '%c'\n", lhdr.format);
+ dst_printf(dst, "filename: %s (len %d)\n", lhdr.fname, (int) lhdr.fname_len);
+ dst_print_time(dst, "timestamp", lhdr.timestamp);
+
+ ret = RNP_SUCCESS;
+ while (!src_eof(&lsrc)) {
+ size_t read = 0;
+ if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) {
+ ret = RNP_ERROR_READ;
+ break;
+ }
+ }
+
+ dst_printf(dst, "data bytes: %lu\n", (unsigned long) lsrc.readb);
+ src_close(&lsrc);
+ indent_dest_decrease(dst);
+ return ret;
+}
+
+static rnp_result_t
+stream_dump_marker(pgp_source_t &src, pgp_dest_t &dst)
+{
+ dst_printf(&dst, "Marker packet\n");
+ indent_dest_increase(&dst);
+ rnp_result_t ret = stream_parse_marker(src);
+ dst_printf(&dst, "contents: %s\n", ret ? "invalid" : PGP_MARKER_CONTENTS);
+ indent_dest_decrease(&dst);
+ return ret;
+}
+
+static rnp_result_t
+stream_dump_packets_raw(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst)
+{
+ char msg[1024 + PGP_MAX_HEADER_SIZE] = {0};
+ char smsg[128] = {0};
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ if (src_eof(src)) {
+ return RNP_SUCCESS;
+ }
+
+ /* do not allow endless recursion */
+ if (++ctx->layers > MAXIMUM_NESTING_LEVEL) {
+ RNP_LOG("Too many OpenPGP nested layers during the dump.");
+ dst_printf(dst, ":too many OpenPGP packet layers, stopping.\n");
+ ret = RNP_SUCCESS;
+ goto finish;
+ }
+
+ while (!src_eof(src)) {
+ pgp_packet_hdr_t hdr = {};
+ size_t off = src->readb;
+ rnp_result_t hdrret = stream_peek_packet_hdr(src, &hdr);
+ if (hdrret) {
+ ret = hdrret;
+ goto finish;
+ }
+
+ if (hdr.partial) {
+ snprintf(msg, sizeof(msg), "partial len");
+ } else if (hdr.indeterminate) {
+ snprintf(msg, sizeof(msg), "indeterminate len");
+ } else {
+ snprintf(msg, sizeof(msg), "len %zu", hdr.pkt_len);
+ }
+ vsnprinthex(smsg, sizeof(smsg), hdr.hdr, hdr.hdr_len);
+ dst_printf(
+ dst, ":off %zu: packet header 0x%s (tag %d, %s)\n", off, smsg, hdr.tag, msg);
+
+ if (ctx->dump_packets) {
+ size_t rlen = hdr.pkt_len + hdr.hdr_len;
+ bool part = false;
+
+ if (!hdr.pkt_len || (rlen > 1024 + hdr.hdr_len)) {
+ rlen = 1024 + hdr.hdr_len;
+ part = true;
+ }
+
+ dst_printf(dst, ":off %zu: packet contents ", off + hdr.hdr_len);
+ if (!src_peek(src, msg, rlen, &rlen)) {
+ dst_printf(dst, "- failed to read\n");
+ } else {
+ rlen -= hdr.hdr_len;
+ if (part || (rlen < hdr.pkt_len)) {
+ dst_printf(dst, "(first %d bytes)\n", (int) rlen);
+ } else {
+ dst_printf(dst, "(%d bytes)\n", (int) rlen);
+ }
+ indent_dest_increase(dst);
+ dst_hexdump(dst, (uint8_t *) msg + hdr.hdr_len, rlen);
+ indent_dest_decrease(dst);
+ }
+ dst_printf(dst, "\n");
+ }
+
+ switch (hdr.tag) {
+ case PGP_PKT_SIGNATURE:
+ 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:
+ ret = stream_dump_key(ctx, src, dst);
+ break;
+ case PGP_PKT_USER_ID:
+ case PGP_PKT_USER_ATTR:
+ ret = stream_dump_userid(src, dst);
+ break;
+ case PGP_PKT_PK_SESSION_KEY:
+ ret = stream_dump_pk_session_key(ctx, src, dst);
+ break;
+ case PGP_PKT_SK_SESSION_KEY:
+ ret = stream_dump_sk_session_key(src, dst);
+ break;
+ case PGP_PKT_SE_DATA:
+ case PGP_PKT_SE_IP_DATA:
+ case PGP_PKT_AEAD_ENCRYPTED:
+ ctx->stream_pkts++;
+ ret = stream_dump_encrypted(src, dst, hdr.tag);
+ break;
+ case PGP_PKT_ONE_PASS_SIG:
+ ret = stream_dump_one_pass(src, dst);
+ break;
+ case PGP_PKT_COMPRESSED:
+ ctx->stream_pkts++;
+ ret = stream_dump_compressed(ctx, src, dst);
+ break;
+ case PGP_PKT_LITDATA:
+ ctx->stream_pkts++;
+ ret = stream_dump_literal(src, dst);
+ break;
+ case PGP_PKT_MARKER:
+ ret = stream_dump_marker(*src, *dst);
+ break;
+ case PGP_PKT_TRUST:
+ case PGP_PKT_MDC:
+ dst_printf(dst, "Skipping unhandled pkt: %d\n\n", (int) hdr.tag);
+ 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");
+ 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");
+ ret = RNP_SUCCESS;
+ goto finish;
+ }
+ }
+
+ ret = RNP_SUCCESS;
+finish:
+ return ret;
+}
+
+static bool
+stream_skip_cleartext(pgp_source_t *src)
+{
+ char buf[4096];
+ size_t read = 0;
+ size_t siglen = strlen(ST_SIG_BEGIN);
+ char * hdrpos;
+
+ while (!src_eof(src)) {
+ if (!src_peek(src, buf, sizeof(buf) - 1, &read) || (read <= siglen)) {
+ return false;
+ }
+ buf[read] = '\0';
+
+ if ((hdrpos = strstr(buf, ST_SIG_BEGIN))) {
+ /* +1 here is to skip \n on the beginning of ST_SIG_BEGIN */
+ src_skip(src, hdrpos - buf + 1);
+ return true;
+ }
+ src_skip(src, read - siglen + 1);
+ }
+ return false;
+}
+
+rnp_result_t
+stream_dump_packets(rnp_dump_ctx_t *ctx, pgp_source_t *src, pgp_dest_t *dst)
+{
+ pgp_source_t armorsrc = {0};
+ pgp_dest_t wrdst = {0};
+ bool armored = false;
+ 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");
+ ret = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ }
+ /* check whether source is armored */
+ if (is_armored_source(src)) {
+ if ((ret = init_armored_src(&armorsrc, src))) {
+ RNP_LOG("failed to parse armored data");
+ goto finish;
+ }
+ armored = true;
+ src = &armorsrc;
+ dst_printf(dst, ":armored input\n");
+ }
+
+ if (src_eof(src)) {
+ dst_printf(dst, ":empty input\n");
+ ret = RNP_SUCCESS;
+ goto finish;
+ }
+
+ if ((ret = init_indent_dest(&wrdst, dst))) {
+ RNP_LOG("failed to init indent dest");
+ goto finish;
+ }
+ indent = true;
+ indent_dest_set(&wrdst, 0);
+
+ ret = stream_dump_packets_raw(ctx, src, &wrdst);
+finish:
+ if (armored) {
+ src_close(&armorsrc);
+ }
+ if (indent) {
+ dst_close(&wrdst, false);
+ }
+ return ret;
+}
+
+static bool
+obj_add_intstr_json(json_object *obj, const char *name, int val, const id_str_pair map[])
+{
+ if (!obj_add_field_json(obj, name, json_object_new_int(val))) {
+ return false;
+ }
+ if (!map) {
+ return true;
+ }
+ char namestr[64] = {0};
+ const char *str = id_str_pair::lookup(map, val, "Unknown");
+ snprintf(namestr, sizeof(namestr), "%s.str", name);
+ return obj_add_field_json(obj, namestr, json_object_new_string(str));
+}
+
+static bool
+obj_add_mpi_json(json_object *obj, const char *name, const pgp_mpi_t *mpi, bool contents)
+{
+ char strname[64] = {0};
+ snprintf(strname, sizeof(strname), "%s.bits", name);
+ if (!obj_add_field_json(obj, strname, json_object_new_int(mpi_bits(mpi)))) {
+ return false;
+ }
+ if (!contents) {
+ return true;
+ }
+ snprintf(strname, sizeof(strname), "%s.raw", name);
+ return obj_add_hex_json(obj, strname, mpi->mpi, mpi->len);
+}
+
+static bool
+subpacket_obj_add_algs(
+ json_object *obj, const char *name, uint8_t *algs, size_t len, const id_str_pair map[])
+{
+ json_object *jso_algs = json_object_new_array();
+ if (!jso_algs || !obj_add_field_json(obj, name, jso_algs)) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ if (!array_add_element_json(jso_algs, json_object_new_int(algs[i]))) {
+ return false;
+ }
+ }
+ if (!map) {
+ return true;
+ }
+
+ char strname[64] = {0};
+ snprintf(strname, sizeof(strname), "%s.str", name);
+
+ jso_algs = json_object_new_array();
+ if (!jso_algs || !obj_add_field_json(obj, strname, jso_algs)) {
+ return false;
+ }
+ for (size_t i = 0; i < len; i++) {
+ if (!array_add_element_json(
+ jso_algs,
+ json_object_new_string(id_str_pair::lookup(map, algs[i], "Unknown")))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static bool
+obj_add_s2k_json(json_object *obj, pgp_s2k_t *s2k)
+{
+ json_object *s2k_obj = json_object_new_object();
+ if (!obj_add_field_json(obj, "s2k", s2k_obj)) {
+ return false;
+ }
+ if (!obj_add_field_json(s2k_obj, "specifier", json_object_new_int(s2k->specifier))) {
+ return false;
+ }
+ if ((s2k->specifier == PGP_S2KS_EXPERIMENTAL) && s2k->gpg_ext_num) {
+ if (!obj_add_field_json(
+ s2k_obj, "gpg extension", json_object_new_int(s2k->gpg_ext_num))) {
+ return false;
+ }
+ if (s2k->gpg_ext_num == PGP_S2K_GPG_SMARTCARD) {
+ size_t slen = s2k->gpg_serial_len > 16 ? 16 : s2k->gpg_serial_len;
+ if (!obj_add_hex_json(s2k_obj, "card serial number", s2k->gpg_serial, slen)) {
+ return false;
+ }
+ }
+ }
+ if (s2k->specifier == PGP_S2KS_EXPERIMENTAL) {
+ return obj_add_hex_json(
+ s2k_obj, "unknown experimental", s2k->experimental.data(), s2k->experimental.size());
+ }
+ if (!obj_add_intstr_json(s2k_obj, "hash algorithm", s2k->hash_alg, hash_alg_map)) {
+ return false;
+ }
+ if (((s2k->specifier == PGP_S2KS_SALTED) ||
+ (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED)) &&
+ !obj_add_hex_json(s2k_obj, "salt", s2k->salt, PGP_SALT_SIZE)) {
+ return false;
+ }
+ if (s2k->specifier == PGP_S2KS_ITERATED_AND_SALTED) {
+ size_t real_iter = pgp_s2k_decode_iterations(s2k->iterations);
+ if (!obj_add_field_json(s2k_obj, "iterations", json_object_new_int(real_iter))) {
+ return false;
+ }
+ }
+ return true;
+}
+
+static rnp_result_t stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx,
+ const pgp_signature_t *sig,
+ json_object * pkt);
+
+static bool
+signature_dump_subpacket_json(rnp_dump_ctx_t * ctx,
+ const pgp_sig_subpkt_t &subpkt,
+ json_object * obj)
+{
+ switch (subpkt.type) {
+ case PGP_SIG_SUBPKT_CREATION_TIME:
+ return obj_add_field_json(
+ obj, "creation time", json_object_new_int64(subpkt.fields.create));
+ case PGP_SIG_SUBPKT_EXPIRATION_TIME:
+ return obj_add_field_json(
+ obj, "expiration time", json_object_new_int64(subpkt.fields.expiry));
+ case PGP_SIG_SUBPKT_EXPORT_CERT:
+ return obj_add_field_json(
+ obj, "exportable", json_object_new_boolean(subpkt.fields.exportable));
+ case PGP_SIG_SUBPKT_TRUST:
+ return obj_add_field_json(
+ obj, "amount", json_object_new_int(subpkt.fields.trust.amount)) &&
+ obj_add_field_json(
+ obj, "level", json_object_new_int(subpkt.fields.trust.level));
+ case PGP_SIG_SUBPKT_REGEXP:
+ return obj_add_field_json(
+ obj,
+ "regexp",
+ json_object_new_string_len(subpkt.fields.regexp.str, subpkt.fields.regexp.len));
+ case PGP_SIG_SUBPKT_REVOCABLE:
+ return obj_add_field_json(
+ obj, "revocable", json_object_new_boolean(subpkt.fields.revocable));
+ case PGP_SIG_SUBPKT_KEY_EXPIRY:
+ return obj_add_field_json(
+ obj, "key expiration", json_object_new_int64(subpkt.fields.expiry));
+ case PGP_SIG_SUBPKT_PREFERRED_SKA:
+ return subpacket_obj_add_algs(obj,
+ "algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ symm_alg_map);
+ case PGP_SIG_SUBPKT_PREFERRED_HASH:
+ return subpacket_obj_add_algs(obj,
+ "algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ hash_alg_map);
+ case PGP_SIG_SUBPKT_PREF_COMPRESS:
+ return subpacket_obj_add_algs(obj,
+ "algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ z_alg_map);
+ case PGP_SIG_SUBPKT_PREFERRED_AEAD:
+ return subpacket_obj_add_algs(obj,
+ "algorithms",
+ subpkt.fields.preferred.arr,
+ subpkt.fields.preferred.len,
+ aead_alg_map);
+ case PGP_SIG_SUBPKT_REVOCATION_KEY:
+ return obj_add_field_json(
+ obj, "class", json_object_new_int(subpkt.fields.revocation_key.klass)) &&
+ obj_add_field_json(
+ obj, "algorithm", json_object_new_int(subpkt.fields.revocation_key.pkalg)) &&
+ obj_add_hex_json(
+ obj, "fingerprint", subpkt.fields.revocation_key.fp, PGP_FINGERPRINT_SIZE);
+ case PGP_SIG_SUBPKT_ISSUER_KEY_ID:
+ return obj_add_hex_json(obj, "issuer keyid", subpkt.fields.issuer, PGP_KEY_ID_SIZE);
+ case PGP_SIG_SUBPKT_KEYSERV_PREFS:
+ return obj_add_field_json(
+ obj, "no-modify", json_object_new_boolean(subpkt.fields.ks_prefs.no_modify));
+ case PGP_SIG_SUBPKT_PREF_KEYSERV:
+ return obj_add_field_json(obj,
+ "uri",
+ json_object_new_string_len(subpkt.fields.preferred_ks.uri,
+ subpkt.fields.preferred_ks.len));
+ case PGP_SIG_SUBPKT_PRIMARY_USER_ID:
+ return obj_add_field_json(
+ obj, "primary", json_object_new_boolean(subpkt.fields.primary_uid));
+ case PGP_SIG_SUBPKT_POLICY_URI:
+ return obj_add_field_json(
+ obj,
+ "uri",
+ json_object_new_string_len(subpkt.fields.policy.uri, subpkt.fields.policy.len));
+ case PGP_SIG_SUBPKT_KEY_FLAGS: {
+ uint8_t flg = subpkt.fields.key_flags;
+ if (!obj_add_field_json(obj, "flags", json_object_new_int(flg))) {
+ return false;
+ }
+ json_object *jso_flg = json_object_new_array();
+ if (!jso_flg || !obj_add_field_json(obj, "flags.str", jso_flg)) {
+ return false;
+ }
+ if ((flg & PGP_KF_CERTIFY) &&
+ !array_add_element_json(jso_flg, json_object_new_string("certify"))) {
+ return false;
+ }
+ if ((flg & PGP_KF_SIGN) &&
+ !array_add_element_json(jso_flg, json_object_new_string("sign"))) {
+ return false;
+ }
+ if ((flg & PGP_KF_ENCRYPT_COMMS) &&
+ !array_add_element_json(jso_flg, json_object_new_string("encrypt_comm"))) {
+ return false;
+ }
+ if ((flg & PGP_KF_ENCRYPT_STORAGE) &&
+ !array_add_element_json(jso_flg, json_object_new_string("encrypt_storage"))) {
+ return false;
+ }
+ if ((flg & PGP_KF_SPLIT) &&
+ !array_add_element_json(jso_flg, json_object_new_string("split"))) {
+ return false;
+ }
+ if ((flg & PGP_KF_AUTH) &&
+ !array_add_element_json(jso_flg, json_object_new_string("auth"))) {
+ return false;
+ }
+ if ((flg & PGP_KF_SHARED) &&
+ !array_add_element_json(jso_flg, json_object_new_string("shared"))) {
+ return false;
+ }
+ return true;
+ }
+ case PGP_SIG_SUBPKT_SIGNERS_USER_ID:
+ return obj_add_field_json(
+ obj,
+ "uid",
+ json_object_new_string_len(subpkt.fields.signer.uid, subpkt.fields.signer.len));
+ case PGP_SIG_SUBPKT_REVOCATION_REASON: {
+ if (!obj_add_intstr_json(
+ obj, "code", subpkt.fields.revocation_reason.code, revoc_reason_map)) {
+ return false;
+ }
+ return obj_add_field_json(
+ obj,
+ "message",
+ json_object_new_string_len(subpkt.fields.revocation_reason.str,
+ subpkt.fields.revocation_reason.len));
+ }
+ case PGP_SIG_SUBPKT_FEATURES:
+ return obj_add_field_json(
+ obj,
+ "mdc",
+ json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_MDC)) &&
+ obj_add_field_json(
+ obj,
+ "aead",
+ json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_AEAD)) &&
+ obj_add_field_json(
+ obj,
+ "v5 keys",
+ json_object_new_boolean(subpkt.fields.features & PGP_KEY_FEATURE_V5));
+ case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE: {
+ json_object *sig = json_object_new_object();
+ if (!sig || !obj_add_field_json(obj, "signature", sig)) {
+ return false;
+ }
+ return !stream_dump_signature_pkt_json(ctx, subpkt.fields.sig, sig);
+ }
+ case PGP_SIG_SUBPKT_ISSUER_FPR:
+ return obj_add_hex_json(
+ obj, "fingerprint", subpkt.fields.issuer_fp.fp, subpkt.fields.issuer_fp.len);
+ case PGP_SIG_SUBPKT_NOTATION_DATA: {
+ bool human = subpkt.fields.notation.human;
+ if (!json_add(obj, "human", human) || !json_add(obj,
+ "name",
+ (char *) subpkt.fields.notation.name,
+ subpkt.fields.notation.nlen)) {
+ return false;
+ }
+ if (human) {
+ return json_add(obj,
+ "value",
+ (char *) subpkt.fields.notation.value,
+ subpkt.fields.notation.vlen);
+ }
+ return obj_add_hex_json(
+ obj, "value", subpkt.fields.notation.value, subpkt.fields.notation.vlen);
+ }
+ default:
+ if (!ctx->dump_packets) {
+ return obj_add_hex_json(obj, "raw", subpkt.data, subpkt.len);
+ }
+ return true;
+ }
+ return true;
+}
+
+static json_object *
+signature_dump_subpackets_json(rnp_dump_ctx_t *ctx, const pgp_signature_t *sig)
+{
+ json_object *res = json_object_new_array();
+
+ for (auto &subpkt : sig->subpkts) {
+ json_object *jso_subpkt = json_object_new_object();
+ if (json_object_array_add(res, jso_subpkt)) {
+ json_object_put(jso_subpkt);
+ goto error;
+ }
+
+ if (!obj_add_intstr_json(jso_subpkt, "type", subpkt.type, sig_subpkt_type_map)) {
+ goto error;
+ }
+ if (!obj_add_field_json(jso_subpkt, "length", json_object_new_int(subpkt.len))) {
+ goto error;
+ }
+ if (!obj_add_field_json(
+ jso_subpkt, "hashed", json_object_new_boolean(subpkt.hashed))) {
+ goto error;
+ }
+ if (!obj_add_field_json(
+ jso_subpkt, "critical", json_object_new_boolean(subpkt.critical))) {
+ goto error;
+ }
+
+ if (ctx->dump_packets &&
+ !obj_add_hex_json(jso_subpkt, "raw", subpkt.data, subpkt.len)) {
+ goto error;
+ }
+
+ if (!signature_dump_subpacket_json(ctx, subpkt, jso_subpkt)) {
+ goto error;
+ }
+ }
+
+ return res;
+error:
+ json_object_put(res);
+ return NULL;
+}
+
+static rnp_result_t
+stream_dump_signature_pkt_json(rnp_dump_ctx_t * ctx,
+ const pgp_signature_t *sig,
+ json_object * pkt)
+{
+ json_object * material = NULL;
+ pgp_signature_material_t sigmaterial = {};
+ rnp_result_t ret = RNP_ERROR_OUT_OF_MEMORY;
+
+ if (!obj_add_field_json(pkt, "version", json_object_new_int(sig->version))) {
+ goto done;
+ }
+ if (!obj_add_intstr_json(pkt, "type", sig->type(), sig_type_map)) {
+ goto done;
+ }
+
+ if (sig->version < PGP_V4) {
+ if (!obj_add_field_json(
+ pkt, "creation time", json_object_new_int(sig->creation_time))) {
+ goto done;
+ }
+ if (!obj_add_hex_json(pkt, "signer", sig->signer.data(), sig->signer.size())) {
+ goto done;
+ }
+ }
+ if (!obj_add_intstr_json(pkt, "algorithm", sig->palg, pubkey_alg_map)) {
+ goto done;
+ }
+ if (!obj_add_intstr_json(pkt, "hash algorithm", sig->halg, hash_alg_map)) {
+ goto done;
+ }
+
+ if (sig->version >= PGP_V4) {
+ json_object *subpkts = signature_dump_subpackets_json(ctx, sig);
+ if (!subpkts) {
+ goto done;
+ }
+ if (!obj_add_field_json(pkt, "subpackets", subpkts)) {
+ goto done;
+ }
+ }
+
+ if (!obj_add_hex_json(pkt, "lbits", sig->lbits, sizeof(sig->lbits))) {
+ goto done;
+ }
+
+ material = json_object_new_object();
+ if (!material || !obj_add_field_json(pkt, "material", material)) {
+ goto done;
+ }
+
+ try {
+ sig->parse_material(sigmaterial);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ switch (sig->palg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!obj_add_mpi_json(material, "s", &sigmaterial.rsa.s, ctx->dump_mpi)) {
+ goto done;
+ }
+ break;
+ case PGP_PKA_DSA:
+ if (!obj_add_mpi_json(material, "r", &sigmaterial.dsa.r, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "s", &sigmaterial.dsa.s, ctx->dump_mpi)) {
+ goto done;
+ }
+ break;
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ case PGP_PKA_ECDH:
+ if (!obj_add_mpi_json(material, "r", &sigmaterial.ecc.r, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "s", &sigmaterial.ecc.s, ctx->dump_mpi)) {
+ goto done;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!obj_add_mpi_json(material, "r", &sigmaterial.eg.r, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "s", &sigmaterial.eg.s, ctx->dump_mpi)) {
+ goto done;
+ }
+ break;
+ default:
+ break;
+ }
+ ret = RNP_SUCCESS;
+done:
+ return ret;
+}
+
+static rnp_result_t
+stream_dump_signature_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt)
+{
+ pgp_signature_t sig;
+ rnp_result_t ret;
+ try {
+ ret = sig.parse(*src);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+ return stream_dump_signature_pkt_json(ctx, &sig, pkt);
+}
+
+static rnp_result_t
+stream_dump_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt)
+{
+ pgp_key_pkt_t key;
+ rnp_result_t ret;
+ pgp_key_id_t keyid = {};
+ pgp_fingerprint_t keyfp = {};
+ json_object * material = NULL;
+
+ try {
+ ret = key.parse(*src);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+
+ if (!obj_add_field_json(pkt, "version", json_object_new_int(key.version))) {
+ goto done;
+ }
+ if (!obj_add_field_json(pkt, "creation time", json_object_new_int64(key.creation_time))) {
+ goto done;
+ }
+ if ((key.version < PGP_V4) &&
+ !obj_add_field_json(pkt, "v3 days", json_object_new_int(key.v3_days))) {
+ goto done;
+ }
+ if (!obj_add_intstr_json(pkt, "algorithm", key.alg, pubkey_alg_map)) {
+ goto done;
+ }
+
+ material = json_object_new_object();
+ if (!material || !obj_add_field_json(pkt, "material", material)) {
+ goto done;
+ }
+
+ switch (key.alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!obj_add_mpi_json(material, "n", &key.material.rsa.n, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "e", &key.material.rsa.e, ctx->dump_mpi)) {
+ goto done;
+ }
+ break;
+ case PGP_PKA_DSA:
+ if (!obj_add_mpi_json(material, "p", &key.material.dsa.p, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "q", &key.material.dsa.q, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "g", &key.material.dsa.g, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "y", &key.material.dsa.y, ctx->dump_mpi)) {
+ goto done;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!obj_add_mpi_json(material, "p", &key.material.eg.p, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "g", &key.material.eg.g, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "y", &key.material.eg.y, ctx->dump_mpi)) {
+ goto done;
+ }
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2: {
+ const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve);
+ if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) {
+ goto done;
+ }
+ if (!obj_add_field_json(material,
+ "curve",
+ json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) {
+ goto done;
+ }
+ break;
+ }
+ case PGP_PKA_ECDH: {
+ const ec_curve_desc_t *cdesc = get_curve_desc(key.material.ec.curve);
+ if (!obj_add_mpi_json(material, "p", &key.material.ec.p, ctx->dump_mpi)) {
+ goto done;
+ }
+ if (!obj_add_field_json(material,
+ "curve",
+ json_object_new_string(cdesc ? cdesc->pgp_name : "unknown"))) {
+ goto done;
+ }
+ if (!obj_add_intstr_json(
+ material, "hash algorithm", key.material.ec.kdf_hash_alg, hash_alg_map)) {
+ goto done;
+ }
+ if (!obj_add_intstr_json(
+ material, "key wrap algorithm", key.material.ec.key_wrap_alg, symm_alg_map)) {
+ goto done;
+ }
+ break;
+ }
+ default:
+ break;
+ }
+
+ if (is_secret_key_pkt(key.tag)) {
+ if (!obj_add_field_json(
+ material, "s2k usage", json_object_new_int(key.sec_protection.s2k.usage))) {
+ goto done;
+ }
+ if (!obj_add_s2k_json(material, &key.sec_protection.s2k)) {
+ goto done;
+ }
+ if (key.sec_protection.s2k.usage &&
+ !obj_add_intstr_json(
+ material, "symmetric algorithm", key.sec_protection.symm_alg, symm_alg_map)) {
+ goto done;
+ }
+ }
+
+ if (pgp_keyid(keyid, key) || !obj_add_hex_json(pkt, "keyid", keyid.data(), keyid.size())) {
+ goto done;
+ }
+
+ if (ctx->dump_grips) {
+ if (pgp_fingerprint(keyfp, key) ||
+ !obj_add_hex_json(pkt, "fingerprint", keyfp.fingerprint, keyfp.length)) {
+ goto done;
+ }
+
+ pgp_key_grip_t grip;
+ if (!rnp_key_store_get_key_grip(&key.material, grip) ||
+ !obj_add_hex_json(pkt, "grip", grip.data(), grip.size())) {
+ goto done;
+ }
+ }
+ ret = RNP_SUCCESS;
+done:
+ return ret;
+}
+
+static rnp_result_t
+stream_dump_userid_json(pgp_source_t *src, json_object *pkt)
+{
+ pgp_userid_pkt_t uid;
+ rnp_result_t ret;
+
+ try {
+ ret = uid.parse(*src);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ switch (uid.tag) {
+ case PGP_PKT_USER_ID:
+ if (!obj_add_field_json(
+ pkt, "userid", json_object_new_string_len((char *) uid.uid, uid.uid_len))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ case PGP_PKT_USER_ATTR:
+ if (!obj_add_hex_json(pkt, "userattr", uid.uid, uid.uid_len)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ default:;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_pk_session_key_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt)
+{
+ pgp_pk_sesskey_t pkey;
+ pgp_encrypted_material_t pkmaterial;
+ rnp_result_t ret;
+
+ try {
+ ret = pkey.parse(*src);
+ if (!pkey.parse_material(pkmaterial)) {
+ ret = RNP_ERROR_BAD_FORMAT;
+ }
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ if (!obj_add_field_json(pkt, "version", json_object_new_int(pkey.version)) ||
+ !obj_add_hex_json(pkt, "keyid", pkey.key_id.data(), pkey.key_id.size()) ||
+ !obj_add_intstr_json(pkt, "algorithm", pkey.alg, pubkey_alg_map)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ json_object *material = json_object_new_object();
+ if (!obj_add_field_json(pkt, "material", material)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ switch (pkey.alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!obj_add_mpi_json(material, "m", &pkmaterial.rsa.m, ctx->dump_mpi)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!obj_add_mpi_json(material, "g", &pkmaterial.eg.g, ctx->dump_mpi) ||
+ !obj_add_mpi_json(material, "m", &pkmaterial.eg.m, ctx->dump_mpi)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ case PGP_PKA_SM2:
+ if (!obj_add_mpi_json(material, "m", &pkmaterial.sm2.m, ctx->dump_mpi)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ case PGP_PKA_ECDH:
+ if (!obj_add_mpi_json(material, "p", &pkmaterial.ecdh.p, ctx->dump_mpi) ||
+ !obj_add_field_json(
+ material, "m.bytes", json_object_new_int(pkmaterial.ecdh.mlen))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (ctx->dump_mpi &&
+ !obj_add_hex_json(material, "m", pkmaterial.ecdh.m, pkmaterial.ecdh.mlen)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ break;
+ default:;
+ }
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_sk_session_key_json(pgp_source_t *src, json_object *pkt)
+{
+ pgp_sk_sesskey_t skey;
+ rnp_result_t ret;
+
+ try {
+ ret = skey.parse(*src);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ if (!obj_add_field_json(pkt, "version", json_object_new_int(skey.version)) ||
+ !obj_add_intstr_json(pkt, "algorithm", skey.alg, symm_alg_map)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if ((skey.version == PGP_SKSK_V5) &&
+ !obj_add_intstr_json(pkt, "aead algorithm", skey.aalg, aead_alg_map)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!obj_add_s2k_json(pkt, &skey.s2k)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if ((skey.version == PGP_SKSK_V5) &&
+ !obj_add_hex_json(pkt, "aead iv", skey.iv, skey.ivlen)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!obj_add_hex_json(pkt, "encrypted key", skey.enckey, skey.enckeylen)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_encrypted_json(pgp_source_t *src, json_object *pkt, pgp_pkt_type_t tag)
+{
+ if (tag != PGP_PKT_AEAD_ENCRYPTED) {
+ /* packet header with tag is already in pkt */
+ return stream_skip_packet(src);
+ }
+
+ /* dumping AEAD data */
+ pgp_aead_hdr_t aead = {};
+ if (!stream_dump_get_aead_hdr(src, &aead)) {
+ return RNP_ERROR_READ;
+ }
+
+ if (!obj_add_field_json(pkt, "version", json_object_new_int(aead.version)) ||
+ !obj_add_intstr_json(pkt, "algorithm", aead.ealg, symm_alg_map) ||
+ !obj_add_intstr_json(pkt, "aead algorithm", aead.aalg, aead_alg_map) ||
+ !obj_add_field_json(pkt, "chunk size", json_object_new_int(aead.csize)) ||
+ !obj_add_hex_json(pkt, "aead iv", aead.iv, aead.ivlen)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_one_pass_json(pgp_source_t *src, json_object *pkt)
+{
+ pgp_one_pass_sig_t onepass;
+ rnp_result_t ret;
+
+ try {
+ ret = onepass.parse(*src);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ return ret;
+ }
+
+ if (!obj_add_field_json(pkt, "version", json_object_new_int(onepass.version))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!obj_add_intstr_json(pkt, "type", onepass.type, sig_type_map)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!obj_add_intstr_json(pkt, "hash algorithm", onepass.halg, hash_alg_map)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!obj_add_intstr_json(pkt, "public key algorithm", onepass.palg, pubkey_alg_map)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!obj_add_hex_json(pkt, "signer", onepass.keyid.data(), onepass.keyid.size())) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!obj_add_field_json(pkt, "nested", json_object_new_boolean(onepass.nested))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_dump_marker_json(pgp_source_t &src, json_object *pkt)
+{
+ rnp_result_t ret = stream_parse_marker(src);
+
+ if (!obj_add_field_json(
+ pkt, "contents", json_object_new_string(ret ? "invalid" : PGP_MARKER_CONTENTS))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return ret;
+}
+
+static rnp_result_t stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx,
+ pgp_source_t * src,
+ json_object ** jso);
+
+static rnp_result_t
+stream_dump_compressed_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object *pkt)
+{
+ pgp_source_t zsrc = {0};
+ uint8_t zalg;
+ rnp_result_t ret;
+ json_object *contents = NULL;
+
+ if ((ret = init_compressed_src(&zsrc, src))) {
+ return ret;
+ }
+
+ get_compressed_src_alg(&zsrc, &zalg);
+ if (!obj_add_intstr_json(pkt, "algorithm", zalg, z_alg_map)) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ ret = stream_dump_raw_packets_json(ctx, &zsrc, &contents);
+ if (!ret && !obj_add_field_json(pkt, "contents", contents)) {
+ json_object_put(contents);
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ }
+done:
+ src_close(&zsrc);
+ return ret;
+}
+
+static rnp_result_t
+stream_dump_literal_json(pgp_source_t *src, json_object *pkt)
+{
+ pgp_source_t lsrc = {0};
+ pgp_literal_hdr_t lhdr = {0};
+ rnp_result_t ret;
+ uint8_t readbuf[16384];
+
+ if ((ret = init_literal_src(&lsrc, src))) {
+ return ret;
+ }
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ get_literal_src_hdr(&lsrc, &lhdr);
+ if (!obj_add_field_json(
+ pkt, "format", json_object_new_string_len((char *) &lhdr.format, 1))) {
+ goto done;
+ }
+ if (!obj_add_field_json(
+ pkt, "filename", json_object_new_string_len(lhdr.fname, lhdr.fname_len))) {
+ goto done;
+ }
+ if (!obj_add_field_json(pkt, "timestamp", json_object_new_int64(lhdr.timestamp))) {
+ goto done;
+ }
+
+ while (!src_eof(&lsrc)) {
+ size_t read = 0;
+ if (!src_read(&lsrc, readbuf, sizeof(readbuf), &read)) {
+ ret = RNP_ERROR_READ;
+ goto done;
+ }
+ }
+
+ if (!obj_add_field_json(pkt, "datalen", json_object_new_int64(lsrc.readb))) {
+ goto done;
+ }
+ ret = RNP_SUCCESS;
+done:
+ src_close(&lsrc);
+ return ret;
+}
+
+static bool
+stream_dump_hdr_json(pgp_source_t *src, pgp_packet_hdr_t *hdr, json_object *pkt)
+{
+ rnp_result_t hdrret = stream_peek_packet_hdr(src, hdr);
+ if (hdrret) {
+ return false;
+ }
+
+ json_object *jso_hdr = json_object_new_object();
+ if (!jso_hdr) {
+ return false;
+ }
+
+ if (!obj_add_field_json(jso_hdr, "offset", json_object_new_int64(src->readb))) {
+ goto error;
+ }
+ if (!obj_add_intstr_json(jso_hdr, "tag", hdr->tag, packet_tag_map)) {
+ goto error;
+ }
+ if (!obj_add_hex_json(jso_hdr, "raw", hdr->hdr, hdr->hdr_len)) {
+ goto error;
+ }
+ if (!hdr->partial && !hdr->indeterminate &&
+ !obj_add_field_json(jso_hdr, "length", json_object_new_int64(hdr->pkt_len))) {
+ goto error;
+ }
+ if (!obj_add_field_json(jso_hdr, "partial", json_object_new_boolean(hdr->partial))) {
+ goto error;
+ }
+ if (!obj_add_field_json(
+ jso_hdr, "indeterminate", json_object_new_boolean(hdr->indeterminate))) {
+ goto error;
+ }
+ return obj_add_field_json(pkt, "header", jso_hdr);
+error:
+ json_object_put(jso_hdr);
+ return false;
+}
+
+static rnp_result_t
+stream_dump_raw_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso)
+{
+ json_object *pkts = NULL;
+ json_object *pkt = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ pkts = json_object_new_array();
+ if (!pkts) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (src_eof(src)) {
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ /* do not allow endless recursion */
+ if (++ctx->layers > MAXIMUM_NESTING_LEVEL) {
+ RNP_LOG("Too many OpenPGP nested layers during the dump.");
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ while (!src_eof(src)) {
+ pgp_packet_hdr_t hdr = {};
+
+ pkt = json_object_new_object();
+ if (!pkt) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ if (!stream_dump_hdr_json(src, &hdr, pkt)) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+
+ if (ctx->dump_packets) {
+ size_t rlen = hdr.pkt_len + hdr.hdr_len;
+ uint8_t buf[2048 + sizeof(hdr.hdr)] = {0};
+
+ if (!hdr.pkt_len || (rlen > 2048 + hdr.hdr_len)) {
+ rlen = 2048 + hdr.hdr_len;
+ }
+ if (!src_peek(src, buf, rlen, &rlen) || (rlen < hdr.hdr_len)) {
+ ret = RNP_ERROR_READ;
+ goto done;
+ }
+ if (!obj_add_hex_json(pkt, "raw", buf + hdr.hdr_len, rlen - hdr.hdr_len)) {
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ goto done;
+ }
+ }
+
+ switch (hdr.tag) {
+ case PGP_PKT_SIGNATURE:
+ ret = stream_dump_signature_json(ctx, src, pkt);
+ break;
+ case PGP_PKT_SECRET_KEY:
+ case PGP_PKT_PUBLIC_KEY:
+ case PGP_PKT_SECRET_SUBKEY:
+ case PGP_PKT_PUBLIC_SUBKEY:
+ ret = stream_dump_key_json(ctx, src, pkt);
+ break;
+ case PGP_PKT_USER_ID:
+ case PGP_PKT_USER_ATTR:
+ ret = stream_dump_userid_json(src, pkt);
+ break;
+ case PGP_PKT_PK_SESSION_KEY:
+ ret = stream_dump_pk_session_key_json(ctx, src, pkt);
+ break;
+ case PGP_PKT_SK_SESSION_KEY:
+ ret = stream_dump_sk_session_key_json(src, pkt);
+ break;
+ case PGP_PKT_SE_DATA:
+ case PGP_PKT_SE_IP_DATA:
+ case PGP_PKT_AEAD_ENCRYPTED:
+ ctx->stream_pkts++;
+ ret = stream_dump_encrypted_json(src, pkt, hdr.tag);
+ break;
+ case PGP_PKT_ONE_PASS_SIG:
+ ret = stream_dump_one_pass_json(src, pkt);
+ break;
+ case PGP_PKT_COMPRESSED:
+ ctx->stream_pkts++;
+ ret = stream_dump_compressed_json(ctx, src, pkt);
+ break;
+ case PGP_PKT_LITDATA:
+ ctx->stream_pkts++;
+ ret = stream_dump_literal_json(src, pkt);
+ break;
+ case PGP_PKT_MARKER:
+ ret = stream_dump_marker_json(*src, pkt);
+ break;
+ case PGP_PKT_TRUST:
+ case PGP_PKT_MDC:
+ ret = stream_skip_packet(src);
+ break;
+ default:
+ ret = stream_skip_packet(src);
+ }
+
+ if (ret) {
+ RNP_LOG("failed to process packet");
+ 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;
+ }
+ if (ctx->stream_pkts > MAXIMUM_STREAM_PKTS) {
+ RNP_LOG("Too many OpenPGP stream packets during the dump.");
+ ret = RNP_SUCCESS;
+ goto done;
+ }
+
+ pkt = NULL;
+ }
+done:
+ if (ret) {
+ json_object_put(pkts);
+ json_object_put(pkt);
+ pkts = NULL;
+ }
+ *jso = pkts;
+ return ret;
+}
+
+rnp_result_t
+stream_dump_packets_json(rnp_dump_ctx_t *ctx, pgp_source_t *src, json_object **jso)
+{
+ 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;
+ goto finish;
+ }
+ }
+ /* check whether source is armored */
+ if (is_armored_source(src)) {
+ if ((ret = init_armored_src(&armorsrc, src))) {
+ RNP_LOG("failed to parse armored data");
+ goto finish;
+ }
+ armored = true;
+ src = &armorsrc;
+ }
+
+ if (src_eof(src)) {
+ ret = RNP_ERROR_NOT_ENOUGH_DATA;
+ goto finish;
+ }
+
+ ret = stream_dump_raw_packets_json(ctx, src, jso);
+finish:
+ if (armored) {
+ src_close(&armorsrc);
+ }
+ return ret;
+}
diff --git a/src/librepgp/stream-dump.h b/src/librepgp/stream-dump.h
new file mode 100644
index 0000000..6c2fcf1
--- /dev/null
+++ b/src/librepgp/stream-dump.h
@@ -0,0 +1,53 @@
+/*
+ * Copyright (c) 2018, [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 STREAM_DUMP_H_
+#define STREAM_DUMP_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "json_object.h"
+#include "json.h"
+#include "rnp.h"
+#include "stream-common.h"
+
+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,
+ pgp_source_t * src,
+ json_object ** jso);
+
+#endif
diff --git a/src/librepgp/stream-key.cpp b/src/librepgp/stream-key.cpp
new file mode 100644
index 0000000..8090ff7
--- /dev/null
+++ b/src/librepgp/stream-key.cpp
@@ -0,0 +1,1469 @@
+/*
+ * 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:
+ *
+ * 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 "config.h"
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <time.h>
+#include <inttypes.h>
+#include "stream-def.h"
+#include "stream-key.h"
+#include "stream-armor.h"
+#include "stream-packet.h"
+#include "stream-sig.h"
+#include "types.h"
+#include "fingerprint.h"
+#include "pgp-key.h"
+#include "crypto.h"
+#include "crypto/signatures.h"
+#include "crypto/mem.h"
+#include "../librekey/key_store_pgp.h"
+#include <set>
+#include <algorithm>
+#include <cassert>
+
+/**
+ * @brief Add signatures from src to dst, skipping the duplicates.
+ *
+ * @param dst Vector which will contain all distinct signatures from src and dst
+ * @param src Vector to merge signatures from
+ * @return true on success or false otherwise. On failure dst may have some sigs appended.
+ */
+static rnp_result_t
+merge_signatures(pgp_signature_list_t &dst, const pgp_signature_list_t &src)
+{
+ for (auto &sig : src) {
+ try {
+ if (std::find(dst.begin(), dst.end(), sig) != dst.end()) {
+ continue;
+ }
+ dst.emplace_back(sig);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+transferable_userid_merge(pgp_transferable_userid_t &dst, const pgp_transferable_userid_t &src)
+{
+ if (dst.uid != src.uid) {
+ RNP_LOG("wrong userid merge attempt");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ return merge_signatures(dst.signatures, src.signatures);
+}
+
+rnp_result_t
+transferable_subkey_from_key(pgp_transferable_subkey_t &dst, const pgp_key_t &key)
+{
+ 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;
+ }
+}
+
+rnp_result_t
+transferable_subkey_merge(pgp_transferable_subkey_t &dst, const pgp_transferable_subkey_t &src)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ if (!dst.subkey.equals(src.subkey, true)) {
+ RNP_LOG("wrong subkey merge call");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if ((ret = merge_signatures(dst.signatures, src.signatures))) {
+ RNP_LOG("failed to merge signatures");
+ }
+ return ret;
+}
+
+rnp_result_t
+transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key)
+{
+ 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;
+ }
+}
+
+static pgp_transferable_userid_t *
+transferable_key_has_userid(pgp_transferable_key_t &src, const pgp_userid_pkt_t &userid)
+{
+ for (auto &uid : src.userids) {
+ if (uid.uid == userid) {
+ return &uid;
+ }
+ }
+ return NULL;
+}
+
+static pgp_transferable_subkey_t *
+transferable_key_has_subkey(pgp_transferable_key_t &src, const pgp_key_pkt_t &subkey)
+{
+ for (auto &srcsub : src.subkeys) {
+ if (srcsub.subkey.equals(subkey, true)) {
+ return &srcsub;
+ }
+ }
+ return NULL;
+}
+
+rnp_result_t
+transferable_key_merge(pgp_transferable_key_t &dst, const pgp_transferable_key_t &src)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ if (!dst.key.equals(src.key, true)) {
+ RNP_LOG("wrong key merge call");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* direct-key signatures */
+ if ((ret = merge_signatures(dst.signatures, src.signatures))) {
+ RNP_LOG("failed to merge signatures");
+ return ret;
+ }
+ /* userids */
+ for (auto &srcuid : src.userids) {
+ pgp_transferable_userid_t *dstuid = transferable_key_has_userid(dst, srcuid.uid);
+ if (dstuid) {
+ if ((ret = transferable_userid_merge(*dstuid, srcuid))) {
+ RNP_LOG("failed to merge userid");
+ return ret;
+ }
+ continue;
+ }
+ /* add userid */
+ try {
+ dst.userids.emplace_back(srcuid);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ /* subkeys */
+ for (auto &srcsub : src.subkeys) {
+ pgp_transferable_subkey_t *dstsub = transferable_key_has_subkey(dst, srcsub.subkey);
+ if (dstsub) {
+ if ((ret = transferable_subkey_merge(*dstsub, srcsub))) {
+ RNP_LOG("failed to merge subkey");
+ return ret;
+ }
+ continue;
+ }
+ /* add subkey */
+ if (is_public_key_pkt(dst.key.tag) != is_public_key_pkt(srcsub.subkey.tag)) {
+ RNP_LOG("warning: adding public/secret subkey to secret/public key");
+ }
+ try {
+ dst.subkeys.emplace_back(srcsub);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ return RNP_SUCCESS;
+}
+
+static bool
+skip_pgp_packets(pgp_source_t &src, const std::set<pgp_pkt_type_t> &pkts)
+{
+ do {
+ int pkt = stream_pkt_type(src);
+ if (!pkt) {
+ break;
+ }
+ if (pkt < 0) {
+ return false;
+ }
+ if (pkts.find((pgp_pkt_type_t) pkt) == pkts.end()) {
+ return true;
+ }
+ uint64_t ppos = src.readb;
+ if (stream_skip_packet(&src)) {
+ RNP_LOG("failed to skip packet at %" PRIu64, ppos);
+ return false;
+ }
+ } while (1);
+
+ return true;
+}
+
+static rnp_result_t
+process_pgp_key_signatures(pgp_source_t &src, pgp_signature_list_t &sigs, bool skiperrors)
+{
+ int ptag;
+ while ((ptag = stream_pkt_type(src)) == PGP_PKT_SIGNATURE) {
+ uint64_t sigpos = src.readb;
+ try {
+ pgp_signature_t sig;
+ rnp_result_t ret = sig.parse(src);
+ if (ret) {
+ RNP_LOG("failed to parse signature at %" PRIu64, sigpos);
+ if (!skiperrors) {
+ return ret;
+ }
+ } else {
+ sigs.emplace_back(std::move(sig));
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) {
+ return RNP_ERROR_READ;
+ }
+ }
+ return ptag < 0 ? RNP_ERROR_BAD_FORMAT : RNP_SUCCESS;
+}
+
+static rnp_result_t
+process_pgp_userid(pgp_source_t &src, pgp_transferable_userid_t &uid, bool skiperrors)
+{
+ rnp_result_t ret;
+ uint64_t uidpos = src.readb;
+ try {
+ ret = uid.uid.parse(src);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ RNP_LOG("failed to parse userid at %" PRIu64, uidpos);
+ return ret;
+ }
+ if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) {
+ return RNP_ERROR_READ;
+ }
+ return process_pgp_key_signatures(src, uid.signatures, skiperrors);
+}
+
+rnp_result_t
+process_pgp_subkey(pgp_source_t &src, pgp_transferable_subkey_t &subkey, bool skiperrors)
+{
+ int ptag;
+ subkey = pgp_transferable_subkey_t();
+ uint64_t keypos = src.readb;
+ if (!is_subkey_pkt(ptag = stream_pkt_type(src))) {
+ RNP_LOG("wrong subkey ptag: %d at %" PRIu64, ptag, keypos);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ rnp_result_t ret = RNP_ERROR_BAD_FORMAT;
+ try {
+ ret = subkey.subkey.parse(src);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ ret = RNP_ERROR_GENERIC;
+ }
+ if (ret) {
+ RNP_LOG("failed to parse subkey at %" PRIu64, keypos);
+ subkey.subkey = {};
+ return ret;
+ }
+
+ if (!skip_pgp_packets(src, {PGP_PKT_TRUST})) {
+ return RNP_ERROR_READ;
+ }
+
+ return process_pgp_key_signatures(src, subkey.signatures, skiperrors);
+}
+
+rnp_result_t
+process_pgp_key_auto(pgp_source_t & src,
+ pgp_transferable_key_t &key,
+ bool allowsub,
+ bool skiperrors)
+{
+ key = {};
+ uint64_t srcpos = src.readb;
+ int ptag = stream_pkt_type(src);
+ if (is_subkey_pkt(ptag) && allowsub) {
+ pgp_transferable_subkey_t subkey;
+ rnp_result_t ret = process_pgp_subkey(src, subkey, skiperrors);
+ if (subkey.subkey.tag != PGP_PKT_RESERVED) {
+ try {
+ key.subkeys.push_back(std::move(subkey));
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ ret = RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+ /* change error code if we didn't process anything at all */
+ if (srcpos == src.readb) {
+ ret = RNP_ERROR_BAD_STATE;
+ }
+ return ret;
+ }
+
+ 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 {
+ 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,
+ PGP_PKT_USER_ID,
+ PGP_PKT_USER_ATTR,
+ PGP_PKT_PUBLIC_SUBKEY,
+ PGP_PKT_SECRET_SUBKEY})) {
+ ret = RNP_ERROR_READ;
+ }
+ /* change error code if we didn't process anything at all */
+ if (srcpos == src.readb) {
+ ret = RNP_ERROR_BAD_STATE;
+ }
+ return ret;
+}
+
+rnp_result_t
+process_pgp_keys(pgp_source_t &src, pgp_key_sequence_t &keys, bool skiperrors)
+{
+ bool has_secret = false;
+ bool has_public = false;
+
+ keys.keys.clear();
+ /* 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 (!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;
+ rnp_result_t ret = process_pgp_key_auto(armor.src(), curkey, false, skiperrors);
+ if (ret && (!skiperrors || (ret != RNP_ERROR_BAD_FORMAT))) {
+ 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);
+
+ keys.keys.emplace_back(std::move(curkey));
+ }
+
+ if (has_secret && has_public) {
+ RNP_LOG("warning! public keys are mixed together with secret ones!");
+ }
+
+ if (armor.error()) {
+ keys.keys.clear();
+ return RNP_ERROR_READ;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+process_pgp_key(pgp_source_t &src, pgp_transferable_key_t &key, bool skiperrors)
+{
+ key = pgp_transferable_key_t();
+ /* 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;
+ }
+
+ rnp_result_t ret = key.key.parse(armor.src());
+ if (ret) {
+ RNP_LOG("failed to parse key pkt at %" PRIu64, keypos);
+ key.key = {};
+ return ret;
+ }
+
+ if (!skip_pgp_packets(armor.src(), {PGP_PKT_TRUST})) {
+ return RNP_ERROR_READ;
+ }
+
+ /* direct-key signatures */
+ if ((ret = process_pgp_key_signatures(armor.src(), key.signatures, skiperrors))) {
+ return ret;
+ }
+
+ /* user ids/attrs with signatures */
+ while ((ptag = stream_pkt_type(armor.src())) > 0) {
+ if ((ptag != PGP_PKT_USER_ID) && (ptag != PGP_PKT_USER_ATTR)) {
+ break;
+ }
+
+ 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(armor.src())) > 0) {
+ if (!is_subkey_pkt(ptag)) {
+ break;
+ }
+
+ pgp_transferable_subkey_t subkey;
+ ret = process_pgp_subkey(armor.src(), subkey, skiperrors);
+ if ((ret == RNP_ERROR_BAD_FORMAT) && skiperrors &&
+ skip_pgp_packets(armor.src(), {PGP_PKT_TRUST, PGP_PKT_SIGNATURE})) {
+ /* skip malformed subkey */
+ continue;
+ }
+ if (ret) {
+ return ret;
+ }
+ key.subkeys.emplace_back(std::move(subkey));
+ }
+ 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)
+{
+ size_t idx;
+ size_t pos = 0;
+ size_t mpilen;
+ size_t blsize;
+
+ if (!(blsize = pgp_cipher_block_size(crypt))) {
+ RNP_LOG("wrong crypto");
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* 4 RSA secret mpis with cleartext header */
+ for (idx = 0; idx < 4; idx++) {
+ if (pos + 2 > len) {
+ RNP_LOG("bad v3 secret key data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ mpilen = (read_uint16(enc + pos) + 7) >> 3;
+ memcpy(dec + pos, enc + pos, 2);
+ pos += 2;
+ if (pos + mpilen > len) {
+ RNP_LOG("bad v3 secret key data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ pgp_cipher_cfb_decrypt(crypt, dec + pos, enc + pos, mpilen);
+ pos += mpilen;
+ if (mpilen < blsize) {
+ RNP_LOG("bad rsa v3 mpi len");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ pgp_cipher_cfb_resync(crypt, enc + pos - blsize);
+ }
+
+ /* sum16 */
+ if (pos + 2 != len) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ memcpy(dec + pos, enc + pos, 2);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+parse_secret_key_mpis(pgp_key_pkt_t &key, const uint8_t *mpis, size_t len)
+{
+ if (!mpis) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ /* check the cleartext data */
+ switch (key.sec_protection.s2k.usage) {
+ case PGP_S2KU_NONE:
+ case PGP_S2KU_ENCRYPTED: {
+ /* calculate and check sum16 of the cleartext */
+ if (len < 2) {
+ RNP_LOG("No space for checksum.");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ uint16_t sum = 0;
+ len -= 2;
+ for (size_t idx = 0; idx < len; idx++) {
+ sum += mpis[idx];
+ }
+ uint16_t expsum = read_uint16(mpis + len);
+ if (sum != expsum) {
+ RNP_LOG("Wrong key checksum, got 0x%X instead of 0x%X.", (int) sum, (int) expsum);
+ return RNP_ERROR_DECRYPT_FAILED;
+ }
+ break;
+ }
+ case PGP_S2KU_ENCRYPTED_AND_HASHED: {
+ if (len < PGP_SHA1_HASH_SIZE) {
+ RNP_LOG("No space for hash");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* calculate and check sha1 hash of the cleartext */
+ uint8_t hval[PGP_SHA1_HASH_SIZE];
+ try {
+ 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) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("hash calculation failed: %s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+ if (memcmp(hval, mpis + len, PGP_SHA1_HASH_SIZE)) {
+ return RNP_ERROR_DECRYPT_FAILED;
+ }
+ break;
+ }
+ default:
+ RNP_LOG("unknown s2k usage: %d", (int) key.sec_protection.s2k.usage);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ try {
+ /* parse mpis depending on algorithm */
+ pgp_packet_body_t body(mpis, len);
+
+ switch (key.alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!body.get(key.material.rsa.d) || !body.get(key.material.rsa.p) ||
+ !body.get(key.material.rsa.q) || !body.get(key.material.rsa.u)) {
+ RNP_LOG("failed to parse rsa secret key data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ case PGP_PKA_DSA:
+ if (!body.get(key.material.dsa.x)) {
+ RNP_LOG("failed to parse dsa secret key data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ case PGP_PKA_ECDH:
+ if (!body.get(key.material.ec.x)) {
+ RNP_LOG("failed to parse ecc secret key data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!body.get(key.material.eg.x)) {
+ RNP_LOG("failed to parse eg secret key data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ default:
+ RNP_LOG("unknown pk alg : %d", (int) key.alg);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (body.left()) {
+ RNP_LOG("extra data in sec key");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ key.material.secret = true;
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+}
+
+rnp_result_t
+decrypt_secret_key(pgp_key_pkt_t *key, const char *password)
+{
+ if (!key) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+ if (!is_secret_key_pkt(key->tag)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* mark material as not validated as it may be valid for public part */
+ key->material.validity.reset();
+
+ /* check whether data is not encrypted */
+ if (!key->sec_protection.s2k.usage) {
+ return parse_secret_key_mpis(*key, key->sec_data, key->sec_len);
+ }
+
+ /* check whether secret key data present */
+ if (!key->sec_len) {
+ RNP_LOG("No secret key data");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* data is encrypted */
+ if (!password) {
+ return RNP_ERROR_NULL_POINTER;
+ }
+
+ if (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB) {
+ RNP_LOG("unsupported secret key encryption mode");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf;
+ size_t keysize = pgp_key_size(key->sec_protection.symm_alg);
+ if (!keysize ||
+ !pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) {
+ RNP_LOG("failed to derive key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ try {
+ rnp::secure_vector<uint8_t> decdata(key->sec_len);
+ pgp_crypt_t crypt;
+ if (!pgp_cipher_cfb_start(
+ &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) {
+ RNP_LOG("failed to start cfb decryption");
+ return RNP_ERROR_DECRYPT_FAILED;
+ }
+
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ switch (key->version) {
+ case PGP_V3:
+ if (!is_rsa_key_alg(key->alg)) {
+ RNP_LOG("non-RSA v3 key");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ break;
+ }
+ ret = decrypt_secret_key_v3(&crypt, decdata.data(), key->sec_data, key->sec_len);
+ break;
+ case PGP_V4:
+ pgp_cipher_cfb_decrypt(&crypt, decdata.data(), key->sec_data, key->sec_len);
+ ret = RNP_SUCCESS;
+ break;
+ default:
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ pgp_cipher_cfb_finish(&crypt);
+ if (ret) {
+ return ret;
+ }
+
+ return parse_secret_key_mpis(*key, decdata.data(), key->sec_len);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+}
+
+static void
+write_secret_key_mpis(pgp_packet_body_t &body, pgp_key_pkt_t &key)
+{
+ /* add mpis */
+ switch (key.alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ body.add(key.material.rsa.d);
+ body.add(key.material.rsa.p);
+ body.add(key.material.rsa.q);
+ body.add(key.material.rsa.u);
+ break;
+ case PGP_PKA_DSA:
+ body.add(key.material.dsa.x);
+ break;
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ case PGP_PKA_ECDH:
+ body.add(key.material.ec.x);
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ body.add(key.material.eg.x);
+ break;
+ default:
+ RNP_LOG("unknown pk alg : %d", (int) key.alg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ /* add sum16 if sha1 is not used */
+ if (key.sec_protection.s2k.usage != PGP_S2KU_ENCRYPTED_AND_HASHED) {
+ uint16_t sum = 0;
+ for (size_t i = 0; i < body.size(); i++) {
+ sum += body.data()[i];
+ }
+ body.add_uint16(sum);
+ return;
+ }
+
+ /* add sha1 hash */
+ 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) {
+ RNP_LOG("failed to finish hash");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+ body.add(hval, PGP_SHA1_HASH_SIZE);
+}
+
+rnp_result_t
+encrypt_secret_key(pgp_key_pkt_t *key, const char *password, rnp::RNG &rng)
+{
+ if (!is_secret_key_pkt(key->tag) || !key->material.secret) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ if (key->sec_protection.s2k.usage &&
+ (key->sec_protection.cipher_mode != PGP_CIPHER_MODE_CFB)) {
+ RNP_LOG("unsupported secret key encryption mode");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ try {
+ /* build secret key data */
+ pgp_packet_body_t body(PGP_PKT_RESERVED);
+ body.mark_secure();
+ write_secret_key_mpis(body, *key);
+
+ /* check whether data is not encrypted */
+ if (key->sec_protection.s2k.usage == PGP_S2KU_NONE) {
+ secure_clear(key->sec_data, key->sec_len);
+ free(key->sec_data);
+ key->sec_data = (uint8_t *) malloc(body.size());
+ if (!key->sec_data) {
+ RNP_LOG("allocation failed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(key->sec_data, body.data(), body.size());
+ key->sec_len = body.size();
+ return RNP_SUCCESS;
+ }
+ if (key->version < PGP_V4) {
+ RNP_LOG("encryption of v3 keys is not supported");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* data is encrypted */
+ size_t keysize = pgp_key_size(key->sec_protection.symm_alg);
+ size_t blsize = pgp_block_size(key->sec_protection.symm_alg);
+ if (!keysize || !blsize) {
+ RNP_LOG("wrong symm alg");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* generate iv and s2k salt */
+ rng.get(key->sec_protection.iv, blsize);
+ if ((key->sec_protection.s2k.specifier != PGP_S2KS_SIMPLE)) {
+ rng.get(key->sec_protection.s2k.salt, PGP_SALT_SIZE);
+ }
+ /* derive key */
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> keybuf;
+ if (!pgp_s2k_derive_key(&key->sec_protection.s2k, password, keybuf.data(), keysize)) {
+ RNP_LOG("failed to derive key");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* encrypt sec data */
+ pgp_crypt_t crypt;
+ if (!pgp_cipher_cfb_start(
+ &crypt, key->sec_protection.symm_alg, keybuf.data(), key->sec_protection.iv)) {
+ RNP_LOG("failed to start cfb encryption");
+ return RNP_ERROR_DECRYPT_FAILED;
+ }
+ pgp_cipher_cfb_encrypt(&crypt, body.data(), body.data(), body.size());
+ pgp_cipher_cfb_finish(&crypt);
+ secure_clear(key->sec_data, key->sec_len);
+ free(key->sec_data);
+ key->sec_data = (uint8_t *) malloc(body.size());
+ if (!key->sec_data) {
+ RNP_LOG("allocation failed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(key->sec_data, body.data(), body.size());
+ key->sec_len = body.size();
+ /* cleanup cleartext fields */
+ forget_secret_key_fields(&key->material);
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+}
+
+void
+forget_secret_key_fields(pgp_key_material_t *key)
+{
+ if (!key || !key->secret) {
+ return;
+ }
+
+ switch (key->alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ mpi_forget(&key->rsa.d);
+ mpi_forget(&key->rsa.p);
+ mpi_forget(&key->rsa.q);
+ mpi_forget(&key->rsa.u);
+ break;
+ case PGP_PKA_DSA:
+ mpi_forget(&key->dsa.x);
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ mpi_forget(&key->eg.x);
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ case PGP_PKA_ECDH:
+ mpi_forget(&key->ec.x);
+ break;
+ default:
+ RNP_LOG("unknown key algorithm: %d", (int) key->alg);
+ }
+
+ key->secret = false;
+}
+
+pgp_userid_pkt_t::pgp_userid_pkt_t(const pgp_userid_pkt_t &src)
+{
+ tag = src.tag;
+ uid_len = src.uid_len;
+ uid = NULL;
+ if (src.uid) {
+ uid = (uint8_t *) malloc(uid_len);
+ if (!uid) {
+ throw std::bad_alloc();
+ }
+ memcpy(uid, src.uid, uid_len);
+ }
+}
+
+pgp_userid_pkt_t::pgp_userid_pkt_t(pgp_userid_pkt_t &&src)
+{
+ tag = src.tag;
+ uid_len = src.uid_len;
+ uid = src.uid;
+ src.uid = NULL;
+}
+
+pgp_userid_pkt_t &
+pgp_userid_pkt_t::operator=(pgp_userid_pkt_t &&src)
+{
+ if (this == &src) {
+ return *this;
+ }
+ tag = src.tag;
+ uid_len = src.uid_len;
+ free(uid);
+ uid = src.uid;
+ src.uid = NULL;
+ return *this;
+}
+
+pgp_userid_pkt_t &
+pgp_userid_pkt_t::operator=(const pgp_userid_pkt_t &src)
+{
+ if (this == &src) {
+ return *this;
+ }
+ tag = src.tag;
+ uid_len = src.uid_len;
+ free(uid);
+ uid = NULL;
+ if (src.uid) {
+ uid = (uint8_t *) malloc(uid_len);
+ if (!uid) {
+ throw std::bad_alloc();
+ }
+ memcpy(uid, src.uid, uid_len);
+ }
+ return *this;
+}
+
+bool
+pgp_userid_pkt_t::operator==(const pgp_userid_pkt_t &src) const
+{
+ return (tag == src.tag) && (uid_len == src.uid_len) && !memcmp(uid, src.uid, uid_len);
+}
+
+bool
+pgp_userid_pkt_t::operator!=(const pgp_userid_pkt_t &src) const
+{
+ return !(*this == src);
+}
+
+pgp_userid_pkt_t::~pgp_userid_pkt_t()
+{
+ free(uid);
+}
+
+void
+pgp_userid_pkt_t::write(pgp_dest_t &dst) const
+{
+ if ((tag != PGP_PKT_USER_ID) && (tag != PGP_PKT_USER_ATTR)) {
+ RNP_LOG("wrong userid tag");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ if (uid_len && !uid) {
+ RNP_LOG("null but non-empty userid");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ pgp_packet_body_t pktbody(tag);
+ if (uid) {
+ pktbody.add(uid, uid_len);
+ }
+ pktbody.write(dst);
+}
+
+rnp_result_t
+pgp_userid_pkt_t::parse(pgp_source_t &src)
+{
+ /* check the tag */
+ int stag = stream_pkt_type(src);
+ if ((stag != PGP_PKT_USER_ID) && (stag != PGP_PKT_USER_ATTR)) {
+ RNP_LOG("wrong userid tag: %d", stag);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ pgp_packet_body_t pkt(PGP_PKT_RESERVED);
+ rnp_result_t res = pkt.read(src);
+ if (res) {
+ return res;
+ }
+
+ /* userid type, i.e. tag */
+ tag = (pgp_pkt_type_t) stag;
+ free(uid);
+ uid = (uint8_t *) malloc(pkt.size());
+ if (!uid) {
+ RNP_LOG("allocation failed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(uid, pkt.data(), pkt.size());
+ uid_len = pkt.size();
+ return RNP_SUCCESS;
+}
+
+pgp_key_pkt_t::pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly)
+{
+ if (pubonly && is_secret_key_pkt(src.tag)) {
+ tag = (src.tag == PGP_PKT_SECRET_KEY) ? PGP_PKT_PUBLIC_KEY : PGP_PKT_PUBLIC_SUBKEY;
+ } else {
+ tag = src.tag;
+ }
+ version = src.version;
+ creation_time = src.creation_time;
+ alg = src.alg;
+ v3_days = src.v3_days;
+ hashed_len = src.hashed_len;
+ hashed_data = NULL;
+ if (src.hashed_data) {
+ hashed_data = (uint8_t *) malloc(hashed_len);
+ if (!hashed_data) {
+ throw std::bad_alloc();
+ }
+ memcpy(hashed_data, src.hashed_data, hashed_len);
+ }
+ material = src.material;
+ if (pubonly) {
+ forget_secret_key_fields(&material);
+ sec_len = 0;
+ sec_data = NULL;
+ sec_protection = {};
+ return;
+ }
+ sec_len = src.sec_len;
+ sec_data = NULL;
+ if (src.sec_data) {
+ sec_data = (uint8_t *) malloc(sec_len);
+ if (!sec_data) {
+ free(hashed_data);
+ hashed_data = NULL;
+ throw std::bad_alloc();
+ }
+ memcpy(sec_data, src.sec_data, sec_len);
+ }
+ sec_protection = src.sec_protection;
+}
+
+pgp_key_pkt_t::pgp_key_pkt_t(pgp_key_pkt_t &&src)
+{
+ tag = src.tag;
+ version = src.version;
+ creation_time = src.creation_time;
+ alg = src.alg;
+ v3_days = src.v3_days;
+ hashed_len = src.hashed_len;
+ hashed_data = src.hashed_data;
+ src.hashed_data = NULL;
+ material = src.material;
+ forget_secret_key_fields(&src.material);
+ sec_len = src.sec_len;
+ sec_data = src.sec_data;
+ src.sec_data = NULL;
+ sec_protection = src.sec_protection;
+}
+
+pgp_key_pkt_t &
+pgp_key_pkt_t::operator=(pgp_key_pkt_t &&src)
+{
+ if (this == &src) {
+ return *this;
+ }
+ tag = src.tag;
+ version = src.version;
+ creation_time = src.creation_time;
+ alg = src.alg;
+ v3_days = src.v3_days;
+ hashed_len = src.hashed_len;
+ free(hashed_data);
+ hashed_data = src.hashed_data;
+ src.hashed_data = NULL;
+ material = src.material;
+ forget_secret_key_fields(&src.material);
+ secure_clear(sec_data, sec_len);
+ free(sec_data);
+ sec_len = src.sec_len;
+ sec_data = src.sec_data;
+ src.sec_data = NULL;
+ src.sec_len = 0;
+ sec_protection = src.sec_protection;
+ return *this;
+}
+
+pgp_key_pkt_t &
+pgp_key_pkt_t::operator=(const pgp_key_pkt_t &src)
+{
+ if (this == &src) {
+ return *this;
+ }
+ tag = src.tag;
+ version = src.version;
+ creation_time = src.creation_time;
+ alg = src.alg;
+ v3_days = src.v3_days;
+ hashed_len = src.hashed_len;
+ free(hashed_data);
+ hashed_data = NULL;
+ if (src.hashed_data) {
+ hashed_data = (uint8_t *) malloc(hashed_len);
+ if (!hashed_data) {
+ throw std::bad_alloc();
+ }
+ memcpy(hashed_data, src.hashed_data, hashed_len);
+ }
+ material = src.material;
+ secure_clear(sec_data, sec_len);
+ free(sec_data);
+ sec_data = NULL;
+ sec_len = src.sec_len;
+ if (src.sec_data) {
+ sec_data = (uint8_t *) malloc(sec_len);
+ if (!sec_data) {
+ free(hashed_data);
+ hashed_data = NULL;
+ throw std::bad_alloc();
+ }
+ memcpy(sec_data, src.sec_data, sec_len);
+ }
+ sec_protection = src.sec_protection;
+ return *this;
+}
+
+pgp_key_pkt_t::~pgp_key_pkt_t()
+{
+ forget_secret_key_fields(&material);
+ free(hashed_data);
+ secure_clear(sec_data, sec_len);
+ free(sec_data);
+}
+
+void
+pgp_key_pkt_t::write(pgp_dest_t &dst)
+{
+ if (!is_key_pkt(tag)) {
+ RNP_LOG("wrong key tag");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ if (!hashed_data) {
+ fill_hashed_data();
+ }
+
+ pgp_packet_body_t pktbody(tag);
+ /* all public key data is written in hashed_data */
+ pktbody.add(hashed_data, hashed_len);
+ /* if we have public key then we do not need further processing */
+ if (!is_secret_key_pkt(tag)) {
+ pktbody.write(dst);
+ return;
+ }
+
+ /* secret key fields should be pre-populated in sec_data field */
+ if ((sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) && (!sec_data || !sec_len)) {
+ RNP_LOG("secret key data is not populated");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ pktbody.add_byte(sec_protection.s2k.usage);
+
+ switch (sec_protection.s2k.usage) {
+ case PGP_S2KU_NONE:
+ break;
+ case PGP_S2KU_ENCRYPTED_AND_HASHED:
+ case PGP_S2KU_ENCRYPTED: {
+ pktbody.add_byte(sec_protection.symm_alg);
+ pktbody.add(sec_protection.s2k);
+ if (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL) {
+ size_t blsize = pgp_block_size(sec_protection.symm_alg);
+ if (!blsize) {
+ RNP_LOG("wrong block size");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ pktbody.add(sec_protection.iv, blsize);
+ }
+ break;
+ }
+ default:
+ RNP_LOG("wrong s2k usage");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ if (sec_len) {
+ /* if key is stored on card, or exported via gpg --export-secret-subkeys, then
+ * sec_data is empty */
+ pktbody.add(sec_data, sec_len);
+ }
+ pktbody.write(dst);
+}
+
+rnp_result_t
+pgp_key_pkt_t::parse(pgp_source_t &src)
+{
+ /* check the key tag */
+ int atag = stream_pkt_type(src);
+ if (!is_key_pkt(atag)) {
+ RNP_LOG("wrong key packet tag: %d", atag);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ pgp_packet_body_t pkt((pgp_pkt_type_t) atag);
+ /* Read the packet into memory */
+ rnp_result_t res = pkt.read(src);
+ if (res) {
+ return res;
+ }
+ /* key type, i.e. tag */
+ tag = (pgp_pkt_type_t) atag;
+ /* version */
+ uint8_t ver = 0;
+ if (!pkt.get(ver) || (ver < PGP_V2) || (ver > PGP_V4)) {
+ RNP_LOG("wrong key packet version");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ version = (pgp_version_t) ver;
+ /* creation time */
+ if (!pkt.get(creation_time)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* v3: validity days */
+ if ((version < PGP_V4) && !pkt.get(v3_days)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* key algorithm */
+ uint8_t analg = 0;
+ if (!pkt.get(analg)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ alg = (pgp_pubkey_alg_t) analg;
+ material.alg = (pgp_pubkey_alg_t) analg;
+ /* v3 keys must be RSA-only */
+ if ((version < PGP_V4) && !is_rsa_key_alg(alg)) {
+ RNP_LOG("wrong v3 pk algorithm");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* algorithm specific fields */
+ switch (alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!pkt.get(material.rsa.n) || !pkt.get(material.rsa.e)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ case PGP_PKA_DSA:
+ if (!pkt.get(material.dsa.p) || !pkt.get(material.dsa.q) || !pkt.get(material.dsa.g) ||
+ !pkt.get(material.dsa.y)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!pkt.get(material.eg.p) || !pkt.get(material.eg.g) || !pkt.get(material.eg.y)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ break;
+ case PGP_PKA_ECDH: {
+ if (!pkt.get(material.ec.curve) || !pkt.get(material.ec.p)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* read KDF parameters. At the moment should be 0x03 0x01 halg ealg */
+ uint8_t len = 0, halg = 0, walg = 0;
+ if (!pkt.get(len) || (len != 3)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (!pkt.get(len) || (len != 1)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (!pkt.get(halg) || !pkt.get(walg)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ material.ec.kdf_hash_alg = (pgp_hash_alg_t) halg;
+ material.ec.key_wrap_alg = (pgp_symm_alg_t) walg;
+ break;
+ }
+ default:
+ RNP_LOG("unknown key algorithm: %d", (int) alg);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* fill hashed data used for signatures */
+ if (!(hashed_data = (uint8_t *) malloc(pkt.size() - pkt.left()))) {
+ RNP_LOG("allocation failed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(hashed_data, pkt.data(), pkt.size() - pkt.left());
+ hashed_len = pkt.size() - pkt.left();
+
+ /* secret key fields if any */
+ if (is_secret_key_pkt(tag)) {
+ uint8_t usage = 0;
+ if (!pkt.get(usage)) {
+ RNP_LOG("failed to read key protection");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ sec_protection.s2k.usage = (pgp_s2k_usage_t) usage;
+ sec_protection.cipher_mode = PGP_CIPHER_MODE_CFB;
+
+ switch (sec_protection.s2k.usage) {
+ case PGP_S2KU_NONE:
+ break;
+ case PGP_S2KU_ENCRYPTED:
+ case PGP_S2KU_ENCRYPTED_AND_HASHED: {
+ /* we have s2k */
+ uint8_t salg = 0;
+ if (!pkt.get(salg) || !pkt.get(sec_protection.s2k)) {
+ RNP_LOG("failed to read key protection");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ sec_protection.symm_alg = (pgp_symm_alg_t) salg;
+ break;
+ }
+ default:
+ /* old-style: usage is symmetric algorithm identifier */
+ sec_protection.symm_alg = (pgp_symm_alg_t) usage;
+ sec_protection.s2k.usage = PGP_S2KU_ENCRYPTED;
+ sec_protection.s2k.specifier = PGP_S2KS_SIMPLE;
+ sec_protection.s2k.hash_alg = PGP_HASH_MD5;
+ break;
+ }
+
+ /* iv */
+ if (sec_protection.s2k.usage &&
+ (sec_protection.s2k.specifier != PGP_S2KS_EXPERIMENTAL)) {
+ size_t bl_size = pgp_block_size(sec_protection.symm_alg);
+ if (!bl_size || !pkt.get(sec_protection.iv, bl_size)) {
+ RNP_LOG("failed to read iv");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ }
+
+ /* encrypted/cleartext secret MPIs are left */
+ size_t asec_len = pkt.left();
+ if (!asec_len) {
+ sec_data = NULL;
+ } else {
+ if (!(sec_data = (uint8_t *) calloc(1, asec_len))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ if (!pkt.get(sec_data, asec_len)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ }
+ sec_len = asec_len;
+ }
+
+ if (pkt.left()) {
+ RNP_LOG("extra %d bytes in key packet", (int) pkt.left());
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ return RNP_SUCCESS;
+}
+
+void
+pgp_key_pkt_t::fill_hashed_data()
+{
+ /* we don't have a need to write v2-v3 signatures */
+ if (version != PGP_V4) {
+ RNP_LOG("unknown key version %d", (int) version);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ pgp_packet_body_t hbody(PGP_PKT_RESERVED);
+ hbody.add_byte(version);
+ hbody.add_uint32(creation_time);
+ hbody.add_byte(alg);
+ /* Algorithm specific fields */
+ switch (alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ hbody.add(material.rsa.n);
+ hbody.add(material.rsa.e);
+ break;
+ case PGP_PKA_DSA:
+ hbody.add(material.dsa.p);
+ hbody.add(material.dsa.q);
+ hbody.add(material.dsa.g);
+ hbody.add(material.dsa.y);
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ hbody.add(material.eg.p);
+ hbody.add(material.eg.g);
+ hbody.add(material.eg.y);
+ break;
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_SM2:
+ hbody.add(material.ec.curve);
+ hbody.add(material.ec.p);
+ break;
+ case PGP_PKA_ECDH:
+ hbody.add(material.ec.curve);
+ hbody.add(material.ec.p);
+ hbody.add_byte(3);
+ hbody.add_byte(1);
+ hbody.add_byte(material.ec.kdf_hash_alg);
+ hbody.add_byte(material.ec.key_wrap_alg);
+ break;
+ default:
+ RNP_LOG("unknown key algorithm: %d", (int) alg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ hashed_data = (uint8_t *) malloc(hbody.size());
+ if (!hashed_data) {
+ RNP_LOG("allocation failed");
+ throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY);
+ }
+ memcpy(hashed_data, hbody.data(), hbody.size());
+ hashed_len = hbody.size();
+}
+
+bool
+pgp_key_pkt_t::equals(const pgp_key_pkt_t &key, bool pubonly) const noexcept
+{
+ /* check tag. We allow public/secret key comparison here */
+ if (pubonly) {
+ if (is_subkey_pkt(tag) && !is_subkey_pkt(key.tag)) {
+ return false;
+ }
+ if (is_key_pkt(tag) && !is_key_pkt(key.tag)) {
+ return false;
+ }
+ } else if (tag != key.tag) {
+ return false;
+ }
+ /* check basic fields */
+ if ((version != key.version) || (alg != key.alg) || (creation_time != key.creation_time)) {
+ return false;
+ }
+ /* check key material */
+ return key_material_equal(&material, &key.material);
+}
+
+pgp_transferable_subkey_t::pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src,
+ bool pubonly)
+{
+ subkey = pgp_key_pkt_t(src.subkey, pubonly);
+ signatures = src.signatures;
+}
+
+pgp_transferable_key_t::pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly)
+{
+ key = pgp_key_pkt_t(src.key, pubonly);
+ userids = src.userids;
+ subkeys = src.subkeys;
+ signatures = src.signatures;
+}
diff --git a/src/librepgp/stream-key.h b/src/librepgp/stream-key.h
new file mode 100644
index 0000000..a19a986
--- /dev/null
+++ b/src/librepgp/stream-key.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright (c) 2018, [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 STREAM_KEY_H_
+#define STREAM_KEY_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "stream-sig.h"
+#include "stream-packet.h"
+
+/** Struct to hold a key packet. May contain public or private key/subkey */
+typedef struct pgp_key_pkt_t {
+ pgp_pkt_type_t tag; /* packet tag: public key/subkey or private key/subkey */
+ pgp_version_t version; /* Key packet version */
+ uint32_t creation_time; /* Key creation time */
+ pgp_pubkey_alg_t alg;
+ uint16_t v3_days; /* v2/v3 validity time */
+
+ uint8_t *hashed_data; /* key's hashed data used for signature calculation */
+ size_t hashed_len;
+
+ pgp_key_material_t material;
+
+ /* secret key data, if available. sec_len == 0, sec_data == NULL for public key/subkey */
+ pgp_key_protection_t sec_protection;
+ uint8_t * sec_data;
+ size_t sec_len;
+
+ pgp_key_pkt_t()
+ : tag(PGP_PKT_RESERVED), version(PGP_VUNKNOWN), creation_time(0), alg(PGP_PKA_NOTHING),
+ v3_days(0), hashed_data(NULL), hashed_len(0), material({}), sec_protection({}),
+ sec_data(NULL), sec_len(0){};
+ pgp_key_pkt_t(const pgp_key_pkt_t &src, bool pubonly = false);
+ pgp_key_pkt_t(pgp_key_pkt_t &&src);
+ pgp_key_pkt_t &operator=(pgp_key_pkt_t &&src);
+ pgp_key_pkt_t &operator=(const pgp_key_pkt_t &src);
+ ~pgp_key_pkt_t();
+
+ void write(pgp_dest_t &dst);
+ rnp_result_t parse(pgp_source_t &src);
+ /** @brief Fills the hashed (signed) data part of the key packet. Must be called before
+ * pgp_key_pkt_t::write() on the newly generated key */
+ void fill_hashed_data();
+ bool equals(const pgp_key_pkt_t &key, bool pubonly = false) const noexcept;
+} pgp_key_pkt_t;
+
+/* userid/userattr with all the corresponding signatures */
+typedef struct pgp_transferable_userid_t {
+ pgp_userid_pkt_t uid;
+ pgp_signature_list_t signatures;
+} pgp_transferable_userid_t;
+
+/* subkey with all corresponding signatures */
+typedef struct pgp_transferable_subkey_t {
+ pgp_key_pkt_t subkey;
+ pgp_signature_list_t signatures;
+
+ pgp_transferable_subkey_t() = default;
+ pgp_transferable_subkey_t(const pgp_transferable_subkey_t &src, bool pubonly = false);
+ pgp_transferable_subkey_t &operator=(const pgp_transferable_subkey_t &) = default;
+} pgp_transferable_subkey_t;
+
+/* transferable key with userids, subkeys and revocation signatures */
+typedef struct pgp_transferable_key_t {
+ pgp_key_pkt_t key; /* main key packet */
+ std::vector<pgp_transferable_userid_t> userids;
+ std::vector<pgp_transferable_subkey_t> subkeys;
+ pgp_signature_list_t signatures;
+
+ pgp_transferable_key_t() = default;
+ pgp_transferable_key_t(const pgp_transferable_key_t &src, bool pubonly = false);
+ pgp_transferable_key_t &operator=(const pgp_transferable_key_t &) = default;
+} pgp_transferable_key_t;
+
+/* sequence of OpenPGP transferable keys */
+typedef struct pgp_key_sequence_t {
+ std::vector<pgp_transferable_key_t> keys;
+} pgp_key_sequence_t;
+
+rnp_result_t transferable_key_from_key(pgp_transferable_key_t &dst, const pgp_key_t &key);
+
+rnp_result_t transferable_key_merge(pgp_transferable_key_t & dst,
+ const pgp_transferable_key_t &src);
+
+rnp_result_t transferable_subkey_from_key(pgp_transferable_subkey_t &dst,
+ const pgp_key_t & key);
+
+rnp_result_t transferable_subkey_merge(pgp_transferable_subkey_t & dst,
+ const pgp_transferable_subkey_t &src);
+
+/* Process single primary key or subkey, skipping all key-related packets on error.
+ If key.key.tag is zero, then (on success) result is subkey and it is stored in
+ key.subkeys[0].
+ If returns RNP_ERROR_BAD_FORMAT then some packets failed parsing, but still key may contain
+ successfully read key or subkey.
+*/
+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_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 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);
+
+#endif
diff --git a/src/librepgp/stream-packet.cpp b/src/librepgp/stream-packet.cpp
new file mode 100644
index 0000000..49dd63d
--- /dev/null
+++ b/src/librepgp/stream-packet.cpp
@@ -0,0 +1,1228 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <inttypes.h>
+#include <rnp/rnp_def.h>
+#include "types.h"
+#include "crypto.h"
+#include "crypto/mem.h"
+#include "stream-packet.h"
+#include "stream-key.h"
+#include <algorithm>
+
+uint32_t
+read_uint32(const uint8_t *buf)
+{
+ return ((uint32_t) buf[0] << 24) | ((uint32_t) buf[1] << 16) | ((uint32_t) buf[2] << 8) |
+ (uint32_t) buf[3];
+}
+
+uint16_t
+read_uint16(const uint8_t *buf)
+{
+ return ((uint16_t) buf[0] << 8) | buf[1];
+}
+
+void
+write_uint16(uint8_t *buf, uint16_t val)
+{
+ buf[0] = val >> 8;
+ buf[1] = val & 0xff;
+}
+
+size_t
+write_packet_len(uint8_t *buf, size_t len)
+{
+ if (len < 192) {
+ buf[0] = len;
+ return 1;
+ } else if (len < 8192 + 192) {
+ buf[0] = ((len - 192) >> 8) + 192;
+ buf[1] = (len - 192) & 0xff;
+ return 2;
+ } else {
+ buf[0] = 0xff;
+ STORE32BE(&buf[1], len);
+ return 5;
+ }
+}
+
+int
+get_packet_type(uint8_t ptag)
+{
+ if (!(ptag & PGP_PTAG_ALWAYS_SET)) {
+ return -1;
+ }
+
+ if (ptag & PGP_PTAG_NEW_FORMAT) {
+ return (int) (ptag & PGP_PTAG_NF_CONTENT_TAG_MASK);
+ } else {
+ return (int) ((ptag & PGP_PTAG_OF_CONTENT_TAG_MASK) >> PGP_PTAG_OF_CONTENT_TAG_SHIFT);
+ }
+}
+
+int
+stream_pkt_type(pgp_source_t &src)
+{
+ if (src_eof(&src)) {
+ return 0;
+ }
+ size_t hdrneed = 0;
+ if (!stream_pkt_hdr_len(src, hdrneed)) {
+ return -1;
+ }
+ uint8_t hdr[PGP_MAX_HEADER_SIZE];
+ if (!src_peek_eq(&src, hdr, hdrneed)) {
+ return -1;
+ }
+ return get_packet_type(hdr[0]);
+}
+
+bool
+stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen)
+{
+ uint8_t buf[2];
+
+ if (!src_peek_eq(&src, buf, 2) || !(buf[0] & PGP_PTAG_ALWAYS_SET)) {
+ return false;
+ }
+
+ if (buf[0] & PGP_PTAG_NEW_FORMAT) {
+ if (buf[1] < 192) {
+ hdrlen = 2;
+ } else if (buf[1] < 224) {
+ hdrlen = 3;
+ } else if (buf[1] < 255) {
+ hdrlen = 2;
+ } else {
+ hdrlen = 6;
+ }
+ return true;
+ }
+
+ switch (buf[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) {
+ case PGP_PTAG_OLD_LEN_1:
+ hdrlen = 2;
+ return true;
+ case PGP_PTAG_OLD_LEN_2:
+ hdrlen = 3;
+ return true;
+ case PGP_PTAG_OLD_LEN_4:
+ hdrlen = 5;
+ return true;
+ case PGP_PTAG_OLD_LEN_INDETERMINATE:
+ hdrlen = 1;
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+get_pkt_len(uint8_t *hdr, size_t *pktlen)
+{
+ if (hdr[0] & PGP_PTAG_NEW_FORMAT) {
+ // 1-byte length
+ if (hdr[1] < 192) {
+ *pktlen = hdr[1];
+ return true;
+ }
+ // 2-byte length
+ if (hdr[1] < 224) {
+ *pktlen = ((size_t)(hdr[1] - 192) << 8) + (size_t) hdr[2] + 192;
+ return true;
+ }
+ // partial length - we do not allow it here
+ if (hdr[1] < 255) {
+ return false;
+ }
+ // 4-byte length
+ *pktlen = read_uint32(&hdr[2]);
+ return true;
+ }
+
+ switch (hdr[0] & PGP_PTAG_OF_LENGTH_TYPE_MASK) {
+ case PGP_PTAG_OLD_LEN_1:
+ *pktlen = hdr[1];
+ return true;
+ case PGP_PTAG_OLD_LEN_2:
+ *pktlen = read_uint16(&hdr[1]);
+ return true;
+ case PGP_PTAG_OLD_LEN_4:
+ *pktlen = read_uint32(&hdr[1]);
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+stream_read_pkt_len(pgp_source_t *src, size_t *pktlen)
+{
+ uint8_t buf[6] = {};
+ size_t read = 0;
+
+ if (!stream_pkt_hdr_len(*src, read)) {
+ return false;
+ }
+
+ if (!src_read_eq(src, buf, read)) {
+ return false;
+ }
+
+ return get_pkt_len(buf, pktlen);
+}
+
+bool
+stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last)
+{
+ uint8_t hdr[5] = {};
+ size_t read = 0;
+
+ if (!src_read(src, hdr, 1, &read)) {
+ RNP_LOG("failed to read header");
+ return false;
+ }
+ if (read < 1) {
+ RNP_LOG("wrong eof");
+ return false;
+ }
+
+ *last = true;
+ // partial length
+ if ((hdr[0] >= 224) && (hdr[0] < 255)) {
+ *last = false;
+ *clen = get_partial_pkt_len(hdr[0]);
+ return true;
+ }
+ // 1-byte length
+ if (hdr[0] < 192) {
+ *clen = hdr[0];
+ return true;
+ }
+ // 2-byte length
+ if (hdr[0] < 224) {
+ if (!src_read_eq(src, &hdr[1], 1)) {
+ RNP_LOG("wrong 2-byte length");
+ return false;
+ }
+ *clen = ((size_t)(hdr[0] - 192) << 8) + (size_t) hdr[1] + 192;
+ return true;
+ }
+ // 4-byte length
+ if (!src_read_eq(src, &hdr[1], 4)) {
+ RNP_LOG("wrong 4-byte length");
+ return false;
+ }
+ *clen = ((size_t) hdr[1] << 24) | ((size_t) hdr[2] << 16) | ((size_t) hdr[3] << 8) |
+ (size_t) hdr[4];
+ return true;
+}
+
+bool
+stream_old_indeterminate_pkt_len(pgp_source_t *src)
+{
+ uint8_t ptag = 0;
+ if (!src_peek_eq(src, &ptag, 1)) {
+ return false;
+ }
+ return !(ptag & PGP_PTAG_NEW_FORMAT) &&
+ ((ptag & PGP_PTAG_OF_LENGTH_TYPE_MASK) == PGP_PTAG_OLD_LEN_INDETERMINATE);
+}
+
+bool
+stream_partial_pkt_len(pgp_source_t *src)
+{
+ uint8_t hdr[2] = {};
+ if (!src_peek_eq(src, hdr, 2)) {
+ return false;
+ }
+ return (hdr[0] & PGP_PTAG_NEW_FORMAT) && (hdr[1] >= 224) && (hdr[1] < 255);
+}
+
+size_t
+get_partial_pkt_len(uint8_t blen)
+{
+ return 1 << (blen & 0x1f);
+}
+
+rnp_result_t
+stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr)
+{
+ size_t hlen = 0;
+ memset(hdr, 0, sizeof(*hdr));
+ if (!stream_pkt_hdr_len(*src, hlen)) {
+ uint8_t hdr2[2] = {0};
+ if (!src_peek_eq(src, hdr2, 2)) {
+ RNP_LOG("pkt header read failed");
+ return RNP_ERROR_READ;
+ }
+
+ RNP_LOG("bad packet header: 0x%02x%02x", hdr2[0], hdr2[1]);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ if (!src_peek_eq(src, hdr->hdr, hlen)) {
+ RNP_LOG("failed to read pkt header");
+ return RNP_ERROR_READ;
+ }
+
+ hdr->hdr_len = hlen;
+ hdr->tag = (pgp_pkt_type_t) get_packet_type(hdr->hdr[0]);
+
+ if (stream_partial_pkt_len(src)) {
+ hdr->partial = true;
+ } else if (stream_old_indeterminate_pkt_len(src)) {
+ hdr->indeterminate = true;
+ } else {
+ (void) get_pkt_len(hdr->hdr, &hdr->pkt_len);
+ }
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+stream_read_packet_partial(pgp_source_t *src, pgp_dest_t *dst)
+{
+ uint8_t hdr = 0;
+ if (!src_read_eq(src, &hdr, 1)) {
+ return RNP_ERROR_READ;
+ }
+
+ bool last = false;
+ size_t partlen = 0;
+ if (!stream_read_partial_chunk_len(src, &partlen, &last)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ uint8_t *buf = (uint8_t *) malloc(PGP_INPUT_CACHE_SIZE);
+ if (!buf) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ while (partlen > 0) {
+ size_t read = std::min(partlen, (size_t) PGP_INPUT_CACHE_SIZE);
+ if (!src_read_eq(src, buf, read)) {
+ free(buf);
+ return RNP_ERROR_READ;
+ }
+ if (dst) {
+ dst_write(dst, buf, read);
+ }
+ partlen -= read;
+ if (partlen > 0) {
+ continue;
+ }
+ if (last) {
+ break;
+ }
+ if (!stream_read_partial_chunk_len(src, &partlen, &last)) {
+ free(buf);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ }
+ free(buf);
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+stream_read_packet(pgp_source_t *src, pgp_dest_t *dst)
+{
+ if (stream_old_indeterminate_pkt_len(src)) {
+ return dst_write_src(src, dst, PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE);
+ }
+
+ if (stream_partial_pkt_len(src)) {
+ return stream_read_packet_partial(src, dst);
+ }
+
+ try {
+ pgp_packet_body_t body(PGP_PKT_RESERVED);
+ rnp_result_t ret = body.read(*src);
+ if (dst) {
+ body.write(*dst, false);
+ }
+ return ret;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+}
+
+rnp_result_t
+stream_skip_packet(pgp_source_t *src)
+{
+ return stream_read_packet(src, NULL);
+}
+
+rnp_result_t
+stream_parse_marker(pgp_source_t &src)
+{
+ try {
+ pgp_packet_body_t pkt(PGP_PKT_MARKER);
+ rnp_result_t res = pkt.read(src);
+ if (res) {
+ return res;
+ }
+ if ((pkt.size() != PGP_MARKER_LEN) ||
+ memcmp(pkt.data(), PGP_MARKER_CONTENTS, PGP_MARKER_LEN)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+}
+
+bool
+is_key_pkt(int tag)
+{
+ switch (tag) {
+ case PGP_PKT_PUBLIC_KEY:
+ case PGP_PKT_PUBLIC_SUBKEY:
+ case PGP_PKT_SECRET_KEY:
+ case PGP_PKT_SECRET_SUBKEY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+is_subkey_pkt(int tag)
+{
+ return (tag == PGP_PKT_PUBLIC_SUBKEY) || (tag == PGP_PKT_SECRET_SUBKEY);
+}
+
+bool
+is_primary_key_pkt(int tag)
+{
+ return (tag == PGP_PKT_PUBLIC_KEY) || (tag == PGP_PKT_SECRET_KEY);
+}
+
+bool
+is_public_key_pkt(int tag)
+{
+ switch (tag) {
+ case PGP_PKT_PUBLIC_KEY:
+ case PGP_PKT_PUBLIC_SUBKEY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+is_secret_key_pkt(int tag)
+{
+ switch (tag) {
+ case PGP_PKT_SECRET_KEY:
+ case PGP_PKT_SECRET_SUBKEY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool
+is_rsa_key_alg(pgp_pubkey_alg_t alg)
+{
+ switch (alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ return true;
+ default:
+ return false;
+ }
+}
+
+pgp_packet_body_t::pgp_packet_body_t(pgp_pkt_type_t tag)
+{
+ data_.reserve(16);
+ tag_ = tag;
+ secure_ = is_secret_key_pkt(tag);
+}
+
+pgp_packet_body_t::pgp_packet_body_t(const uint8_t *data, size_t len)
+{
+ data_.assign(data, data + len);
+ tag_ = PGP_PKT_RESERVED;
+ secure_ = false;
+}
+
+pgp_packet_body_t::~pgp_packet_body_t()
+{
+ if (secure_) {
+ secure_clear(data_.data(), data_.size());
+ }
+}
+
+uint8_t *
+pgp_packet_body_t::data() noexcept
+{
+ return data_.data();
+}
+
+size_t
+pgp_packet_body_t::size() const noexcept
+{
+ return data_.size();
+}
+
+size_t
+pgp_packet_body_t::left() const noexcept
+{
+ return data_.size() - pos_;
+}
+
+bool
+pgp_packet_body_t::get(uint8_t &val) noexcept
+{
+ if (pos_ >= data_.size()) {
+ return false;
+ }
+ val = data_[pos_++];
+ return true;
+}
+
+bool
+pgp_packet_body_t::get(uint16_t &val) noexcept
+{
+ if (pos_ + 2 > data_.size()) {
+ return false;
+ }
+ val = read_uint16(data_.data() + pos_);
+ pos_ += 2;
+ return true;
+}
+
+bool
+pgp_packet_body_t::get(uint32_t &val) noexcept
+{
+ if (pos_ + 4 > data_.size()) {
+ return false;
+ }
+ val = read_uint32(data_.data() + pos_);
+ pos_ += 4;
+ return true;
+}
+
+bool
+pgp_packet_body_t::get(uint8_t *val, size_t len) noexcept
+{
+ if (pos_ + len > data_.size()) {
+ return false;
+ }
+ memcpy(val, data_.data() + pos_, len);
+ pos_ += len;
+ return true;
+}
+
+bool
+pgp_packet_body_t::get(pgp_key_id_t &val) noexcept
+{
+ static_assert(std::tuple_size<pgp_key_id_t>::value == PGP_KEY_ID_SIZE,
+ "pgp_key_id_t size mismatch");
+ return get(val.data(), val.size());
+}
+
+bool
+pgp_packet_body_t::get(pgp_mpi_t &val) noexcept
+{
+ uint16_t bits = 0;
+ if (!get(bits)) {
+ return false;
+ }
+ size_t len = (bits + 7) >> 3;
+ if (len > PGP_MPINT_SIZE) {
+ RNP_LOG("too large mpi");
+ return false;
+ }
+ if (!len) {
+ RNP_LOG("0 mpi");
+ return false;
+ }
+ if (!get(val.mpi, len)) {
+ RNP_LOG("failed to read mpi body");
+ return false;
+ }
+ /* check the mpi bit count */
+ val.len = len;
+ size_t mbits = mpi_bits(&val);
+ if (mbits != bits) {
+ RNP_LOG(
+ "Warning! Wrong mpi bit count: got %" PRIu16 ", but actual is %zu", bits, mbits);
+ }
+ return true;
+}
+
+bool
+pgp_packet_body_t::get(pgp_curve_t &val) noexcept
+{
+ uint8_t oidlen = 0;
+ if (!get(oidlen)) {
+ return false;
+ }
+ uint8_t oid[MAX_CURVE_OID_HEX_LEN] = {0};
+ if (!oidlen || (oidlen == 0xff) || (oidlen > sizeof(oid))) {
+ RNP_LOG("unsupported curve oid len: %" PRIu8, oidlen);
+ return false;
+ }
+ if (!get(oid, oidlen)) {
+ return false;
+ }
+ pgp_curve_t res = find_curve_by_OID(oid, oidlen);
+ if (res == PGP_CURVE_MAX) {
+ RNP_LOG("unsupported curve");
+ return false;
+ }
+ val = res;
+ return true;
+}
+
+bool
+pgp_packet_body_t::get(pgp_s2k_t &s2k) noexcept
+{
+ uint8_t spec = 0, halg = 0;
+ if (!get(spec) || !get(halg)) {
+ return false;
+ }
+ s2k.specifier = (pgp_s2k_specifier_t) spec;
+ s2k.hash_alg = (pgp_hash_alg_t) halg;
+
+ switch (s2k.specifier) {
+ case PGP_S2KS_SIMPLE:
+ return true;
+ case PGP_S2KS_SALTED:
+ return get(s2k.salt, PGP_SALT_SIZE);
+ case PGP_S2KS_ITERATED_AND_SALTED: {
+ uint8_t iter = 0;
+ if (!get(s2k.salt, PGP_SALT_SIZE) || !get(iter)) {
+ return false;
+ }
+ s2k.iterations = iter;
+ return true;
+ }
+ case PGP_S2KS_EXPERIMENTAL: {
+ try {
+ s2k.experimental = {data_.begin() + pos_, data_.end()};
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ uint8_t gnu[3] = {0};
+ if (!get(gnu, 3) || memcmp(gnu, "GNU", 3)) {
+ RNP_LOG("Unknown experimental s2k. Skipping.");
+ pos_ = data_.size();
+ s2k.gpg_ext_num = PGP_S2K_GPG_NONE;
+ return true;
+ }
+ uint8_t ext_num = 0;
+ if (!get(ext_num)) {
+ return false;
+ }
+ if ((ext_num != PGP_S2K_GPG_NO_SECRET) && (ext_num != PGP_S2K_GPG_SMARTCARD)) {
+ RNP_LOG("Unsupported gpg extension num: %" PRIu8 ", skipping", ext_num);
+ pos_ = data_.size();
+ s2k.gpg_ext_num = PGP_S2K_GPG_NONE;
+ return true;
+ }
+ s2k.gpg_ext_num = (pgp_s2k_gpg_extension_t) ext_num;
+ if (s2k.gpg_ext_num == PGP_S2K_GPG_NO_SECRET) {
+ return true;
+ }
+ if (!get(s2k.gpg_serial_len)) {
+ RNP_LOG("Failed to get GPG serial len");
+ return false;
+ }
+ size_t len = s2k.gpg_serial_len;
+ if (s2k.gpg_serial_len > 16) {
+ RNP_LOG("Warning: gpg_serial_len is %d", (int) len);
+ len = 16;
+ }
+ if (!get(s2k.gpg_serial, len)) {
+ RNP_LOG("Failed to get GPG serial");
+ return false;
+ }
+ return true;
+ }
+ default:
+ RNP_LOG("unknown s2k specifier: %d", (int) s2k.specifier);
+ return false;
+ }
+}
+
+void
+pgp_packet_body_t::add(const void *data, size_t len)
+{
+ data_.insert(data_.end(), (uint8_t *) data, (uint8_t *) data + len);
+}
+
+void
+pgp_packet_body_t::add_byte(uint8_t bt)
+{
+ data_.push_back(bt);
+}
+
+void
+pgp_packet_body_t::add_uint16(uint16_t val)
+{
+ uint8_t bytes[2];
+ write_uint16(bytes, val);
+ add(bytes, 2);
+}
+
+void
+pgp_packet_body_t::add_uint32(uint32_t val)
+{
+ uint8_t bytes[4];
+ STORE32BE(bytes, val);
+ add(bytes, 4);
+}
+
+void
+pgp_packet_body_t::add(const pgp_key_id_t &val)
+{
+ add(val.data(), val.size());
+}
+
+void
+pgp_packet_body_t::add(const pgp_mpi_t &val)
+{
+ if (!val.len) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ unsigned idx = 0;
+ while ((idx < val.len - 1) && (!val.mpi[idx])) {
+ idx++;
+ }
+
+ unsigned bits = (val.len - idx - 1) << 3;
+ unsigned hibyte = val.mpi[idx];
+ while (hibyte) {
+ bits++;
+ hibyte = hibyte >> 1;
+ }
+
+ uint8_t hdr[2] = {(uint8_t)(bits >> 8), (uint8_t)(bits & 0xff)};
+ add(hdr, 2);
+ add(val.mpi + idx, val.len - idx);
+}
+
+void
+pgp_packet_body_t::add_subpackets(const pgp_signature_t &sig, bool hashed)
+{
+ pgp_packet_body_t spbody(PGP_PKT_RESERVED);
+
+ for (auto &subpkt : sig.subpkts) {
+ if (subpkt.hashed != hashed) {
+ continue;
+ }
+
+ uint8_t splen[6];
+ size_t lenlen = write_packet_len(splen, subpkt.len + 1);
+ spbody.add(splen, lenlen);
+ spbody.add_byte(subpkt.type | (subpkt.critical << 7));
+ spbody.add(subpkt.data, subpkt.len);
+ }
+
+ if (spbody.data_.size() > 0xffff) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ add_uint16(spbody.data_.size());
+ add(spbody.data_.data(), spbody.data_.size());
+}
+
+void
+pgp_packet_body_t::add(const pgp_curve_t curve)
+{
+ const ec_curve_desc_t *desc = get_curve_desc(curve);
+ if (!desc) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ add_byte((uint8_t) desc->OIDhex_len);
+ add(desc->OIDhex, (uint8_t) desc->OIDhex_len);
+}
+
+void
+pgp_packet_body_t::add(const pgp_s2k_t &s2k)
+{
+ add_byte(s2k.specifier);
+ add_byte(s2k.hash_alg);
+
+ switch (s2k.specifier) {
+ case PGP_S2KS_SIMPLE:
+ return;
+ case PGP_S2KS_SALTED:
+ add(s2k.salt, PGP_SALT_SIZE);
+ return;
+ case PGP_S2KS_ITERATED_AND_SALTED: {
+ unsigned iter = s2k.iterations;
+ if (iter > 255) {
+ iter = pgp_s2k_encode_iterations(iter);
+ }
+ add(s2k.salt, PGP_SALT_SIZE);
+ add_byte(iter);
+ return;
+ }
+ case PGP_S2KS_EXPERIMENTAL: {
+ if ((s2k.gpg_ext_num != PGP_S2K_GPG_NO_SECRET) &&
+ (s2k.gpg_ext_num != PGP_S2K_GPG_SMARTCARD)) {
+ RNP_LOG("Unknown experimental s2k.");
+ add(s2k.experimental.data(), s2k.experimental.size());
+ return;
+ }
+ add("GNU", 3);
+ add_byte(s2k.gpg_ext_num);
+ if (s2k.gpg_ext_num == PGP_S2K_GPG_SMARTCARD) {
+ static_assert(sizeof(s2k.gpg_serial) == 16, "invalid gpg serial length");
+ size_t slen = s2k.gpg_serial_len > 16 ? 16 : s2k.gpg_serial_len;
+ add_byte(s2k.gpg_serial_len);
+ add(s2k.gpg_serial, slen);
+ }
+ return;
+ }
+ default:
+ RNP_LOG("unknown s2k specifier");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+}
+
+rnp_result_t
+pgp_packet_body_t::read(pgp_source_t &src) noexcept
+{
+ /* Make sure we have enough data for packet header */
+ if (!src_peek_eq(&src, hdr_, 2)) {
+ return RNP_ERROR_READ;
+ }
+
+ /* Read the packet header and length */
+ size_t len = 0;
+ if (!stream_pkt_hdr_len(src, len)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (!src_peek_eq(&src, hdr_, len)) {
+ return RNP_ERROR_READ;
+ }
+ hdr_len_ = len;
+
+ int ptag = get_packet_type(hdr_[0]);
+ if ((ptag < 0) || ((tag_ != PGP_PKT_RESERVED) && (tag_ != ptag))) {
+ RNP_LOG("tag mismatch: %d vs %d", (int) tag_, ptag);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ tag_ = (pgp_pkt_type_t) ptag;
+
+ if (!stream_read_pkt_len(&src, &len)) {
+ return RNP_ERROR_READ;
+ }
+
+ /* early exit for the empty packet */
+ if (!len) {
+ return RNP_SUCCESS;
+ }
+
+ if (len > PGP_MAX_PKT_SIZE) {
+ RNP_LOG("too large packet");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* Read the packet contents */
+ try {
+ data_.resize(len);
+ } catch (const std::exception &e) {
+ RNP_LOG("malloc of %d bytes failed, %s", (int) len, e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ size_t read = 0;
+ if (!src_read(&src, data_.data(), len, &read) || (read != len)) {
+ RNP_LOG("read %d instead of %d", (int) read, (int) len);
+ return RNP_ERROR_READ;
+ }
+ pos_ = 0;
+ return RNP_SUCCESS;
+}
+
+void
+pgp_packet_body_t::write(pgp_dest_t &dst, bool hdr) noexcept
+{
+ if (hdr) {
+ uint8_t hdrbt[6] = {
+ (uint8_t)(tag_ | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT), 0, 0, 0, 0, 0};
+ size_t hlen = 1 + write_packet_len(&hdrbt[1], data_.size());
+ dst_write(&dst, hdrbt, hlen);
+ }
+ dst_write(&dst, data_.data(), data_.size());
+}
+
+void
+pgp_packet_body_t::mark_secure(bool secure) noexcept
+{
+ secure_ = secure;
+}
+
+void
+pgp_sk_sesskey_t::write(pgp_dest_t &dst) const
+{
+ pgp_packet_body_t pktbody(PGP_PKT_SK_SESSION_KEY);
+ /* version and algorithm fields */
+ pktbody.add_byte(version);
+ pktbody.add_byte(alg);
+ if (version == PGP_SKSK_V5) {
+ pktbody.add_byte(aalg);
+ }
+ /* S2K specifier */
+ pktbody.add_byte(s2k.specifier);
+ pktbody.add_byte(s2k.hash_alg);
+
+ switch (s2k.specifier) {
+ case PGP_S2KS_SIMPLE:
+ break;
+ case PGP_S2KS_SALTED:
+ pktbody.add(s2k.salt, sizeof(s2k.salt));
+ break;
+ case PGP_S2KS_ITERATED_AND_SALTED:
+ pktbody.add(s2k.salt, sizeof(s2k.salt));
+ pktbody.add_byte(s2k.iterations);
+ break;
+ default:
+ RNP_LOG("Unexpected s2k specifier: %d", (int) s2k.specifier);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ /* v5 : iv */
+ if (version == PGP_SKSK_V5) {
+ pktbody.add(iv, ivlen);
+ }
+ /* encrypted key and auth tag for v5 */
+ if (enckeylen) {
+ pktbody.add(enckey, enckeylen);
+ }
+ /* write packet */
+ pktbody.write(dst);
+}
+
+rnp_result_t
+pgp_sk_sesskey_t::parse(pgp_source_t &src)
+{
+ pgp_packet_body_t pkt(PGP_PKT_SK_SESSION_KEY);
+ rnp_result_t res = pkt.read(src);
+ if (res) {
+ return res;
+ }
+
+ /* version */
+ uint8_t bt;
+ if (!pkt.get(bt) || ((bt != PGP_SKSK_V4) && (bt != PGP_SKSK_V5))) {
+ RNP_LOG("wrong packet version");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ version = bt;
+ /* symmetric algorithm */
+ if (!pkt.get(bt)) {
+ RNP_LOG("failed to get symm alg");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ alg = (pgp_symm_alg_t) bt;
+
+ if (version == PGP_SKSK_V5) {
+ /* aead algorithm */
+ if (!pkt.get(bt)) {
+ RNP_LOG("failed to get aead alg");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ aalg = (pgp_aead_alg_t) bt;
+ if ((aalg != PGP_AEAD_EAX) && (aalg != PGP_AEAD_OCB)) {
+ RNP_LOG("unsupported AEAD algorithm : %d", (int) aalg);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+
+ /* s2k */
+ if (!pkt.get(s2k)) {
+ RNP_LOG("failed to parse s2k");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* v4 key */
+ if (version == PGP_SKSK_V4) {
+ /* encrypted session key if present */
+ size_t keylen = pkt.left();
+ if (keylen) {
+ if (keylen > PGP_MAX_KEY_SIZE + 1) {
+ RNP_LOG("too long esk");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (!pkt.get(enckey, keylen)) {
+ RNP_LOG("failed to get key");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ }
+ enckeylen = keylen;
+ return RNP_SUCCESS;
+ }
+
+ /* v5: iv + esk + tag. For both EAX and OCB ivlen and taglen are 16 octets */
+ size_t noncelen = pgp_cipher_aead_nonce_len(aalg);
+ size_t taglen = pgp_cipher_aead_tag_len(aalg);
+ size_t keylen = 0;
+
+ if (pkt.left() > noncelen + taglen + PGP_MAX_KEY_SIZE) {
+ RNP_LOG("too long esk");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (pkt.left() < noncelen + taglen + 8) {
+ RNP_LOG("too short esk");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* iv */
+ if (!pkt.get(iv, noncelen)) {
+ RNP_LOG("failed to get iv");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ ivlen = noncelen;
+
+ /* key */
+ keylen = pkt.left();
+ if (!pkt.get(enckey, keylen)) {
+ RNP_LOG("failed to get key");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ enckeylen = keylen;
+ return RNP_SUCCESS;
+}
+
+void
+pgp_pk_sesskey_t::write(pgp_dest_t &dst) const
+{
+ pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY);
+ pktbody.add_byte(version);
+ pktbody.add(key_id);
+ pktbody.add_byte(alg);
+ pktbody.add(material_buf.data(), material_buf.size());
+ pktbody.write(dst);
+}
+
+rnp_result_t
+pgp_pk_sesskey_t::parse(pgp_source_t &src)
+{
+ pgp_packet_body_t pkt(PGP_PKT_PK_SESSION_KEY);
+ rnp_result_t res = pkt.read(src);
+ if (res) {
+ return res;
+ }
+ /* version */
+ uint8_t bt = 0;
+ if (!pkt.get(bt) || (bt != PGP_PKSK_V3)) {
+ RNP_LOG("wrong packet version");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ version = bt;
+ /* key id */
+ if (!pkt.get(key_id)) {
+ RNP_LOG("failed to get key id");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* public key algorithm */
+ if (!pkt.get(bt)) {
+ RNP_LOG("failed to get palg");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ alg = (pgp_pubkey_alg_t) bt;
+
+ /* raw signature material */
+ if (!pkt.left()) {
+ RNP_LOG("No encrypted material");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ try {
+ material_buf.resize(pkt.left());
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ /* we cannot fail here */
+ pkt.get(material_buf.data(), material_buf.size());
+ /* check whether it can be parsed */
+ pgp_encrypted_material_t material = {};
+ if (!parse_material(material)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ return RNP_SUCCESS;
+}
+
+bool
+pgp_pk_sesskey_t::parse_material(pgp_encrypted_material_t &material) const
+{
+ pgp_packet_body_t pkt(material_buf.data(), material_buf.size());
+ switch (alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ /* RSA m */
+ if (!pkt.get(material.rsa.m)) {
+ RNP_LOG("failed to get rsa m");
+ return false;
+ }
+ break;
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ /* ElGamal g, m */
+ if (!pkt.get(material.eg.g) || !pkt.get(material.eg.m)) {
+ RNP_LOG("failed to get elgamal mpis");
+ return false;
+ }
+ break;
+ case PGP_PKA_SM2:
+ /* SM2 m */
+ if (!pkt.get(material.sm2.m)) {
+ RNP_LOG("failed to get sm2 m");
+ return false;
+ }
+ break;
+ case PGP_PKA_ECDH: {
+ /* ECDH ephemeral point */
+ if (!pkt.get(material.ecdh.p)) {
+ RNP_LOG("failed to get ecdh p");
+ return false;
+ }
+ /* ECDH m */
+ uint8_t bt = 0;
+ if (!pkt.get(bt)) {
+ RNP_LOG("failed to get ecdh m len");
+ return false;
+ }
+ if (bt > ECDH_WRAPPED_KEY_SIZE) {
+ RNP_LOG("wrong ecdh m len");
+ return false;
+ }
+ material.ecdh.mlen = bt;
+ if (!pkt.get(material.ecdh.m, bt)) {
+ RNP_LOG("failed to get ecdh m len");
+ return false;
+ }
+ break;
+ }
+ default:
+ RNP_LOG("unknown pk alg %d", (int) alg);
+ return false;
+ }
+
+ if (pkt.left()) {
+ RNP_LOG("extra %d bytes in pk packet", (int) pkt.left());
+ return false;
+ }
+ return true;
+}
+
+void
+pgp_pk_sesskey_t::write_material(const pgp_encrypted_material_t &material)
+{
+ pgp_packet_body_t pktbody(PGP_PKT_PK_SESSION_KEY);
+
+ switch (alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ pktbody.add(material.rsa.m);
+ break;
+ case PGP_PKA_SM2:
+ pktbody.add(material.sm2.m);
+ break;
+ case PGP_PKA_ECDH:
+ pktbody.add(material.ecdh.p);
+ pktbody.add_byte(material.ecdh.mlen);
+ pktbody.add(material.ecdh.m, material.ecdh.mlen);
+ break;
+ case PGP_PKA_ELGAMAL:
+ pktbody.add(material.eg.g);
+ pktbody.add(material.eg.m);
+ break;
+ default:
+ RNP_LOG("Unknown pk alg: %d", (int) alg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ material_buf = {pktbody.data(), pktbody.data() + pktbody.size()};
+}
+
+void
+pgp_one_pass_sig_t::write(pgp_dest_t &dst) const
+{
+ pgp_packet_body_t pktbody(PGP_PKT_ONE_PASS_SIG);
+ pktbody.add_byte(version);
+ pktbody.add_byte(type);
+ pktbody.add_byte(halg);
+ pktbody.add_byte(palg);
+ pktbody.add(keyid);
+ pktbody.add_byte(nested);
+ pktbody.write(dst);
+}
+
+rnp_result_t
+pgp_one_pass_sig_t::parse(pgp_source_t &src)
+{
+ pgp_packet_body_t pkt(PGP_PKT_ONE_PASS_SIG);
+ /* Read the packet into memory */
+ rnp_result_t res = pkt.read(src);
+ if (res) {
+ return res;
+ }
+
+ uint8_t buf[13] = {0};
+ if ((pkt.size() != 13) || !pkt.get(buf, 13)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* version */
+ if (buf[0] != 3) {
+ RNP_LOG("wrong packet version");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ version = buf[0];
+ /* signature type */
+ type = (pgp_sig_type_t) buf[1];
+ /* hash algorithm */
+ halg = (pgp_hash_alg_t) buf[2];
+ /* pk algorithm */
+ palg = (pgp_pubkey_alg_t) buf[3];
+ /* key id */
+ static_assert(std::tuple_size<decltype(keyid)>::value == PGP_KEY_ID_SIZE,
+ "pgp_one_pass_sig_t.keyid size mismatch");
+ memcpy(keyid.data(), &buf[4], PGP_KEY_ID_SIZE);
+ /* nested flag */
+ nested = buf[12];
+ return RNP_SUCCESS;
+}
diff --git a/src/librepgp/stream-packet.h b/src/librepgp/stream-packet.h
new file mode 100644
index 0000000..f88c96f
--- /dev/null
+++ b/src/librepgp/stream-packet.h
@@ -0,0 +1,323 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef STREAM_PACKET_H_
+#define STREAM_PACKET_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "types.h"
+#include "stream-common.h"
+
+/* maximum size of the 'small' packet */
+#define PGP_MAX_PKT_SIZE 0x100000
+
+/* maximum size of indeterminate-size packet allowed with old length format */
+#define PGP_MAX_OLD_LEN_INDETERMINATE_PKT_SIZE 0x40000000
+
+typedef struct pgp_packet_hdr_t {
+ pgp_pkt_type_t tag; /* packet tag */
+ uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* PGP packet header, needed for AEAD */
+ size_t hdr_len; /* length of the header */
+ size_t pkt_len; /* packet body length if non-partial and non-indeterminate */
+ bool partial; /* partial length packet */
+ bool indeterminate; /* indeterminate length packet */
+} pgp_packet_hdr_t;
+
+/* structure for convenient writing or parsing of non-stream packets */
+typedef struct pgp_packet_body_t {
+ private:
+ pgp_pkt_type_t tag_; /* packet tag */
+ std::vector<uint8_t> data_; /* packet bytes */
+ /* fields below are filled only for parsed packet */
+ uint8_t hdr_[PGP_MAX_HEADER_SIZE]{}; /* packet header bytes */
+ size_t hdr_len_{}; /* number of bytes in hdr */
+ size_t pos_{}; /* current read position in packet data */
+ bool secure_{}; /* contents of the packet are secure so must be wiped in the destructor */
+ public:
+ /** @brief initialize writing of packet body
+ * @param tag tag of the packet
+ **/
+ pgp_packet_body_t(pgp_pkt_type_t tag);
+ /** @brief init packet body (without headers) with memory. Used for easier data parsing.
+ * @param data buffer with packet body part
+ * @param len number of available bytes in mem
+ */
+ pgp_packet_body_t(const uint8_t *data, size_t len);
+
+ pgp_packet_body_t(const pgp_packet_body_t &src) = delete;
+ pgp_packet_body_t(pgp_packet_body_t &&src) = delete;
+ pgp_packet_body_t &operator=(const pgp_packet_body_t &) = delete;
+ pgp_packet_body_t &operator=(pgp_packet_body_t &&) = delete;
+ ~pgp_packet_body_t();
+
+ /** @brief pointer to the data, kept in the packet */
+ uint8_t *data() noexcept;
+ /** @brief number of bytes, kept in the packet (without the header) */
+ size_t size() const noexcept;
+ /** @brief number of bytes left to read */
+ size_t left() const noexcept;
+ /** @brief get next byte from the packet body, populated with read() call.
+ * @param val result will be stored here on success
+ * @return true on success or false otherwise (if end of the packet is reached)
+ **/
+ bool get(uint8_t &val) noexcept;
+ /** @brief get next big-endian uint16 from the packet body, populated with read() call.
+ * @param val result will be stored here on success
+ * @return true on success or false otherwise (if end of the packet is reached)
+ **/
+ bool get(uint16_t &val) noexcept;
+ /** @brief get next big-endian uint32 from the packet body, populated with read() call.
+ * @param val result will be stored here on success
+ * @return true on success or false otherwise (if end of the packet is reached)
+ **/
+ bool get(uint32_t &val) noexcept;
+ /** @brief get some bytes from the packet body, populated with read() call.
+ * @param val packet body bytes will be stored here. Must be capable of storing len bytes.
+ * @param len number of bytes to read
+ * @return true on success or false otherwise (if end of the packet is reached)
+ **/
+ bool get(uint8_t *val, size_t len) noexcept;
+ /** @brief get next keyid from the packet body, populated with read() call.
+ * @param val result will be stored here on success
+ * @return true on success or false otherwise (if end of the packet is reached)
+ **/
+ bool get(pgp_key_id_t &val) noexcept;
+ /** @brief get next mpi from the packet body, populated with read() call.
+ * @param val result will be stored here on success
+ * @return true on success or false otherwise (if end of the packet is reached
+ * or mpi is ill-formed)
+ **/
+ bool get(pgp_mpi_t &val) noexcept;
+ /** @brief Read ECC key curve and convert it to pgp_curve_t */
+ bool get(pgp_curve_t &val) noexcept;
+ /** @brief read s2k from the packet */
+ bool get(pgp_s2k_t &s2k) noexcept;
+ /** @brief append some bytes to the packet body */
+ void add(const void *data, size_t len);
+ /** @brief append single byte to the packet body */
+ void add_byte(uint8_t bt);
+ /** @brief append big endian 16-bit value to the packet body */
+ void add_uint16(uint16_t val);
+ /** @brief append big endian 32-bit value to the packet body */
+ void add_uint32(uint32_t val);
+ /** @brief append keyid to the packet body */
+ void add(const pgp_key_id_t &val);
+ /** @brief add pgp mpi (including header) to the packet body */
+ void add(const pgp_mpi_t &val);
+ /**
+ * @brief add pgp signature subpackets (including their length) to the packet body
+ * @param sig signature, containing subpackets
+ * @param hashed whether write hashed or not hashed subpackets
+ */
+ void add_subpackets(const pgp_signature_t &sig, bool hashed);
+ /** @brief add ec curve description to the packet body */
+ void add(const pgp_curve_t curve);
+ /** @brief add s2k description to the packet body */
+ void add(const pgp_s2k_t &s2k);
+ /** @brief read 'short-length' packet body (including tag and length bytes) from the source
+ * @param src source to read from
+ * @return RNP_SUCCESS or error code if operation failed
+ **/
+ rnp_result_t read(pgp_source_t &src) noexcept;
+ /** @brief write packet header, length and body to the dst
+ * @param dst destination to write to.
+ * @param hdr write packet's header or not
+ **/
+ void write(pgp_dest_t &dst, bool hdr = true) noexcept;
+ /** @brief mark contents as secure, so secure_clear() must be called in the destructor */
+ void mark_secure(bool secure = true) noexcept;
+} pgp_packet_body_t;
+
+/** public-key encrypted session key packet */
+typedef struct pgp_pk_sesskey_t {
+ unsigned version{};
+ pgp_key_id_t key_id{};
+ pgp_pubkey_alg_t alg{};
+ std::vector<uint8_t> material_buf{};
+
+ void write(pgp_dest_t &dst) const;
+ rnp_result_t parse(pgp_source_t &src);
+ /**
+ * @brief Parse encrypted material which is stored in packet in raw.
+ * @param material on success parsed material will be stored here.
+ * @return true on success or false otherwise. May also throw an exception.
+ */
+ bool parse_material(pgp_encrypted_material_t &material) const;
+ /**
+ * @brief Write encrypted material to the material_buf.
+ * @param material populated encrypted material.
+ */
+ void write_material(const pgp_encrypted_material_t &material);
+} pgp_pk_sesskey_t;
+
+/** pkp_sk_sesskey_t */
+typedef struct pgp_sk_sesskey_t {
+ unsigned version{};
+ pgp_symm_alg_t alg{};
+ pgp_s2k_t s2k{};
+ uint8_t enckey[PGP_MAX_KEY_SIZE + PGP_AEAD_MAX_TAG_LEN + 1]{};
+ unsigned enckeylen{};
+ /* v5 specific fields */
+ pgp_aead_alg_t aalg{};
+ uint8_t iv[PGP_MAX_BLOCK_SIZE]{};
+ unsigned ivlen{};
+
+ void write(pgp_dest_t &dst) const;
+ rnp_result_t parse(pgp_source_t &src);
+} pgp_sk_sesskey_t;
+
+/** pgp_one_pass_sig_t */
+typedef struct pgp_one_pass_sig_t {
+ uint8_t version{};
+ pgp_sig_type_t type{};
+ pgp_hash_alg_t halg{};
+ pgp_pubkey_alg_t palg{};
+ pgp_key_id_t keyid{};
+ unsigned nested{};
+
+ void write(pgp_dest_t &dst) const;
+ rnp_result_t parse(pgp_source_t &src);
+} pgp_one_pass_sig_t;
+
+/** Struct to hold userid or userattr packet. We don't parse userattr now, just storing the
+ * binary blob as it is. It may be distinguished by tag field.
+ */
+typedef struct pgp_userid_pkt_t {
+ pgp_pkt_type_t tag;
+ uint8_t * uid;
+ size_t uid_len;
+
+ pgp_userid_pkt_t() : tag(PGP_PKT_RESERVED), uid(NULL), uid_len(0){};
+ pgp_userid_pkt_t(const pgp_userid_pkt_t &src);
+ pgp_userid_pkt_t(pgp_userid_pkt_t &&src);
+ pgp_userid_pkt_t &operator=(pgp_userid_pkt_t &&src);
+ pgp_userid_pkt_t &operator=(const pgp_userid_pkt_t &src);
+ bool operator==(const pgp_userid_pkt_t &src) const;
+ bool operator!=(const pgp_userid_pkt_t &src) const;
+ ~pgp_userid_pkt_t();
+
+ void write(pgp_dest_t &dst) const;
+ rnp_result_t parse(pgp_source_t &src);
+} pgp_userid_pkt_t;
+
+uint16_t read_uint16(const uint8_t *buf);
+
+uint32_t read_uint32(const uint8_t *buf);
+
+void write_uint16(uint8_t *buf, uint16_t val);
+
+/** @brief write new packet length
+ * @param buf pre-allocated buffer, must have 5 bytes
+ * @param len packet length
+ * @return number of bytes, saved in buf
+ **/
+size_t write_packet_len(uint8_t *buf, size_t len);
+
+/** @brief get packet type from the packet header byte
+ * @param ptag first byte of the packet header
+ * @return packet type or -1 if ptag is wrong
+ **/
+int get_packet_type(uint8_t ptag);
+
+/** @brief peek the packet type from the stream
+ * @param src source to peek from
+ * @return packet tag or -1 if read failed or packet header is malformed
+ */
+int stream_pkt_type(pgp_source_t &src);
+
+/** @brief Peek length of the packet header. Returns false on error.
+ * @param src source to read length from
+ * @param hdrlen header length will be put here on success. Cannot be NULL.
+ * @return true on success or false if there is a read error or packet length
+ * is ill-formed
+ **/
+bool stream_pkt_hdr_len(pgp_source_t &src, size_t &hdrlen);
+
+bool stream_old_indeterminate_pkt_len(pgp_source_t *src);
+
+bool stream_partial_pkt_len(pgp_source_t *src);
+
+size_t get_partial_pkt_len(uint8_t blen);
+
+/** @brief Read packet length for fixed-size (say, small) packet. Returns false on error.
+ * Will also read packet tag byte. We do not allow partial length here as well as large
+ * packets (so ignoring possible size_t overflow)
+ *
+ * @param src source to read length from
+ * @param pktlen packet length will be stored here on success. Cannot be NULL.
+ * @return true on success or false if there is read error or packet length is ill-formed
+ **/
+bool stream_read_pkt_len(pgp_source_t *src, size_t *pktlen);
+
+/** @brief Read partial packet chunk length.
+ *
+ * @param src source to read length from
+ * @param clen chunk length will be stored here on success. Cannot be NULL.
+ * @param last will be set to true if chunk is last (i.e. has non-partial length)
+ * @return true on success or false if there is read error or packet length is ill-formed
+ **/
+bool stream_read_partial_chunk_len(pgp_source_t *src, size_t *clen, bool *last);
+
+/** @brief get and parse OpenPGP packet header to the structure.
+ * Note: this will not read but just peek required bytes.
+ *
+ * @param src source to read from
+ * @param hdr header structure
+ * @return RNP_SUCCESS or error code if operation failed
+ **/
+rnp_result_t stream_peek_packet_hdr(pgp_source_t *src, pgp_packet_hdr_t *hdr);
+
+/* Packet handling functions */
+
+/** @brief read OpenPGP packet from the stream, and write its contents to another stream.
+ * @param src source with packet data
+ * @param dst destination to write packet contents. All write failures on dst
+ * will be ignored. Can be NULL if you need just to skip packet.
+ * @return RNP_SUCCESS or error code if operation failed.
+ */
+rnp_result_t stream_read_packet(pgp_source_t *src, pgp_dest_t *dst);
+
+rnp_result_t stream_skip_packet(pgp_source_t *src);
+
+rnp_result_t stream_parse_marker(pgp_source_t &src);
+
+/* Public/Private key or Subkey */
+
+bool is_key_pkt(int tag);
+
+bool is_subkey_pkt(int tag);
+
+bool is_primary_key_pkt(int tag);
+
+bool is_public_key_pkt(int tag);
+
+bool is_secret_key_pkt(int tag);
+
+bool is_rsa_key_alg(pgp_pubkey_alg_t alg);
+
+#endif
diff --git a/src/librepgp/stream-parse.cpp b/src/librepgp/stream-parse.cpp
new file mode 100644
index 0000000..5ec4d64
--- /dev/null
+++ b/src/librepgp/stream-parse.cpp
@@ -0,0 +1,2636 @@
+/*
+ * Copyright (c) 2017-2023, [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 "config.h"
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <time.h>
+#include <cinttypes>
+#include <cassert>
+#include <rnp/rnp_def.h>
+#include "stream-ctx.h"
+#include "stream-def.h"
+#include "stream-parse.h"
+#include "stream-armor.h"
+#include "stream-packet.h"
+#include "stream-sig.h"
+#include "str-utils.h"
+#include "types.h"
+#include "crypto/s2k.h"
+#include "crypto.h"
+#include "crypto/signatures.h"
+#include "fingerprint.h"
+#include "pgp-key.h"
+
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif
+#ifdef HAVE_BZLIB_H
+#include <bzlib.h>
+#endif
+
+typedef enum pgp_message_t {
+ PGP_MESSAGE_UNKNOWN = 0,
+ PGP_MESSAGE_NORMAL,
+ PGP_MESSAGE_DETACHED,
+ PGP_MESSAGE_CLEARTEXT
+} pgp_message_t;
+
+typedef struct pgp_processing_ctx_t {
+ pgp_parse_handler_t handler;
+ pgp_source_t * signed_src;
+ pgp_source_t * literal_src;
+ pgp_message_t msg_type;
+ pgp_dest_t output;
+ std::list<pgp_source_t> sources;
+
+ ~pgp_processing_ctx_t();
+} pgp_processing_ctx_t;
+
+/* common fields for encrypted, compressed and literal data */
+typedef struct pgp_source_packet_param_t {
+ pgp_source_t * readsrc; /* source to read from, could be partial*/
+ pgp_source_t * origsrc; /* original source passed to init_*_src */
+ pgp_packet_hdr_t hdr; /* packet header info */
+} pgp_source_packet_param_t;
+
+typedef struct pgp_source_encrypted_param_t {
+ pgp_source_packet_param_t pkt{}; /* underlying packet-related params */
+ std::vector<pgp_sk_sesskey_t> symencs; /* array of sym-encrypted session keys */
+ std::vector<pgp_pk_sesskey_t> pubencs; /* array of pk-encrypted session keys */
+ rnp::AuthType auth_type; /* Authentication type */
+ bool auth_validated{}; /* Auth tag (MDC or AEAD) was already validated */
+ pgp_crypt_t decrypt{}; /* decrypting crypto */
+ std::unique_ptr<rnp::Hash> mdc; /* mdc SHA1 hash */
+ size_t chunklen{}; /* size of AEAD chunk in bytes */
+ size_t chunkin{}; /* number of bytes read from the current chunk */
+ size_t chunkidx{}; /* index of the current chunk */
+ uint8_t cache[PGP_AEAD_CACHE_LEN]; /* read cache */
+ size_t cachelen{}; /* number of bytes in the cache */
+ size_t cachepos{}; /* index of first unread byte in the cache */
+ pgp_aead_hdr_t aead_hdr; /* AEAD encryption parameters */
+ uint8_t aead_ad[PGP_AEAD_MAX_AD_LEN]; /* additional data */
+ size_t aead_adlen{}; /* length of the additional data */
+ pgp_symm_alg_t salg; /* data encryption algorithm */
+ pgp_parse_handler_t * handler{}; /* parsing handler with callbacks */
+
+ pgp_source_encrypted_param_t() : auth_type(rnp::AuthType::None), salg(PGP_SA_UNKNOWN)
+ {
+ }
+
+ bool
+ use_cfb()
+ {
+ return auth_type != rnp::AuthType::AEADv1;
+ }
+} pgp_source_encrypted_param_t;
+
+typedef struct pgp_source_signed_param_t {
+ pgp_parse_handler_t *handler; /* parsing handler with callbacks */
+ pgp_source_t * readsrc; /* source to read from */
+ bool detached; /* detached signature */
+ bool cleartext; /* source is cleartext signed */
+ bool clr_eod; /* cleartext data is over */
+ bool clr_fline; /* first line of the cleartext */
+ bool clr_mline; /* in the middle of the very long line */
+ uint8_t out[CT_BUF_LEN]; /* cleartext output cache for easier parsing */
+ size_t outlen; /* total bytes in out */
+ size_t outpos; /* offset of first available byte in out */
+ bool max_line_warn; /* warning about too long line is already issued */
+ size_t text_line_len; /* length of a current line in a text document */
+ long stripped_crs; /* number of trailing CR characters stripped from the end of the last
+ processed chunk */
+
+ std::vector<pgp_one_pass_sig_t> onepasses; /* list of one-pass singatures */
+ std::list<pgp_signature_t> sigs; /* list of signatures */
+ std::vector<pgp_signature_info_t> siginfos; /* signature validation info */
+ rnp::HashList hashes; /* hash contexts */
+ rnp::HashList txt_hashes; /* hash contexts for text-mode sigs */
+
+ pgp_source_signed_param_t() = default;
+ ~pgp_source_signed_param_t() = default;
+} pgp_source_signed_param_t;
+
+typedef struct pgp_source_compressed_param_t {
+ pgp_source_packet_param_t pkt; /* underlying packet-related params */
+ pgp_compression_type_t alg;
+ union {
+ z_stream z;
+ bz_stream bz;
+ };
+ uint8_t in[PGP_INPUT_CACHE_SIZE / 2];
+ size_t inpos;
+ size_t inlen;
+ bool zend;
+} pgp_source_compressed_param_t;
+
+typedef struct pgp_source_literal_param_t {
+ pgp_source_packet_param_t pkt; /* underlying packet-related params */
+ pgp_literal_hdr_t hdr; /* literal packet fields */
+} pgp_source_literal_param_t;
+
+typedef struct pgp_source_partial_param_t {
+ pgp_source_t *readsrc; /* source to read from */
+ int type; /* type of the packet */
+ size_t psize; /* size of the current part */
+ size_t pleft; /* bytes left to read from the current part */
+ bool last; /* current part is last */
+} pgp_source_partial_param_t;
+
+static bool
+is_pgp_source(pgp_source_t &src)
+{
+ uint8_t buf;
+ if (!src_peek_eq(&src, &buf, 1)) {
+ return false;
+ }
+
+ switch (get_packet_type(buf)) {
+ case PGP_PKT_PK_SESSION_KEY:
+ case PGP_PKT_SK_SESSION_KEY:
+ case PGP_PKT_ONE_PASS_SIG:
+ case PGP_PKT_SIGNATURE:
+ case PGP_PKT_SE_DATA:
+ case PGP_PKT_SE_IP_DATA:
+ case PGP_PKT_COMPRESSED:
+ case PGP_PKT_LITDATA:
+ case PGP_PKT_MARKER:
+ return true;
+ default:
+ return false;
+ }
+}
+
+static bool
+partial_pkt_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ if (src->eof) {
+ *readres = 0;
+ return true;
+ }
+
+ pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ size_t read;
+ size_t write = 0;
+ while (len > 0) {
+ if (!param->pleft && param->last) {
+ // we have the last chunk
+ *readres = write;
+ return true;
+ }
+ if (!param->pleft) {
+ // reading next chunk
+ if (!stream_read_partial_chunk_len(param->readsrc, &read, &param->last)) {
+ return false;
+ }
+ param->psize = read;
+ param->pleft = read;
+ }
+
+ if (!param->pleft) {
+ *readres = write;
+ return true;
+ }
+
+ read = param->pleft > len ? len : param->pleft;
+ if (!src_read(param->readsrc, buf, read, &read)) {
+ RNP_LOG("failed to read data chunk");
+ return false;
+ }
+ if (!read) {
+ RNP_LOG("unexpected eof");
+ *readres = write;
+ return true;
+ }
+ write += read;
+ len -= read;
+ buf = (uint8_t *) buf + read;
+ param->pleft -= read;
+ }
+
+ *readres = write;
+ return true;
+}
+
+static void
+partial_pkt_src_close(pgp_source_t *src)
+{
+ pgp_source_partial_param_t *param = (pgp_source_partial_param_t *) src->param;
+ if (param) {
+ free(src->param);
+ src->param = NULL;
+ }
+}
+
+static rnp_result_t
+init_partial_pkt_src(pgp_source_t *src, pgp_source_t *readsrc, pgp_packet_hdr_t &hdr)
+{
+ pgp_source_partial_param_t *param;
+ if (!init_src_common(src, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ assert(hdr.partial);
+ /* we are sure that header is indeterminate */
+ param = (pgp_source_partial_param_t *) src->param;
+ param->type = hdr.tag;
+ param->psize = get_partial_pkt_len(hdr.hdr[1]);
+ param->pleft = param->psize;
+ param->last = false;
+ param->readsrc = readsrc;
+
+ src->read = partial_pkt_src_read;
+ src->close = partial_pkt_src_close;
+ src->type = PGP_STREAM_PARLEN_PACKET;
+
+ if (param->psize < PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE) {
+ RNP_LOG("first part of partial length packet sequence has size %d and that's less "
+ "than allowed by the protocol",
+ (int) param->psize);
+ }
+
+ return RNP_SUCCESS;
+}
+
+static bool
+literal_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+ pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+ return src_read(param->pkt.readsrc, buf, len, read);
+}
+
+static void
+literal_src_close(pgp_source_t *src)
+{
+ pgp_source_literal_param_t *param = (pgp_source_literal_param_t *) src->param;
+ if (param) {
+ if (param->pkt.hdr.partial) {
+ src_close(param->pkt.readsrc);
+ free(param->pkt.readsrc);
+ param->pkt.readsrc = NULL;
+ }
+
+ free(src->param);
+ src->param = NULL;
+ }
+}
+
+static bool
+compressed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ if (src->eof || param->zend) {
+ *readres = 0;
+ return true;
+ }
+
+ if (param->alg == PGP_C_NONE) {
+ if (!src_read(param->pkt.readsrc, buf, len, readres)) {
+ RNP_LOG("failed to read uncompressed data");
+ return false;
+ }
+ return true;
+ }
+ if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) {
+ param->z.next_out = (Bytef *) buf;
+ param->z.avail_out = len;
+ param->z.next_in = param->in + param->inpos;
+ param->z.avail_in = param->inlen - param->inpos;
+
+ while ((param->z.avail_out > 0) && (!param->zend)) {
+ if (param->z.avail_in == 0) {
+ size_t read = 0;
+ if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) {
+ RNP_LOG("failed to read data");
+ return false;
+ }
+ param->z.next_in = param->in;
+ param->z.avail_in = read;
+ param->inlen = read;
+ param->inpos = 0;
+ }
+ int ret = inflate(&param->z, Z_SYNC_FLUSH);
+ if (ret == Z_STREAM_END) {
+ param->zend = true;
+ if (param->z.avail_in > 0) {
+ RNP_LOG("data beyond the end of z stream");
+ }
+ break;
+ }
+ if (ret != Z_OK) {
+ RNP_LOG("inflate error %d", ret);
+ return false;
+ }
+ if (!param->z.avail_in && src_eof(param->pkt.readsrc)) {
+ RNP_LOG("unexpected end of zlib stream");
+ return false;
+ }
+ }
+ param->inpos = param->z.next_in - param->in;
+ *readres = len - param->z.avail_out;
+ return true;
+ }
+#ifdef HAVE_BZLIB_H
+ if (param->alg == PGP_C_BZIP2) {
+ param->bz.next_out = (char *) buf;
+ param->bz.avail_out = len;
+ param->bz.next_in = (char *) (param->in + param->inpos);
+ param->bz.avail_in = param->inlen - param->inpos;
+
+ while ((param->bz.avail_out > 0) && (!param->zend)) {
+ if (param->bz.avail_in == 0) {
+ size_t read = 0;
+ if (!src_read(param->pkt.readsrc, param->in, sizeof(param->in), &read)) {
+ RNP_LOG("failed to read data");
+ return false;
+ }
+ param->bz.next_in = (char *) param->in;
+ param->bz.avail_in = read;
+ param->inlen = read;
+ param->inpos = 0;
+ }
+ int ret = BZ2_bzDecompress(&param->bz);
+ if (ret == BZ_STREAM_END) {
+ param->zend = true;
+ if (param->bz.avail_in > 0) {
+ RNP_LOG("data beyond the end of z stream");
+ }
+ break;
+ }
+ if (ret != BZ_OK) {
+ RNP_LOG("bzdecompress error %d", ret);
+ return false;
+ }
+ if (!param->bz.avail_in && src_eof(param->pkt.readsrc)) {
+ RNP_LOG("unexpected end of bzip stream");
+ return false;
+ }
+ }
+
+ param->inpos = (uint8_t *) param->bz.next_in - param->in;
+ *readres = len - param->bz.avail_out;
+ return true;
+ }
+#endif
+ return false;
+}
+
+static void
+compressed_src_close(pgp_source_t *src)
+{
+ pgp_source_compressed_param_t *param = (pgp_source_compressed_param_t *) src->param;
+ if (!param) {
+ return;
+ }
+
+ if (param->pkt.hdr.partial) {
+ src_close(param->pkt.readsrc);
+ free(param->pkt.readsrc);
+ param->pkt.readsrc = NULL;
+ }
+
+#ifdef HAVE_BZLIB_H
+ if (param->alg == PGP_C_BZIP2) {
+ BZ2_bzDecompressEnd(&param->bz);
+ }
+#endif
+ if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) {
+ inflateEnd(&param->z);
+ }
+
+ free(src->param);
+ src->param = NULL;
+}
+
+#if defined(ENABLE_AEAD)
+static bool
+encrypted_start_aead_chunk(pgp_source_encrypted_param_t *param, size_t idx, bool last)
+{
+ uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN];
+ size_t nlen;
+
+ /* set chunk index for additional data */
+ STORE64BE(param->aead_ad + param->aead_adlen - 8, idx);
+
+ if (last) {
+ uint64_t total = idx * param->chunklen;
+ if (idx && param->chunkin) {
+ total -= param->chunklen - param->chunkin;
+ }
+
+ if (!param->chunkin) {
+ /* reset the crypto in case we had empty chunk before the last one */
+ pgp_cipher_aead_reset(&param->decrypt);
+ }
+ STORE64BE(param->aead_ad + param->aead_adlen, total);
+ param->aead_adlen += 8;
+ }
+
+ if (!pgp_cipher_aead_set_ad(&param->decrypt, param->aead_ad, param->aead_adlen)) {
+ RNP_LOG("failed to set ad");
+ return false;
+ }
+
+ /* setup chunk */
+ param->chunkidx = idx;
+ param->chunkin = 0;
+
+ /* set chunk index for nonce */
+ nlen = pgp_cipher_aead_nonce(param->aead_hdr.aalg, param->aead_hdr.iv, nonce, idx);
+
+ /* start cipher */
+ return pgp_cipher_aead_start(&param->decrypt, nonce, nlen);
+}
+
+/* read and decrypt bytes to the cache. Should be called only on empty cache. */
+static bool
+encrypted_src_read_aead_part(pgp_source_encrypted_param_t *param)
+{
+ bool lastchunk = false;
+ bool chunkend = false;
+ bool res = false;
+ size_t read;
+ size_t tagread;
+ size_t taglen;
+
+ param->cachepos = 0;
+ param->cachelen = 0;
+
+ if (param->auth_validated) {
+ return true;
+ }
+
+ /* it is always 16 for defined EAX and OCB, however this may change in future */
+ taglen = pgp_cipher_aead_tag_len(param->aead_hdr.aalg);
+ read = sizeof(param->cache) - 2 * PGP_AEAD_MAX_TAG_LEN;
+
+ if (read >= param->chunklen - param->chunkin) {
+ read = param->chunklen - param->chunkin;
+ chunkend = true;
+ } else {
+ read = read - read % pgp_cipher_aead_granularity(&param->decrypt);
+ }
+
+ if (!src_read(param->pkt.readsrc, param->cache, read, &read)) {
+ return false;
+ }
+
+ /* checking whether we have enough input for the final tags */
+ if (!src_peek(param->pkt.readsrc, param->cache + read, taglen * 2, &tagread)) {
+ return false;
+ }
+
+ if (tagread < taglen * 2) {
+ /* this would mean the end of the stream */
+ if ((param->chunkin == 0) && (read + tagread == taglen)) {
+ /* we have empty chunk and final tag */
+ chunkend = false;
+ lastchunk = true;
+ } else if (read + tagread >= 2 * taglen) {
+ /* we have end of chunk and final tag */
+ chunkend = true;
+ lastchunk = true;
+ } else {
+ RNP_LOG("unexpected end of data");
+ return false;
+ }
+ }
+
+ if (!chunkend && !lastchunk) {
+ param->chunkin += read;
+ res = pgp_cipher_aead_update(&param->decrypt, param->cache, param->cache, read);
+ if (res) {
+ param->cachelen = read;
+ }
+ return res;
+ }
+
+ if (chunkend) {
+ if (tagread > taglen) {
+ src_skip(param->pkt.readsrc, tagread - taglen);
+ }
+
+ res = pgp_cipher_aead_finish(
+ &param->decrypt, param->cache, param->cache, read + tagread - taglen);
+ if (!res) {
+ RNP_LOG("failed to finalize aead chunk");
+ return res;
+ }
+ param->cachelen = read + tagread - 2 * taglen;
+ param->chunkin += param->cachelen;
+ }
+
+ size_t chunkidx = param->chunkidx;
+ if (chunkend && param->chunkin) {
+ chunkidx++;
+ }
+
+ if (!(res = encrypted_start_aead_chunk(param, chunkidx, lastchunk))) {
+ RNP_LOG("failed to start aead chunk");
+ return res;
+ }
+
+ if (lastchunk) {
+ if (tagread > 0) {
+ src_skip(param->pkt.readsrc, tagread);
+ }
+
+ size_t off = read + tagread - taglen;
+ res = pgp_cipher_aead_finish(
+ &param->decrypt, param->cache + off, param->cache + off, taglen);
+ if (!res) {
+ RNP_LOG("wrong last chunk");
+ return res;
+ }
+ param->auth_validated = true;
+ }
+
+ return res;
+}
+#endif
+
+static bool
+encrypted_src_read_aead(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+#if !defined(ENABLE_AEAD)
+ return false;
+#else
+ pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param;
+ size_t cbytes;
+ size_t left = len;
+
+ do {
+ /* check whether we have something in the cache */
+ cbytes = param->cachelen - param->cachepos;
+ if (cbytes > 0) {
+ if (cbytes >= left) {
+ memcpy(buf, param->cache + param->cachepos, left);
+ param->cachepos += left;
+ if (param->cachepos == param->cachelen) {
+ param->cachepos = param->cachelen = 0;
+ }
+ *read = len;
+ return true;
+ }
+ memcpy(buf, param->cache + param->cachepos, cbytes);
+ buf = (uint8_t *) buf + cbytes;
+ left -= cbytes;
+ param->cachepos = param->cachelen = 0;
+ }
+
+ /* read something into cache */
+ if (!encrypted_src_read_aead_part(param)) {
+ return false;
+ }
+ } while ((left > 0) && (param->cachelen > 0));
+
+ *read = len - left;
+ return true;
+#endif
+}
+
+static bool
+encrypted_src_read_cfb(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param;
+ if (param == NULL) {
+ return false;
+ }
+
+ if (src->eof) {
+ *readres = 0;
+ return true;
+ }
+
+ size_t read;
+ if (!src_read(param->pkt.readsrc, buf, len, &read)) {
+ return false;
+ }
+ if (!read) {
+ *readres = 0;
+ return true;
+ }
+
+ bool parsemdc = false;
+ uint8_t mdcbuf[MDC_V1_SIZE];
+ if (param->auth_type == rnp::AuthType::MDC) {
+ size_t mdcread = 0;
+ /* make sure there are always 22 bytes left on input */
+ if (!src_peek(param->pkt.readsrc, mdcbuf, MDC_V1_SIZE, &mdcread) ||
+ (mdcread + read < MDC_V1_SIZE)) {
+ RNP_LOG("wrong mdc read state");
+ return false;
+ }
+ if (mdcread < MDC_V1_SIZE) {
+ src_skip(param->pkt.readsrc, mdcread);
+ size_t mdcsub = MDC_V1_SIZE - mdcread;
+ memmove(&mdcbuf[mdcsub], mdcbuf, mdcread);
+ memcpy(mdcbuf, (uint8_t *) buf + read - mdcsub, mdcsub);
+ read -= mdcsub;
+ parsemdc = true;
+ }
+ }
+
+ pgp_cipher_cfb_decrypt(&param->decrypt, (uint8_t *) buf, (uint8_t *) buf, read);
+
+ if (param->auth_type == rnp::AuthType::MDC) {
+ try {
+ param->mdc->add(buf, read);
+
+ if (parsemdc) {
+ pgp_cipher_cfb_decrypt(&param->decrypt, mdcbuf, mdcbuf, MDC_V1_SIZE);
+ pgp_cipher_cfb_finish(&param->decrypt);
+ param->mdc->add(mdcbuf, 2);
+ uint8_t hash[PGP_SHA1_HASH_SIZE] = {0};
+ 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;
+ }
+
+ if (memcmp(&mdcbuf[2], hash, PGP_SHA1_HASH_SIZE) != 0) {
+ RNP_LOG("mdc hash check failed");
+ return false;
+ }
+ param->auth_validated = true;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("mdc update failed: %s", e.what());
+ return false;
+ }
+ }
+ *readres = read;
+ return true;
+}
+
+static rnp_result_t
+encrypted_src_finish(pgp_source_t *src)
+{
+ pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param;
+
+ /* report to the handler that decryption is finished */
+ if (param->handler->on_decryption_done) {
+ bool validated = (param->auth_type != rnp::AuthType::None) && param->auth_validated;
+ param->handler->on_decryption_done(validated, param->handler->param);
+ }
+
+ if ((param->auth_type == rnp::AuthType::None) || param->auth_validated) {
+ return RNP_SUCCESS;
+ }
+ switch (param->auth_type) {
+ case rnp::AuthType::MDC:
+ RNP_LOG("mdc was not validated");
+ break;
+ case rnp::AuthType::AEADv1:
+ RNP_LOG("aead last chunk was not validated");
+ break;
+ default:
+ RNP_LOG("auth was not validated");
+ break;
+ }
+ return RNP_ERROR_BAD_STATE;
+}
+
+static void
+encrypted_src_close(pgp_source_t *src)
+{
+ pgp_source_encrypted_param_t *param = (pgp_source_encrypted_param_t *) src->param;
+ if (!param) {
+ return;
+ }
+ if (param->pkt.hdr.partial) {
+ src_close(param->pkt.readsrc);
+ free(param->pkt.readsrc);
+ param->pkt.readsrc = NULL;
+ }
+
+ if (!param->use_cfb()) {
+#if defined(ENABLE_AEAD)
+ pgp_cipher_aead_destroy(&param->decrypt);
+#endif
+ } else {
+ pgp_cipher_cfb_finish(&param->decrypt);
+ }
+
+ delete param;
+ src->param = NULL;
+}
+
+static void
+add_hash_for_sig(pgp_source_signed_param_t *param, pgp_sig_type_t stype, pgp_hash_alg_t halg)
+{
+ /* Cleartext always uses param->hashes instead of param->txt_hashes */
+ if (!param->cleartext && (stype == PGP_SIG_TEXT)) {
+ param->txt_hashes.add_alg(halg);
+ }
+ param->hashes.add_alg(halg);
+}
+
+static const rnp::Hash *
+get_hash_for_sig(pgp_source_signed_param_t &param, pgp_signature_info_t &sinfo)
+{
+ /* Cleartext always uses param->hashes instead of param->txt_hashes */
+ if (!param.cleartext && (sinfo.sig->type() == PGP_SIG_TEXT)) {
+ return param.txt_hashes.get(sinfo.sig->halg);
+ }
+ return param.hashes.get(sinfo.sig->halg);
+}
+
+static void
+signed_validate_signature(pgp_source_signed_param_t &param, 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(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()) {
+ keyctx.search.type = PGP_KEY_SEARCH_KEYID;
+ keyctx.search.by.keyid = sinfo.sig->keyid();
+ } else {
+ RNP_LOG("cannot get signer's key fp or id from signature.");
+ sinfo.unknown = true;
+ return;
+ }
+ /* Get the public key */
+ pgp_key_t *key = pgp_request_key(param.handler->key_provider, &keyctx);
+ if (!key) {
+ /* fallback to secret key */
+ keyctx.secret = true;
+ if (!(key = pgp_request_key(param.handler->key_provider, &keyctx))) {
+ RNP_LOG("signer's key not found");
+ sinfo.no_signer = true;
+ return;
+ }
+ }
+ try {
+ /* 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;
+ }
+}
+
+static long
+stripped_line_len(uint8_t *begin, uint8_t *end)
+{
+ uint8_t *stripped_end = end;
+
+ while (stripped_end >= begin && (*stripped_end == CH_CR || *stripped_end == CH_LF)) {
+ stripped_end--;
+ }
+
+ return stripped_end - begin + 1;
+}
+
+static void
+signed_src_update(pgp_source_t *src, const void *buf, size_t len)
+{
+ if (!len) {
+ return;
+ }
+ /* check for extremely unlikely pointer overflow/wrap case */
+ if (((uint8_t *) buf + len) < ((uint8_t *) buf + len - 1)) {
+ signed_src_update(src, buf, len - 1);
+ uint8_t last = *((uint8_t *) buf + len - 1);
+ signed_src_update(src, &last, 1);
+ }
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ try {
+ param->hashes.add(buf, len);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ }
+ /* update text-mode sig hashes */
+ if (param->txt_hashes.hashes.empty()) {
+ return;
+ }
+
+ uint8_t *ch = (uint8_t *) buf;
+ uint8_t *linebeg = ch;
+ uint8_t *end = (uint8_t *) buf + len;
+ /* we support LF and CRLF line endings */
+ while (ch < end) {
+ /* continue if not reached LF */
+ if (*ch != CH_LF) {
+ if (*ch != CH_CR && param->stripped_crs > 0) {
+ while (param->stripped_crs--) {
+ try {
+ param->txt_hashes.add(ST_CR, 1);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ }
+ }
+ param->stripped_crs = 0;
+ }
+
+ if (!param->max_line_warn && param->text_line_len >= MAXIMUM_GNUPG_LINELEN) {
+ RNP_LOG("Canonical text document signature: line is too long, may cause "
+ "incompatibility with other implementations. Consider using binary "
+ "signature instead.");
+ param->max_line_warn = true;
+ }
+
+ ch++;
+ param->text_line_len++;
+ continue;
+ }
+ /* reached eol: dump line contents */
+ param->stripped_crs = 0;
+ param->text_line_len = 0;
+ if (ch > linebeg) {
+ long stripped_len = stripped_line_len(linebeg, ch);
+ if (stripped_len > 0) {
+ try {
+ param->txt_hashes.add(linebeg, stripped_len);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ }
+ }
+ }
+ /* dump EOL */
+ try {
+ param->txt_hashes.add(ST_CRLF, 2);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ }
+ ch++;
+ linebeg = ch;
+ }
+ /* check if we have undumped line contents */
+ if (linebeg < end) {
+ long stripped_len = stripped_line_len(linebeg, end - 1);
+ if (stripped_len < end - linebeg) {
+ param->stripped_crs = end - linebeg - stripped_len;
+ }
+ if (stripped_len > 0) {
+ try {
+ param->txt_hashes.add(linebeg, stripped_len);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ }
+ }
+ }
+}
+
+static bool
+signed_src_read(pgp_source_t *src, void *buf, size_t len, size_t *read)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+ return src_read(param->readsrc, buf, len, read);
+}
+
+static void
+signed_src_close(pgp_source_t *src)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ if (!param) {
+ return;
+ }
+ delete param;
+ src->param = NULL;
+}
+
+#define MAX_SIGNATURES 16384
+
+static rnp_result_t
+signed_read_single_signature(pgp_source_signed_param_t *param,
+ pgp_source_t * readsrc,
+ pgp_signature_t ** sig)
+{
+ uint8_t ptag;
+ if (!src_peek_eq(readsrc, &ptag, 1)) {
+ RNP_LOG("failed to read signature packet header");
+ return RNP_ERROR_READ;
+ }
+
+ int ptype = get_packet_type(ptag);
+ if (ptype != PGP_PKT_SIGNATURE) {
+ RNP_LOG("unexpected packet %d", ptype);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ if (param->siginfos.size() >= MAX_SIGNATURES) {
+ RNP_LOG("Too many signatures in the stream.");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ try {
+ param->siginfos.emplace_back();
+ pgp_signature_info_t &siginfo = param->siginfos.back();
+ pgp_signature_t readsig;
+ if (readsig.parse(*readsrc)) {
+ RNP_LOG("failed to parse signature");
+ siginfo.unknown = true;
+ if (sig) {
+ *sig = NULL;
+ }
+ return RNP_SUCCESS;
+ }
+ param->sigs.push_back(std::move(readsig));
+ siginfo.sig = &param->sigs.back();
+ if (sig) {
+ *sig = siginfo.sig;
+ }
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+}
+
+static rnp_result_t
+signed_read_cleartext_signatures(pgp_source_t &src, pgp_source_signed_param_t *param)
+{
+ 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;
+ }
+}
+
+static rnp_result_t
+signed_read_signatures(pgp_source_t *src)
+{
+ 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++) {
+ 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("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, param);
+ } else {
+ ret = signed_read_signatures(src);
+ }
+
+ if (ret) {
+ return ret;
+ }
+
+ if (!src_eof(src)) {
+ RNP_LOG("warning: unexpected data on the stream end");
+ }
+
+ /* validating signatures */
+ for (auto &sinfo : param->siginfos) {
+ if (!sinfo.sig) {
+ continue;
+ }
+ signed_validate_signature(*param, sinfo);
+ }
+
+ /* checking the validation results */
+ ret = RNP_ERROR_SIGNATURE_INVALID;
+ for (auto &sinfo : param->siginfos) {
+ 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) {
+ param->handler->on_signatures(param->siginfos, param->handler->param);
+ }
+ return ret;
+}
+
+/*
+ * str is a string to tokenize.
+ * delims is a string containing a list of delimiter characters.
+ * result is a container<string_type> that supports push_back.
+ */
+template <typename T>
+static void
+tokenize(const typename T::value_type &str, const typename T::value_type &delims, T &result)
+{
+ typedef typename T::value_type::size_type string_size_t;
+ const string_size_t npos = T::value_type::npos;
+
+ result.clear();
+ string_size_t current;
+ string_size_t next = 0;
+ do {
+ next = str.find_first_not_of(delims, next);
+ if (next == npos) {
+ break;
+ }
+ current = next;
+ next = str.find_first_of(delims, current);
+ string_size_t count = (next == npos) ? npos : (next - current);
+ result.push_back(str.substr(current, count));
+ } while (next != npos);
+}
+
+static bool
+cleartext_parse_headers(pgp_source_t *src)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ char hdr[1024] = {0};
+ char * hval;
+ pgp_hash_alg_t halg;
+ size_t hdrlen;
+
+ do {
+ if (!src_peek_line(param->readsrc, hdr, sizeof(hdr), &hdrlen)) {
+ RNP_LOG("failed to peek line");
+ return false;
+ }
+
+ if (!hdrlen) {
+ break;
+ }
+
+ if (rnp::is_blank_line(hdr, hdrlen)) {
+ src_skip(param->readsrc, hdrlen);
+ break;
+ }
+
+ try {
+ if ((hdrlen >= 6) && !strncmp(hdr, ST_HEADER_HASH, 6)) {
+ hval = hdr + 6;
+
+ std::string remainder = hval;
+
+ const std::string delimiters = ", \t";
+ std::vector<std::string> tokens;
+
+ tokenize(remainder, delimiters, tokens);
+
+ for (const auto &token : tokens) {
+ if ((halg = rnp::Hash::alg(token.c_str())) == PGP_HASH_UNKNOWN) {
+ RNP_LOG("unknown halg: %s", token.c_str());
+ continue;
+ }
+ add_hash_for_sig(param, PGP_SIG_TEXT, halg);
+ }
+ } else {
+ RNP_LOG("unknown header '%s'", hdr);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+
+ src_skip(param->readsrc, hdrlen);
+
+ if (!src_skip_eol(param->readsrc)) {
+ return false;
+ }
+ } while (1);
+
+ /* we have exactly one empty line after the headers */
+ return src_skip_eol(param->readsrc);
+}
+
+static void
+cleartext_process_line(pgp_source_t *src, const uint8_t *buf, size_t len, bool eol)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ uint8_t * bufen = (uint8_t *) buf + len - 1;
+
+ /* check for dashes only if we are not in the middle */
+ if (!param->clr_mline && (len > 0) && (buf[0] == CH_DASH)) {
+ if ((len > 1) && (buf[1] == CH_SPACE)) {
+ buf += 2;
+ len -= 2;
+ } else if ((len > 5) && !memcmp(buf, ST_DASHES, 5)) {
+ param->clr_eod = true;
+ return;
+ } else {
+ RNP_LOG("dash at the line begin");
+ }
+ }
+
+ /* hash eol if it is not the first line and we are not in the middle */
+ if (!param->clr_fline && !param->clr_mline) {
+ /* we hash \r\n after the previous line to not hash the last eol before the sig */
+ signed_src_update(src, ST_CRLF, 2);
+ }
+
+ if (!len) {
+ return;
+ }
+
+ if (len + param->outlen > sizeof(param->out)) {
+ RNP_LOG("wrong state");
+ return;
+ }
+
+ /* if we have eol after this line then strip trailing spaces and tabs */
+ if (eol) {
+ for (; (bufen >= buf) &&
+ ((*bufen == CH_SPACE) || (*bufen == CH_TAB) || (*bufen == CH_CR));
+ bufen--)
+ ;
+ }
+
+ if ((len = bufen + 1 - buf)) {
+ memcpy(param->out + param->outlen, buf, len);
+ param->outlen += len;
+ signed_src_update(src, buf, len);
+ }
+}
+
+static bool
+cleartext_src_read(pgp_source_t *src, void *buf, size_t len, size_t *readres)
+{
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+ if (!param) {
+ return false;
+ }
+
+ uint8_t srcb[CT_BUF_LEN];
+ uint8_t *cur, *en, *bg;
+ size_t read = 0;
+ size_t origlen = len;
+
+ read = param->outlen - param->outpos;
+ if (read >= len) {
+ memcpy(buf, param->out + param->outpos, len);
+ param->outpos += len;
+ if (param->outpos == param->outlen) {
+ param->outpos = param->outlen = 0;
+ }
+ *readres = len;
+ return true;
+ } else if (read > 0) {
+ memcpy(buf, param->out + param->outpos, read);
+ len -= read;
+ buf = (uint8_t *) buf + read;
+ param->outpos = param->outlen = 0;
+ }
+
+ if (param->clr_eod) {
+ *readres = origlen - len;
+ return true;
+ }
+
+ do {
+ if (!src_peek(param->readsrc, srcb, sizeof(srcb), &read)) {
+ return false;
+ } else if (!read) {
+ break;
+ }
+
+ /* processing data line by line, eol could be \n or \r\n */
+ for (cur = srcb, bg = srcb, en = cur + read; cur < en; cur++) {
+ if ((*cur == CH_LF) ||
+ ((*cur == CH_CR) && (cur + 1 < en) && (*(cur + 1) == CH_LF))) {
+ cleartext_process_line(src, bg, cur - bg, true);
+ /* processing eol */
+ if (param->clr_eod) {
+ break;
+ }
+
+ /* processing eol */
+ param->clr_fline = false;
+ param->clr_mline = false;
+ if (*cur == CH_CR) {
+ param->out[param->outlen++] = *cur++;
+ }
+ param->out[param->outlen++] = *cur;
+ bg = cur + 1;
+ }
+ }
+
+ /* if line is larger then 4k then just dump it out */
+ if ((bg == srcb) && !param->clr_eod) {
+ /* if last char is \r, and it's not the end of stream, then do not dump it */
+ if ((en > bg) && (*(en - 1) == CH_CR) && (read > 1)) {
+ en--;
+ }
+ cleartext_process_line(src, bg, en - bg, false);
+ param->clr_mline = true;
+ bg = en;
+ }
+ src_skip(param->readsrc, bg - srcb);
+
+ /* put data from the param->out to buf */
+ read = param->outlen > len ? len : param->outlen;
+ memcpy(buf, param->out, read);
+ buf = (uint8_t *) buf + read;
+ len -= read;
+
+ if (read == param->outlen) {
+ param->outlen = 0;
+ } else {
+ param->outpos = read;
+ }
+
+ /* we got to the signature marker */
+ if (param->clr_eod || !len) {
+ break;
+ }
+ } while (1);
+
+ *readres = origlen - len;
+ return true;
+}
+
+static bool
+encrypted_decrypt_cfb_header(pgp_source_encrypted_param_t *param,
+ pgp_symm_alg_t alg,
+ uint8_t * key)
+{
+ pgp_crypt_t crypt;
+ uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2];
+ uint8_t dechdr[PGP_MAX_BLOCK_SIZE + 2];
+ unsigned blsize;
+
+ if (!(blsize = pgp_block_size(alg))) {
+ return false;
+ }
+
+ /* reading encrypted header to check the password validity */
+ if (!src_peek_eq(param->pkt.readsrc, enchdr, blsize + 2)) {
+ RNP_LOG("failed to read encrypted header");
+ return false;
+ }
+
+ /* having symmetric key in keybuf let's decrypt blocksize + 2 bytes and check them */
+ if (!pgp_cipher_cfb_start(&crypt, alg, key, NULL)) {
+ RNP_LOG("failed to start cipher");
+ return false;
+ }
+
+ pgp_cipher_cfb_decrypt(&crypt, dechdr, enchdr, blsize + 2);
+
+ if ((dechdr[blsize] != dechdr[blsize - 2]) || (dechdr[blsize + 1] != dechdr[blsize - 1])) {
+ RNP_LOG("checksum check failed");
+ goto error;
+ }
+
+ src_skip(param->pkt.readsrc, blsize + 2);
+ param->decrypt = crypt;
+
+ /* init mdc if it is here */
+ /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB
+ * resynchronization is done after encrypting this prefix data. */
+ if (param->auth_type == rnp::AuthType::None) {
+ pgp_cipher_cfb_resync(&param->decrypt, enchdr + 2);
+ return true;
+ }
+
+ try {
+ 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;
+error:
+ pgp_cipher_cfb_finish(&crypt);
+ return false;
+}
+
+static bool
+encrypted_start_aead(pgp_source_encrypted_param_t *param, pgp_symm_alg_t alg, uint8_t *key)
+{
+#if !defined(ENABLE_AEAD)
+ RNP_LOG("AEAD is not enabled.");
+ return false;
+#else
+ size_t gran;
+
+ if (alg != param->aead_hdr.ealg) {
+ return false;
+ }
+
+ /* initialize cipher with key */
+ if (!pgp_cipher_aead_init(
+ &param->decrypt, param->aead_hdr.ealg, param->aead_hdr.aalg, key, true)) {
+ return false;
+ }
+
+ gran = pgp_cipher_aead_granularity(&param->decrypt);
+ if (gran > sizeof(param->cache)) {
+ RNP_LOG("wrong granularity");
+ return false;
+ }
+
+ return encrypted_start_aead_chunk(param, 0, false);
+#endif
+}
+
+static bool
+encrypted_try_key(pgp_source_encrypted_param_t *param,
+ pgp_pk_sesskey_t * sesskey,
+ pgp_key_pkt_t * seckey,
+ rnp::SecurityContext & ctx)
+{
+ pgp_encrypted_material_t encmaterial;
+ try {
+ if (!sesskey->parse_material(encmaterial)) {
+ return false;
+ }
+ seckey->material.validate(ctx, false);
+ if (!seckey->material.valid()) {
+ RNP_LOG("Attempt to decrypt using the key with invalid material.");
+ return false;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+
+ rnp::secure_array<uint8_t, PGP_MPINT_SIZE> decbuf;
+ /* Decrypting session key value */
+ rnp_result_t err;
+ bool res = false;
+ pgp_key_material_t *keymaterial = &seckey->material;
+ size_t declen = 0;
+ switch (sesskey->alg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY:
+ err = rsa_decrypt_pkcs1(
+ &ctx.rng, decbuf.data(), &declen, &encmaterial.rsa, &keymaterial->rsa);
+ if (err) {
+ RNP_LOG("RSA decryption failure");
+ return false;
+ }
+ break;
+ case PGP_PKA_SM2:
+#if defined(ENABLE_SM2)
+ declen = decbuf.size();
+ err = sm2_decrypt(decbuf.data(), &declen, &encmaterial.sm2, &keymaterial->ec);
+ if (err != RNP_SUCCESS) {
+ RNP_LOG("SM2 decryption failure, error %x", (int) err);
+ return false;
+ }
+ break;
+#else
+ RNP_LOG("SM2 decryption is not available.");
+ return false;
+#endif
+ case PGP_PKA_ELGAMAL:
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN: {
+ const rnp_result_t ret = elgamal_decrypt_pkcs1(
+ &ctx.rng, decbuf.data(), &declen, &encmaterial.eg, &keymaterial->eg);
+ if (ret) {
+ RNP_LOG("ElGamal decryption failure [%X]", ret);
+ return false;
+ }
+ break;
+ }
+ case PGP_PKA_ECDH: {
+ if (!curve_supported(keymaterial->ec.curve)) {
+ RNP_LOG("ECDH decrypt: curve %d is not supported.", (int) keymaterial->ec.curve);
+ return false;
+ }
+ pgp_fingerprint_t fingerprint;
+ if (pgp_fingerprint(fingerprint, *seckey)) {
+ RNP_LOG("ECDH fingerprint calculation failed");
+ return false;
+ }
+ 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);
+ if (err != RNP_SUCCESS) {
+ RNP_LOG("ECDH decryption error %u", err);
+ return false;
+ }
+ break;
+ }
+ default:
+ RNP_LOG("unsupported public key algorithm %d\n", seckey->alg);
+ return false;
+ }
+
+ /* Check algorithm and key length */
+ if (!pgp_is_sa_supported(decbuf[0])) {
+ RNP_LOG("Unsupported symmetric algorithm %" PRIu8, decbuf[0]);
+ return false;
+ }
+
+ pgp_symm_alg_t salg = static_cast<pgp_symm_alg_t>(decbuf[0]);
+ size_t keylen = pgp_key_size(salg);
+ if (declen != keylen + 3) {
+ RNP_LOG("invalid symmetric key length");
+ return false;
+ }
+
+ /* Validate checksum */
+ rnp::secure_array<unsigned, 1> checksum;
+ for (unsigned i = 1; i <= keylen; i++) {
+ checksum[0] += decbuf[i];
+ }
+
+ if ((checksum[0] & 0xffff) !=
+ (decbuf[keylen + 2] | ((unsigned) decbuf[keylen + 1] << 8))) {
+ RNP_LOG("wrong checksum\n");
+ return false;
+ }
+
+ if (param->use_cfb()) {
+ /* Decrypt header */
+ res = encrypted_decrypt_cfb_header(param, salg, &decbuf[1]);
+ } else {
+ /* Start AEAD decrypting, assuming we have correct key */
+ res = encrypted_start_aead(param, salg, &decbuf[1]);
+ }
+ if (res) {
+ param->salg = salg;
+ }
+ return res;
+}
+
+#if defined(ENABLE_AEAD)
+static bool
+encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey)
+{
+ /* TODO: this method is exact duplicate as in stream-write.c. Not sure where to put it */
+ uint8_t ad_data[4];
+
+ ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT;
+ ad_data[1] = skey->version;
+ ad_data[2] = skey->alg;
+ ad_data[3] = skey->aalg;
+
+ return pgp_cipher_aead_set_ad(crypt, ad_data, 4);
+}
+#endif
+
+static int
+encrypted_try_password(pgp_source_encrypted_param_t *param, const char *password)
+{
+ bool keyavail = false; /* tried password at least once */
+
+ for (auto &skey : param->symencs) {
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 1> keybuf;
+ /* deriving symmetric key from password */
+ size_t keysize = pgp_key_size(skey.alg);
+ if (!keysize || !pgp_s2k_derive_key(&skey.s2k, password, keybuf.data(), keysize)) {
+ continue;
+ }
+ pgp_crypt_t crypt;
+ pgp_symm_alg_t alg;
+
+ if (skey.version == PGP_SKSK_V4) {
+ /* v4 symmetrically-encrypted session key */
+ if (skey.enckeylen > 0) {
+ /* decrypting session key */
+ if (!pgp_cipher_cfb_start(&crypt, skey.alg, keybuf.data(), NULL)) {
+ continue;
+ }
+
+ pgp_cipher_cfb_decrypt(&crypt, keybuf.data(), skey.enckey, skey.enckeylen);
+ pgp_cipher_cfb_finish(&crypt);
+
+ alg = (pgp_symm_alg_t) keybuf[0];
+ keysize = pgp_key_size(alg);
+ if (!keysize || (keysize + 1 != skey.enckeylen)) {
+ continue;
+ }
+ memmove(keybuf.data(), keybuf.data() + 1, keysize);
+ } else {
+ alg = (pgp_symm_alg_t) skey.alg;
+ }
+
+ if (!pgp_block_size(alg)) {
+ continue;
+ }
+ keyavail = true;
+ } else if (skey.version == PGP_SKSK_V5) {
+#if !defined(ENABLE_AEAD)
+ continue;
+#else
+ /* v5 AEAD-encrypted session key */
+ size_t taglen = pgp_cipher_aead_tag_len(skey.aalg);
+ size_t ceklen = pgp_key_size(param->aead_hdr.ealg);
+ if (!taglen || !ceklen || (ceklen + taglen != skey.enckeylen)) {
+ RNP_LOG("CEK len/alg mismatch");
+ continue;
+ }
+ alg = skey.alg;
+
+ /* initialize cipher */
+ if (!pgp_cipher_aead_init(&crypt, skey.alg, skey.aalg, keybuf.data(), true)) {
+ continue;
+ }
+
+ /* set additional data */
+ if (!encrypted_sesk_set_ad(&crypt, &skey)) {
+ RNP_LOG("failed to set ad");
+ continue;
+ }
+
+ /* calculate nonce */
+ uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN];
+ size_t noncelen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0);
+
+ /* start cipher, decrypt key and verify tag */
+ keyavail =
+ pgp_cipher_aead_start(&crypt, nonce, noncelen) &&
+ pgp_cipher_aead_finish(&crypt, keybuf.data(), skey.enckey, skey.enckeylen);
+ pgp_cipher_aead_destroy(&crypt);
+
+ /* we have decrypted key so let's start decryption */
+ if (!keyavail) {
+ continue;
+ }
+#endif
+ } else {
+ continue;
+ }
+
+ /* Decrypt header for CFB */
+ if (param->use_cfb() && !encrypted_decrypt_cfb_header(param, alg, keybuf.data())) {
+ continue;
+ }
+ if (!param->use_cfb() &&
+ !encrypted_start_aead(param, param->aead_hdr.ealg, keybuf.data())) {
+ continue;
+ }
+
+ param->salg = param->use_cfb() ? alg : param->aead_hdr.ealg;
+ /* inform handler that we used this symenc */
+ if (param->handler->on_decryption_start) {
+ param->handler->on_decryption_start(NULL, &skey, param->handler->param);
+ }
+ return 1;
+ }
+
+ if (!param->use_cfb() && pgp_block_size(param->aead_hdr.ealg)) {
+ /* we know aead symm alg even if we wasn't able to start decryption */
+ param->salg = param->aead_hdr.ealg;
+ }
+
+ if (!keyavail) {
+ RNP_LOG("no supported sk available");
+ return -1;
+ }
+ return 0;
+}
+
+/** @brief Initialize common to stream packets params, including partial data source */
+static rnp_result_t
+init_packet_params(pgp_source_packet_param_t &param)
+{
+ param.origsrc = NULL;
+
+ /* save packet header */
+ rnp_result_t ret = stream_peek_packet_hdr(param.readsrc, &param.hdr);
+ if (ret) {
+ return ret;
+ }
+ src_skip(param.readsrc, param.hdr.hdr_len);
+ if (!param.hdr.partial) {
+ return RNP_SUCCESS;
+ }
+
+ /* initialize partial reader if needed */
+ pgp_source_t *partsrc = (pgp_source_t *) calloc(1, sizeof(*partsrc));
+ if (!partsrc) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ rnp_result_t errcode = init_partial_pkt_src(partsrc, param.readsrc, param.hdr);
+ if (errcode) {
+ free(partsrc);
+ return errcode;
+ }
+ param.origsrc = param.readsrc;
+ param.readsrc = partsrc;
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+init_literal_src(pgp_source_t *src, pgp_source_t *readsrc)
+{
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ pgp_source_literal_param_t *param;
+ uint8_t format = 0;
+ uint8_t nlen = 0;
+ uint8_t timestamp[4];
+
+ if (!init_src_common(src, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_source_literal_param_t *) src->param;
+ param->pkt.readsrc = readsrc;
+ src->read = literal_src_read;
+ src->close = literal_src_close;
+ src->type = PGP_STREAM_LITERAL;
+
+ /* Reading packet length/checking whether it is partial */
+ if ((ret = init_packet_params(param->pkt))) {
+ goto finish;
+ }
+
+ /* data format */
+ if (!src_read_eq(param->pkt.readsrc, &format, 1)) {
+ RNP_LOG("failed to read data format");
+ ret = RNP_ERROR_READ;
+ goto finish;
+ }
+
+ switch (format) {
+ case 'b':
+ case 't':
+ case 'u':
+ case 'l':
+ case '1':
+ break;
+ default:
+ RNP_LOG("unknown data format %" PRIu8, format);
+ ret = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ param->hdr.format = format;
+ /* file name */
+ if (!src_read_eq(param->pkt.readsrc, &nlen, 1)) {
+ RNP_LOG("failed to read file name length");
+ ret = RNP_ERROR_READ;
+ goto finish;
+ }
+ if (nlen && !src_read_eq(param->pkt.readsrc, param->hdr.fname, nlen)) {
+ RNP_LOG("failed to read file name");
+ ret = RNP_ERROR_READ;
+ goto finish;
+ }
+ param->hdr.fname[nlen] = 0;
+ param->hdr.fname_len = nlen;
+ /* timestamp */
+ if (!src_read_eq(param->pkt.readsrc, timestamp, 4)) {
+ RNP_LOG("failed to read file timestamp");
+ ret = RNP_ERROR_READ;
+ goto finish;
+ }
+ param->hdr.timestamp = read_uint32(timestamp);
+
+ if (!param->pkt.hdr.indeterminate && !param->pkt.hdr.partial) {
+ /* format filename-length filename timestamp */
+ const uint16_t nbytes = 1 + 1 + nlen + 4;
+ if (param->pkt.hdr.pkt_len < nbytes) {
+ ret = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ src->size = param->pkt.hdr.pkt_len - nbytes;
+ src->knownsize = 1;
+ }
+ ret = RNP_SUCCESS;
+finish:
+ if (ret != RNP_SUCCESS) {
+ src_close(src);
+ }
+ return ret;
+}
+
+bool
+get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr)
+{
+ pgp_source_literal_param_t *param;
+
+ if (src->type != PGP_STREAM_LITERAL) {
+ RNP_LOG("wrong stream");
+ return false;
+ }
+
+ param = (pgp_source_literal_param_t *) src->param;
+ *hdr = param->hdr;
+ return true;
+}
+
+rnp_result_t
+init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc)
+{
+ rnp_result_t errcode = RNP_ERROR_GENERIC;
+ pgp_source_compressed_param_t *param;
+ uint8_t alg;
+ int zret;
+
+ if (!init_src_common(src, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_source_compressed_param_t *) src->param;
+ param->pkt.readsrc = readsrc;
+ src->read = compressed_src_read;
+ src->close = compressed_src_close;
+ src->type = PGP_STREAM_COMPRESSED;
+
+ /* Reading packet length/checking whether it is partial */
+ errcode = init_packet_params(param->pkt);
+ if (errcode != RNP_SUCCESS) {
+ goto finish;
+ }
+
+ /* Reading compression algorithm */
+ if (!src_read_eq(param->pkt.readsrc, &alg, 1)) {
+ RNP_LOG("failed to read compression algorithm");
+ errcode = RNP_ERROR_READ;
+ goto finish;
+ }
+
+ /* Initializing decompression */
+ switch (alg) {
+ case PGP_C_NONE:
+ break;
+ case PGP_C_ZIP:
+ case PGP_C_ZLIB:
+ (void) memset(&param->z, 0x0, sizeof(param->z));
+ zret =
+ alg == PGP_C_ZIP ? (int) inflateInit2(&param->z, -15) : (int) inflateInit(&param->z);
+ if (zret != Z_OK) {
+ RNP_LOG("failed to init zlib, error %d", zret);
+ errcode = RNP_ERROR_READ;
+ goto finish;
+ }
+ break;
+#ifdef HAVE_BZLIB_H
+ case PGP_C_BZIP2:
+ (void) memset(&param->bz, 0x0, sizeof(param->bz));
+ zret = BZ2_bzDecompressInit(&param->bz, 0, 0);
+ if (zret != BZ_OK) {
+ RNP_LOG("failed to init bz, error %d", zret);
+ errcode = RNP_ERROR_READ;
+ goto finish;
+ }
+ break;
+#endif
+ default:
+ RNP_LOG("unknown compression algorithm: %d", (int) alg);
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ param->alg = (pgp_compression_type_t) alg;
+ param->inlen = 0;
+ param->inpos = 0;
+
+ errcode = RNP_SUCCESS;
+finish:
+ if (errcode != RNP_SUCCESS) {
+ src_close(src);
+ }
+ return errcode;
+}
+
+bool
+get_compressed_src_alg(pgp_source_t *src, uint8_t *alg)
+{
+ pgp_source_compressed_param_t *param;
+
+ if (src->type != PGP_STREAM_COMPRESSED) {
+ RNP_LOG("wrong stream");
+ return false;
+ }
+
+ param = (pgp_source_compressed_param_t *) src->param;
+ *alg = param->alg;
+ return true;
+}
+
+bool
+get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr)
+{
+ uint8_t hdrbt[4] = {0};
+
+ if (!src_read_eq(src, hdrbt, 4)) {
+ return false;
+ }
+
+ hdr->version = hdrbt[0];
+ hdr->ealg = (pgp_symm_alg_t) hdrbt[1];
+ hdr->aalg = (pgp_aead_alg_t) hdrbt[2];
+ hdr->csize = hdrbt[3];
+
+ if (!(hdr->ivlen = pgp_cipher_aead_nonce_len(hdr->aalg))) {
+ RNP_LOG("wrong aead nonce length: alg %d", (int) hdr->aalg);
+ return false;
+ }
+
+ return src_read_eq(src, hdr->iv, hdr->ivlen);
+}
+
+#define MAX_RECIPIENTS 16384
+
+static rnp_result_t
+encrypted_read_packet_data(pgp_source_encrypted_param_t *param)
+{
+ int ptype;
+ /* Reading pk/sk encrypted session key(s) */
+ try {
+ size_t errors = 0;
+ bool stop = false;
+ while (!stop) {
+ if (param->pubencs.size() + param->symencs.size() + errors > MAX_RECIPIENTS) {
+ RNP_LOG("Too many recipients of the encrypted message. Aborting.");
+ return RNP_ERROR_BAD_STATE;
+ }
+ uint8_t ptag;
+ if (!src_peek_eq(param->pkt.readsrc, &ptag, 1)) {
+ RNP_LOG("failed to read packet header");
+ return RNP_ERROR_READ;
+ }
+ ptype = get_packet_type(ptag);
+ switch (ptype) {
+ case PGP_PKT_SK_SESSION_KEY: {
+ pgp_sk_sesskey_t skey;
+ rnp_result_t ret = skey.parse(*param->pkt.readsrc);
+ if (ret == RNP_ERROR_READ) {
+ RNP_LOG("SKESK: Premature end of data.");
+ return ret;
+ }
+ if (ret) {
+ RNP_LOG("Failed to parse SKESK, skipping.");
+ errors++;
+ 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 == RNP_ERROR_READ) {
+ RNP_LOG("PKESK: Premature end of data.");
+ return ret;
+ }
+ if (ret) {
+ RNP_LOG("Failed to parse PKESK, skipping.");
+ errors++;
+ continue;
+ }
+ param->pubencs.push_back(pkey);
+ break;
+ }
+ case PGP_PKT_SE_DATA:
+ case PGP_PKT_SE_IP_DATA:
+ case PGP_PKT_AEAD_ENCRYPTED:
+ stop = true;
+ break;
+ default:
+ RNP_LOG("unknown packet type: %d", ptype);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ }
+ } catch (const rnp::rnp_exception &e) {
+ RNP_LOG("%s: %d", e.what(), e.code());
+ return e.code();
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_GENERIC;
+ }
+
+ /* Reading packet length/checking whether it is partial */
+ rnp_result_t errcode = init_packet_params(param->pkt);
+ if (errcode) {
+ return errcode;
+ }
+
+ /* Reading header of encrypted packet */
+ if (ptype == PGP_PKT_AEAD_ENCRYPTED) {
+ param->auth_type = rnp::AuthType::AEADv1;
+ uint8_t hdr[4];
+ if (!src_peek_eq(param->pkt.readsrc, hdr, 4)) {
+ return RNP_ERROR_READ;
+ }
+
+ if (!get_aead_src_hdr(param->pkt.readsrc, &param->aead_hdr)) {
+ RNP_LOG("failed to read AEAD header");
+ return RNP_ERROR_READ;
+ }
+
+ /* check AEAD encrypted data packet header */
+ if (param->aead_hdr.version != 1) {
+ RNP_LOG("unknown aead ver: %d", param->aead_hdr.version);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if ((param->aead_hdr.aalg != PGP_AEAD_EAX) && (param->aead_hdr.aalg != PGP_AEAD_OCB)) {
+ RNP_LOG("unknown aead alg: %d", (int) param->aead_hdr.aalg);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ 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.hdr[0];
+ memcpy(param->aead_ad + 1, hdr, 4);
+ memset(param->aead_ad + 5, 0, 8);
+ } else if (ptype == PGP_PKT_SE_IP_DATA) {
+ uint8_t mdcver;
+ if (!src_read_eq(param->pkt.readsrc, &mdcver, 1)) {
+ return RNP_ERROR_READ;
+ }
+
+ if (mdcver != 1) {
+ RNP_LOG("unknown mdc ver: %d", (int) mdcver);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ param->auth_type = rnp::AuthType::MDC;
+ }
+ param->auth_validated = false;
+
+ return RNP_SUCCESS;
+}
+
+#define MAX_HIDDEN_TRIES 64
+
+static rnp_result_t
+init_encrypted_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc)
+{
+ if (!init_src_common(src, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ pgp_source_encrypted_param_t *param = new (std::nothrow) pgp_source_encrypted_param_t();
+ if (!param) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ src->param = param;
+ param->pkt.readsrc = readsrc;
+ param->handler = handler;
+
+ src->close = encrypted_src_close;
+ src->finish = encrypted_src_finish;
+ src->type = PGP_STREAM_ENCRYPTED;
+
+ /* Read the packet-related information */
+ rnp_result_t errcode = encrypted_read_packet_data(param);
+ if (errcode) {
+ goto finish;
+ }
+
+ src->read = !param->use_cfb() ? encrypted_src_read_aead : encrypted_src_read_cfb;
+
+ /* Obtaining the symmetric key */
+ if (!handler->password_provider) {
+ RNP_LOG("no password provider");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ /* informing handler about the available pubencs/symencs */
+ if (handler->on_recipients) {
+ handler->on_recipients(param->pubencs, param->symencs, handler->param);
+ }
+
+ bool have_key;
+ have_key = false;
+ /* Trying public-key decryption */
+ if (!param->pubencs.empty()) {
+ if (!handler->key_provider) {
+ RNP_LOG("no key provider");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ pgp_key_request_ctx_t keyctx(PGP_OP_DECRYPT, true, PGP_KEY_SEARCH_KEYID);
+
+ size_t pubidx = 0;
+ size_t hidden_tries = 0;
+ errcode = RNP_ERROR_NO_SUITABLE_KEY;
+ while (pubidx < param->pubencs.size()) {
+ auto &pubenc = param->pubencs[pubidx];
+ keyctx.search.by.keyid = pubenc.key_id;
+ /* Get the key if any */
+ pgp_key_t *seckey = pgp_request_key(handler->key_provider, &keyctx);
+ if (!seckey) {
+ pubidx++;
+ continue;
+ }
+ /* Check whether key fits our needs */
+ bool hidden = pubenc.key_id == pgp_key_id_t({});
+ if (!hidden || (++hidden_tries >= MAX_HIDDEN_TRIES)) {
+ pubidx++;
+ }
+ if (!seckey->has_secret() || !seckey->can_encrypt()) {
+ continue;
+ }
+ /* Check whether key is of required algorithm for hidden keyid */
+ if (hidden && seckey->alg() != pubenc.alg) {
+ continue;
+ }
+ /* Decrypt key */
+ rnp::KeyLocker seclock(*seckey);
+ if (!seckey->unlock(*handler->password_provider, PGP_OP_DECRYPT)) {
+ errcode = RNP_ERROR_BAD_PASSWORD;
+ continue;
+ }
+
+ /* Try to initialize the decryption */
+ rnp::LogStop logstop(hidden);
+ if (encrypted_try_key(param, &pubenc, &seckey->pkt(), *handler->ctx->ctx)) {
+ have_key = true;
+ /* inform handler that we used this pubenc */
+ if (handler->on_decryption_start) {
+ handler->on_decryption_start(&pubenc, NULL, handler->param);
+ }
+ break;
+ }
+ }
+ }
+
+ /* Trying password-based decryption */
+ if (!have_key && !param->symencs.empty()) {
+ rnp::secure_array<char, MAX_PASSWORD_LENGTH> password;
+ 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;
+ }
+
+ int intres = encrypted_try_password(param, password.data());
+ if (intres > 0) {
+ have_key = true;
+ } else if (intres < 0) {
+ errcode = RNP_ERROR_NOT_SUPPORTED;
+ } else {
+ errcode = RNP_ERROR_BAD_PASSWORD;
+ }
+ }
+
+ /* report decryption start to the handler */
+ if (handler->on_decryption_info) {
+ handler->on_decryption_info(param->auth_type == rnp::AuthType::MDC,
+ param->aead_hdr.aalg,
+ param->salg,
+ handler->param);
+ }
+
+ if (!have_key) {
+ RNP_LOG("failed to obtain decrypting key or password");
+ if (!errcode) {
+ errcode = RNP_ERROR_NO_SUITABLE_KEY;
+ }
+ goto finish;
+ }
+ errcode = RNP_SUCCESS;
+finish:
+ if (errcode != RNP_SUCCESS) {
+ src_close(src);
+ }
+ return errcode;
+}
+
+static rnp_result_t
+init_cleartext_signed_src(pgp_source_t *src)
+{
+ char buf[64];
+ size_t hdrlen = strlen(ST_CLEAR_BEGIN);
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) src->param;
+
+ /* checking header line */
+ if (!src_read_eq(param->readsrc, buf, hdrlen)) {
+ RNP_LOG("failed to read header");
+ return RNP_ERROR_READ;
+ }
+
+ if (memcmp(ST_CLEAR_BEGIN, buf, hdrlen)) {
+ RNP_LOG("wrong header");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* eol */
+ if (!src_skip_eol(param->readsrc)) {
+ RNP_LOG("no eol after the cleartext header");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* parsing Hash headers */
+ if (!cleartext_parse_headers(src)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* now we are good to go */
+ param->clr_fline = true;
+ return RNP_SUCCESS;
+}
+
+#define MAX_SIG_ERRORS 65536
+
+static rnp_result_t
+init_signed_src(pgp_parse_handler_t *handler, pgp_source_t *src, pgp_source_t *readsrc)
+{
+ rnp_result_t errcode = RNP_ERROR_GENERIC;
+ pgp_source_signed_param_t *param;
+ uint8_t ptag;
+ int ptype;
+ pgp_signature_t * sig = NULL;
+ bool cleartext;
+ size_t sigerrors = 0;
+
+ if (!init_src_common(src, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ try {
+ param = new pgp_source_signed_param_t();
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ src->param = param;
+
+ cleartext = is_cleartext_source(readsrc);
+ param->readsrc = readsrc;
+ param->handler = handler;
+ param->cleartext = cleartext;
+ param->stripped_crs = 0;
+ src->read = cleartext ? cleartext_src_read : signed_src_read;
+ src->close = signed_src_close;
+ src->finish = signed_src_finish;
+ src->type = cleartext ? PGP_STREAM_CLEARTEXT : PGP_STREAM_SIGNED;
+
+ /* we need key provider to validate signatures */
+ if (!handler->key_provider) {
+ RNP_LOG("no key provider");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ if (cleartext) {
+ errcode = init_cleartext_signed_src(src);
+ goto finish;
+ }
+
+ /* Reading one-pass and signature packets */
+ while (true) {
+ /* stop early if we are in zip-bomb with erroneous packets */
+ if (sigerrors >= MAX_SIG_ERRORS) {
+ RNP_LOG("Too many one-pass/signature errors. Stopping.");
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+
+ size_t readb = readsrc->readb;
+ if (!src_peek_eq(readsrc, &ptag, 1)) {
+ RNP_LOG("failed to read packet header");
+ errcode = RNP_ERROR_READ;
+ goto finish;
+ }
+
+ ptype = get_packet_type(ptag);
+
+ if (ptype == PGP_PKT_ONE_PASS_SIG) {
+ if (param->onepasses.size() >= MAX_SIGNATURES) {
+ RNP_LOG("Too many one-pass signatures.");
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ pgp_one_pass_sig_t onepass;
+ try {
+ errcode = onepass.parse(*readsrc);
+ } catch (const std::exception &e) {
+ errcode = RNP_ERROR_GENERIC;
+ }
+ if (errcode) {
+ if (errcode == RNP_ERROR_READ) {
+ goto finish;
+ }
+ if (readb == readsrc->readb) {
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ sigerrors++;
+ continue;
+ }
+
+ try {
+ param->onepasses.push_back(onepass);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ errcode = RNP_ERROR_OUT_OF_MEMORY;
+ goto finish;
+ }
+
+ /* adding hash context */
+ try {
+ add_hash_for_sig(param, onepass.type, onepass.halg);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to create hash %d for onepass %d : %s.",
+ (int) onepass.halg,
+ (int) onepass.type,
+ e.what());
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ if (onepass.nested) {
+ /* despite the name non-zero value means that it is the last one-pass */
+ break;
+ }
+ } else if (ptype == PGP_PKT_SIGNATURE) {
+ /* no need to check the error here - we already know tag */
+ if (signed_read_single_signature(param, readsrc, &sig)) {
+ sigerrors++;
+ }
+ /* adding hash context */
+ if (sig) {
+ try {
+ add_hash_for_sig(param, sig->type(), sig->halg);
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to create hash %d for sig %d : %s.",
+ (int) sig->halg,
+ (int) sig->type(),
+ e.what());
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+ }
+ } else {
+ break;
+ }
+
+ /* check if we are not it endless loop */
+ if (readb == readsrc->readb) {
+ errcode = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ /* for detached signature we'll get eof */
+ if (src_eof(readsrc)) {
+ param->detached = true;
+ break;
+ }
+ }
+
+ /* checking what we have now */
+ if (param->onepasses.empty() && param->sigs.empty()) {
+ RNP_LOG("no signatures");
+ errcode = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+ if (!param->onepasses.empty() && !param->sigs.empty()) {
+ RNP_LOG("warning: one-passes are mixed with signatures");
+ }
+
+ errcode = RNP_SUCCESS;
+finish:
+ if (errcode != RNP_SUCCESS) {
+ src_close(src);
+ }
+
+ return errcode;
+}
+
+pgp_processing_ctx_t::~pgp_processing_ctx_t()
+{
+ for (auto &src : sources) {
+ src_close(&src);
+ }
+}
+
+/** @brief build PGP source sequence down to the literal data packet
+ *
+ **/
+static rnp_result_t
+init_packet_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src)
+{
+ pgp_source_t *lsrc = &src;
+ size_t srcnum = ctx.sources.size();
+
+ while (1) {
+ uint8_t ptag = 0;
+ if (!src_peek_eq(lsrc, &ptag, 1)) {
+ RNP_LOG("cannot read packet tag");
+ return RNP_ERROR_READ;
+ }
+
+ int type = get_packet_type(ptag);
+ if (type < 0) {
+ RNP_LOG("wrong pkt tag %d", (int) ptag);
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ if (ctx.sources.size() - srcnum == MAXIMUM_NESTING_LEVEL) {
+ RNP_LOG("Too many nested OpenPGP packets");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ pgp_source_t psrc = {};
+ rnp_result_t ret = RNP_ERROR_BAD_FORMAT;
+ switch (type) {
+ case PGP_PKT_PK_SESSION_KEY:
+ case PGP_PKT_SK_SESSION_KEY:
+ ret = init_encrypted_src(&ctx.handler, &psrc, lsrc);
+ break;
+ case PGP_PKT_ONE_PASS_SIG:
+ case PGP_PKT_SIGNATURE:
+ ret = init_signed_src(&ctx.handler, &psrc, lsrc);
+ break;
+ case PGP_PKT_COMPRESSED:
+ ret = init_compressed_src(&psrc, lsrc);
+ break;
+ case PGP_PKT_LITDATA:
+ if ((lsrc != &src) && (lsrc->type != PGP_STREAM_ENCRYPTED) &&
+ (lsrc->type != PGP_STREAM_SIGNED) && (lsrc->type != PGP_STREAM_COMPRESSED)) {
+ RNP_LOG("unexpected literal pkt");
+ ret = RNP_ERROR_BAD_FORMAT;
+ break;
+ }
+ ret = init_literal_src(&psrc, lsrc);
+ break;
+ case PGP_PKT_MARKER:
+ if (ctx.sources.size() != srcnum) {
+ RNP_LOG("Warning: marker packet wrapped in pgp stream.");
+ }
+ ret = stream_parse_marker(*lsrc);
+ if (ret) {
+ RNP_LOG("Invalid marker packet");
+ return ret;
+ }
+ continue;
+ default:
+ RNP_LOG("unexpected pkt %d", type);
+ ret = RNP_ERROR_BAD_FORMAT;
+ }
+
+ if (ret) {
+ return ret;
+ }
+
+ try {
+ ctx.sources.push_back(psrc);
+ lsrc = &ctx.sources.back();
+ } catch (const std::exception &e) {
+ src_close(&psrc);
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ if (lsrc->type == PGP_STREAM_LITERAL) {
+ ctx.literal_src = lsrc;
+ ctx.msg_type = PGP_MESSAGE_NORMAL;
+ return RNP_SUCCESS;
+ }
+ if (lsrc->type == PGP_STREAM_SIGNED) {
+ ctx.signed_src = lsrc;
+ pgp_source_signed_param_t *param = (pgp_source_signed_param_t *) lsrc->param;
+ if (param->detached) {
+ ctx.msg_type = PGP_MESSAGE_DETACHED;
+ return RNP_SUCCESS;
+ }
+ }
+ }
+}
+
+static rnp_result_t
+init_cleartext_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src)
+{
+ pgp_source_t clrsrc = {};
+ rnp_result_t res;
+
+ if ((res = init_signed_src(&ctx.handler, &clrsrc, &src))) {
+ return res;
+ }
+ try {
+ ctx.sources.push_back(clrsrc);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ src_close(&clrsrc);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+init_armored_sequence(pgp_processing_ctx_t &ctx, pgp_source_t &src)
+{
+ pgp_source_t armorsrc = {};
+ rnp_result_t res;
+
+ if ((res = init_armored_src(&armorsrc, &src))) {
+ return res;
+ }
+
+ try {
+ ctx.sources.push_back(armorsrc);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ src_close(&armorsrc);
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ return init_packet_sequence(ctx, ctx.sources.back());
+}
+
+rnp_result_t
+process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src)
+{
+ rnp_result_t res = RNP_ERROR_BAD_FORMAT;
+ rnp_result_t fres;
+ pgp_processing_ctx_t ctx = {};
+ pgp_source_t * decsrc = NULL;
+ pgp_source_t datasrc = {0};
+ pgp_dest_t * outdest = NULL;
+ bool closeout = true;
+ uint8_t * readbuf = NULL;
+
+ ctx.handler = *handler;
+ /* Building readers sequence. Checking whether it is binary data */
+ if (is_pgp_source(src)) {
+ res = init_packet_sequence(ctx, src);
+ } else {
+ /* Trying armored or cleartext data */
+ if (is_cleartext_source(&src)) {
+ /* Initializing cleartext message */
+ res = init_cleartext_sequence(ctx, src);
+ } else if (is_armored_source(&src)) {
+ /* Initializing armored message */
+ res = init_armored_sequence(ctx, src);
+ } else {
+ RNP_LOG("not an OpenPGP data provided");
+ res = RNP_ERROR_BAD_FORMAT;
+ goto finish;
+ }
+ }
+
+ if (res != RNP_SUCCESS) {
+ goto finish;
+ }
+
+ if ((readbuf = (uint8_t *) calloc(1, PGP_INPUT_CACHE_SIZE)) == NULL) {
+ RNP_LOG("allocation failure");
+ res = RNP_ERROR_OUT_OF_MEMORY;
+ goto finish;
+ }
+
+ if (ctx.msg_type == PGP_MESSAGE_DETACHED) {
+ /* detached signature case */
+ if (!handler->ctx->detached) {
+ RNP_LOG("Unexpected detached signature input.");
+ res = RNP_ERROR_BAD_STATE;
+ goto finish;
+ }
+ if (!handler->src_provider || !handler->src_provider(handler, &datasrc)) {
+ RNP_LOG("no data source for detached signature verification");
+ res = RNP_ERROR_READ;
+ goto finish;
+ }
+
+ while (!datasrc.eof) {
+ size_t read = 0;
+ if (!src_read(&datasrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (read > 0) {
+ signed_src_update(ctx.signed_src, readbuf, read);
+ }
+ }
+ src_close(&datasrc);
+ } else {
+ if (handler->ctx->detached) {
+ RNP_LOG("Detached signature expected.");
+ res = RNP_ERROR_BAD_STATE;
+ goto finish;
+ }
+ /* file processing case */
+ decsrc = &ctx.sources.back();
+ char * filename = NULL;
+ uint32_t mtime = 0;
+
+ if (ctx.literal_src) {
+ auto *param = static_cast<pgp_source_literal_param_t *>(ctx.literal_src->param);
+ filename = param->hdr.fname;
+ mtime = param->hdr.timestamp;
+ }
+
+ if (!handler->dest_provider ||
+ !handler->dest_provider(handler, &outdest, &closeout, filename, mtime)) {
+ res = RNP_ERROR_WRITE;
+ goto finish;
+ }
+
+ /* reading the input */
+ while (!decsrc->eof) {
+ size_t read = 0;
+ if (!src_read(decsrc, readbuf, PGP_INPUT_CACHE_SIZE, &read)) {
+ res = RNP_ERROR_GENERIC;
+ break;
+ }
+ if (!read) {
+ continue;
+ }
+ if (ctx.signed_src) {
+ signed_src_update(ctx.signed_src, readbuf, read);
+ }
+ dst_write(outdest, readbuf, read);
+ if (outdest->werr != RNP_SUCCESS) {
+ RNP_LOG("failed to output data");
+ res = RNP_ERROR_WRITE;
+ break;
+ }
+ }
+ }
+
+ /* finalizing the input. Signatures are checked on this step */
+ if (res == RNP_SUCCESS) {
+ for (auto &ctxsrc : ctx.sources) {
+ fres = src_finish(&ctxsrc);
+ if (fres) {
+ res = fres;
+ }
+ }
+ }
+
+ if (closeout && (ctx.msg_type != PGP_MESSAGE_DETACHED)) {
+ dst_close(outdest, res != RNP_SUCCESS);
+ }
+
+finish:
+ free(readbuf);
+ return res;
+}
diff --git a/src/librepgp/stream-parse.h b/src/librepgp/stream-parse.h
new file mode 100644
index 0000000..4f22b9a
--- /dev/null
+++ b/src/librepgp/stream-parse.h
@@ -0,0 +1,123 @@
+/*
+ * Copyright (c) 2017, [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 STREAM_PARSE_H_
+#define STREAM_PARSE_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "stream-ctx.h"
+#include "stream-packet.h"
+
+typedef struct pgp_parse_handler_t pgp_parse_handler_t;
+typedef struct pgp_signature_info_t pgp_signature_info_t;
+typedef bool pgp_destination_func_t(pgp_parse_handler_t *handler,
+ pgp_dest_t ** dst,
+ bool * closedst,
+ const char * filename,
+ uint32_t mtime);
+typedef bool pgp_source_func_t(pgp_parse_handler_t *handler, pgp_source_t *src);
+typedef void pgp_signatures_func_t(const std::vector<pgp_signature_info_t> &sigs, void *param);
+
+typedef void pgp_on_recipients_func_t(const std::vector<pgp_pk_sesskey_t> &recipients,
+ const std::vector<pgp_sk_sesskey_t> &passwords,
+ void * param);
+typedef void pgp_decryption_start_func_t(pgp_pk_sesskey_t *pubenc,
+ pgp_sk_sesskey_t *symenc,
+ void * param);
+typedef void pgp_decryption_info_func_t(bool mdc,
+ pgp_aead_alg_t aead,
+ pgp_symm_alg_t salg,
+ void * param);
+typedef void pgp_decryption_done_func_t(bool validated, void *param);
+
+/* handler used to return needed information during pgp source processing */
+typedef struct pgp_parse_handler_t {
+ pgp_password_provider_t *password_provider; /* if NULL then default will be used */
+ pgp_key_provider_t * key_provider; /* must be set when key is required, i.e. during
+ signing/verification/public key encryption and
+ deryption */
+ pgp_destination_func_t *dest_provider; /* called when destination stream is required */
+ pgp_source_func_t * src_provider; /* required to provider source during the detached
+ signature verification */
+ pgp_on_recipients_func_t * on_recipients; /* called before decryption start */
+ pgp_decryption_start_func_t *on_decryption_start; /* called when decryption key obtained */
+ pgp_decryption_info_func_t * on_decryption_info; /* called when decryption is started */
+ pgp_decryption_done_func_t * on_decryption_done; /* called when decryption is finished */
+ pgp_signatures_func_t * on_signatures; /* for signature verification results */
+
+ rnp_ctx_t *ctx; /* operation context */
+ void * param; /* additional parameters */
+} pgp_parse_handler_t;
+
+/* @brief Process the OpenPGP source: file, memory, stdin
+ * Function will parse input data, provided by any source conforming to pgp_source_t,
+ * autodetecting whether it is armored, cleartext or binary.
+ * @param handler handler to respond on stream reader callbacks
+ * @param src initialized source with cache
+ * @return RNP_SUCCESS on success or error code otherwise
+ **/
+rnp_result_t process_pgp_source(pgp_parse_handler_t *handler, pgp_source_t &src);
+
+/* @brief Init source with OpenPGP compressed data packet
+ * @param src allocated pgp_source_t structure
+ * @param readsrc source to read compressed data from
+ * @return RNP_SUCCESS on success or error code otherwise
+ */
+rnp_result_t init_compressed_src(pgp_source_t *src, pgp_source_t *readsrc);
+
+/* @brief Get compression algorithm used in compressed source
+ * @param src compressed source, initialized with init_compressed_src
+ * @param alg algorithm will be written here. Cannot be NULL.
+ * @return true if operation succeeded and alg is populate or false otherwise
+ */
+bool get_compressed_src_alg(pgp_source_t *src, uint8_t *alg);
+
+/* @brief Init source with OpenPGP literal data packet
+ * @param src allocated pgp_source_t structure
+ * @param readsrc source to read literal data from
+ * @return RNP_SUCCESS on success or error code otherwise
+ */
+rnp_result_t init_literal_src(pgp_source_t *src, pgp_source_t *readsrc);
+
+/* @brief Get the literal data packet information fields (not the OpenPGP packet header)
+ * @param src literal data source, initialized with init_literal_src
+ * @param hdr pointer to header structure, where result will be stored
+ * @return true on success or false otherwise
+ */
+bool get_literal_src_hdr(pgp_source_t *src, pgp_literal_hdr_t *hdr);
+
+/* @brief Get the AEAD-encrypted packet information fields (not the OpenPGP packet header)
+ * @param src AEAD-encrypted data source (starting from packet data itself, not the header)
+ * @param hdr pointer to header structure, where result will be stored
+ * @return true on success or false otherwise
+ */
+bool get_aead_src_hdr(pgp_source_t *src, pgp_aead_hdr_t *hdr);
+
+#endif
diff --git a/src/librepgp/stream-sig.cpp b/src/librepgp/stream-sig.cpp
new file mode 100644
index 0000000..6f3bc81
--- /dev/null
+++ b/src/librepgp/stream-sig.cpp
@@ -0,0 +1,1557 @@
+/*
+ * 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:
+ *
+ * 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 "config.h"
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#include <type_traits>
+#include <stdexcept>
+#include <rnp/rnp_def.h>
+#include "types.h"
+#include "stream-sig.h"
+#include "stream-packet.h"
+#include "stream-armor.h"
+#include "pgp-key.h"
+#include "crypto/signatures.h"
+
+#include <time.h>
+
+void
+signature_hash_key(const pgp_key_pkt_t &key, rnp::Hash &hash)
+{
+ uint8_t hdr[3] = {0x99, 0x00, 0x00};
+ if (key.hashed_data) {
+ write_uint16(hdr + 1, key.hashed_len);
+ hash.add(hdr, 3);
+ hash.add(key.hashed_data, key.hashed_len);
+ return;
+ }
+
+ /* call self recursively if hashed data is not filled, to overcome const restriction */
+ pgp_key_pkt_t keycp(key, true);
+ keycp.fill_hashed_data();
+ signature_hash_key(keycp, hash);
+}
+
+void
+signature_hash_userid(const pgp_userid_pkt_t &uid, rnp::Hash &hash, pgp_version_t sigver)
+{
+ if (sigver < PGP_V4) {
+ hash.add(uid.uid, uid.uid_len);
+ return;
+ }
+
+ uint8_t hdr[5] = {0};
+ switch (uid.tag) {
+ case PGP_PKT_USER_ID:
+ hdr[0] = 0xB4;
+ break;
+ case PGP_PKT_USER_ATTR:
+ hdr[0] = 0xD1;
+ break;
+ default:
+ RNP_LOG("wrong uid");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ STORE32BE(hdr + 1, uid.uid_len);
+ hash.add(hdr, 5);
+ hash.add(uid.uid, uid.uid_len);
+}
+
+std::unique_ptr<rnp::Hash>
+signature_hash_certification(const pgp_signature_t & sig,
+ const pgp_key_pkt_t & key,
+ const pgp_userid_pkt_t &userid)
+{
+ auto hash = signature_init(key.material, sig.halg);
+ signature_hash_key(key, *hash);
+ signature_hash_userid(userid, *hash, sig.version);
+ return hash;
+}
+
+std::unique_ptr<rnp::Hash>
+signature_hash_binding(const pgp_signature_t &sig,
+ const pgp_key_pkt_t & key,
+ const pgp_key_pkt_t & subkey)
+{
+ auto hash = signature_init(key.material, sig.halg);
+ signature_hash_key(key, *hash);
+ signature_hash_key(subkey, *hash);
+ return hash;
+}
+
+std::unique_ptr<rnp::Hash>
+signature_hash_direct(const pgp_signature_t &sig, const pgp_key_pkt_t &key)
+{
+ 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)
+{
+ sigs.clear();
+ /* 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();
+ }
+ if (armor.eof()) {
+ break;
+ }
+ int ptag = stream_pkt_type(armor.src());
+ if (ptag != PGP_PKT_SIGNATURE) {
+ RNP_LOG("wrong signature tag: %d", ptag);
+ sigs.clear();
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ sigs.emplace_back();
+ rnp_result_t ret = sigs.back().parse(armor.src());
+ if (ret) {
+ sigs.clear();
+ return ret;
+ }
+ }
+ if (armor.error()) {
+ sigs.clear();
+ return RNP_ERROR_READ;
+ }
+ return RNP_SUCCESS;
+}
+
+pgp_sig_subpkt_t::pgp_sig_subpkt_t(const pgp_sig_subpkt_t &src)
+{
+ type = src.type;
+ len = src.len;
+ data = (uint8_t *) malloc(len);
+ if (!data) {
+ throw std::bad_alloc();
+ }
+ memcpy(data, src.data, len);
+ critical = src.critical;
+ hashed = src.hashed;
+ parsed = false;
+ parse();
+}
+
+pgp_sig_subpkt_t::pgp_sig_subpkt_t(pgp_sig_subpkt_t &&src)
+{
+ type = src.type;
+ len = src.len;
+ data = src.data;
+ src.data = NULL;
+ critical = src.critical;
+ hashed = src.hashed;
+ parsed = src.parsed;
+ memcpy(&fields, &src.fields, sizeof(fields));
+ src.fields = {};
+}
+
+pgp_sig_subpkt_t &
+pgp_sig_subpkt_t::operator=(pgp_sig_subpkt_t &&src)
+{
+ if (&src == this) {
+ return *this;
+ }
+
+ if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) {
+ delete fields.sig;
+ }
+ type = src.type;
+ len = src.len;
+ free(data);
+ data = src.data;
+ src.data = NULL;
+ critical = src.critical;
+ hashed = src.hashed;
+ parsed = src.parsed;
+ fields = src.fields;
+ src.fields = {};
+ return *this;
+}
+
+pgp_sig_subpkt_t &
+pgp_sig_subpkt_t::operator=(const pgp_sig_subpkt_t &src)
+{
+ if (&src == this) {
+ return *this;
+ }
+
+ if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) {
+ delete fields.sig;
+ }
+ type = src.type;
+ len = src.len;
+ free(data);
+ data = (uint8_t *) malloc(len);
+ if (!data) {
+ throw std::bad_alloc();
+ }
+ memcpy(data, src.data, len);
+ critical = src.critical;
+ hashed = src.hashed;
+ parsed = false;
+ fields = {};
+ parse();
+ return *this;
+}
+
+bool
+pgp_sig_subpkt_t::parse()
+{
+ bool oklen = true;
+ bool checked = true;
+
+ switch (type) {
+ case PGP_SIG_SUBPKT_CREATION_TIME:
+ if (!hashed) {
+ RNP_LOG("creation time subpacket must be hashed");
+ checked = false;
+ }
+ if ((oklen = len == 4)) {
+ fields.create = read_uint32(data);
+ }
+ break;
+ case PGP_SIG_SUBPKT_EXPIRATION_TIME:
+ case PGP_SIG_SUBPKT_KEY_EXPIRY:
+ if ((oklen = len == 4)) {
+ fields.expiry = read_uint32(data);
+ }
+ break;
+ case PGP_SIG_SUBPKT_EXPORT_CERT:
+ if ((oklen = len == 1)) {
+ fields.exportable = data[0] != 0;
+ }
+ break;
+ case PGP_SIG_SUBPKT_TRUST:
+ if ((oklen = len == 2)) {
+ fields.trust.level = data[0];
+ fields.trust.amount = data[1];
+ }
+ break;
+ case PGP_SIG_SUBPKT_REGEXP:
+ fields.regexp.str = (const char *) data;
+ fields.regexp.len = len;
+ break;
+ case PGP_SIG_SUBPKT_REVOCABLE:
+ if ((oklen = len == 1)) {
+ fields.revocable = data[0] != 0;
+ }
+ break;
+ case PGP_SIG_SUBPKT_PREFERRED_SKA:
+ case PGP_SIG_SUBPKT_PREFERRED_HASH:
+ case PGP_SIG_SUBPKT_PREF_COMPRESS:
+ case PGP_SIG_SUBPKT_PREFERRED_AEAD:
+ fields.preferred.arr = data;
+ fields.preferred.len = len;
+ break;
+ case PGP_SIG_SUBPKT_REVOCATION_KEY:
+ if ((oklen = len == 22)) {
+ fields.revocation_key.klass = data[0];
+ fields.revocation_key.pkalg = (pgp_pubkey_alg_t) data[1];
+ fields.revocation_key.fp = &data[2];
+ }
+ break;
+ case PGP_SIG_SUBPKT_ISSUER_KEY_ID:
+ if ((oklen = len == 8)) {
+ fields.issuer = data;
+ }
+ break;
+ case PGP_SIG_SUBPKT_NOTATION_DATA:
+ if ((oklen = len >= 8)) {
+ memcpy(fields.notation.flags, data, 4);
+ fields.notation.human = fields.notation.flags[0] & 0x80;
+ fields.notation.nlen = read_uint16(&data[4]);
+ fields.notation.vlen = read_uint16(&data[6]);
+ if (len != 8 + fields.notation.nlen + fields.notation.vlen) {
+ oklen = false;
+ } else {
+ fields.notation.name = data + 8;
+ fields.notation.value = fields.notation.name + fields.notation.nlen;
+ }
+ }
+ break;
+ case PGP_SIG_SUBPKT_KEYSERV_PREFS:
+ if ((oklen = len >= 1)) {
+ fields.ks_prefs.no_modify = (data[0] & 0x80) != 0;
+ }
+ break;
+ case PGP_SIG_SUBPKT_PREF_KEYSERV:
+ fields.preferred_ks.uri = (const char *) data;
+ fields.preferred_ks.len = len;
+ break;
+ case PGP_SIG_SUBPKT_PRIMARY_USER_ID:
+ if ((oklen = len == 1)) {
+ fields.primary_uid = data[0] != 0;
+ }
+ break;
+ case PGP_SIG_SUBPKT_POLICY_URI:
+ fields.policy.uri = (const char *) data;
+ fields.policy.len = len;
+ break;
+ case PGP_SIG_SUBPKT_KEY_FLAGS:
+ if ((oklen = len >= 1)) {
+ fields.key_flags = data[0];
+ }
+ break;
+ case PGP_SIG_SUBPKT_SIGNERS_USER_ID:
+ fields.signer.uid = (const char *) data;
+ fields.signer.len = len;
+ break;
+ case PGP_SIG_SUBPKT_REVOCATION_REASON:
+ if ((oklen = len >= 1)) {
+ fields.revocation_reason.code = (pgp_revocation_type_t) data[0];
+ fields.revocation_reason.str = (const char *) &data[1];
+ fields.revocation_reason.len = len - 1;
+ }
+ break;
+ case PGP_SIG_SUBPKT_FEATURES:
+ if ((oklen = len >= 1)) {
+ fields.features = data[0];
+ }
+ break;
+ case PGP_SIG_SUBPKT_SIGNATURE_TARGET:
+ if ((oklen = len >= 18)) {
+ fields.sig_target.pkalg = (pgp_pubkey_alg_t) data[0];
+ fields.sig_target.halg = (pgp_hash_alg_t) data[1];
+ fields.sig_target.hash = &data[2];
+ fields.sig_target.hlen = len - 2;
+ }
+ break;
+ case PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE:
+ try {
+ /* parse signature */
+ pgp_packet_body_t pkt(data, len);
+ pgp_signature_t sig;
+ oklen = checked = !sig.parse(pkt);
+ if (checked) {
+ fields.sig = new pgp_signature_t(std::move(sig));
+ }
+ break;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return false;
+ }
+ case PGP_SIG_SUBPKT_ISSUER_FPR:
+ if ((oklen = len >= 21)) {
+ fields.issuer_fp.version = data[0];
+ fields.issuer_fp.fp = &data[1];
+ fields.issuer_fp.len = len - 1;
+ }
+ break;
+ case PGP_SIG_SUBPKT_PRIVATE_100:
+ case PGP_SIG_SUBPKT_PRIVATE_101:
+ case PGP_SIG_SUBPKT_PRIVATE_102:
+ case PGP_SIG_SUBPKT_PRIVATE_103:
+ case PGP_SIG_SUBPKT_PRIVATE_104:
+ case PGP_SIG_SUBPKT_PRIVATE_105:
+ case PGP_SIG_SUBPKT_PRIVATE_106:
+ case PGP_SIG_SUBPKT_PRIVATE_107:
+ case PGP_SIG_SUBPKT_PRIVATE_108:
+ case PGP_SIG_SUBPKT_PRIVATE_109:
+ case PGP_SIG_SUBPKT_PRIVATE_110:
+ oklen = true;
+ checked = !critical;
+ if (!checked) {
+ RNP_LOG("unknown critical private subpacket %d", (int) type);
+ }
+ break;
+ case PGP_SIG_SUBPKT_RESERVED_1:
+ case PGP_SIG_SUBPKT_RESERVED_8:
+ case PGP_SIG_SUBPKT_PLACEHOLDER:
+ case PGP_SIG_SUBPKT_RESERVED_13:
+ case PGP_SIG_SUBPKT_RESERVED_14:
+ case PGP_SIG_SUBPKT_RESERVED_15:
+ case PGP_SIG_SUBPKT_RESERVED_17:
+ case PGP_SIG_SUBPKT_RESERVED_18:
+ case PGP_SIG_SUBPKT_RESERVED_19:
+ /* do not report reserved/placeholder subpacket */
+ return !critical;
+ default:
+ RNP_LOG("unknown subpacket : %d", (int) type);
+ return !critical;
+ }
+
+ if (!oklen) {
+ RNP_LOG("wrong len %d of subpacket type %d", (int) len, (int) type);
+ } else {
+ parsed = 1;
+ }
+ return oklen && checked;
+}
+
+pgp_sig_subpkt_t::~pgp_sig_subpkt_t()
+{
+ if (parsed && (type == PGP_SIG_SUBPKT_EMBEDDED_SIGNATURE)) {
+ delete fields.sig;
+ }
+ free(data);
+}
+
+pgp_signature_t::pgp_signature_t(const pgp_signature_t &src)
+{
+ version = src.version;
+ type_ = src.type_;
+ palg = src.palg;
+ halg = src.halg;
+ memcpy(lbits, src.lbits, sizeof(src.lbits));
+ creation_time = src.creation_time;
+ signer = src.signer;
+
+ hashed_len = src.hashed_len;
+ hashed_data = NULL;
+ if (src.hashed_data) {
+ if (!(hashed_data = (uint8_t *) malloc(hashed_len))) {
+ throw std::bad_alloc();
+ }
+ memcpy(hashed_data, src.hashed_data, hashed_len);
+ }
+ material_len = src.material_len;
+ material_buf = NULL;
+ if (src.material_buf) {
+ if (!(material_buf = (uint8_t *) malloc(material_len))) {
+ throw std::bad_alloc();
+ }
+ memcpy(material_buf, src.material_buf, material_len);
+ }
+ subpkts = src.subpkts;
+}
+
+pgp_signature_t::pgp_signature_t(pgp_signature_t &&src)
+{
+ version = src.version;
+ type_ = src.type_;
+ palg = src.palg;
+ halg = src.halg;
+ memcpy(lbits, src.lbits, sizeof(src.lbits));
+ creation_time = src.creation_time;
+ signer = src.signer;
+ hashed_len = src.hashed_len;
+ hashed_data = src.hashed_data;
+ src.hashed_data = NULL;
+ material_len = src.material_len;
+ material_buf = src.material_buf;
+ src.material_buf = NULL;
+ subpkts = std::move(src.subpkts);
+}
+
+pgp_signature_t &
+pgp_signature_t::operator=(pgp_signature_t &&src)
+{
+ if (this == &src) {
+ return *this;
+ }
+
+ version = src.version;
+ type_ = src.type_;
+ palg = src.palg;
+ halg = src.halg;
+ memcpy(lbits, src.lbits, sizeof(src.lbits));
+ creation_time = src.creation_time;
+ signer = src.signer;
+ hashed_len = src.hashed_len;
+ free(hashed_data);
+ hashed_data = src.hashed_data;
+ src.hashed_data = NULL;
+ material_len = src.material_len;
+ free(material_buf);
+ material_buf = src.material_buf;
+ src.material_buf = NULL;
+ subpkts = std::move(src.subpkts);
+
+ return *this;
+}
+
+pgp_signature_t &
+pgp_signature_t::operator=(const pgp_signature_t &src)
+{
+ if (this == &src) {
+ return *this;
+ }
+
+ version = src.version;
+ type_ = src.type_;
+ palg = src.palg;
+ halg = src.halg;
+ memcpy(lbits, src.lbits, sizeof(src.lbits));
+ creation_time = src.creation_time;
+ signer = src.signer;
+
+ hashed_len = src.hashed_len;
+ free(hashed_data);
+ hashed_data = NULL;
+ if (src.hashed_data) {
+ if (!(hashed_data = (uint8_t *) malloc(hashed_len))) {
+ throw std::bad_alloc();
+ }
+ memcpy(hashed_data, src.hashed_data, hashed_len);
+ }
+ material_len = src.material_len;
+ free(material_buf);
+ material_buf = NULL;
+ if (src.material_buf) {
+ if (!(material_buf = (uint8_t *) malloc(material_len))) {
+ throw std::bad_alloc();
+ }
+ memcpy(material_buf, src.material_buf, material_len);
+ }
+ subpkts = src.subpkts;
+
+ return *this;
+}
+
+bool
+pgp_signature_t::operator==(const pgp_signature_t &src) const
+{
+ if ((lbits[0] != src.lbits[0]) || (lbits[1] != src.lbits[1])) {
+ return false;
+ }
+ if ((hashed_len != src.hashed_len) || memcmp(hashed_data, src.hashed_data, hashed_len)) {
+ return false;
+ }
+ return (material_len == src.material_len) &&
+ !memcmp(material_buf, src.material_buf, material_len);
+}
+
+bool
+pgp_signature_t::operator!=(const pgp_signature_t &src) const
+{
+ return !(*this == src);
+}
+
+pgp_signature_t::~pgp_signature_t()
+{
+ free(hashed_data);
+ free(material_buf);
+}
+
+pgp_sig_id_t
+pgp_signature_t::get_id() const
+{
+ 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<decltype(res)>::value == PGP_SHA1_HASH_SIZE,
+ "pgp_sig_id_t size mismatch");
+ hash->finish(res.data());
+ return res;
+}
+
+pgp_sig_subpkt_t *
+pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed)
+{
+ if (version < PGP_V4) {
+ return NULL;
+ }
+ for (auto &subpkt : subpkts) {
+ /* if hashed is false then accept any hashed/not hashed subpacket */
+ if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) {
+ return &subpkt;
+ }
+ }
+ return NULL;
+}
+
+const pgp_sig_subpkt_t *
+pgp_signature_t::get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const
+{
+ if (version < PGP_V4) {
+ return NULL;
+ }
+ for (auto &subpkt : subpkts) {
+ /* if hashed is false then accept any hashed/not hashed subpacket */
+ if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) {
+ return &subpkt;
+ }
+ }
+ return NULL;
+}
+
+bool
+pgp_signature_t::has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed) const
+{
+ if (version < PGP_V4) {
+ return false;
+ }
+ for (auto &subpkt : subpkts) {
+ /* if hashed is false then accept any hashed/not hashed subpacket */
+ if ((subpkt.type == stype) && (!hashed || subpkt.hashed)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool
+pgp_signature_t::has_keyid() const
+{
+ return (version < PGP_V4) || has_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false) ||
+ has_keyfp();
+}
+
+pgp_key_id_t
+pgp_signature_t::keyid() const noexcept
+{
+ /* version 3 uses signature field */
+ if (version < PGP_V4) {
+ return signer;
+ }
+
+ /* version 4 and up use subpackets */
+ pgp_key_id_t res{};
+ static_assert(std::tuple_size<decltype(res)>::value == PGP_KEY_ID_SIZE,
+ "pgp_key_id_t size mismatch");
+
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false);
+ if (subpkt) {
+ memcpy(res.data(), subpkt->fields.issuer, PGP_KEY_ID_SIZE);
+ return res;
+ }
+ if ((subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR))) {
+ memcpy(res.data(),
+ subpkt->fields.issuer_fp.fp + subpkt->fields.issuer_fp.len - PGP_KEY_ID_SIZE,
+ PGP_KEY_ID_SIZE);
+ return res;
+ }
+ return res;
+}
+
+void
+pgp_signature_t::set_keyid(const pgp_key_id_t &id)
+{
+ if (version < PGP_V4) {
+ signer = id;
+ return;
+ }
+
+ static_assert(std::tuple_size<std::remove_reference<decltype(id)>::type>::value ==
+ PGP_KEY_ID_SIZE,
+ "pgp_key_id_t size mismatch");
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, PGP_KEY_ID_SIZE, true);
+ subpkt.parsed = true;
+ subpkt.hashed = false;
+ memcpy(subpkt.data, id.data(), PGP_KEY_ID_SIZE);
+ subpkt.fields.issuer = subpkt.data;
+}
+
+bool
+pgp_signature_t::has_keyfp() const
+{
+ if (version < PGP_V4) {
+ return false;
+ }
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR);
+ return subpkt && (subpkt->fields.issuer_fp.len <= PGP_FINGERPRINT_SIZE);
+}
+
+pgp_fingerprint_t
+pgp_signature_t::keyfp() const noexcept
+{
+ pgp_fingerprint_t res{};
+ if (version < PGP_V4) {
+ return res;
+ }
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR);
+ if (!subpkt || (subpkt->fields.issuer_fp.len > sizeof(res.fingerprint))) {
+ return res;
+ }
+ res.length = subpkt->fields.issuer_fp.len;
+ memcpy(res.fingerprint, subpkt->fields.issuer_fp.fp, subpkt->fields.issuer_fp.len);
+ return res;
+}
+
+void
+pgp_signature_t::set_keyfp(const pgp_fingerprint_t &fp)
+{
+ if (version < PGP_V4) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR, 1 + fp.length, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ subpkt.data[0] = 4;
+ memcpy(subpkt.data + 1, fp.fingerprint, fp.length);
+ subpkt.fields.issuer_fp.len = fp.length;
+ subpkt.fields.issuer_fp.version = subpkt.data[0];
+ subpkt.fields.issuer_fp.fp = subpkt.data + 1;
+}
+
+uint32_t
+pgp_signature_t::creation() const
+{
+ if (version < PGP_V4) {
+ return creation_time;
+ }
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME);
+ return subpkt ? subpkt->fields.create : 0;
+}
+
+void
+pgp_signature_t::set_creation(uint32_t ctime)
+{
+ if (version < PGP_V4) {
+ creation_time = ctime;
+ return;
+ }
+
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_CREATION_TIME, 4, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ STORE32BE(subpkt.data, ctime);
+ subpkt.fields.create = ctime;
+}
+
+uint32_t
+pgp_signature_t::expiration() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME);
+ return subpkt ? subpkt->fields.expiry : 0;
+}
+
+void
+pgp_signature_t::set_expiration(uint32_t etime)
+{
+ if (version < PGP_V4) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_EXPIRATION_TIME, 4, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ STORE32BE(subpkt.data, etime);
+ subpkt.fields.expiry = etime;
+}
+
+uint32_t
+pgp_signature_t::key_expiration() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY);
+ return subpkt ? subpkt->fields.expiry : 0;
+}
+
+void
+pgp_signature_t::set_key_expiration(uint32_t etime)
+{
+ if (version < PGP_V4) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_EXPIRY, 4, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ STORE32BE(subpkt.data, etime);
+ subpkt.fields.expiry = etime;
+}
+
+uint8_t
+pgp_signature_t::key_flags() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS);
+ return subpkt ? subpkt->fields.key_flags : 0;
+}
+
+void
+pgp_signature_t::set_key_flags(uint8_t flags)
+{
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEY_FLAGS, 1, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ subpkt.data[0] = flags;
+ subpkt.fields.key_flags = flags;
+}
+
+bool
+pgp_signature_t::primary_uid() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID);
+ return subpkt ? subpkt->fields.primary_uid : false;
+}
+
+void
+pgp_signature_t::set_primary_uid(bool primary)
+{
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PRIMARY_USER_ID, 1, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ subpkt.data[0] = primary;
+ subpkt.fields.primary_uid = primary;
+}
+
+std::vector<uint8_t>
+pgp_signature_t::preferred(pgp_sig_subpacket_type_t type) const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(type);
+ return subpkt ? std::vector<uint8_t>(subpkt->fields.preferred.arr,
+ subpkt->fields.preferred.arr +
+ subpkt->fields.preferred.len) :
+ std::vector<uint8_t>();
+}
+
+void
+pgp_signature_t::set_preferred(const std::vector<uint8_t> &data, pgp_sig_subpacket_type_t type)
+{
+ if (version < PGP_V4) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ if (data.empty()) {
+ pgp_sig_subpkt_t *subpkt = get_subpkt(type);
+ if (subpkt) {
+ remove_subpkt(subpkt);
+ }
+ return;
+ }
+
+ pgp_sig_subpkt_t &subpkt = add_subpkt(type, data.size(), true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ memcpy(subpkt.data, data.data(), data.size());
+ subpkt.fields.preferred.arr = subpkt.data;
+ subpkt.fields.preferred.len = data.size();
+}
+
+std::vector<uint8_t>
+pgp_signature_t::preferred_symm_algs() const
+{
+ return preferred(PGP_SIG_SUBPKT_PREFERRED_SKA);
+}
+
+void
+pgp_signature_t::set_preferred_symm_algs(const std::vector<uint8_t> &algs)
+{
+ set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_SKA);
+}
+
+std::vector<uint8_t>
+pgp_signature_t::preferred_hash_algs() const
+{
+ return preferred(PGP_SIG_SUBPKT_PREFERRED_HASH);
+}
+
+void
+pgp_signature_t::set_preferred_hash_algs(const std::vector<uint8_t> &algs)
+{
+ set_preferred(algs, PGP_SIG_SUBPKT_PREFERRED_HASH);
+}
+
+std::vector<uint8_t>
+pgp_signature_t::preferred_z_algs() const
+{
+ return preferred(PGP_SIG_SUBPKT_PREF_COMPRESS);
+}
+
+void
+pgp_signature_t::set_preferred_z_algs(const std::vector<uint8_t> &algs)
+{
+ set_preferred(algs, PGP_SIG_SUBPKT_PREF_COMPRESS);
+}
+
+uint8_t
+pgp_signature_t::key_server_prefs() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS);
+ return subpkt ? subpkt->data[0] : 0;
+}
+
+void
+pgp_signature_t::set_key_server_prefs(uint8_t prefs)
+{
+ if (version < PGP_V4) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_KEYSERV_PREFS, 1, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ subpkt.data[0] = prefs;
+ subpkt.fields.ks_prefs.no_modify = prefs & 0x80;
+}
+
+std::string
+pgp_signature_t::key_server() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV);
+ return subpkt ? std::string((char *) subpkt->data, subpkt->len) : "";
+}
+
+void
+pgp_signature_t::set_key_server(const std::string &uri)
+{
+ if (version < PGP_V4) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ if (uri.empty()) {
+ pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV);
+ if (subpkt) {
+ remove_subpkt(subpkt);
+ }
+ return;
+ }
+
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_PREF_KEYSERV, uri.size(), true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ memcpy(subpkt.data, uri.data(), uri.size());
+ subpkt.fields.preferred_ks.uri = (char *) subpkt.data;
+ subpkt.fields.preferred_ks.len = uri.size();
+}
+
+uint8_t
+pgp_signature_t::trust_level() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST);
+ return subpkt ? subpkt->fields.trust.level : 0;
+}
+
+uint8_t
+pgp_signature_t::trust_amount() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_TRUST);
+ return subpkt ? subpkt->fields.trust.amount : 0;
+}
+
+void
+pgp_signature_t::set_trust(uint8_t level, uint8_t amount)
+{
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_TRUST, 2, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ subpkt.data[0] = level;
+ subpkt.data[1] = amount;
+ subpkt.fields.trust.level = level;
+ subpkt.fields.trust.amount = amount;
+}
+
+bool
+pgp_signature_t::revocable() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCABLE);
+ return subpkt ? subpkt->fields.revocable : true;
+}
+
+void
+pgp_signature_t::set_revocable(bool status)
+{
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCABLE, 1, true);
+ subpkt.parsed = true;
+ subpkt.hashed = true;
+ subpkt.data[0] = status;
+ subpkt.fields.revocable = status;
+}
+
+std::string
+pgp_signature_t::revocation_reason() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON);
+ return subpkt ? std::string(subpkt->fields.revocation_reason.str,
+ subpkt->fields.revocation_reason.len) :
+ "";
+}
+
+pgp_revocation_type_t
+pgp_signature_t::revocation_code() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON);
+ return subpkt ? subpkt->fields.revocation_reason.code : PGP_REVOCATION_NO_REASON;
+}
+
+void
+pgp_signature_t::set_revocation_reason(pgp_revocation_type_t code, const std::string &reason)
+{
+ size_t datalen = 1 + reason.size();
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_REVOCATION_REASON, datalen, true);
+ subpkt.hashed = true;
+ subpkt.data[0] = code;
+ memcpy(subpkt.data + 1, reason.data(), reason.size());
+
+ if (!subpkt.parse()) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+}
+
+bool
+pgp_signature_t::key_has_features(pgp_key_feature_t flags) const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_FEATURES);
+ return subpkt ? subpkt->data[0] & flags : false;
+}
+
+void
+pgp_signature_t::set_key_features(pgp_key_feature_t flags)
+{
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_FEATURES, 1, true);
+ subpkt.hashed = true;
+ subpkt.data[0] = flags;
+ subpkt.fields.features = flags;
+ subpkt.parsed = true;
+}
+
+std::string
+pgp_signature_t::signer_uid() const
+{
+ const pgp_sig_subpkt_t *subpkt = get_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID);
+ return subpkt ? std::string(subpkt->fields.signer.uid, subpkt->fields.signer.len) : "";
+}
+
+void
+pgp_signature_t::set_signer_uid(const std::string &uid)
+{
+ pgp_sig_subpkt_t &subpkt = add_subpkt(PGP_SIG_SUBPKT_SIGNERS_USER_ID, uid.size(), true);
+ subpkt.hashed = true;
+ memcpy(subpkt.data, uid.data(), uid.size());
+ subpkt.fields.signer.uid = (const char *) subpkt.data;
+ subpkt.fields.signer.len = subpkt.len;
+ subpkt.parsed = true;
+}
+
+void
+pgp_signature_t::add_notation(const std::string & name,
+ const std::vector<uint8_t> &value,
+ bool human,
+ bool critical)
+{
+ auto nlen = name.size();
+ auto vlen = value.size();
+ if ((nlen > 0xffff) || (vlen > 0xffff)) {
+ RNP_LOG("wrong length");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ auto &subpkt = add_subpkt(PGP_SIG_SUBPKT_NOTATION_DATA, 8 + nlen + vlen, false);
+ subpkt.hashed = true;
+ subpkt.critical = critical;
+ if (human) {
+ subpkt.data[0] = 0x80;
+ }
+ write_uint16(subpkt.data + 4, nlen);
+ write_uint16(subpkt.data + 6, vlen);
+ memcpy(subpkt.data + 8, name.data(), nlen);
+ memcpy(subpkt.data + 8 + nlen, value.data(), vlen);
+ if (!subpkt.parse()) {
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+}
+
+void
+pgp_signature_t::add_notation(const std::string &name, const std::string &value, bool critical)
+{
+ add_notation(name, std::vector<uint8_t>(value.begin(), value.end()), true, critical);
+}
+
+void
+pgp_signature_t::set_embedded_sig(const pgp_signature_t &esig)
+{
+ 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);
+ subpkt.hashed = false;
+ size_t skip = esigpkt.raw.size() - len;
+ memcpy(subpkt.data, esigpkt.raw.data() + skip, len);
+ subpkt.fields.sig = new pgp_signature_t(esig);
+ subpkt.parsed = true;
+}
+
+pgp_sig_subpkt_t &
+pgp_signature_t::add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse)
+{
+ if (version < PGP_V4) {
+ RNP_LOG("wrong signature version");
+ throw std::invalid_argument("version");
+ }
+
+ uint8_t *newdata = (uint8_t *) calloc(1, datalen);
+ if (!newdata) {
+ RNP_LOG("Allocation failed");
+ throw std::bad_alloc();
+ }
+
+ pgp_sig_subpkt_t *subpkt = NULL;
+ if (reuse && (subpkt = get_subpkt(type))) {
+ *subpkt = {};
+ } else {
+ subpkts.push_back({});
+ subpkt = &subpkts.back();
+ }
+
+ subpkt->data = newdata;
+ subpkt->type = type;
+ subpkt->len = datalen;
+ return *subpkt;
+}
+
+void
+pgp_signature_t::remove_subpkt(pgp_sig_subpkt_t *subpkt)
+{
+ for (auto it = subpkts.begin(); it < subpkts.end(); it++) {
+ if (&*it == subpkt) {
+ subpkts.erase(it);
+ return;
+ }
+ }
+}
+
+bool
+pgp_signature_t::matches_onepass(const pgp_one_pass_sig_t &onepass) const
+{
+ if (!has_keyid()) {
+ return false;
+ }
+ return (halg == onepass.halg) && (palg == onepass.palg) && (type_ == onepass.type) &&
+ (onepass.keyid == keyid());
+}
+
+rnp_result_t
+pgp_signature_t::parse_v3(pgp_packet_body_t &pkt)
+{
+ /* parse v3-specific fields, not the whole signature */
+ uint8_t buf[16] = {};
+ if (!pkt.get(buf, 16)) {
+ RNP_LOG("cannot get enough bytes");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* length of hashed data, 5 */
+ if (buf[0] != 5) {
+ RNP_LOG("wrong length of hashed data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* hashed data */
+ free(hashed_data);
+ if (!(hashed_data = (uint8_t *) malloc(5))) {
+ RNP_LOG("allocation failed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ memcpy(hashed_data, &buf[1], 5);
+ hashed_len = 5;
+ /* signature type */
+ type_ = (pgp_sig_type_t) buf[1];
+ /* creation time */
+ creation_time = read_uint32(&buf[2]);
+ /* signer's key id */
+ static_assert(std::tuple_size<decltype(signer)>::value == PGP_KEY_ID_SIZE,
+ "v3 signer field size mismatch");
+ memcpy(signer.data(), &buf[6], PGP_KEY_ID_SIZE);
+ /* public key algorithm */
+ palg = (pgp_pubkey_alg_t) buf[14];
+ /* hash algorithm */
+ halg = (pgp_hash_alg_t) buf[15];
+ return RNP_SUCCESS;
+}
+
+#define MAX_SUBPACKETS 64
+
+bool
+pgp_signature_t::parse_subpackets(uint8_t *buf, size_t len, bool hashed)
+{
+ bool res = true;
+
+ while (len > 0) {
+ if (subpkts.size() >= MAX_SUBPACKETS) {
+ RNP_LOG("too many signature subpackets");
+ return false;
+ }
+ if (len < 2) {
+ RNP_LOG("got single byte %d", (int) *buf);
+ return false;
+ }
+
+ /* subpacket length */
+ size_t splen;
+ if (*buf < 192) {
+ splen = *buf;
+ buf++;
+ len--;
+ } else if (*buf < 255) {
+ splen = ((buf[0] - 192) << 8) + buf[1] + 192;
+ buf += 2;
+ len -= 2;
+ } else {
+ if (len < 5) {
+ RNP_LOG("got 4-byte len but only %d bytes in buffer", (int) len);
+ return false;
+ }
+ splen = read_uint32(&buf[1]);
+ buf += 5;
+ len -= 5;
+ }
+
+ if (splen < 1) {
+ RNP_LOG("got subpacket with 0 length");
+ return false;
+ }
+
+ /* subpacket data */
+ if (len < splen) {
+ RNP_LOG("got subpacket len %d, while only %d bytes left", (int) splen, (int) len);
+ return false;
+ }
+
+ pgp_sig_subpkt_t subpkt;
+ if (!(subpkt.data = (uint8_t *) malloc(splen - 1))) {
+ RNP_LOG("subpacket data allocation failed");
+ return false;
+ }
+
+ subpkt.type = (pgp_sig_subpacket_type_t)(*buf & 0x7f);
+ subpkt.critical = !!(*buf & 0x80);
+ subpkt.hashed = hashed;
+ subpkt.parsed = 0;
+ memcpy(subpkt.data, buf + 1, splen - 1);
+ subpkt.len = splen - 1;
+
+ res = res && subpkt.parse();
+ subpkts.push_back(std::move(subpkt));
+ len -= splen;
+ buf += splen;
+ }
+ return res;
+}
+
+rnp_result_t
+pgp_signature_t::parse_v4(pgp_packet_body_t &pkt)
+{
+ /* parse v4-specific fields, not the whole signature */
+ uint8_t buf[5];
+ if (!pkt.get(buf, 5)) {
+ RNP_LOG("cannot get first 5 bytes");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+
+ /* signature type */
+ type_ = (pgp_sig_type_t) buf[0];
+ /* public key algorithm */
+ palg = (pgp_pubkey_alg_t) buf[1];
+ /* hash algorithm */
+ halg = (pgp_hash_alg_t) buf[2];
+ /* hashed subpackets length */
+ uint16_t splen = read_uint16(&buf[3]);
+ /* hashed subpackets length + 2 bytes of length of unhashed subpackets */
+ if (pkt.left() < (size_t)(splen + 2)) {
+ RNP_LOG("wrong packet or hashed subpackets length");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* building hashed data */
+ free(hashed_data);
+ if (!(hashed_data = (uint8_t *) malloc(splen + 6))) {
+ RNP_LOG("allocation failed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ hashed_data[0] = version;
+ memcpy(hashed_data + 1, buf, 5);
+
+ if (!pkt.get(hashed_data + 6, splen)) {
+ RNP_LOG("cannot get hashed subpackets data");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ hashed_len = splen + 6;
+ /* parsing hashed subpackets */
+ if (!parse_subpackets(hashed_data + 6, splen, true)) {
+ RNP_LOG("failed to parse hashed subpackets");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* reading unhashed subpackets */
+ if (!pkt.get(splen)) {
+ RNP_LOG("cannot get unhashed len");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ if (pkt.left() < splen) {
+ RNP_LOG("not enough data for unhashed subpackets");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ std::vector<uint8_t> spbuf(splen);
+ if (!pkt.get(spbuf.data(), splen)) {
+ RNP_LOG("read of unhashed subpackets failed");
+ return RNP_ERROR_READ;
+ }
+ if (!parse_subpackets(spbuf.data(), splen, false)) {
+ RNP_LOG("failed to parse unhashed subpackets");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+pgp_signature_t::parse(pgp_packet_body_t &pkt)
+{
+ uint8_t ver = 0;
+ if (!pkt.get(ver)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ version = (pgp_version_t) ver;
+
+ /* v3 or v4 signature body */
+ rnp_result_t res;
+ if ((ver == PGP_V2) || (ver == PGP_V3)) {
+ res = parse_v3(pkt);
+ } else if (ver == PGP_V4) {
+ res = parse_v4(pkt);
+ } else {
+ RNP_LOG("unknown signature version: %d", (int) ver);
+ res = RNP_ERROR_BAD_FORMAT;
+ }
+
+ if (res) {
+ return res;
+ }
+
+ /* left 16 bits of the hash */
+ if (!pkt.get(lbits, 2)) {
+ RNP_LOG("not enough data for hash left bits");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ /* raw signature material */
+ material_len = pkt.left();
+ if (!material_len) {
+ RNP_LOG("No signature material");
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ material_buf = (uint8_t *) malloc(material_len);
+ if (!material_buf) {
+ RNP_LOG("Allocation failed");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ /* we cannot fail here */
+ pkt.get(material_buf, material_len);
+ /* check whether it can be parsed */
+ pgp_signature_material_t material = {};
+ if (!parse_material(material)) {
+ return RNP_ERROR_BAD_FORMAT;
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+pgp_signature_t::parse(pgp_source_t &src)
+{
+ pgp_packet_body_t pkt(PGP_PKT_SIGNATURE);
+ rnp_result_t res = pkt.read(src);
+ if (res) {
+ return res;
+ }
+ return parse(pkt);
+}
+
+bool
+pgp_signature_t::parse_material(pgp_signature_material_t &material) const
+{
+ pgp_packet_body_t pkt(material_buf, material_len);
+
+ switch (palg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ if (!pkt.get(material.rsa.s)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_DSA:
+ if (!pkt.get(material.dsa.r) || !pkt.get(material.dsa.s)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_EDDSA:
+ if (version < PGP_V4) {
+ RNP_LOG("Warning! v3 EdDSA signature.");
+ }
+ [[fallthrough]];
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ case PGP_PKA_ECDH:
+ if (!pkt.get(material.ecc.r) || !pkt.get(material.ecc.s)) {
+ return false;
+ }
+ break;
+ case PGP_PKA_ELGAMAL: /* we support reading it but will not validate */
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ if (!pkt.get(material.eg.r) || !pkt.get(material.eg.s)) {
+ return false;
+ }
+ break;
+ default:
+ RNP_LOG("Unknown pk algorithm : %d", (int) palg);
+ return false;
+ }
+
+ if (pkt.left()) {
+ RNP_LOG("extra %d bytes in signature packet", (int) pkt.left());
+ return false;
+ }
+ return true;
+}
+
+void
+pgp_signature_t::write(pgp_dest_t &dst) const
+{
+ if ((version < PGP_V2) || (version > PGP_V4)) {
+ RNP_LOG("don't know version %d", (int) version);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+
+ pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE);
+
+ if (version < PGP_V4) {
+ /* for v3 signatures hashed data includes only type + creation_time */
+ pktbody.add_byte(version);
+ pktbody.add_byte(hashed_len);
+ pktbody.add(hashed_data, hashed_len);
+ pktbody.add(signer);
+ pktbody.add_byte(palg);
+ pktbody.add_byte(halg);
+ } else {
+ /* for v4 sig->hashed_data must contain most of signature fields */
+ pktbody.add(hashed_data, hashed_len);
+ pktbody.add_subpackets(*this, false);
+ }
+ pktbody.add(lbits, 2);
+ /* write mpis */
+ pktbody.add(material_buf, material_len);
+ pktbody.write(dst);
+}
+
+void
+pgp_signature_t::write_material(const pgp_signature_material_t &material)
+{
+ pgp_packet_body_t pktbody(PGP_PKT_SIGNATURE);
+ switch (palg) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_SIGN_ONLY:
+ pktbody.add(material.rsa.s);
+ break;
+ case PGP_PKA_DSA:
+ pktbody.add(material.dsa.r);
+ pktbody.add(material.dsa.s);
+ break;
+ case PGP_PKA_EDDSA:
+ case PGP_PKA_ECDSA:
+ case PGP_PKA_SM2:
+ case PGP_PKA_ECDH:
+ pktbody.add(material.ecc.r);
+ pktbody.add(material.ecc.s);
+ break;
+ case PGP_PKA_ELGAMAL: /* we support writing it but will not generate */
+ case PGP_PKA_ELGAMAL_ENCRYPT_OR_SIGN:
+ pktbody.add(material.eg.r);
+ pktbody.add(material.eg.s);
+ break;
+ default:
+ RNP_LOG("Unknown pk algorithm : %d", (int) palg);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ free(material_buf);
+ material_buf = (uint8_t *) malloc(pktbody.size());
+ if (!material_buf) {
+ RNP_LOG("allocation failed");
+ throw rnp::rnp_exception(RNP_ERROR_OUT_OF_MEMORY);
+ }
+ memcpy(material_buf, pktbody.data(), pktbody.size());
+ material_len = pktbody.size();
+}
+
+void
+pgp_signature_t::fill_hashed_data()
+{
+ /* we don't have a need to write v2-v3 signatures */
+ if ((version < PGP_V2) || (version > PGP_V4)) {
+ RNP_LOG("don't know version %d", (int) version);
+ throw rnp::rnp_exception(RNP_ERROR_BAD_PARAMETERS);
+ }
+ pgp_packet_body_t hbody(PGP_PKT_RESERVED);
+ if (version < PGP_V4) {
+ hbody.add_byte(type());
+ hbody.add_uint32(creation_time);
+ } else {
+ hbody.add_byte(version);
+ hbody.add_byte(type());
+ hbody.add_byte(palg);
+ hbody.add_byte(halg);
+ hbody.add_subpackets(*this, true);
+ }
+
+ free(hashed_data);
+ hashed_data = (uint8_t *) malloc(hbody.size());
+ if (!hashed_data) {
+ RNP_LOG("allocation failed");
+ throw std::bad_alloc();
+ }
+ memcpy(hashed_data, hbody.data(), hbody.size());
+ hashed_len = hbody.size();
+}
+
+void
+rnp_selfsig_cert_info_t::populate(pgp_userid_pkt_t &uid, pgp_signature_t &sig)
+{
+ /* populate signature */
+ sig.set_type(PGP_CERT_POSITIVE);
+ if (key_expiration) {
+ sig.set_key_expiration(key_expiration);
+ }
+ if (key_flags) {
+ sig.set_key_flags(key_flags);
+ }
+ if (primary) {
+ sig.set_primary_uid(true);
+ }
+ if (!prefs.symm_algs.empty()) {
+ sig.set_preferred_symm_algs(prefs.symm_algs);
+ }
+ if (!prefs.hash_algs.empty()) {
+ sig.set_preferred_hash_algs(prefs.hash_algs);
+ }
+ if (!prefs.z_algs.empty()) {
+ sig.set_preferred_z_algs(prefs.z_algs);
+ }
+ if (!prefs.ks_prefs.empty()) {
+ sig.set_key_server_prefs(prefs.ks_prefs[0]);
+ }
+ if (!prefs.key_server.empty()) {
+ sig.set_key_server(prefs.key_server);
+ }
+ /* populate uid */
+ uid.tag = PGP_PKT_USER_ID;
+ 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, userid.data(), uid.uid_len);
+}
diff --git a/src/librepgp/stream-sig.h b/src/librepgp/stream-sig.h
new file mode 100644
index 0000000..4f36c38
--- /dev/null
+++ b/src/librepgp/stream-sig.h
@@ -0,0 +1,437 @@
+/*
+ * 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:
+ *
+ * 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 STREAM_SIG_H_
+#define STREAM_SIG_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "stream-packet.h"
+
+typedef struct pgp_signature_t {
+ private:
+ pgp_sig_type_t type_;
+ std::vector<uint8_t> preferred(pgp_sig_subpacket_type_t type) const;
+ void set_preferred(const std::vector<uint8_t> &data, pgp_sig_subpacket_type_t type);
+ rnp_result_t parse_v3(pgp_packet_body_t &pkt);
+ rnp_result_t parse_v4(pgp_packet_body_t &pkt);
+ bool parse_subpackets(uint8_t *buf, size_t len, bool hashed);
+
+ public:
+ pgp_version_t version;
+ /* common v3 and v4 fields */
+ pgp_pubkey_alg_t palg;
+ pgp_hash_alg_t halg;
+ uint8_t lbits[2];
+ uint8_t * hashed_data;
+ size_t hashed_len;
+ uint8_t * material_buf; /* raw signature material */
+ size_t material_len; /* raw signature material length */
+
+ /* v3 - only fields */
+ uint32_t creation_time;
+ pgp_key_id_t signer;
+
+ /* v4 - only fields */
+ std::vector<pgp_sig_subpkt_t> subpkts;
+
+ pgp_signature_t()
+ : type_(PGP_SIG_BINARY), version(PGP_VUNKNOWN), palg(PGP_PKA_NOTHING),
+ halg(PGP_HASH_UNKNOWN), hashed_data(NULL), hashed_len(0), material_buf(NULL),
+ material_len(0), creation_time(0){};
+ pgp_signature_t(const pgp_signature_t &src);
+ pgp_signature_t(pgp_signature_t &&src);
+ pgp_signature_t &operator=(pgp_signature_t &&src);
+ pgp_signature_t &operator=(const pgp_signature_t &src);
+ bool operator==(const pgp_signature_t &src) const;
+ bool operator!=(const pgp_signature_t &src) const;
+ ~pgp_signature_t();
+
+ /* @brief Get signature's type */
+ pgp_sig_type_t
+ type() const
+ {
+ return type_;
+ };
+ void
+ 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.
+ * @param stype subpacket type.
+ * @param hashed If true (default), then will search for subpacket only in hashed (i.e.
+ * covered by signature) area, otherwise will search in both hashed and non-hashed areas.
+ * @return pointer to the subpacket, or NULL if subpacket was not found.
+ */
+ pgp_sig_subpkt_t * get_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true);
+ const pgp_sig_subpkt_t *get_subpkt(pgp_sig_subpacket_type_t stype,
+ bool hashed = true) const;
+ /* @brief Check whether v4 signature has subpacket of the specified type/hashedness */
+ bool has_subpkt(pgp_sig_subpacket_type_t stype, bool hashed = true) const;
+ /* @brief Check whether signature has signing key id (via v3 field, or v4 key id/key fp
+ * subpacket) */
+ bool has_keyid() const;
+ /**
+ * @brief Get signer's key id if available. Availability may be checked via has_keyid().
+ * @return signer's key id if available, or empty (zero-filled) keyid otherwise.
+ */
+ pgp_key_id_t keyid() const noexcept;
+ /** @brief Set the signer's key id for the signature being populated. Version should be set
+ * prior of setting key id. */
+ void set_keyid(const pgp_key_id_t &id);
+ /**
+ * @brief Check whether signature has valid issuer fingerprint subpacket.
+ * @return true if there is one, and it can be safely returned via keyfp() method or false
+ * otherwise.
+ */
+ bool has_keyfp() const;
+ /**
+ * @brief Get signing key's fingerprint if it is available. Availability may be checked via
+ * has_keyfp() method.
+ * @return fingerprint (or empty zero-size fp in case it is unavailable)
+ */
+ pgp_fingerprint_t keyfp() const noexcept;
+
+ /** @brief Set signing key's fingerprint. Works only for signatures with version 4 and up,
+ * so version should be set prior to fingerprint. */
+ void set_keyfp(const pgp_fingerprint_t &fp);
+
+ /**
+ * @brief Get signature's creation time
+ * @return time in seconds since the Jan 1, 1970 UTC. 0 is the default value and returned
+ * even if creation time is not available
+ */
+ uint32_t creation() const;
+
+ /**
+ * @brief Set signature's creation time
+ * @param ctime creation time in seconds since the Jan 1, 1970 UTC.
+ */
+ void set_creation(uint32_t ctime);
+
+ /**
+ * @brief Get the signature's expiration time
+ * @return expiration time in seconds since the creation time. 0 if signature never
+ * expires.
+ */
+ uint32_t expiration() const;
+
+ /**
+ * @brief Set the signature's expiration time
+ * @param etime expiration time
+ */
+ void set_expiration(uint32_t etime);
+
+ /**
+ * @brief Get the key expiration time
+ * @return expiration time in seconds since the creation time. 0 if key never expires.
+ */
+ uint32_t key_expiration() const;
+
+ /**
+ * @brief Set the key expiration time
+ * @param etime expiration time
+ */
+ void set_key_expiration(uint32_t etime);
+
+ /**
+ * @brief Get the key flags
+ * @return byte of key flags. If there is no corresponding subpackets then 0 is returned.
+ */
+ uint8_t key_flags() const;
+
+ /**
+ * @brief Set the key flags
+ * @param flags byte of key flags
+ */
+ void set_key_flags(uint8_t flags);
+
+ /**
+ * @brief Get the primary user id flag
+ * @return true if user id is marked as primary or false otherwise
+ */
+ bool primary_uid() const;
+
+ /**
+ * @brief Set the primary user id flag
+ * @param primary true if user id should be marked as primary
+ */
+ void set_primary_uid(bool primary);
+
+ /** @brief Get preferred symmetric algorithms if any. If there are no ones then empty
+ * vector is returned. */
+ std::vector<uint8_t> preferred_symm_algs() const;
+
+ /** @brief Set the preferred symmetric algorithms. If empty vector is passed then
+ * corresponding subpacket is deleted. */
+ void set_preferred_symm_algs(const std::vector<uint8_t> &algs);
+
+ /** @brief Get preferred hash algorithms if any. If there are no ones then empty vector is
+ * returned.*/
+ std::vector<uint8_t> preferred_hash_algs() const;
+
+ /** @brief Set the preferred hash algorithms. If empty vector is passed then corresponding
+ * subpacket is deleted. */
+ void set_preferred_hash_algs(const std::vector<uint8_t> &algs);
+
+ /** @brief Get preferred compression algorithms if any. If there are no ones then empty
+ * vector is returned.*/
+ std::vector<uint8_t> preferred_z_algs() const;
+
+ /** @brief Set the preferred compression algorithms. If empty vector is passed then
+ * corresponding subpacket is deleted. */
+ void set_preferred_z_algs(const std::vector<uint8_t> &algs);
+
+ /** @brief Get key server preferences flags. If subpacket is not available then 0 is
+ * returned. */
+ uint8_t key_server_prefs() const;
+
+ /** @brief Set key server preferences flags. */
+ void set_key_server_prefs(uint8_t prefs);
+
+ /** @brief Get preferred key server URI, if available. Otherwise empty string is returned.
+ */
+ std::string key_server() const;
+
+ /** @brief Set preferred key server URI. If it is empty string then subpacket is deleted if
+ * it is available. */
+ void set_key_server(const std::string &uri);
+
+ /** @brief Get trust level, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14.
+ * for the detailed information on trust level and amount.
+ */
+ uint8_t trust_level() const;
+
+ /** @brief Get trust amount, if available. Otherwise will return 0. See RFC 4880, 5.2.3.14.
+ * for the detailed information on trust level and amount.
+ */
+ uint8_t trust_amount() const;
+
+ /** @brief Set the trust level and amount. See RFC 4880, 5.2.3.14.
+ * for the detailed information on trust level and amount.
+ */
+ void set_trust(uint8_t level, uint8_t amount);
+
+ /** @brief check whether signature is revocable. True by default.
+ */
+ bool revocable() const;
+
+ /** @brief Set the signature's revocability status.
+ */
+ void set_revocable(bool status);
+
+ /** @brief Get the key/subkey revocation reason in humand-readable form. If there is no
+ * revocation reason subpacket, then empty string will be returned.
+ */
+ std::string revocation_reason() const;
+
+ /** @brief Get the key/subkey revocation code. If there is no revocation reason subpacket,
+ * then PGP_REVOCATION_NO_REASON will be rerturned. See the RFC 4880, 5.2.3.24 for
+ * the detailed explanation.
+ */
+ pgp_revocation_type_t revocation_code() const;
+
+ /** @brief Set the revocation reason and code for key/subkey revocation signature. See the
+ * RFC 4880, 5.2.3.24 for the detailed explanation.
+ */
+ void set_revocation_reason(pgp_revocation_type_t code, const std::string &reason);
+
+ /**
+ * @brief Check whether signer's key supports certain feature(s). Makes sense only for
+ * self-signature, for more details see the RFC 4880bis, 5.2.3.25. If there is
+ * no corresponding subpacket then false will be returned.
+ * @param flags one or more flags, combined via bitwise OR operation.
+ * @return true if key is claimed to support all of the features listed in flags, or false
+ * otherwise
+ */
+ bool key_has_features(pgp_key_feature_t flags) const;
+
+ /**
+ * @brief Set the features supported by the signer's key, makes sense only for
+ * self-signature. For more details see the RFC 4880bis, 5.2.3.25.
+ * @param flags one or more flags, combined via bitwise OR operation.
+ */
+ void set_key_features(pgp_key_feature_t flags);
+
+ /** @brief Get signer's user id, if available. Otherwise empty string is returned. See the
+ * RFC 4880bis, 5.2.3.23 for details.
+ */
+ std::string signer_uid() const;
+
+ /**
+ * @brief Set the signer's uid, responcible for the signature creation. See the RFC
+ * 4880bis, 5.2.3.23 for details.
+ */
+ void set_signer_uid(const std::string &uid);
+
+ /**
+ * @brief Add notation.
+ */
+ void add_notation(const std::string & name,
+ const std::vector<uint8_t> &value,
+ bool human = true,
+ bool critical = false);
+
+ /**
+ * @brief Add human-readable notation.
+ */
+ void add_notation(const std::string &name,
+ const std::string &value,
+ bool critical = false);
+
+ /**
+ * @brief Set the embedded signature.
+ * @param esig populated and calculated embedded signature.
+ */
+ void set_embedded_sig(const pgp_signature_t &esig);
+
+ /**
+ * @brief Add subpacket of the specified type to v4 signature
+ * @param type type of the subpacket
+ * @param datalen length of the subpacket body
+ * @param reuse replace already existing subpacket of the specified type if any
+ * @return reference to the subpacket structure or throws an exception
+ */
+ pgp_sig_subpkt_t &add_subpkt(pgp_sig_subpacket_type_t type, size_t datalen, bool reuse);
+
+ /**
+ * @brief Remove signature's subpacket
+ * @param subpkt subpacket to remove. If not in the subpackets list then no action is
+ * taken.
+ */
+ void remove_subpkt(pgp_sig_subpkt_t *subpkt);
+
+ /**
+ * @brief Check whether signature packet matches one-pass signature packet.
+ * @param onepass reference to the read one-pass signature packet
+ * @return true if sig corresponds to onepass or false otherwise
+ */
+ bool matches_onepass(const pgp_one_pass_sig_t &onepass) const;
+
+ /**
+ * @brief Parse signature body (i.e. without checking the packet header).
+ *
+ * @param pkt packet body with data.
+ * @return RNP_SUCCESS or error code if failed. May also throw an exception.
+ */
+ rnp_result_t parse(pgp_packet_body_t &pkt);
+
+ /**
+ * @brief Parse signature packet from source.
+ *
+ * @param src source with data.
+ * @return RNP_SUCCESS or error code if failed. May also throw an exception.
+ */
+ rnp_result_t parse(pgp_source_t &src);
+
+ /**
+ * @brief Parse signature material, stored in the signature in raw.
+ *
+ * @param material on success parsed material will be stored here.
+ * @return true on success or false otherwise. May also throw an exception.
+ */
+ bool parse_material(pgp_signature_material_t &material) const;
+
+ /**
+ * @brief Write signature to the destination. May throw an exception.
+ */
+ void write(pgp_dest_t &dst) const;
+
+ /**
+ * @brief Write the signature material's raw representation. May throw an exception.
+ *
+ * @param material populated signature material.
+ */
+ void write_material(const pgp_signature_material_t &material);
+
+ /**
+ * @brief Fill signature's hashed data. This includes all the fields from signature which
+ * are hashed after the previous document or key fields.
+ */
+ void fill_hashed_data();
+} pgp_signature_t;
+
+typedef std::vector<pgp_signature_t> pgp_signature_list_t;
+
+/* information about the validated signature */
+typedef struct pgp_signature_info_t {
+ pgp_signature_t *sig{}; /* signature, or NULL if there were parsing error */
+ bool valid{}; /* signature is cryptographically valid (but may be expired) */
+ bool unknown{}; /* signature is unknown - parsing error, wrong version, etc */
+ bool no_signer{}; /* no signer's public key available */
+ bool expired{}; /* signature is expired */
+ bool signer_valid{}; /* assume that signing key is valid */
+ bool ignore_expiry{}; /* ignore signer's key expiration time */
+} pgp_signature_info_t;
+
+/**
+ * @brief Hash key packet. Used in signatures and v4 fingerprint calculation.
+ * Throws exception on error.
+ * @param key key packet, must be populated
+ * @param hash initialized hash context
+ */
+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);
+
+std::unique_ptr<rnp::Hash> signature_hash_certification(const pgp_signature_t & sig,
+ const pgp_key_pkt_t & key,
+ const pgp_userid_pkt_t &userid);
+
+std::unique_ptr<rnp::Hash> signature_hash_binding(const pgp_signature_t &sig,
+ const pgp_key_pkt_t & key,
+ const pgp_key_pkt_t & subkey);
+
+std::unique_ptr<rnp::Hash> 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);
+
+#endif
diff --git a/src/librepgp/stream-write.cpp b/src/librepgp/stream-write.cpp
new file mode 100644
index 0000000..60d867a
--- /dev/null
+++ b/src/librepgp/stream-write.cpp
@@ -0,0 +1,1973 @@
+/*
+ * Copyright (c) 2017-2023, [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 "config.h"
+#include <sys/stat.h>
+#include <stdlib.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <sys/param.h>
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#include <string.h>
+#ifdef HAVE_ZLIB_H
+#include <zlib.h>
+#endif
+#ifdef HAVE_BZLIB_H
+#include <bzlib.h>
+#endif
+#include <rnp/rnp_def.h>
+#include "stream-def.h"
+#include "stream-ctx.h"
+#include "stream-write.h"
+#include "stream-packet.h"
+#include "stream-armor.h"
+#include "stream-sig.h"
+#include "pgp-key.h"
+#include "fingerprint.h"
+#include "types.h"
+#include "crypto/signatures.h"
+#include "defaults.h"
+#include <time.h>
+#include <algorithm>
+
+/* 8192 bytes, as GnuPG */
+#define PGP_PARTIAL_PKT_SIZE_BITS (13)
+#define PGP_PARTIAL_PKT_BLOCK_SIZE (1 << PGP_PARTIAL_PKT_SIZE_BITS)
+
+/* common fields for encrypted, compressed and literal data */
+typedef struct pgp_dest_packet_param_t {
+ pgp_dest_t *writedst; /* destination to write to, could be partial */
+ pgp_dest_t *origdst; /* original dest passed to init_*_dst */
+ bool partial; /* partial length packet */
+ bool indeterminate; /* indeterminate length packet */
+ int tag; /* packet tag */
+ uint8_t hdr[PGP_MAX_HEADER_SIZE]; /* header, including length, as it was written */
+ size_t hdrlen; /* number of bytes in hdr */
+} pgp_dest_packet_param_t;
+
+typedef struct pgp_dest_compressed_param_t {
+ pgp_dest_packet_param_t pkt;
+ pgp_compression_type_t alg;
+ union {
+ z_stream z;
+ bz_stream bz;
+ };
+ bool zstarted; /* whether we initialize zlib/bzip2 */
+ 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 */
+ rnp::AuthType auth_type; /* Authentication type: MDC, AEAD or none */
+ pgp_crypt_t encrypt; /* encrypting crypto */
+ std::unique_ptr<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_encrypted_param_t;
+
+typedef struct pgp_dest_signer_info_t {
+ pgp_one_pass_sig_t onepass;
+ pgp_key_t * key;
+ pgp_hash_alg_t halg;
+ int64_t sigcreate;
+ uint64_t sigexpire;
+} pgp_dest_signer_info_t;
+
+typedef struct pgp_dest_signed_param_t {
+ pgp_dest_t * writedst; /* destination to write to */
+ rnp_ctx_t * ctx; /* rnp operation context with additional parameters */
+ pgp_password_provider_t *password_provider; /* password provider from write handler */
+ std::vector<pgp_dest_signer_info_t> siginfos; /* list of pgp_dest_signer_info_t */
+ rnp::HashList hashes; /* hashes to pass raw data through and then sign */
+ bool clr_start; /* we are on the start of the line */
+ uint8_t clr_buf[CT_BUF_LEN]; /* buffer to hold partial line data */
+ size_t clr_buflen; /* number of bytes in buffer */
+
+ pgp_dest_signed_param_t() = default;
+ ~pgp_dest_signed_param_t() = default;
+} pgp_dest_signed_param_t;
+
+typedef struct pgp_dest_partial_param_t {
+ pgp_dest_t *writedst;
+ uint8_t part[PGP_PARTIAL_PKT_BLOCK_SIZE];
+ uint8_t parthdr; /* header byte for the current part */
+ size_t partlen; /* length of the current part, up to PARTIAL_PKT_BLOCK_SIZE */
+ size_t len; /* bytes cached in part */
+} pgp_dest_partial_param_t;
+
+static rnp_result_t
+partial_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param;
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (len > param->partlen - param->len) {
+ /* we have full part - in block and in buf */
+ size_t wrlen = param->partlen - param->len;
+ dst_write(param->writedst, &param->parthdr, 1);
+ dst_write(param->writedst, param->part, param->len);
+ dst_write(param->writedst, buf, wrlen);
+
+ buf = (uint8_t *) buf + wrlen;
+ len -= wrlen;
+ param->len = 0;
+
+ /* writing all full parts directly from buf */
+ while (len >= param->partlen) {
+ dst_write(param->writedst, &param->parthdr, 1);
+ dst_write(param->writedst, buf, param->partlen);
+ buf = (uint8_t *) buf + param->partlen;
+ len -= param->partlen;
+ }
+ }
+
+ /* caching rest of the buf */
+ if (len > 0) {
+ memcpy(&param->part[param->len], buf, len);
+ param->len += len;
+ }
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+partial_dst_finish(pgp_dest_t *dst)
+{
+ pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param;
+ uint8_t hdr[5];
+ int lenlen;
+
+ lenlen = write_packet_len(hdr, param->len);
+ dst_write(param->writedst, hdr, lenlen);
+ dst_write(param->writedst, param->part, param->len);
+
+ return param->writedst->werr;
+}
+
+static void
+partial_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_partial_param_t *param = (pgp_dest_partial_param_t *) dst->param;
+
+ if (!param) {
+ return;
+ }
+
+ free(param);
+ dst->param = NULL;
+}
+
+static rnp_result_t
+init_partial_pkt_dst(pgp_dest_t *dst, pgp_dest_t *writedst)
+{
+ pgp_dest_partial_param_t *param;
+
+ if (!init_dst_common(dst, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_dest_partial_param_t *) dst->param;
+ param->writedst = writedst;
+ param->partlen = PGP_PARTIAL_PKT_BLOCK_SIZE;
+ param->parthdr = 0xE0 | PGP_PARTIAL_PKT_SIZE_BITS;
+ dst->param = param;
+ dst->write = partial_dst_write;
+ dst->finish = partial_dst_finish;
+ dst->close = partial_dst_close;
+ dst->type = PGP_STREAM_PARLEN_PACKET;
+
+ return RNP_SUCCESS;
+}
+
+/** @brief helper function for streamed packets (literal, encrypted and compressed).
+ * Allocates part len destination if needed and writes header
+ **/
+static bool
+init_streamed_packet(pgp_dest_packet_param_t *param, pgp_dest_t *dst)
+{
+ rnp_result_t ret;
+
+ if (param->partial) {
+ param->hdr[0] = param->tag | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT;
+ dst_write(dst, &param->hdr, 1);
+
+ if ((param->writedst = (pgp_dest_t *) calloc(1, sizeof(*param->writedst))) == NULL) {
+ RNP_LOG("part len dest allocation failed");
+ return false;
+ }
+ ret = init_partial_pkt_dst(param->writedst, dst);
+ if (ret != RNP_SUCCESS) {
+ free(param->writedst);
+ param->writedst = NULL;
+ return false;
+ }
+ param->origdst = dst;
+
+ param->hdr[1] = ((pgp_dest_partial_param_t *) param->writedst->param)->parthdr;
+ param->hdrlen = 2;
+ return true;
+ }
+
+ if (param->indeterminate) {
+ if (param->tag > 0xf) {
+ RNP_LOG("indeterminate tag > 0xf");
+ }
+
+ param->hdr[0] = ((param->tag & 0xf) << PGP_PTAG_OF_CONTENT_TAG_SHIFT) |
+ PGP_PTAG_OLD_LEN_INDETERMINATE;
+ param->hdrlen = 1;
+ dst_write(dst, &param->hdr, 1);
+
+ param->writedst = dst;
+ param->origdst = dst;
+ return true;
+ }
+
+ RNP_LOG("wrong call");
+ return false;
+}
+
+static rnp_result_t
+finish_streamed_packet(pgp_dest_packet_param_t *param)
+{
+ if (param->partial) {
+ return dst_finish(param->writedst);
+ }
+ return RNP_SUCCESS;
+}
+
+static void
+close_streamed_packet(pgp_dest_packet_param_t *param, bool discard)
+{
+ if (param->partial) {
+ dst_close(param->writedst, discard);
+ free(param->writedst);
+ param->writedst = NULL;
+ }
+}
+
+static rnp_result_t
+encrypted_dst_write_cfb(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param;
+ size_t sz;
+
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (param->auth_type == rnp::AuthType::MDC) {
+ try {
+ param->mdc->add(buf, len);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+ }
+
+ while (len > 0) {
+ sz = len > sizeof(param->cache) ? sizeof(param->cache) : len;
+ pgp_cipher_cfb_encrypt(&param->encrypt, param->cache, (const uint8_t *) buf, sz);
+ dst_write(param->pkt.writedst, param->cache, sz);
+ len -= sz;
+ buf = (uint8_t *) buf + sz;
+ }
+
+ return RNP_SUCCESS;
+}
+
+#if defined(ENABLE_AEAD)
+static rnp_result_t
+encrypted_start_aead_chunk(pgp_dest_encrypted_param_t *param, size_t idx, bool last)
+{
+ uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN];
+ size_t nlen;
+ size_t taglen;
+ bool res;
+ uint64_t total;
+
+ taglen = pgp_cipher_aead_tag_len(param->aalg);
+
+ /* finish the previous chunk if needed*/
+ if ((idx > 0) && (param->chunkout + param->cachelen > 0)) {
+ if (param->cachelen + taglen > sizeof(param->cache)) {
+ RNP_LOG("wrong state in aead");
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ if (!pgp_cipher_aead_finish(
+ &param->encrypt, param->cache, param->cache, param->cachelen)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ dst_write(param->pkt.writedst, param->cache, param->cachelen + taglen);
+ }
+
+ /* set chunk index for additional data */
+ STORE64BE(param->ad + param->adlen - 8, idx);
+
+ if (last) {
+ if (!(param->chunkout + param->cachelen)) {
+ /* we need to clearly reset it since cipher was initialized but not finished */
+ pgp_cipher_aead_reset(&param->encrypt);
+ }
+
+ total = idx * param->chunklen;
+ if (param->cachelen + param->chunkout) {
+ if (param->chunklen < (param->cachelen + param->chunkout)) {
+ RNP_LOG("wrong last chunk state in aead");
+ return RNP_ERROR_BAD_STATE;
+ }
+ total -= param->chunklen - param->cachelen - param->chunkout;
+ }
+
+ STORE64BE(param->ad + param->adlen, total);
+ param->adlen += 8;
+ }
+ if (!pgp_cipher_aead_set_ad(&param->encrypt, param->ad, param->adlen)) {
+ RNP_LOG("failed to set ad");
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* set chunk index for nonce */
+ nlen = pgp_cipher_aead_nonce(param->aalg, param->iv, nonce, idx);
+
+ /* start cipher */
+ res = pgp_cipher_aead_start(&param->encrypt, nonce, nlen);
+
+ /* write final authentication tag */
+ if (last) {
+ res = res && pgp_cipher_aead_finish(&param->encrypt, param->cache, param->cache, 0);
+ if (res) {
+ dst_write(param->pkt.writedst, param->cache, taglen);
+ }
+ }
+
+ param->chunkidx = idx;
+ param->chunkout = 0;
+
+ return res ? RNP_SUCCESS : RNP_ERROR_BAD_PARAMETERS;
+}
+#endif
+
+static rnp_result_t
+encrypted_dst_write_aead(pgp_dest_t *dst, const void *buf, size_t len)
+{
+#if !defined(ENABLE_AEAD)
+ RNP_LOG("AEAD is not enabled.");
+ return RNP_ERROR_WRITE;
+#else
+ pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param;
+
+ size_t sz;
+ size_t gran;
+ rnp_result_t res;
+
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (!len) {
+ return RNP_SUCCESS;
+ }
+
+ /* because of botan's FFI granularity we need to make things a bit complicated */
+ gran = pgp_cipher_aead_granularity(&param->encrypt);
+
+ if (param->cachelen > param->chunklen - param->chunkout) {
+ RNP_LOG("wrong AEAD cache state");
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ while (len > 0) {
+ sz = std::min(sizeof(param->cache) - PGP_AEAD_MAX_TAG_LEN - param->cachelen, len);
+ sz = std::min(sz, param->chunklen - param->chunkout - param->cachelen);
+ memcpy(param->cache + param->cachelen, buf, sz);
+ param->cachelen += sz;
+
+ if (param->cachelen == param->chunklen - param->chunkout) {
+ /* we have the tail of the chunk in cache */
+ if ((res = encrypted_start_aead_chunk(param, param->chunkidx + 1, false))) {
+ return res;
+ }
+ param->cachelen = 0;
+ } else if (param->cachelen >= gran) {
+ /* we have part of the chunk - so need to adjust it to the granularity */
+ size_t gransz = param->cachelen - param->cachelen % gran;
+ if (!pgp_cipher_aead_update(&param->encrypt, param->cache, param->cache, gransz)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+ dst_write(param->pkt.writedst, param->cache, gransz);
+ memmove(param->cache, param->cache + gransz, param->cachelen - gransz);
+ param->cachelen -= gransz;
+ param->chunkout += gransz;
+ }
+
+ len -= sz;
+ buf = (uint8_t *) buf + sz;
+ }
+
+ return RNP_SUCCESS;
+#endif
+}
+
+static rnp_result_t
+encrypted_dst_finish(pgp_dest_t *dst)
+{
+ pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param;
+
+ if (param->auth_type == rnp::AuthType::AEADv1) {
+#if !defined(ENABLE_AEAD)
+ RNP_LOG("AEAD is not enabled.");
+ rnp_result_t res = RNP_ERROR_NOT_IMPLEMENTED;
+#else
+ size_t chunks = param->chunkidx;
+ /* if we didn't write anything in current chunk then discard it and restart */
+ if (param->chunkout || param->cachelen) {
+ chunks++;
+ }
+
+ rnp_result_t res = encrypted_start_aead_chunk(param, chunks, true);
+ pgp_cipher_aead_destroy(&param->encrypt);
+#endif
+ if (res) {
+ finish_streamed_packet(&param->pkt);
+ return res;
+ }
+ } else if (param->auth_type == rnp::AuthType::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 = nullptr;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+ pgp_cipher_cfb_encrypt(&param->encrypt, mdcbuf, mdcbuf, MDC_V1_SIZE);
+ dst_write(param->pkt.writedst, mdcbuf, MDC_V1_SIZE);
+ }
+
+ return finish_streamed_packet(&param->pkt);
+}
+
+static void
+encrypted_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param;
+
+ if (!param) {
+ return;
+ }
+
+ if (param->auth_type == rnp::AuthType::AEADv1) {
+#if defined(ENABLE_AEAD)
+ pgp_cipher_aead_destroy(&param->encrypt);
+#endif
+ } else {
+ pgp_cipher_cfb_finish(&param->encrypt);
+ }
+ close_streamed_packet(&param->pkt, discard);
+ delete param;
+ dst->param = NULL;
+}
+
+static rnp_result_t
+encrypted_add_recipient(pgp_write_handler_t *handler,
+ pgp_dest_t * dst,
+ pgp_key_t * userkey,
+ const uint8_t * key,
+ const unsigned keylen)
+{
+ pgp_pk_sesskey_t pkey;
+ pgp_dest_encrypted_param_t *param = (pgp_dest_encrypted_param_t *) dst->param;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ /* Use primary key if good for encryption, otherwise look in subkey list */
+ userkey = find_suitable_key(PGP_OP_ENCRYPT, userkey, handler->key_provider);
+ if (!userkey) {
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+
+ /* Fill pkey */
+ pkey.version = PGP_PKSK_V3;
+ pkey.alg = userkey->alg();
+ pkey.key_id = userkey->keyid();
+
+ /* Encrypt the session key */
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE + 3> enckey;
+ enckey[0] = param->ctx->ealg;
+ memcpy(&enckey[1], key, keylen);
+
+ /* Calculate checksum */
+ rnp::secure_array<unsigned, 1> checksum;
+
+ for (unsigned i = 1; i <= keylen; i++) {
+ checksum[0] += enckey[i];
+ }
+ enckey[keylen + 1] = (checksum[0] >> 8) & 0xff;
+ enckey[keylen + 2] = checksum[0] & 0xff;
+
+ pgp_encrypted_material_t material;
+
+ switch (userkey->alg()) {
+ case PGP_PKA_RSA:
+ case PGP_PKA_RSA_ENCRYPT_ONLY: {
+ ret = rsa_encrypt_pkcs1(&handler->ctx->ctx->rng,
+ &material.rsa,
+ enckey.data(),
+ keylen + 3,
+ &userkey->material().rsa);
+ if (ret) {
+ RNP_LOG("rsa_encrypt_pkcs1 failed");
+ return ret;
+ }
+ break;
+ }
+ case PGP_PKA_SM2: {
+#if defined(ENABLE_SM2)
+ ret = sm2_encrypt(&handler->ctx->ctx->rng,
+ &material.sm2,
+ enckey.data(),
+ keylen + 3,
+ PGP_HASH_SM3,
+ &userkey->material().ec);
+ if (ret) {
+ RNP_LOG("sm2_encrypt failed");
+ return ret;
+ }
+ break;
+#else
+ RNP_LOG("sm2_encrypt is not available");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+#endif
+ }
+ case PGP_PKA_ECDH: {
+ if (!curve_supported(userkey->material().ec.curve)) {
+ RNP_LOG("ECDH encrypt: curve %d is not supported.",
+ (int) userkey->material().ec.curve);
+ return RNP_ERROR_NOT_SUPPORTED;
+ }
+ ret = ecdh_encrypt_pkcs5(&handler->ctx->ctx->rng,
+ &material.ecdh,
+ enckey.data(),
+ keylen + 3,
+ &userkey->material().ec,
+ userkey->fp());
+ if (ret) {
+ RNP_LOG("ECDH encryption failed %d", ret);
+ return ret;
+ }
+ break;
+ }
+ case PGP_PKA_ELGAMAL: {
+ ret = elgamal_encrypt_pkcs1(&handler->ctx->ctx->rng,
+ &material.eg,
+ enckey.data(),
+ keylen + 3,
+ &userkey->material().eg);
+ if (ret) {
+ RNP_LOG("pgp_elgamal_public_encrypt failed");
+ return ret;
+ }
+ break;
+ }
+ default:
+ RNP_LOG("unsupported alg: %d", (int) userkey->alg());
+ return ret;
+ }
+
+ /* Writing symmetric key encrypted session key packet */
+ try {
+ pkey.write_material(material);
+ pkey.write(*param->pkt.origdst);
+ return param->pkt.origdst->werr;
+ } catch (const std::exception &e) {
+ return RNP_ERROR_WRITE;
+ }
+}
+
+#if defined(ENABLE_AEAD)
+static bool
+encrypted_sesk_set_ad(pgp_crypt_t *crypt, pgp_sk_sesskey_t *skey)
+{
+ uint8_t ad_data[4];
+
+ ad_data[0] = PGP_PKT_SK_SESSION_KEY | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT;
+ ad_data[1] = skey->version;
+ ad_data[2] = skey->alg;
+ ad_data[3] = skey->aalg;
+
+ return pgp_cipher_aead_set_ad(crypt, ad_data, 4);
+}
+#endif
+
+static rnp_result_t
+encrypted_add_password(rnp_symmetric_pass_info_t * pass,
+ pgp_dest_encrypted_param_t *param,
+ uint8_t * key,
+ const unsigned keylen,
+ bool singlepass)
+{
+ pgp_sk_sesskey_t skey = {};
+ pgp_crypt_t kcrypt;
+
+ skey.s2k = pass->s2k;
+
+ if (param->auth_type != rnp::AuthType::AEADv1) {
+ skey.version = PGP_SKSK_V4;
+ if (singlepass) {
+ /* if there are no public keys then we do not encrypt session key in the packet */
+ skey.alg = param->ctx->ealg;
+ skey.enckeylen = 0;
+ memcpy(key, pass->key.data(), keylen);
+ } else {
+ /* We may use different algo for CEK and KEK */
+ skey.enckeylen = keylen + 1;
+ skey.enckey[0] = param->ctx->ealg;
+ memcpy(&skey.enckey[1], key, keylen);
+ skey.alg = pass->s2k_cipher;
+ if (!pgp_cipher_cfb_start(&kcrypt, skey.alg, pass->key.data(), NULL)) {
+ RNP_LOG("key encryption failed");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ pgp_cipher_cfb_encrypt(&kcrypt, skey.enckey, skey.enckey, skey.enckeylen);
+ pgp_cipher_cfb_finish(&kcrypt);
+ }
+ } else {
+#if !defined(ENABLE_AEAD)
+ RNP_LOG("AEAD support is not enabled.");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+#else
+ /* AEAD-encrypted v5 packet */
+ if ((param->ctx->aalg != PGP_AEAD_EAX) && (param->ctx->aalg != PGP_AEAD_OCB)) {
+ RNP_LOG("unsupported AEAD algorithm");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ skey.version = PGP_SKSK_V5;
+ skey.alg = pass->s2k_cipher;
+ skey.aalg = param->ctx->aalg;
+ skey.ivlen = pgp_cipher_aead_nonce_len(skey.aalg);
+ skey.enckeylen = keylen + pgp_cipher_aead_tag_len(skey.aalg);
+
+ try {
+ param->ctx->ctx->rng.get(skey.iv, skey.ivlen);
+ } catch (const std::exception &e) {
+ return RNP_ERROR_RNG;
+ }
+
+ /* initialize cipher */
+ if (!pgp_cipher_aead_init(&kcrypt, skey.alg, skey.aalg, pass->key.data(), false)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* set additional data */
+ if (!encrypted_sesk_set_ad(&kcrypt, &skey)) {
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* calculate nonce */
+ uint8_t nonce[PGP_AEAD_MAX_NONCE_LEN];
+ size_t nlen = pgp_cipher_aead_nonce(skey.aalg, skey.iv, nonce, 0);
+
+ /* start cipher, encrypt key and get tag */
+ bool res = pgp_cipher_aead_start(&kcrypt, nonce, nlen) &&
+ pgp_cipher_aead_finish(&kcrypt, skey.enckey, key, keylen);
+
+ pgp_cipher_aead_destroy(&kcrypt);
+
+ if (!res) {
+ return RNP_ERROR_BAD_STATE;
+ }
+#endif
+ }
+
+ /* Writing symmetric key encrypted session key packet */
+ try {
+ skey.write(*param->pkt.origdst);
+ } catch (const std::exception &e) {
+ return RNP_ERROR_WRITE;
+ }
+ return param->pkt.origdst->werr;
+}
+
+static rnp_result_t
+encrypted_start_cfb(pgp_dest_encrypted_param_t *param, uint8_t *enckey)
+{
+ uint8_t mdcver = 1;
+ uint8_t enchdr[PGP_MAX_BLOCK_SIZE + 2]; /* encrypted header */
+ unsigned blsize;
+
+ if (param->auth_type == rnp::AuthType::MDC) {
+ /* initializing the mdc */
+ dst_write(param->pkt.writedst, &mdcver, 1);
+
+ try {
+ 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;
+ }
+ }
+
+ /* initializing the crypto */
+ if (!pgp_cipher_cfb_start(&param->encrypt, param->ctx->ealg, enckey, NULL)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* generating and writing iv/password check bytes */
+ blsize = pgp_block_size(param->ctx->ealg);
+ try {
+ param->ctx->ctx->rng.get(enchdr, blsize);
+ enchdr[blsize] = enchdr[blsize - 2];
+ enchdr[blsize + 1] = enchdr[blsize - 1];
+
+ if (param->auth_type == rnp::AuthType::MDC) {
+ param->mdc->add(enchdr, blsize + 2);
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ pgp_cipher_cfb_encrypt(&param->encrypt, enchdr, enchdr, blsize + 2);
+
+ /* RFC 4880, 5.13: Unlike the Symmetrically Encrypted Data Packet, no special CFB
+ * resynchronization is done after encrypting this prefix data. */
+ if (param->auth_type == rnp::AuthType::None) {
+ pgp_cipher_cfb_resync(&param->encrypt, enchdr + 2);
+ }
+
+ dst_write(param->pkt.writedst, enchdr, blsize + 2);
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+encrypted_start_aead(pgp_dest_encrypted_param_t *param, uint8_t *enckey)
+{
+#if !defined(ENABLE_AEAD)
+ RNP_LOG("AEAD support is not enabled.");
+ return RNP_ERROR_NOT_IMPLEMENTED;
+#else
+ uint8_t hdr[4 + PGP_AEAD_MAX_NONCE_LEN];
+ size_t nlen;
+
+ if (pgp_block_size(param->ctx->ealg) != 16) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ /* fill header */
+ hdr[0] = 1;
+ hdr[1] = param->ctx->ealg;
+ hdr[2] = param->ctx->aalg;
+ hdr[3] = param->ctx->abits;
+
+ /* generate iv */
+ nlen = pgp_cipher_aead_nonce_len(param->ctx->aalg);
+ try {
+ param->ctx->ctx->rng.get(param->iv, nlen);
+ } catch (const std::exception &e) {
+ return RNP_ERROR_RNG;
+ }
+ memcpy(hdr + 4, param->iv, nlen);
+
+ /* output header */
+ dst_write(param->pkt.writedst, hdr, 4 + nlen);
+
+ /* initialize encryption */
+ param->chunklen = 1L << (hdr[3] + 6);
+ param->chunkout = 0;
+
+ /* fill additional/authenticated data */
+ param->ad[0] = PGP_PKT_AEAD_ENCRYPTED | PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT;
+ memcpy(param->ad + 1, hdr, 4);
+ memset(param->ad + 5, 0, 8);
+ param->adlen = 13;
+
+ /* initialize cipher */
+ if (!pgp_cipher_aead_init(
+ &param->encrypt, param->ctx->ealg, param->ctx->aalg, enckey, false)) {
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ return encrypted_start_aead_chunk(param, 0, false);
+#endif
+}
+
+static rnp_result_t
+init_encrypted_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst)
+{
+ pgp_dest_encrypted_param_t *param;
+ bool singlepass = true;
+ unsigned pkeycount = 0;
+ unsigned skeycount = 0;
+ unsigned keylen;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ keylen = pgp_key_size(handler->ctx->ealg);
+ if (!keylen) {
+ RNP_LOG("unknown symmetric algorithm");
+ 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: %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 > 16)) {
+ RNP_LOG("wrong AEAD chunk bits: %d", handler->ctx->abits);
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ }
+
+ if (!init_dst_common(dst, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ try {
+ param = new pgp_dest_encrypted_param_t();
+ dst->param = param;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ param->auth_type =
+ handler->ctx->aalg == PGP_AEAD_NONE ? rnp::AuthType::MDC : rnp::AuthType::AEADv1;
+ param->aalg = handler->ctx->aalg;
+ param->ctx = handler->ctx;
+ param->pkt.origdst = writedst;
+ dst->write = param->auth_type == rnp::AuthType::AEADv1 ? encrypted_dst_write_aead :
+ encrypted_dst_write_cfb;
+ dst->finish = encrypted_dst_finish;
+ dst->close = encrypted_dst_close;
+ dst->type = PGP_STREAM_ENCRYPTED;
+
+ pkeycount = handler->ctx->recipients.size();
+ skeycount = handler->ctx->passwords.size();
+
+ rnp::secure_array<uint8_t, PGP_MAX_KEY_SIZE> enckey; /* content encryption key */
+ if (!pkeycount && !skeycount) {
+ RNP_LOG("no recipients");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ if ((pkeycount > 0) || (skeycount > 1) || (param->auth_type == rnp::AuthType::AEADv1)) {
+ try {
+ handler->ctx->ctx->rng.get(enckey.data(), keylen);
+ } catch (const std::exception &e) {
+ ret = RNP_ERROR_RNG;
+ goto finish;
+ }
+ singlepass = false;
+ }
+
+ /* Configuring and writing pk-encrypted session keys */
+ for (auto recipient : handler->ctx->recipients) {
+ ret = encrypted_add_recipient(handler, dst, recipient, enckey.data(), keylen);
+ if (ret) {
+ goto finish;
+ }
+ }
+
+ /* Configuring and writing sk-encrypted session key(s) */
+ for (auto &passinfo : handler->ctx->passwords) {
+ ret = encrypted_add_password(&passinfo, param, enckey.data(), keylen, singlepass);
+ if (ret != RNP_SUCCESS) {
+ goto finish;
+ }
+ }
+
+ /* Initializing partial packet writer */
+ param->pkt.partial = true;
+ param->pkt.indeterminate = false;
+ if (param->auth_type == rnp::AuthType::AEADv1) {
+ param->pkt.tag = PGP_PKT_AEAD_ENCRYPTED;
+ } else {
+ /* We do not generate PGP_PKT_SE_DATA, leaving this just in case */
+ param->pkt.tag =
+ param->auth_type == rnp::AuthType::MDC ? PGP_PKT_SE_IP_DATA : PGP_PKT_SE_DATA;
+ }
+
+ /* initializing partial data length writer */
+ /* we may use intederminate len packet here as well, for compatibility or so on */
+ if (!init_streamed_packet(&param->pkt, writedst)) {
+ RNP_LOG("failed to init streamed packet");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ if (param->auth_type == rnp::AuthType::AEADv1) {
+ /* initialize AEAD encryption */
+ ret = encrypted_start_aead(param, enckey.data());
+ } else {
+ /* initialize old CFB or CFB with MDC */
+ ret = encrypted_start_cfb(param, enckey.data());
+ }
+finish:
+ handler->ctx->passwords.clear();
+ if (ret) {
+ encrypted_dst_close(dst, true);
+ }
+ return ret;
+}
+
+static rnp_result_t
+signed_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param;
+ dst_write(param->writedst, buf, len);
+ return RNP_SUCCESS;
+}
+
+static void
+cleartext_dst_writeline(pgp_dest_signed_param_t *param,
+ const uint8_t * buf,
+ size_t len,
+ bool eol)
+{
+ const uint8_t *ptr;
+
+ /* dash-escaping line if needed */
+ if (param->clr_start && len &&
+ ((buf[0] == CH_DASH) || ((len >= 4) && !strncmp((const char *) buf, ST_FROM, 4)))) {
+ dst_write(param->writedst, ST_DASHSP, 2);
+ }
+
+ /* output data */
+ dst_write(param->writedst, buf, len);
+
+ try {
+ if (eol) {
+ bool hashcrlf = false;
+ ptr = buf + len - 1;
+
+ /* skipping trailing characters - space, tab, carriage return, line feed */
+ while ((ptr >= buf) && ((*ptr == CH_SPACE) || (*ptr == CH_TAB) ||
+ (*ptr == CH_CR) || (*ptr == CH_LF))) {
+ if (*ptr == CH_LF) {
+ hashcrlf = true;
+ }
+ ptr--;
+ }
+
+ /* hashing line body and \r\n */
+ param->hashes.add(buf, ptr + 1 - buf);
+ if (hashcrlf) {
+ param->hashes.add(ST_CRLF, 2);
+ }
+ param->clr_start = hashcrlf;
+ } else if (len > 0) {
+ /* hashing just line's data */
+ param->hashes.add(buf, len);
+ param->clr_start = false;
+ }
+ } catch (const std::exception &e) {
+ RNP_LOG("failed to hash data: %s", e.what());
+ }
+}
+
+static size_t
+cleartext_dst_scanline(const uint8_t *buf, size_t len, bool *eol)
+{
+ for (const uint8_t *ptr = buf, *end = buf + len; ptr < end; ptr++) {
+ if (*ptr == CH_LF) {
+ if (eol) {
+ *eol = true;
+ }
+ return ptr - buf + 1;
+ }
+ }
+
+ if (eol) {
+ *eol = false;
+ }
+ return len;
+}
+
+static rnp_result_t
+cleartext_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ const uint8_t * linebg = (const uint8_t *) buf;
+ size_t linelen;
+ size_t cplen;
+ bool eol;
+ pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param;
+
+ if (param->clr_buflen > 0) {
+ /* number of edge cases may happen here */
+ linelen = cleartext_dst_scanline(linebg, len, &eol);
+
+ if (param->clr_buflen + linelen < sizeof(param->clr_buf)) {
+ /* fits into buffer */
+ memcpy(param->clr_buf + param->clr_buflen, linebg, linelen);
+ param->clr_buflen += linelen;
+ if (!eol) {
+ /* do not write the line if we don't have whole */
+ return RNP_SUCCESS;
+ }
+
+ cleartext_dst_writeline(param, param->clr_buf, param->clr_buflen, true);
+ param->clr_buflen = 0;
+ } else {
+ /* we have line longer than 4k */
+ cplen = sizeof(param->clr_buf) - param->clr_buflen;
+ memcpy(param->clr_buf + param->clr_buflen, linebg, cplen);
+ cleartext_dst_writeline(param, param->clr_buf, sizeof(param->clr_buf), false);
+
+ if (eol || (linelen >= sizeof(param->clr_buf))) {
+ cleartext_dst_writeline(param, linebg + cplen, linelen - cplen, eol);
+ param->clr_buflen = 0;
+ } else {
+ param->clr_buflen = linelen - cplen;
+ memcpy(param->clr_buf, linebg + cplen, param->clr_buflen);
+ return RNP_SUCCESS;
+ }
+ }
+
+ linebg += linelen;
+ len -= linelen;
+ }
+
+ /* if we get here then we don't have data in param->clr_buf */
+ while (len > 0) {
+ linelen = cleartext_dst_scanline(linebg, len, &eol);
+
+ if (!eol && (linelen < sizeof(param->clr_buf))) {
+ memcpy(param->clr_buf, linebg, linelen);
+ param->clr_buflen = linelen;
+ return RNP_SUCCESS;
+ }
+
+ cleartext_dst_writeline(param, linebg, linelen, eol);
+ linebg += linelen;
+ len -= linelen;
+ }
+
+ return RNP_SUCCESS;
+}
+
+static void
+signed_fill_signature(pgp_dest_signed_param_t &param,
+ pgp_signature_t & sig,
+ pgp_dest_signer_info_t & signer)
+{
+ /* fill signature fields, assuming sign_init was called on it */
+ if (signer.sigcreate) {
+ sig.set_creation(signer.sigcreate);
+ }
+ sig.set_expiration(signer.sigexpire);
+ sig.fill_hashed_data();
+
+ auto listh = param.hashes.get(sig.halg);
+ if (!listh) {
+ RNP_LOG("failed to obtain hash");
+ throw rnp::rnp_exception(RNP_ERROR_BAD_STATE);
+ }
+
+ /* decrypt the secret key if needed */
+ rnp::KeyLocker keylock(*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(), *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, param->ctx->ctx->time());
+ sig.palg = signer->onepass.palg;
+ sig.set_type(signer->onepass.type);
+ } else {
+ 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);
+ return writedst->werr;
+ } catch (const rnp::rnp_exception &e) {
+ return e.code();
+ } catch (const std::exception &e) {
+ RNP_LOG("Failed to write signature: %s", e.what());
+ return RNP_ERROR_WRITE;
+ }
+}
+
+static rnp_result_t
+signed_dst_finish(pgp_dest_t *dst)
+{
+ rnp_result_t ret;
+ pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param;
+
+ /* attached signature, we keep onepasses in order of signatures */
+ for (auto &sinfo : param->siginfos) {
+ if ((ret = signed_write_signature(param, &sinfo, param->writedst))) {
+ RNP_LOG("failed to calculate signature");
+ return ret;
+ }
+ }
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+signed_detached_dst_finish(pgp_dest_t *dst)
+{
+ rnp_result_t ret;
+ pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param;
+
+ /* just calculating and writing signatures to the output */
+ for (auto &sinfo : param->siginfos) {
+ if ((ret = signed_write_signature(param, &sinfo, param->writedst))) {
+ RNP_LOG("failed to calculate detached signature");
+ return ret;
+ }
+ }
+
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+cleartext_dst_finish(pgp_dest_t *dst)
+{
+ 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 */
+ 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;
+ }
+}
+
+static void
+signed_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param;
+ if (!param) {
+ return;
+ }
+ delete param;
+ dst->param = NULL;
+}
+
+static void
+signed_dst_update(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_signed_param_t *param = (pgp_dest_signed_param_t *) dst->param;
+ param->hashes.add(buf, len);
+}
+
+static rnp_result_t
+signed_add_signer(pgp_dest_signed_param_t *param, rnp_signer_info_t *signer, bool last)
+{
+ pgp_dest_signer_info_t sinfo = {};
+
+ if (!signer->key->is_secret()) {
+ RNP_LOG("secret key required for signing");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+ /* validate signing key material if didn't before */
+ signer->key->pkt().material.validate(*param->ctx->ctx, false);
+ if (!signer->key->pkt().material.valid()) {
+ RNP_LOG("attempt to sign to the key with invalid material");
+ return RNP_ERROR_NO_SUITABLE_KEY;
+ }
+
+ /* copy fields */
+ sinfo.key = signer->key;
+ sinfo.sigcreate = signer->sigcreate;
+ sinfo.sigexpire = signer->sigexpire;
+
+ /* Add hash to the list */
+ sinfo.halg = pgp_hash_adjust_alg_to_key(signer->halg, &signer->key->pkt());
+ try {
+ param->hashes.add_alg(sinfo.halg);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ // Do not add onepass for detached/clearsign
+ if (param->ctx->detached || param->ctx->clearsign) {
+ sinfo.onepass.version = 0;
+ try {
+ param->siginfos.push_back(sinfo);
+ return RNP_SUCCESS;
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ }
+
+ // Setup and add onepass
+ sinfo.onepass.version = 3;
+ sinfo.onepass.type = PGP_SIG_BINARY;
+ sinfo.onepass.halg = sinfo.halg;
+ sinfo.onepass.palg = sinfo.key->alg();
+ sinfo.onepass.keyid = sinfo.key->keyid();
+ sinfo.onepass.nested = false;
+ try {
+ param->siginfos.push_back(sinfo);
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ // write onepasses in reverse order so signature order will match signers list
+ if (!last) {
+ return RNP_SUCCESS;
+ }
+ try {
+ for (auto it = param->siginfos.rbegin(); it != param->siginfos.rend(); it++) {
+ pgp_dest_signer_info_t &sinfo = *it;
+ sinfo.onepass.nested = &sinfo == &param->siginfos.front();
+ sinfo.onepass.write(*param->writedst);
+ }
+ return param->writedst->werr;
+ } catch (const std::exception &e) {
+ return RNP_ERROR_WRITE;
+ }
+}
+
+static rnp_result_t
+init_signed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst)
+{
+ pgp_dest_signed_param_t *param;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+
+ if (!handler->key_provider) {
+ RNP_LOG("no key provider");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if (!init_dst_common(dst, 0)) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+ try {
+ param = new pgp_dest_signed_param_t();
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ dst->param = param;
+ param->writedst = writedst;
+ param->ctx = handler->ctx;
+ param->password_provider = handler->password_provider;
+ if (param->ctx->clearsign) {
+ dst->type = PGP_STREAM_CLEARTEXT;
+ dst->write = cleartext_dst_write;
+ dst->finish = cleartext_dst_finish;
+ param->clr_start = true;
+ } else {
+ dst->type = PGP_STREAM_SIGNED;
+ dst->write = signed_dst_write;
+ dst->finish = param->ctx->detached ? signed_detached_dst_finish : signed_dst_finish;
+ }
+ dst->close = signed_dst_close;
+
+ /* Getting signer's infos, writing one-pass signatures if needed */
+ for (auto &sg : handler->ctx->signers) {
+ ret = signed_add_signer(param, &sg, &sg == &handler->ctx->signers.back());
+ if (ret) {
+ RNP_LOG("failed to add one-pass signature for signer");
+ goto finish;
+ }
+ }
+
+ /* Do we have any signatures? */
+ 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());
+ dst_write(param->writedst, hname, strlen(hname));
+ if (&hash != &param->hashes.hashes.back()) {
+ dst_write(param->writedst, ST_COMMA, 1);
+ }
+ }
+
+ dst_write(param->writedst, ST_CRLFCRLF, strlen(ST_CRLFCRLF));
+ }
+
+ ret = RNP_SUCCESS;
+finish:
+ if (ret != RNP_SUCCESS) {
+ signed_dst_close(dst, true);
+ }
+
+ return ret;
+}
+
+static rnp_result_t
+compressed_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param;
+ int zret;
+
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) {
+ param->z.next_in = (unsigned char *) buf;
+ param->z.avail_in = len;
+ param->z.next_out = param->cache + param->len;
+ param->z.avail_out = sizeof(param->cache) - param->len;
+
+ while (param->z.avail_in > 0) {
+ zret = deflate(&param->z, Z_NO_FLUSH);
+ /* Z_OK, Z_BUF_ERROR are ok for us, Z_STREAM_END will not happen here */
+ if (zret == Z_STREAM_ERROR) {
+ RNP_LOG("wrong deflate state");
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* writing only full blocks, the rest will be written in close */
+ if (param->z.avail_out == 0) {
+ dst_write(param->pkt.writedst, param->cache, sizeof(param->cache));
+ param->len = 0;
+ param->z.next_out = param->cache;
+ param->z.avail_out = sizeof(param->cache);
+ }
+ }
+
+ param->len = sizeof(param->cache) - param->z.avail_out;
+ return RNP_SUCCESS;
+ } else if (param->alg == PGP_C_BZIP2) {
+#ifdef HAVE_BZLIB_H
+ param->bz.next_in = (char *) buf;
+ param->bz.avail_in = len;
+ param->bz.next_out = (char *) (param->cache + param->len);
+ param->bz.avail_out = sizeof(param->cache) - param->len;
+
+ while (param->bz.avail_in > 0) {
+ zret = BZ2_bzCompress(&param->bz, BZ_RUN);
+ if (zret < 0) {
+ RNP_LOG("error %d", zret);
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* writing only full blocks, the rest will be written in close */
+ if (param->bz.avail_out == 0) {
+ dst_write(param->pkt.writedst, param->cache, sizeof(param->cache));
+ param->len = 0;
+ param->bz.next_out = (char *) param->cache;
+ param->bz.avail_out = sizeof(param->cache);
+ }
+ }
+
+ param->len = sizeof(param->cache) - param->bz.avail_out;
+ return RNP_SUCCESS;
+#else
+ return RNP_ERROR_NOT_IMPLEMENTED;
+#endif
+ } else {
+ RNP_LOG("unknown algorithm");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+}
+
+static rnp_result_t
+compressed_dst_finish(pgp_dest_t *dst)
+{
+ int zret;
+ pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param;
+
+ if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) {
+ param->z.next_in = Z_NULL;
+ param->z.avail_in = 0;
+ param->z.next_out = param->cache + param->len;
+ param->z.avail_out = sizeof(param->cache) - param->len;
+ do {
+ zret = deflate(&param->z, Z_FINISH);
+
+ if (zret == Z_STREAM_ERROR) {
+ RNP_LOG("wrong deflate state");
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ if (param->z.avail_out == 0) {
+ dst_write(param->pkt.writedst, param->cache, sizeof(param->cache));
+ param->len = 0;
+ param->z.next_out = param->cache;
+ param->z.avail_out = sizeof(param->cache);
+ }
+ } while (zret != Z_STREAM_END);
+
+ param->len = sizeof(param->cache) - param->z.avail_out;
+ dst_write(param->pkt.writedst, param->cache, param->len);
+ }
+#ifdef HAVE_BZLIB_H
+ if (param->alg == PGP_C_BZIP2) {
+ param->bz.next_in = NULL;
+ param->bz.avail_in = 0;
+ param->bz.next_out = (char *) (param->cache + param->len);
+ param->bz.avail_out = sizeof(param->cache) - param->len;
+
+ do {
+ zret = BZ2_bzCompress(&param->bz, BZ_FINISH);
+ if (zret < 0) {
+ RNP_LOG("wrong bzip2 state %d", zret);
+ return RNP_ERROR_BAD_STATE;
+ }
+
+ /* writing only full blocks, the rest will be written in close */
+ if (param->bz.avail_out == 0) {
+ dst_write(param->pkt.writedst, param->cache, sizeof(param->cache));
+ param->len = 0;
+ param->bz.next_out = (char *) param->cache;
+ param->bz.avail_out = sizeof(param->cache);
+ }
+ } while (zret != BZ_STREAM_END);
+
+ param->len = sizeof(param->cache) - param->bz.avail_out;
+ dst_write(param->pkt.writedst, param->cache, param->len);
+ }
+#endif
+
+ if (param->pkt.writedst->werr) {
+ return param->pkt.writedst->werr;
+ }
+
+ return finish_streamed_packet(&param->pkt);
+}
+
+static void
+compressed_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_compressed_param_t *param = (pgp_dest_compressed_param_t *) dst->param;
+
+ if (!param) {
+ return;
+ }
+
+ if (param->zstarted) {
+ if ((param->alg == PGP_C_ZIP) || (param->alg == PGP_C_ZLIB)) {
+ deflateEnd(&param->z);
+ }
+#ifdef HAVE_BZLIB_H
+ if (param->alg == PGP_C_BZIP2) {
+ BZ2_bzCompressEnd(&param->bz);
+ }
+#endif
+ }
+
+ close_streamed_packet(&param->pkt, discard);
+ free(param);
+ dst->param = NULL;
+}
+
+static rnp_result_t
+init_compressed_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst)
+{
+ pgp_dest_compressed_param_t *param;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ uint8_t buf;
+ int zret;
+
+ if (!init_dst_common(dst, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_dest_compressed_param_t *) dst->param;
+ dst->write = compressed_dst_write;
+ dst->finish = compressed_dst_finish;
+ dst->close = compressed_dst_close;
+ dst->type = PGP_STREAM_COMPRESSED;
+ param->alg = (pgp_compression_type_t) handler->ctx->zalg;
+ param->pkt.partial = true;
+ param->pkt.indeterminate = false;
+ param->pkt.tag = PGP_PKT_COMPRESSED;
+
+ /* initializing partial length or indeterminate packet, writing header */
+ if (!init_streamed_packet(&param->pkt, writedst)) {
+ RNP_LOG("failed to init streamed packet");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+
+ /* compression algorithm */
+ buf = param->alg;
+ dst_write(param->pkt.writedst, &buf, 1);
+
+ /* initializing compression */
+ switch (param->alg) {
+ case PGP_C_ZIP:
+ case PGP_C_ZLIB:
+ (void) memset(&param->z, 0x0, sizeof(param->z));
+ if (param->alg == PGP_C_ZIP) {
+ zret = deflateInit2(
+ &param->z, handler->ctx->zlevel, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY);
+ } else {
+ zret = deflateInit(&param->z, handler->ctx->zlevel);
+ }
+
+ if (zret != Z_OK) {
+ RNP_LOG("failed to init zlib, error %d", zret);
+ ret = RNP_ERROR_NOT_SUPPORTED;
+ goto finish;
+ }
+ break;
+#ifdef HAVE_BZLIB_H
+ case PGP_C_BZIP2:
+ (void) memset(&param->bz, 0x0, sizeof(param->bz));
+ zret = BZ2_bzCompressInit(&param->bz, handler->ctx->zlevel, 0, 0);
+ if (zret != BZ_OK) {
+ RNP_LOG("failed to init bz, error %d", zret);
+ ret = RNP_ERROR_NOT_SUPPORTED;
+ goto finish;
+ }
+ break;
+#endif
+ default:
+ RNP_LOG("unknown compression algorithm");
+ ret = RNP_ERROR_NOT_SUPPORTED;
+ goto finish;
+ }
+ param->zstarted = true;
+ ret = RNP_SUCCESS;
+finish:
+ if (ret != RNP_SUCCESS) {
+ compressed_dst_close(dst, true);
+ }
+
+ return ret;
+}
+
+static rnp_result_t
+literal_dst_write(pgp_dest_t *dst, const void *buf, size_t len)
+{
+ pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param;
+
+ if (!param) {
+ RNP_LOG("wrong param");
+ return RNP_ERROR_BAD_PARAMETERS;
+ }
+
+ dst_write(param->writedst, buf, len);
+ return RNP_SUCCESS;
+}
+
+static rnp_result_t
+literal_dst_finish(pgp_dest_t *dst)
+{
+ return finish_streamed_packet((pgp_dest_packet_param_t *) dst->param);
+}
+
+static void
+literal_dst_close(pgp_dest_t *dst, bool discard)
+{
+ pgp_dest_packet_param_t *param = (pgp_dest_packet_param_t *) dst->param;
+
+ if (!param) {
+ return;
+ }
+
+ close_streamed_packet(param, discard);
+ free(param);
+ dst->param = NULL;
+}
+
+static rnp_result_t
+init_literal_dst(pgp_write_handler_t *handler, pgp_dest_t *dst, pgp_dest_t *writedst)
+{
+ pgp_dest_packet_param_t *param;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ size_t flen = 0;
+ uint8_t buf[4];
+
+ if (!init_dst_common(dst, sizeof(*param))) {
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ param = (pgp_dest_packet_param_t *) dst->param;
+ dst->write = literal_dst_write;
+ dst->finish = literal_dst_finish;
+ dst->close = literal_dst_close;
+ dst->type = PGP_STREAM_LITERAL;
+ param->partial = true;
+ param->indeterminate = false;
+ param->tag = PGP_PKT_LITDATA;
+
+ /* initializing partial length or indeterminate packet, writing header */
+ if (!init_streamed_packet(param, writedst)) {
+ RNP_LOG("failed to init streamed packet");
+ ret = RNP_ERROR_BAD_PARAMETERS;
+ goto finish;
+ }
+ /* content type - forcing binary now */
+ buf[0] = (uint8_t) 'b';
+ /* filename */
+ flen = handler->ctx->filename.size();
+ if (flen > 255) {
+ RNP_LOG("filename too long, truncating");
+ flen = 255;
+ }
+ buf[1] = (uint8_t) flen;
+ dst_write(param->writedst, buf, 2);
+ if (flen) {
+ dst_write(param->writedst, handler->ctx->filename.c_str(), flen);
+ }
+ /* timestamp */
+ STORE32BE(buf, handler->ctx->filemtime);
+ dst_write(param->writedst, buf, 4);
+ ret = RNP_SUCCESS;
+finish:
+ if (ret != RNP_SUCCESS) {
+ literal_dst_close(dst, true);
+ }
+
+ return ret;
+}
+
+static rnp_result_t
+process_stream_sequence(pgp_source_t *src,
+ pgp_dest_t * streams,
+ unsigned count,
+ pgp_dest_t * sstream,
+ pgp_dest_t * wstream)
+{
+ std::unique_ptr<uint8_t[]> readbuf(new (std::nothrow) uint8_t[PGP_INPUT_CACHE_SIZE]);
+ if (!readbuf) {
+ RNP_LOG("allocation failure");
+ return RNP_ERROR_OUT_OF_MEMORY;
+ }
+
+ /* processing source stream */
+ while (!src->eof) {
+ size_t read = 0;
+ if (!src_read(src, readbuf.get(), PGP_INPUT_CACHE_SIZE, &read)) {
+ RNP_LOG("failed to read from source");
+ return RNP_ERROR_READ;
+ } else if (!read) {
+ continue;
+ }
+
+ if (sstream) {
+ signed_dst_update(sstream, readbuf.get(), read);
+ }
+
+ if (wstream) {
+ dst_write(wstream, readbuf.get(), read);
+
+ for (int i = count - 1; i >= 0; i--) {
+ if (streams[i].werr) {
+ RNP_LOG("failed to process data");
+ return RNP_ERROR_WRITE;
+ }
+ }
+ }
+ }
+
+ /* finalizing destinations */
+ for (int i = count - 1; i >= 0; i--) {
+ rnp_result_t ret = dst_finish(&streams[i]);
+ if (ret) {
+ RNP_LOG("failed to finish stream");
+ return ret;
+ }
+ }
+ return RNP_SUCCESS;
+}
+
+rnp_result_t
+rnp_sign_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
+ [compressing stream, partial writing stream] - compression is enabled, and not detached
+ signing stream
+ 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 (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) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* if compression is enabled then pushing compressing stream */
+ if (!ctx.detached && !ctx.clearsign && (ctx.zlevel > 0)) {
+ if ((ret =
+ init_compressed_dst(handler, &dests[destc], destc ? &dests[destc - 1] : dst))) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* 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 (!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, sstream, wstream);
+finish:
+ for (int i = destc - 1; i >= 0; i--) {
+ dst_close(&dests[i], ret);
+ }
+ return ret;
+}
+
+rnp_result_t
+rnp_encrypt_sign_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] - compression is enabled
+ signing stream
+ literal data stream, partial writing stream
+ */
+ pgp_dest_t dests[5];
+ 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 (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 (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 (ctx.zlevel > 0) {
+ if ((ret = init_compressed_dst(handler, &dests[destc], &dests[destc - 1]))) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* 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++;
+ }
+
+ /* pushing literal data stream */
+ if (!ctx.no_wrap) {
+ if ((ret = init_literal_dst(handler, &dests[destc], &dests[destc - 1]))) {
+ goto finish;
+ }
+ destc++;
+ }
+
+ /* process source with streams stack */
+ ret = process_stream_sequence(src, dests, destc, sstream, &dests[destc - 1]);
+finish:
+ for (size_t i = destc; i > 0; i--) {
+ dst_close(&dests[i - 1], ret);
+ }
+ return ret;
+}
+
+rnp_result_t
+rnp_compress_src(pgp_source_t &src, pgp_dest_t &dst, pgp_compression_type_t zalg, int zlevel)
+{
+ pgp_write_handler_t handler = {};
+ rnp_ctx_t ctx;
+ ctx.zalg = zalg;
+ ctx.zlevel = zlevel;
+ handler.ctx = &ctx;
+
+ pgp_dest_t compressed = {};
+ rnp_result_t ret = init_compressed_dst(&handler, &compressed, &dst);
+ if (ret) {
+ goto done;
+ }
+ ret = dst_write_src(&src, &compressed);
+done:
+ dst_close(&compressed, ret);
+ return ret;
+}
+
+rnp_result_t
+rnp_wrap_src(pgp_source_t &src, pgp_dest_t &dst, const std::string &filename, uint32_t modtime)
+{
+ pgp_write_handler_t handler = {};
+ rnp_ctx_t ctx;
+ ctx.filename = filename;
+ ctx.filemtime = modtime;
+ handler.ctx = &ctx;
+
+ pgp_dest_t literal = {};
+ rnp_result_t ret = init_literal_dst(&handler, &literal, &dst);
+ if (ret) {
+ goto done;
+ }
+
+ ret = dst_write_src(&src, &literal);
+done:
+ dst_close(&literal, ret);
+ return ret;
+}
+
+rnp_result_t
+rnp_raw_encrypt_src(pgp_source_t & src,
+ pgp_dest_t & dst,
+ const std::string & password,
+ rnp::SecurityContext &secctx)
+{
+ pgp_write_handler_t handler = {};
+ rnp_ctx_t ctx;
+
+ ctx.ctx = &secctx;
+ ctx.ealg = DEFAULT_PGP_SYMM_ALG;
+ handler.ctx = &ctx;
+ pgp_dest_t encrypted = {};
+
+ 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);
+ if (ret) {
+ goto done;
+ }
+
+ ret = dst_write_src(&src, &encrypted);
+done:
+ dst_close(&encrypted, ret);
+ return ret;
+}
diff --git a/src/librepgp/stream-write.h b/src/librepgp/stream-write.h
new file mode 100644
index 0000000..49431f9
--- /dev/null
+++ b/src/librepgp/stream-write.h
@@ -0,0 +1,83 @@
+/*
+ * Copyright (c) 2017, [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 STREAM_WRITE_H_
+#define STREAM_WRITE_H_
+
+#include <stdint.h>
+#include <stdbool.h>
+#include <sys/types.h>
+#include "rnp.h"
+#include "stream-common.h"
+#include "stream-ctx.h"
+
+typedef struct pgp_write_handler_t {
+ pgp_password_provider_t *password_provider;
+ pgp_key_provider_t * key_provider;
+ rnp_ctx_t * ctx;
+
+ void *param;
+} pgp_write_handler_t;
+
+/** @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
+ * @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_sign_src(pgp_write_handler_t *handler, pgp_source_t *src, pgp_dest_t *dst);
+
+/** @brief encrypt and sign the input data. Signatures will be enrypted together with data.
+ * @param handler handler handler to respond on stream processor callbacks, and additional
+ * processing parameters, including rnp_ctx_t
+ * @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_sign_src(pgp_write_handler_t *handler,
+ pgp_source_t * src,
+ pgp_dest_t * dst);
+
+/* Following functions are used only in tests currently. Later could be used in CLI for debug
+ * commands like --wrap-literal, --encrypt-raw, --compress-raw, etc. */
+
+rnp_result_t rnp_compress_src(pgp_source_t & src,
+ pgp_dest_t & dst,
+ pgp_compression_type_t zalg,
+ int zlevel);
+
+rnp_result_t rnp_wrap_src(pgp_source_t & src,
+ pgp_dest_t & dst,
+ const std::string &filename,
+ uint32_t modtime);
+
+rnp_result_t rnp_raw_encrypt_src(pgp_source_t & src,
+ pgp_dest_t & dst,
+ const std::string & password,
+ rnp::SecurityContext &secctx);
+
+#endif
diff --git a/src/rnp/CMakeLists.txt b/src/rnp/CMakeLists.txt
new file mode 100644
index 0000000..d3199e7
--- /dev/null
+++ b/src/rnp/CMakeLists.txt
@@ -0,0 +1,100 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+if(MSVC)
+ # remove extra ${Configuration} subfolder
+ set(ArchiveOutputDir ${CMAKE_BINARY_DIR}\\src\\rnp)
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ArchiveOutputDir})
+
+ set(RuntimeOutputDir ${CMAKE_BINARY_DIR}\\src\\rnp)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${RuntimeOutputDir})
+
+ find_path(GETOPT_INCLUDE_DIR
+ NAMES getopt.h
+ )
+ find_library(GETOPT_LIBRARY
+ NAMES getopt
+ )
+ find_path(DIRENT_INCLUDE_DIR
+ NAMES dirent.h
+ )
+endif()
+
+# for the headers
+find_package(JSON-C 0.11 REQUIRED)
+
+add_executable(rnp
+ rnp.cpp
+ fficli.cpp
+ rnpcfg.cpp
+ ../rnpkeys/tui.cpp
+)
+
+if(BUILD_SHARED_LIBS)
+ target_sources(rnp PRIVATE ../lib/logging.cpp $<TARGET_OBJECTS:rnp-common>)
+endif(BUILD_SHARED_LIBS)
+
+target_include_directories(rnp
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+ "${JSON-C_INCLUDE_DIRS}"
+)
+if(MSVC)
+ target_include_directories(rnp
+ PRIVATE
+ "${GETOPT_INCLUDE_DIR}"
+ "${DIRENT_INCLUDE_DIR}"
+ )
+endif()
+
+target_link_libraries(rnp
+ PRIVATE
+ librnp
+ JSON-C::JSON-C
+)
+if(MSVC)
+ target_link_libraries(rnp
+ PRIVATE
+ "${GETOPT_LIBRARY}"
+ )
+endif(MSVC)
+
+include(GNUInstallDirs)
+install(TARGETS rnp
+ RUNTIME
+ DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ COMPONENT cli
+)
+
+# Build and install man page
+if (ENABLE_DOC)
+ add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/rnp.1.adoc" ${RNP_VERSION})
+endif()
diff --git a/src/rnp/fficli.cpp b/src/rnp/fficli.cpp
new file mode 100644
index 0000000..fa118ee
--- /dev/null
+++ b/src/rnp/fficli.cpp
@@ -0,0 +1,3229 @@
+/*
+ * Copyright (c) 2019-2021, [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 "config.h"
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <stdarg.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <string>
+#include <vector>
+#include <iterator>
+#include <cassert>
+#include <ctype.h>
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <sys/param.h>
+#include <unistd.h>
+#endif
+
+#ifndef _WIN32
+#include <termios.h>
+#ifdef HAVE_SYS_RESOURCE_H
+#include <sys/resource.h>
+#endif
+#endif
+
+#ifdef _WIN32
+#include <crtdbg.h>
+#endif
+
+#include "fficli.h"
+#include "str-utils.h"
+#include "file-utils.h"
+#include "time-utils.h"
+#include "defaults.h"
+
+#ifndef RNP_USE_STD_REGEX
+#include <regex.h>
+#else
+#include <regex>
+#endif
+
+#ifdef HAVE_SYS_RESOURCE_H
+/* When system resource consumption limit controls are available this
+ * can be used to attempt to disable core dumps which may leak
+ * sensitive data.
+ *
+ * Returns false if disabling core dumps failed, returns true if disabling
+ * core dumps succeeded. errno will be set to the result from setrlimit in
+ * the event of failure.
+ */
+static bool
+disable_core_dumps(void)
+{
+ struct rlimit limit;
+ int error;
+
+ errno = 0;
+ memset(&limit, 0, sizeof(limit));
+ error = setrlimit(RLIMIT_CORE, &limit);
+
+ if (error == 0) {
+ error = getrlimit(RLIMIT_CORE, &limit);
+ if (error) {
+ ERR_MSG("Warning - cannot turn off core dumps");
+ return false;
+ } else if (limit.rlim_cur == 0) {
+ return true; // disabling core dumps ok
+ } else {
+ return false; // failed for some reason?
+ }
+ }
+ return false;
+}
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+#include <stdexcept>
+
+static std::vector<std::string>
+get_utf8_args()
+{
+ int arg_nb;
+ wchar_t **arg_w;
+
+ arg_w = CommandLineToArgvW(GetCommandLineW(), &arg_nb);
+ if (!arg_w) {
+ throw std::runtime_error("CommandLineToArgvW failed");
+ }
+
+ try {
+ std::vector<std::string> result;
+ result.reserve(arg_nb);
+ for (int i = 0; i < arg_nb; i++) {
+ auto utf8 = wstr_to_utf8(arg_w[i]);
+ result.push_back(utf8);
+ }
+ LocalFree(arg_w);
+ return result;
+ } catch (...) {
+ LocalFree(arg_w);
+ throw;
+ }
+}
+
+void
+rnp_win_clear_args(int argc, char **argv)
+{
+ for (int i = 0; i < argc; i++) {
+ if (argv[i]) {
+ free(argv[i]);
+ }
+ }
+ delete argv;
+}
+
+bool
+rnp_win_substitute_cmdline_args(int *argc, char ***argv)
+{
+ int argc_utf8 = 0;
+ 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 + 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;
+ }
+ *argc = argc_utf8;
+ *argv = argv_utf8_cstrs;
+ return true;
+}
+#endif
+
+static bool
+set_pass_fd(FILE **file, int passfd)
+{
+ if (!file) {
+ return false;
+ }
+ *file = rnp_fdopen(passfd, "r");
+ if (!*file) {
+ ERR_MSG("Cannot open fd %d for reading", passfd);
+ return false;
+ }
+ return true;
+}
+
+static char *
+ptimestr(char *dest, size_t size, time_t t)
+{
+ struct tm tm = {};
+ rnp_gmtime(t, tm);
+ (void) snprintf(dest,
+ size,
+ "%s%04d-%02d-%02d",
+ rnp_y2k38_warning(t) ? ">=" : "",
+ tm.tm_year + 1900,
+ tm.tm_mon + 1,
+ tm.tm_mday);
+ return dest;
+}
+
+static bool
+cli_rnp_get_confirmation(const cli_rnp_t *rnp, const char *msg, ...)
+{
+ char reply[10];
+ va_list ap;
+
+ while (true) {
+ va_start(ap, msg);
+ vfprintf(rnp->userio_out, msg, ap);
+ va_end(ap);
+ fprintf(rnp->userio_out, " (y/N) ");
+ fflush(rnp->userio_out);
+
+ if (fgets(reply, sizeof(reply), rnp->userio_in) == NULL) {
+ return false;
+ }
+
+ rnp::strip_eol(reply);
+
+ if (strlen(reply) > 0) {
+ if (toupper(reply[0]) == 'Y') {
+ return true;
+ } else if (toupper(reply[0]) == 'N') {
+ return false;
+ }
+
+ fprintf(rnp->userio_out, "Sorry, response '%s' not understood.\n", reply);
+ } else {
+ return false;
+ }
+ }
+
+ return false;
+}
+
+static bool
+rnp_ask_filename(const std::string &msg, std::string &res, cli_rnp_t &rnp)
+{
+ fprintf(rnp.userio_out, "%s", msg.c_str());
+ fflush(rnp.userio_out);
+ char fname[128] = {0};
+ std::string path;
+ do {
+ if (!fgets(fname, sizeof(fname), rnp.userio_in)) {
+ return false;
+ }
+ path = path + std::string(fname);
+ if (rnp::strip_eol(path)) {
+ res = path;
+ return true;
+ }
+ if (path.size() >= 2048) {
+ fprintf(rnp.userio_out, "%s", "Too long filename, aborting.");
+ fflush(rnp.userio_out);
+ return false;
+ }
+ } while (1);
+}
+
+/** @brief checks whether file exists already and asks user for the new filename
+ * @param path output file name with path. May be an empty string, then user is asked for it.
+ * @param res resulting output path will be stored here.
+ * @param rnp initialized cli_rnp_t structure with additional data
+ * @return true on success, or false otherwise (user cancels the operation)
+ **/
+
+static bool
+rnp_get_output_filename(const std::string &path, std::string &res, cli_rnp_t &rnp)
+{
+ std::string newpath = path;
+ if (newpath.empty() &&
+ !rnp_ask_filename("Please enter the output filename: ", newpath, rnp)) {
+ return false;
+ }
+
+ while (true) {
+ if (!rnp_file_exists(newpath.c_str())) {
+ res = newpath;
+ return true;
+ }
+ if (rnp.cfg().get_bool(CFG_OVERWRITE) ||
+ cli_rnp_get_confirmation(
+ &rnp,
+ "File '%s' already exists. Would you like to overwrite it?",
+ newpath.c_str())) {
+ rnp_unlink(newpath.c_str());
+ res = newpath;
+ return true;
+ }
+
+ if (!rnp_ask_filename("Please enter the new filename: ", newpath, rnp)) {
+ return false;
+ }
+ if (newpath.empty()) {
+ return false;
+ }
+ }
+}
+
+static bool
+stdin_getpass(const char *prompt, char *buffer, size_t size, cli_rnp_t &rnp)
+{
+#ifndef _WIN32
+ struct termios saved_flags, noecho_flags;
+ bool restore_ttyflags = false;
+#endif
+ bool ok = false;
+ FILE *in = NULL;
+ FILE *out = NULL;
+ FILE *userio_in = rnp.userio_in ? rnp.userio_in : stdin;
+
+ // validate args
+ if (!buffer) {
+ goto end;
+ }
+ // doesn't hurt
+ *buffer = '\0';
+
+ if (!rnp.cfg().get_bool(CFG_NOTTY)) {
+#ifndef _WIN32
+ in = fopen("/dev/tty", "w+ce");
+#endif
+ out = in;
+ }
+
+ if (!in) {
+ in = userio_in;
+ out = 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
+ // save the original termios
+ if (tcgetattr(fileno(in), &saved_flags) == 0) {
+ noecho_flags = saved_flags;
+ // disable echo in the local modes
+ noecho_flags.c_lflag = (noecho_flags.c_lflag & ~ECHO) | ECHONL | ISIG;
+ restore_ttyflags = (tcsetattr(fileno(in), TCSANOW, &noecho_flags) == 0);
+ }
+#endif
+ if (prompt) {
+ fputs(prompt, out);
+ }
+ if (fgets(buffer, size, in) == NULL) {
+ goto end;
+ }
+
+ rnp::strip_eol(buffer);
+ ok = true;
+end:
+#ifndef _WIN32
+ if (restore_ttyflags) {
+ tcsetattr(fileno(in), TCSAFLUSH, &saved_flags);
+ }
+#endif
+ if (in && (in != userio_in)) {
+ fclose(in);
+ }
+ return ok;
+}
+
+static bool
+ffi_pass_callback_stdin(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char buf[],
+ size_t buf_len)
+{
+ char * keyid = NULL;
+ char target[64] = {0};
+ char prompt[128] = {0};
+ char * buffer = NULL;
+ bool ok = false;
+ bool protect = false;
+ bool add_subkey = false;
+ bool decrypt_symmetric = false;
+ bool encrypt_symmetric = false;
+ bool is_primary = false;
+ cli_rnp_t *rnp = static_cast<cli_rnp_t *>(app_ctx);
+
+ if (!ffi || !pgp_context) {
+ goto done;
+ }
+
+ if (!strcmp(pgp_context, "protect")) {
+ protect = true;
+ } else if (!strcmp(pgp_context, "add subkey")) {
+ add_subkey = 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 || add_subkey) && 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 (decrypt_symmetric) {
+ snprintf(prompt, sizeof(prompt), "Enter password to decrypt data: ");
+ } else if (encrypt_symmetric) {
+ snprintf(prompt, sizeof(prompt), "Enter password to encrypt data: ");
+ } else {
+ snprintf(prompt, sizeof(prompt), "Enter password for %s to %s: ", target, pgp_context);
+ }
+
+ if (!stdin_getpass(prompt, buf, buf_len, *rnp)) {
+ goto done;
+ }
+ if (protect || encrypt_symmetric) {
+ if (protect) {
+ snprintf(prompt, sizeof(prompt), "Repeat password for %s: ", target);
+ } else {
+ snprintf(prompt, sizeof(prompt), "Repeat password: ");
+ }
+
+ if (!stdin_getpass(prompt, buffer, buf_len, *rnp)) {
+ goto done;
+ }
+ 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 || add_subkey) && 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);
+ free(buffer);
+ return ok;
+}
+
+static bool
+ffi_pass_callback_file(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char buf[],
+ size_t buf_len)
+{
+ if (!app_ctx || !buf || !buf_len) {
+ return false;
+ }
+
+ FILE *fp = (FILE *) app_ctx;
+ if (!fgets(buf, buf_len, fp)) {
+ return false;
+ }
+ rnp::strip_eol(buf);
+ return true;
+}
+
+static bool
+ffi_pass_callback_string(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char buf[],
+ size_t buf_len)
+{
+ if (!app_ctx || !buf || !buf_len) {
+ return false;
+ }
+
+ const char *pswd = (const char *) app_ctx;
+ if (strlen(pswd) >= buf_len) {
+ return false;
+ }
+
+ strncpy(buf, pswd, buf_len);
+ return true;
+}
+
+static void
+ffi_key_callback(rnp_ffi_t ffi,
+ void * app_ctx,
+ const char *identifier_type,
+ const char *identifier,
+ bool secret)
+{
+ cli_rnp_t *rnp = static_cast<cli_rnp_t *>(app_ctx);
+
+ if (rnp::str_case_eq(identifier_type, "keyid") &&
+ rnp::str_case_eq(identifier, "0000000000000000")) {
+ if (rnp->hidden_msg) {
+ return;
+ }
+ ERR_MSG("This message has hidden recipient. Will attempt to use all secret keys for "
+ "decryption.");
+ rnp->hidden_msg = 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
+
+cli_rnp_t::~cli_rnp_t()
+{
+ end();
+#ifdef _WIN32
+ if (subst_argv) {
+ rnp_win_clear_args(subst_argc, subst_argv);
+ }
+#endif
+}
+
+int
+cli_rnp_t::ret_code(bool success)
+{
+ return success ? EXIT_SUCCESS : EXIT_FAILURE;
+}
+
+#ifdef _WIN32
+void
+cli_rnp_t::substitute_args(int *argc, char ***argv)
+{
+ rnp_win_substitute_cmdline_args(argc, argv);
+ subst_argc = *argc;
+ subst_argv = *argv;
+}
+#endif
+
+bool
+cli_rnp_t::init(const rnp_cfg &cfg)
+{
+ cfg_.copy(cfg);
+
+ /* Configure user's io streams. */
+ if (!cfg_.get_bool(CFG_NOTTY)) {
+ userio_in = (isatty(fileno(stdin)) ? stdin : fopen("/dev/tty", "r"));
+ userio_in = (userio_in ? userio_in : stdin);
+ userio_out = (isatty(fileno(stdout)) ? stdout : fopen("/dev/tty", "a+"));
+ userio_out = (userio_out ? userio_out : stdout);
+ } else {
+ userio_in = stdin;
+ userio_out = stdout;
+ }
+
+#ifndef _WIN32
+ /* If system resource constraints are in effect then attempt to
+ * disable core dumps.
+ */
+ bool coredumps = true;
+ if (!cfg_.get_bool(CFG_COREDUMPS)) {
+#ifdef HAVE_SYS_RESOURCE_H
+ coredumps = !disable_core_dumps();
+#endif
+ }
+
+ 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 == "<stderr>")) {
+ resfp = stderr;
+ } else if (ress == "<stdout>") {
+ resfp = stdout;
+ } else if (!(resfp = rnp_fopen(ress.c_str(), "w"))) {
+ ERR_MSG("Cannot open results %s for writing", ress.c_str());
+ return false;
+ }
+
+ bool res = false;
+ const std::string pformat = pubformat();
+ const std::string sformat = secformat();
+ 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");
+ return false;
+ }
+
+ // by default use stdin password provider
+ if (rnp_ffi_set_pass_provider(ffi, ffi_pass_callback_stdin, this)) {
+ goto done;
+ }
+
+ // set key provider, currently for informational purposes only
+ if (rnp_ffi_set_key_provider(ffi, ffi_key_callback, this)) {
+ goto done;
+ }
+
+ // setup file/pipe password input if requested
+ if (cfg_.get_int(CFG_PASSFD, -1) >= 0) {
+ if (!set_pass_fd(&passfp, cfg_.get_int(CFG_PASSFD))) {
+ goto done;
+ }
+ 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);
+ ffi = NULL;
+ }
+ return res;
+}
+
+void
+cli_rnp_t::end()
+{
+ if (passfp) {
+ fclose(passfp);
+ passfp = NULL;
+ }
+ if (resfp && (resfp != stderr) && (resfp != stdout)) {
+ fclose(resfp);
+ resfp = NULL;
+ }
+ if (userio_in && userio_in != stdin) {
+ fclose(userio_in);
+ }
+ userio_in = NULL;
+ if (userio_out && userio_out != stdout) {
+ fclose(userio_out);
+ }
+ 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 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.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.c_str());
+ }
+ rnp_input_destroy(keyin);
+
+ if (ret) {
+ return false;
+ }
+
+ size_t keycount = 0;
+ if (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.c_str());
+ }
+ return true;
+}
+
+bool
+cli_rnp_t::load_keyrings(bool loadsecret)
+{
+ /* Read public keys */
+ if (rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC)) {
+ ERR_MSG("failed to clear public keyring");
+ return false;
+ }
+
+ if (!load_keyring(false)) {
+ return false;
+ }
+
+ /* Only read secret keys if we need to */
+ if (loadsecret) {
+ if (rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET)) {
+ ERR_MSG("failed to clear secret keyring");
+ return false;
+ }
+
+ if (!load_keyring(true)) {
+ return false;
+ }
+ }
+ if (defkey().empty()) {
+ set_defkey();
+ }
+ return true;
+}
+
+void
+cli_rnp_t::set_defkey()
+{
+ rnp_identifier_iterator_t it = NULL;
+ rnp_key_handle_t handle = NULL;
+ const char * grip = NULL;
+
+ cfg_.unset(CFG_KR_DEF_KEY);
+ if (rnp_identifier_iterator_create(ffi, &it, "grip")) {
+ ERR_MSG("failed to create key iterator");
+ return;
+ }
+
+ while (!rnp_identifier_iterator_next(it, &grip)) {
+ bool is_subkey = false;
+ bool is_secret = false;
+
+ if (!grip) {
+ break;
+ }
+ if (rnp_locate_key(ffi, "grip", grip, &handle)) {
+ ERR_MSG("failed to locate key");
+ continue;
+ }
+ if (rnp_key_is_sub(handle, &is_subkey) || is_subkey) {
+ goto next;
+ }
+ if (rnp_key_have_secret(handle, &is_secret)) {
+ goto next;
+ }
+ if (!cfg_.has(CFG_KR_DEF_KEY) || is_secret) {
+ cfg_.set_str(CFG_KR_DEF_KEY, grip);
+ /* if we have secret primary key then use it as default */
+ if (is_secret) {
+ break;
+ }
+ }
+ next:
+ rnp_key_handle_destroy(handle);
+ handle = NULL;
+ }
+ rnp_key_handle_destroy(handle);
+ rnp_identifier_iterator_destroy(it);
+}
+
+bool
+cli_rnp_t::is_cv25519_subkey(rnp_key_handle_t handle)
+{
+ bool primary = false;
+ if (rnp_key_is_primary(handle, &primary)) {
+ ERR_MSG("Error: failed to check for subkey.");
+ return false;
+ }
+ if (primary) {
+ return false;
+ }
+ char *alg = NULL;
+ if (rnp_key_get_alg(handle, &alg)) {
+ ERR_MSG("Error: failed to check key's alg.");
+ return false;
+ }
+ bool ecdh = !strcmp(alg, RNP_ALGNAME_ECDH);
+ rnp_buffer_destroy(alg);
+ if (!ecdh) {
+ return false;
+ }
+ char *curve = NULL;
+ if (rnp_key_get_curve(handle, &curve)) {
+ ERR_MSG("Error: failed to check key's curve.");
+ return false;
+ }
+ bool cv25519 = !strcmp(curve, "Curve25519");
+ rnp_buffer_destroy(curve);
+ return cv25519;
+}
+
+bool
+cli_rnp_t::get_protection(rnp_key_handle_t handle,
+ std::string & hash,
+ std::string & cipher,
+ size_t & iterations)
+{
+ bool prot = false;
+ if (rnp_key_is_protected(handle, &prot)) {
+ ERR_MSG("Error: failed to check key's protection.");
+ return false;
+ }
+ if (!prot) {
+ hash = "";
+ cipher = "";
+ iterations = 0;
+ return true;
+ }
+
+ char *val = NULL;
+ try {
+ if (rnp_key_get_protection_hash(handle, &val)) {
+ ERR_MSG("Error: failed to retrieve key's protection hash.");
+ return false;
+ }
+ hash = val;
+ rnp_buffer_destroy(val);
+ if (rnp_key_get_protection_cipher(handle, &val)) {
+ ERR_MSG("Error: failed to retrieve key's protection cipher.");
+ return false;
+ }
+ cipher = val;
+ rnp_buffer_destroy(val);
+ if (rnp_key_get_protection_iterations(handle, &iterations)) {
+ ERR_MSG("Error: failed to retrieve key's protection iterations.");
+ return false;
+ }
+ return true;
+ } catch (const std::exception &e) {
+ ERR_MSG("Error: failed to retrieve key's properties: %s", e.what());
+ rnp_buffer_destroy(val);
+ return false;
+ }
+}
+
+bool
+cli_rnp_t::check_cv25519_bits(rnp_key_handle_t key, char *prot_password, bool &tweaked)
+{
+ /* unlock key first to check whether bits are tweaked */
+ if (prot_password && rnp_key_unlock(key, prot_password)) {
+ ERR_MSG("Error: failed to unlock key. Did you specify valid password?");
+ return false;
+ }
+ rnp_result_t ret = rnp_key_25519_bits_tweaked(key, &tweaked);
+ if (ret) {
+ ERR_MSG("Error: failed to check whether key's bits are tweaked.");
+ }
+ if (prot_password) {
+ rnp_key_lock(key);
+ }
+ return !ret;
+}
+
+bool
+cli_rnp_t::fix_cv25519_subkey(const std::string &key, bool checkonly)
+{
+ std::vector<rnp_key_handle_t> keys;
+ if (!cli_rnp_keys_matching_string(
+ this, keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) {
+ ERR_MSG("Secret keys matching '%s' not found.", key.c_str());
+ return false;
+ }
+ bool res = false;
+ std::string prot_hash;
+ std::string prot_cipher;
+ size_t prot_iterations;
+ char * prot_password = NULL;
+ bool tweaked = false;
+
+ if (keys.size() > 1) {
+ ERR_MSG(
+ "Ambiguous input: too many keys found for '%s'. Did you use keyid or fingerprint?",
+ key.c_str());
+ goto done;
+ }
+ cli_rnp_print_key_info(userio_out, ffi, keys[0], true, false);
+ if (!is_cv25519_subkey(keys[0])) {
+ ERR_MSG("Error: specified key is not Curve25519 ECDH subkey.");
+ goto done;
+ }
+
+ if (!get_protection(keys[0], prot_hash, prot_cipher, prot_iterations)) {
+ goto done;
+ }
+
+ if (!prot_hash.empty() &&
+ (rnp_request_password(ffi, keys[0], "unprotect", &prot_password) || !prot_password)) {
+ ERR_MSG("Error: failed to obtain protection password.");
+ goto done;
+ }
+
+ if (!check_cv25519_bits(keys[0], prot_password, tweaked)) {
+ goto done;
+ }
+
+ if (checkonly) {
+ fprintf(userio_out,
+ tweaked ? "Cv25519 key bits are set correctly and do not require fixing.\n" :
+ "Warning: Cv25519 key bits need fixing.\n");
+ res = tweaked;
+ goto done;
+ }
+
+ if (tweaked) {
+ ERR_MSG("Warning: key's bits are fixed already, no action is required.");
+ res = true;
+ goto done;
+ }
+
+ /* now unprotect so we can tweak bits */
+ if (!prot_hash.empty()) {
+ if (rnp_key_unprotect(keys[0], prot_password)) {
+ ERR_MSG("Error: failed to unprotect key. Did you specify valid password?");
+ goto done;
+ }
+ if (rnp_key_unlock(keys[0], NULL)) {
+ ERR_MSG("Error: failed to unlock key.");
+ goto done;
+ }
+ }
+
+ /* tweak key bits and protect back */
+ if (rnp_key_25519_bits_tweak(keys[0])) {
+ ERR_MSG("Error: failed to tweak key's bits.");
+ goto done;
+ }
+
+ if (!prot_hash.empty() && rnp_key_protect(keys[0],
+ prot_password,
+ prot_cipher.c_str(),
+ NULL,
+ prot_hash.c_str(),
+ prot_iterations)) {
+ ERR_MSG("Error: failed to protect key back.");
+ goto done;
+ }
+
+ res = cli_rnp_save_keyrings(this);
+done:
+ clear_key_handles(keys);
+ if (prot_password) {
+ rnp_buffer_clear(prot_password, strlen(prot_password) + 1);
+ rnp_buffer_destroy(prot_password);
+ }
+ return res;
+}
+
+bool
+cli_rnp_t::set_key_expire(const std::string &key)
+{
+ std::vector<rnp_key_handle_t> keys;
+ if (!cli_rnp_keys_matching_string(
+ this, keys, key, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS)) {
+ ERR_MSG("Secret keys matching '%s' not found.", key.c_str());
+ return false;
+ }
+ bool res = false;
+ uint32_t expiration = 0;
+ if (keys.size() > 1) {
+ ERR_MSG("Ambiguous input: too many keys found for '%s'.", key.c_str());
+ goto done;
+ }
+ if (!cfg().get_expiration(CFG_SET_KEY_EXPIRE, expiration) ||
+ rnp_key_set_expiration(keys[0], expiration)) {
+ ERR_MSG("Failed to set key expiration.");
+ goto done;
+ }
+ res = cli_rnp_save_keyrings(this);
+done:
+ if (res) {
+ cli_rnp_print_key_info(stdout, ffi, keys[0], true, false);
+ }
+ clear_key_handles(keys);
+ return res;
+}
+
+bool
+cli_rnp_t::add_new_subkey(const std::string &key)
+{
+ rnp_cfg &lcfg = cfg();
+ if (!cli_rnp_set_generate_params(lcfg, true)) {
+ ERR_MSG("Subkey generation setup failed.");
+ return false;
+ }
+ std::vector<rnp_key_handle_t> keys;
+ if (!cli_rnp_keys_matching_string(this, keys, key, CLI_SEARCH_SECRET)) {
+ ERR_MSG("Secret keys matching '%s' not found.", key.c_str());
+ return false;
+ }
+ bool res = false;
+ rnp_op_generate_t genkey = NULL;
+ rnp_key_handle_t subkey = NULL;
+ char * password = NULL;
+
+ if (keys.size() > 1) {
+ ERR_MSG("Ambiguous input: too many keys found for '%s'.", key.c_str());
+ goto done;
+ }
+ if (rnp_op_generate_subkey_create(
+ &genkey, ffi, keys[0], cfg().get_cstr(CFG_KG_SUBKEY_ALG))) {
+ ERR_MSG("Failed to initialize subkey generation.");
+ goto done;
+ }
+ if (cfg().has(CFG_KG_SUBKEY_BITS) &&
+ rnp_op_generate_set_bits(genkey, cfg().get_int(CFG_KG_SUBKEY_BITS))) {
+ ERR_MSG("Failed to set subkey bits.");
+ goto done;
+ }
+ if (cfg().has(CFG_KG_SUBKEY_CURVE) &&
+ rnp_op_generate_set_curve(genkey, cfg().get_cstr(CFG_KG_SUBKEY_CURVE))) {
+ ERR_MSG("Failed to set subkey curve.");
+ goto done;
+ }
+ if (cfg().has(CFG_KG_SUBKEY_EXPIRATION)) {
+ uint32_t expiration = 0;
+ if (!cfg().get_expiration(CFG_KG_SUBKEY_EXPIRATION, expiration) ||
+ rnp_op_generate_set_expiration(genkey, expiration)) {
+ ERR_MSG("Failed to set subkey expiration.");
+ goto done;
+ }
+ }
+ // TODO : set DSA qbits
+ if (rnp_op_generate_set_hash(genkey, cfg().get_cstr(CFG_KG_HASH))) {
+ ERR_MSG("Failed to set hash algorithm.");
+ goto done;
+ }
+ if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &subkey)) {
+ ERR_MSG("Subkey generation failed.");
+ goto done;
+ }
+ if (rnp_request_password(ffi, subkey, "protect", &password)) {
+ ERR_MSG("Failed to obtain protection password.");
+ goto done;
+ }
+ if (*password) {
+ rnp_result_t ret = rnp_key_protect(subkey,
+ password,
+ cfg().get_cstr(CFG_KG_PROT_ALG),
+ NULL,
+ cfg().get_cstr(CFG_KG_PROT_HASH),
+ cfg().get_int(CFG_KG_PROT_ITERATIONS));
+ rnp_buffer_clear(password, strlen(password) + 1);
+ rnp_buffer_destroy(password);
+ if (ret) {
+ ERR_MSG("Failed to protect key.");
+ goto done;
+ }
+ } else {
+ rnp_buffer_destroy(password);
+ }
+ res = cli_rnp_save_keyrings(this);
+done:
+ if (res) {
+ cli_rnp_print_key_info(stdout, ffi, keys[0], true, false);
+ if (subkey) {
+ cli_rnp_print_key_info(stdout, ffi, subkey, true, false);
+ }
+ }
+ clear_key_handles(keys);
+ rnp_op_generate_destroy(genkey);
+ rnp_key_handle_destroy(subkey);
+ return res;
+}
+
+bool
+cli_rnp_t::edit_key(const std::string &key)
+{
+ int edit_options = 0;
+
+ if (cfg().get_bool(CFG_CHK_25519_BITS)) {
+ edit_options++;
+ }
+ if (cfg().get_bool(CFG_FIX_25519_BITS)) {
+ edit_options++;
+ }
+ if (cfg().get_bool(CFG_ADD_SUBKEY)) {
+ edit_options++;
+ }
+ if (cfg().has(CFG_SET_KEY_EXPIRE)) {
+ edit_options++;
+ }
+
+ if (!edit_options) {
+ ERR_MSG("You should specify one of the editing options for --edit-key.");
+ return false;
+ }
+ if (edit_options > 1) {
+ ERR_MSG("Only one key edit option can be executed at a time.");
+ return false;
+ }
+
+ if (cfg().get_bool(CFG_CHK_25519_BITS)) {
+ return fix_cv25519_subkey(key, true);
+ }
+ if (cfg().get_bool(CFG_FIX_25519_BITS)) {
+ return fix_cv25519_subkey(key, false);
+ }
+
+ if (cfg().get_bool(CFG_ADD_SUBKEY)) {
+ return add_new_subkey(key);
+ }
+
+ if (cfg().has(CFG_SET_KEY_EXPIRE)) {
+ return set_key_expire(key);
+ }
+
+ return false;
+}
+
+const char *
+json_obj_get_str(json_object *obj, const char *key)
+{
+ json_object *fld = NULL;
+ if (!json_object_object_get_ex(obj, key, &fld)) {
+ return NULL;
+ }
+ return json_object_get_string(fld);
+}
+
+static char *
+cli_key_usage_str(rnp_key_handle_t key, char *buf)
+{
+ char *orig = buf;
+ bool allow = false;
+
+ if (!rnp_key_allows_usage(key, "encrypt", &allow) && allow) {
+ *buf++ = 'E';
+ }
+ allow = false;
+ if (!rnp_key_allows_usage(key, "sign", &allow) && allow) {
+ *buf++ = 'S';
+ }
+ allow = false;
+ if (!rnp_key_allows_usage(key, "certify", &allow) && allow) {
+ *buf++ = 'C';
+ }
+ allow = false;
+ if (!rnp_key_allows_usage(key, "authenticate", &allow) && allow) {
+ *buf++ = 'A';
+ }
+ *buf = '\0';
+ return orig;
+}
+
+std::string
+cli_rnp_escape_string(const std::string &src)
+{
+ static const int SPECIAL_CHARS_COUNT = 0x20;
+ static const char *escape_map[SPECIAL_CHARS_COUNT + 1] = {
+ "\\x00", "\\x01", "\\x02", "\\x03", "\\x04", "\\x05", "\\x06", "\\x07",
+ "\\b", "\\x09", "\\n", "\\v", "\\f", "\\r", "\\x0e", "\\x0f",
+ "\\x10", "\\x11", "\\x12", "\\x13", "\\x14", "\\x15", "\\x16", "\\x17",
+ "\\x18", "\\x19", "\\x1a", "\\x1b", "\\x1c", "\\x1d", "\\x1e", "\\x1f",
+ "\\x20" // space should not be auto-replaced
+ };
+ std::string result;
+ // we want to replace leading and trailing spaces with escape codes to make them visible
+ auto original_len = src.length();
+ std::string rtrimmed = src;
+ bool leading_space = true;
+ rtrimmed.erase(rtrimmed.find_last_not_of(0x20) + 1);
+ result.reserve(original_len);
+ for (char const &c : rtrimmed) {
+ leading_space &= c == 0x20;
+ if (leading_space || (c >= 0 && c < SPECIAL_CHARS_COUNT)) {
+ result.append(escape_map[(int) c]);
+ } else {
+ result.push_back(c);
+ }
+ }
+ // printing trailing spaces
+ for (auto pos = rtrimmed.length(); pos < original_len; pos++) {
+ result.append(escape_map[0x20]);
+ }
+ return result;
+}
+
+static const std::string alg_aliases[] = {
+ "3DES", "TRIPLEDES", "3-DES", "TRIPLEDES", "CAST-5", "CAST5",
+ "AES", "AES128", "AES-128", "AES128", "AES-192", "AES192",
+ "AES-256", "AES256", "CAMELLIA-128", "CAMELLIA128", "CAMELLIA-192", "CAMELLIA192",
+ "CAMELLIA-256", "CAMELLIA256", "SHA", "SHA1", "SHA-1", "SHA1",
+ "SHA-224", "SHA224", "SHA-256", "SHA256", "SHA-384", "SHA384",
+ "SHA-512", "SHA512", "RIPEMD-160", "RIPEMD160"};
+
+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::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;
+ result.reserve(src.length());
+ regex_t r = {};
+ regmatch_t matches[1];
+ if (regcomp(&r, "\\\\x[0-9a-f]([0-9a-f])?", REG_EXTENDED | REG_ICASE) != 0)
+ return src;
+
+ int offset = 0;
+ while (regexec(&r, src.c_str() + offset, 1, matches, 0) == 0) {
+ result.append(src, offset, matches[0].rm_so);
+ int hexoff = matches[0].rm_so + 2;
+ std::string hex;
+ hex.push_back(src[offset + hexoff]);
+ if (hexoff + 1 < matches[0].rm_eo) {
+ hex.push_back(src[offset + hexoff + 1]);
+ }
+ char decoded = stoi(hex, 0, 16);
+ if ((decoded >= 0x7B && decoded <= 0x7D) || (decoded >= 0x24 && decoded <= 0x2E) ||
+ decoded == 0x5C || decoded == 0x5E) {
+ result.push_back('\\');
+ result.push_back(decoded);
+ } else if ((decoded == '[' || decoded == ']') &&
+ /* not enclosed in [] */ (result.empty() || result.back() != '[')) {
+ result.push_back('[');
+ result.push_back(decoded);
+ result.push_back(']');
+ } else {
+ result.push_back(decoded);
+ }
+ offset += matches[0].rm_eo;
+ }
+
+ result.append(src.begin() + offset, src.end());
+
+ 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;
+
+ /* header */
+ if (rnp_key_have_secret(key, &secret) || rnp_key_is_primary(key, &primary)) {
+ fprintf(fp, "Key error.\n");
+ return;
+ }
+
+ if (psecret && secret) {
+ header = primary ? "sec" : "ssb";
+ } else {
+ header = primary ? "pub" : "sub";
+ }
+ if (primary) {
+ 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 */
+ char *alg = NULL;
+ (void) rnp_key_get_alg(key, &alg);
+ fprintf(fp, "%s ", cli_rnp_normalize_key_alg(alg));
+ /* key id */
+ char *keyid = NULL;
+ (void) rnp_key_get_keyid(key, &keyid);
+ fprintf(fp, "%s", rnp::lowercase(keyid));
+ /* key creation time */
+ uint32_t create = 0;
+ (void) rnp_key_get_creation(key, &create);
+ fprintf(fp, " %s", ptimestr(buf, sizeof(buf), create));
+ /* key usage */
+ 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) {
+ ptimestr(buf, sizeof(buf), create + expiry);
+ fprintf(fp, " [%s %s]", expired ? "EXPIRED" : "EXPIRES", buf);
+ }
+ /* key is revoked */
+ if (revoked) {
+ fprintf(fp, " [REVOKED]");
+ }
+ /* 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;
+
+ 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]" : 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;
+ (void) rnp_uid_get_signature_at(uid, j, &sig);
+ if (!sig) {
+ continue;
+ }
+ cli_rnp_print_sig_info(fp, ffi, sig);
+ rnp_signature_handle_destroy(sig);
+ }
+ (void) rnp_uid_handle_destroy(uid);
+ }
+
+ rnp_buffer_destroy(alg);
+ rnp_buffer_destroy(keyid);
+ rnp_buffer_destroy(keyfp);
+}
+
+bool
+cli_rnp_save_keyrings(cli_rnp_t *rnp)
+{
+ rnp_output_t output = NULL;
+ rnp_result_t pub_ret = 0;
+ rnp_result_t sec_ret = 0;
+ const std::string &ppath = rnp->pubpath();
+ const std::string &spath = rnp->secpath();
+
+ // check whether we have G10 secret keyring - then need to create directory
+ if (rnp->secformat() == "G10") {
+ struct stat path_stat;
+ if (rnp_stat(spath.c_str(), &path_stat) != -1) {
+ if (!S_ISDIR(path_stat.st_mode)) {
+ ERR_MSG("G10 keystore should be a directory: %s", spath.c_str());
+ return false;
+ }
+ } else {
+ if (errno != ENOENT) {
+ ERR_MSG("stat(%s): %s", spath.c_str(), strerror(errno));
+ return false;
+ }
+ if (RNP_MKDIR(spath.c_str(), S_IRWXU) != 0) {
+ ERR_MSG("mkdir(%s, S_IRWXU): %s", spath.c_str(), strerror(errno));
+ return false;
+ }
+ }
+ }
+
+ // public keyring
+ if (!(pub_ret = rnp_output_to_path(&output, ppath.c_str()))) {
+ pub_ret =
+ rnp_save_keys(rnp->ffi, rnp->pubformat().c_str(), output, RNP_LOAD_SAVE_PUBLIC_KEYS);
+ rnp_output_destroy(output);
+ }
+ if (pub_ret) {
+ ERR_MSG("failed to write pubring to path '%s'", ppath.c_str());
+ }
+
+ // secret keyring
+ if (!(sec_ret = rnp_output_to_path(&output, spath.c_str()))) {
+ sec_ret =
+ rnp_save_keys(rnp->ffi, rnp->secformat().c_str(), output, RNP_LOAD_SAVE_SECRET_KEYS);
+ rnp_output_destroy(output);
+ }
+ if (sec_ret) {
+ ERR_MSG("failed to write secring to path '%s'", spath.c_str());
+ }
+
+ return !pub_ret && !sec_ret;
+}
+
+bool
+cli_rnp_generate_key(cli_rnp_t *rnp, const char *username)
+{
+ /* set key generation parameters to rnp_cfg_t */
+ rnp_cfg &cfg = rnp->cfg();
+ if (!cli_rnp_set_generate_params(cfg)) {
+ ERR_MSG("Key generation setup failed.");
+ return false;
+ }
+ /* generate the primary key */
+ rnp_op_generate_t genkey = NULL;
+ rnp_key_handle_t primary = NULL;
+ rnp_key_handle_t subkey = NULL;
+ bool res = false;
+
+ if (rnp_op_generate_create(&genkey, rnp->ffi, cfg.get_cstr(CFG_KG_PRIMARY_ALG))) {
+ ERR_MSG("Failed to initialize key generation.");
+ return false;
+ }
+ if (username && rnp_op_generate_set_userid(genkey, username)) {
+ ERR_MSG("Failed to set userid.");
+ goto done;
+ }
+ if (cfg.has(CFG_KG_PRIMARY_BITS) &&
+ rnp_op_generate_set_bits(genkey, cfg.get_int(CFG_KG_PRIMARY_BITS))) {
+ ERR_MSG("Failed to set key bits.");
+ goto done;
+ }
+ if (cfg.has(CFG_KG_PRIMARY_CURVE) &&
+ rnp_op_generate_set_curve(genkey, cfg.get_cstr(CFG_KG_PRIMARY_CURVE))) {
+ ERR_MSG("Failed to set key curve.");
+ goto done;
+ }
+ if (cfg.has(CFG_KG_PRIMARY_EXPIRATION)) {
+ uint32_t expiration = 0;
+ if (!cfg.get_expiration(CFG_KG_PRIMARY_EXPIRATION, expiration) ||
+ rnp_op_generate_set_expiration(genkey, expiration)) {
+ ERR_MSG("Failed to set primary key expiration.");
+ goto done;
+ }
+ }
+ // TODO : set DSA qbits
+ if (rnp_op_generate_set_hash(genkey, cfg.get_cstr(CFG_KG_HASH))) {
+ ERR_MSG("Failed to set hash algorithm.");
+ goto done;
+ }
+
+ fprintf(rnp->userio_out, "Generating a new key...\n");
+ if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &primary)) {
+ ERR_MSG("Primary key generation failed.");
+ goto done;
+ }
+
+ if (!cfg.has(CFG_KG_SUBKEY_ALG)) {
+ res = true;
+ goto done;
+ }
+
+ rnp_op_generate_destroy(genkey);
+ genkey = NULL;
+ if (rnp_op_generate_subkey_create(
+ &genkey, rnp->ffi, primary, cfg.get_cstr(CFG_KG_SUBKEY_ALG))) {
+ ERR_MSG("Failed to initialize subkey generation.");
+ goto done;
+ }
+ if (cfg.has(CFG_KG_SUBKEY_BITS) &&
+ rnp_op_generate_set_bits(genkey, cfg.get_int(CFG_KG_SUBKEY_BITS))) {
+ ERR_MSG("Failed to set subkey bits.");
+ goto done;
+ }
+ if (cfg.has(CFG_KG_SUBKEY_CURVE) &&
+ rnp_op_generate_set_curve(genkey, cfg.get_cstr(CFG_KG_SUBKEY_CURVE))) {
+ ERR_MSG("Failed to set subkey curve.");
+ goto done;
+ }
+ if (cfg.has(CFG_KG_SUBKEY_EXPIRATION)) {
+ uint32_t expiration = 0;
+ if (!cfg.get_expiration(CFG_KG_SUBKEY_EXPIRATION, expiration) ||
+ rnp_op_generate_set_expiration(genkey, expiration)) {
+ ERR_MSG("Failed to set subkey expiration.");
+ goto done;
+ }
+ }
+ // TODO : set DSA qbits
+ if (rnp_op_generate_set_hash(genkey, cfg.get_cstr(CFG_KG_HASH))) {
+ ERR_MSG("Failed to set hash algorithm.");
+ goto done;
+ }
+ if (rnp_op_generate_execute(genkey) || rnp_op_generate_get_key(genkey, &subkey)) {
+ ERR_MSG("Subkey generation failed.");
+ goto done;
+ }
+
+ // protect
+ for (auto key : {primary, subkey}) {
+ char *password = NULL;
+ if (rnp_request_password(rnp->ffi, key, "protect", &password)) {
+ ERR_MSG("Failed to obtain protection password.");
+ goto done;
+ }
+ if (*password) {
+ rnp_result_t ret = rnp_key_protect(key,
+ password,
+ cfg.get_cstr(CFG_KG_PROT_ALG),
+ NULL,
+ cfg.get_cstr(CFG_KG_PROT_HASH),
+ cfg.get_int(CFG_KG_PROT_ITERATIONS));
+ rnp_buffer_clear(password, strlen(password) + 1);
+ rnp_buffer_destroy(password);
+ if (ret) {
+ ERR_MSG("Failed to protect key.");
+ goto done;
+ }
+ } else {
+ rnp_buffer_destroy(password);
+ }
+ }
+ res = cli_rnp_save_keyrings(rnp);
+done:
+ if (res) {
+ cli_rnp_print_key_info(stdout, rnp->ffi, primary, true, false);
+ if (subkey) {
+ cli_rnp_print_key_info(stdout, rnp->ffi, subkey, true, false);
+ }
+ }
+ rnp_op_generate_destroy(genkey);
+ rnp_key_handle_destroy(primary);
+ rnp_key_handle_destroy(subkey);
+ return res;
+}
+
+static bool
+key_matches_string(rnp_key_handle_t handle, const std::string &str)
+{
+ bool matches = false;
+ char * id = NULL;
+ size_t idlen = 0;
+#ifndef RNP_USE_STD_REGEX
+ regex_t r = {};
+#else
+ std::regex re;
+#endif
+ size_t uid_count = 0;
+ bool boolres = false;
+
+ if (str.empty()) {
+ matches = true;
+ goto done;
+ }
+ if (rnp::is_hex(str) && (str.length() >= RNP_KEYID_SIZE)) {
+ std::string hexstr = rnp::strip_hex(str);
+ size_t len = hexstr.length();
+
+ /* check whether it's key id */
+ if ((len == RNP_KEYID_SIZE * 2) || (len == RNP_KEYID_SIZE)) {
+ if (rnp_key_get_keyid(handle, &id)) {
+ goto done;
+ }
+ if ((idlen = strlen(id)) < len) {
+ goto done;
+ }
+ if (strncasecmp(hexstr.c_str(), id + idlen - len, len) == 0) {
+ matches = true;
+ goto done;
+ }
+ rnp_buffer_destroy(id);
+ id = NULL;
+ }
+
+ /* check fingerprint */
+ if (len == RNP_FP_SIZE * 2) {
+ if (rnp_key_get_fprint(handle, &id)) {
+ goto done;
+ }
+ if (strlen(id) != len) {
+ goto done;
+ }
+ if (strncasecmp(hexstr.c_str(), id, len) == 0) {
+ matches = true;
+ goto done;
+ }
+ rnp_buffer_destroy(id);
+ id = NULL;
+ }
+
+ /* check grip */
+ if (len == RNP_GRIP_SIZE * 2) {
+ if (rnp_key_get_grip(handle, &id)) {
+ goto done;
+ }
+ if (strlen(id) != len) {
+ goto done;
+ }
+ if (strncasecmp(hexstr.c_str(), id, len) == 0) {
+ matches = true;
+ goto done;
+ }
+ rnp_buffer_destroy(id);
+ id = NULL;
+ }
+ /* let then search for hex userid */
+ }
+
+ /* no need to check for userid over the subkey */
+ if (rnp_key_is_sub(handle, &boolres) || boolres) {
+ goto done;
+ }
+ if (rnp_key_get_uid_count(handle, &uid_count) || (uid_count == 0)) {
+ goto done;
+ }
+
+#ifndef RNP_USE_STD_REGEX
+ /* match on full name or email address as a NOSUB, ICASE regexp */
+ if (regcomp(&r, cli_rnp_unescape_for_regcomp(str).c_str(), REG_EXTENDED | REG_ICASE) !=
+ 0) {
+ goto done;
+ }
+#else
+ try {
+ re.assign(str, std::regex_constants::ECMAScript | std::regex_constants::icase);
+ } catch (const std::exception &e) {
+ ERR_MSG("Invalid regular expression : %s, error %s.", str.c_str(), e.what());
+ goto done;
+ }
+#endif
+
+ for (size_t idx = 0; idx < uid_count; idx++) {
+ if (rnp_key_get_uid_at(handle, idx, &id)) {
+ goto regdone;
+ }
+#ifndef RNP_USE_STD_REGEX
+ if (regexec(&r, id, 0, NULL, 0) == 0) {
+ matches = true;
+ goto regdone;
+ }
+#else
+ if (std::regex_search(id, re)) {
+ matches = true;
+ goto regdone;
+ }
+#endif
+ rnp_buffer_destroy(id);
+ id = NULL;
+ }
+
+regdone:
+#ifndef RNP_USE_STD_REGEX
+ regfree(&r);
+#endif
+done:
+ rnp_buffer_destroy(id);
+ return matches;
+}
+
+static bool
+key_matches_flags(rnp_key_handle_t key, int flags)
+{
+ /* check whether secret key search is requested */
+ bool secret = false;
+ if (rnp_key_have_secret(key, &secret)) {
+ return false;
+ }
+ if ((flags & CLI_SEARCH_SECRET) && !secret) {
+ return false;
+ }
+ /* check whether no subkeys allowed */
+ bool subkey = false;
+ if (rnp_key_is_sub(key, &subkey)) {
+ return false;
+ }
+ if (!subkey) {
+ return true;
+ }
+ if (!(flags & CLI_SEARCH_SUBKEYS)) {
+ return false;
+ }
+ /* check whether subkeys should be put after primary (if it is available) */
+ if ((flags & CLI_SEARCH_SUBKEYS_AFTER) != CLI_SEARCH_SUBKEYS_AFTER) {
+ return true;
+ }
+
+ char *grip = NULL;
+ if (rnp_key_get_primary_grip(key, &grip)) {
+ return false;
+ }
+ if (!grip) {
+ return true;
+ }
+ rnp_buffer_destroy(grip);
+ return false;
+}
+
+void
+clear_key_handles(std::vector<rnp_key_handle_t> &keys)
+{
+ for (auto handle : keys) {
+ rnp_key_handle_destroy(handle);
+ }
+ keys.clear();
+}
+
+static bool
+add_key_to_array(rnp_ffi_t ffi,
+ std::vector<rnp_key_handle_t> &keys,
+ rnp_key_handle_t key,
+ int flags)
+{
+ bool subkey = false;
+ bool subkeys = (flags & CLI_SEARCH_SUBKEYS_AFTER) == CLI_SEARCH_SUBKEYS_AFTER;
+ if (rnp_key_is_sub(key, &subkey)) {
+ return false;
+ }
+
+ try {
+ keys.push_back(key);
+ } catch (const std::exception &e) {
+ ERR_MSG("%s", e.what());
+ return false;
+ }
+ if (!subkeys || subkey) {
+ return true;
+ }
+
+ std::vector<rnp_key_handle_t> subs;
+ size_t sub_count = 0;
+ if (rnp_key_get_subkey_count(key, &sub_count)) {
+ goto error;
+ }
+
+ try {
+ for (size_t i = 0; i < sub_count; i++) {
+ rnp_key_handle_t sub_handle = NULL;
+ if (rnp_key_get_subkey_at(key, i, &sub_handle)) {
+ goto error;
+ }
+ subs.push_back(sub_handle);
+ }
+ std::move(subs.begin(), subs.end(), std::back_inserter(keys));
+ } catch (const std::exception &e) {
+ ERR_MSG("%s", e.what());
+ goto error;
+ }
+ return true;
+error:
+ keys.pop_back();
+ clear_key_handles(subs);
+ return false;
+}
+
+bool
+cli_rnp_keys_matching_string(cli_rnp_t * rnp,
+ std::vector<rnp_key_handle_t> &keys,
+ const std::string & str,
+ int flags)
+{
+ bool res = false;
+ rnp_identifier_iterator_t it = NULL;
+ rnp_key_handle_t handle = NULL;
+ const char * fp = NULL;
+
+ /* iterate through the keys */
+ if (rnp_identifier_iterator_create(rnp->ffi, &it, "fingerprint")) {
+ return false;
+ }
+
+ while (!rnp_identifier_iterator_next(it, &fp)) {
+ if (!fp) {
+ break;
+ }
+ if (rnp_locate_key(rnp->ffi, "fingerprint", fp, &handle) || !handle) {
+ goto done;
+ }
+ if (!key_matches_flags(handle, flags) || !key_matches_string(handle, str.c_str())) {
+ rnp_key_handle_destroy(handle);
+ continue;
+ }
+ if (!add_key_to_array(rnp->ffi, keys, handle, flags)) {
+ rnp_key_handle_destroy(handle);
+ goto done;
+ }
+ if (flags & CLI_SEARCH_FIRST_ONLY) {
+ res = true;
+ goto done;
+ }
+ }
+ res = !keys.empty();
+done:
+ rnp_identifier_iterator_destroy(it);
+ return res;
+}
+
+bool
+cli_rnp_keys_matching_strings(cli_rnp_t * rnp,
+ std::vector<rnp_key_handle_t> & keys,
+ const std::vector<std::string> &strs,
+ int flags)
+{
+ bool res = false;
+ clear_key_handles(keys);
+
+ for (const std::string &str : strs) {
+ if (!cli_rnp_keys_matching_string(rnp, keys, str, flags & ~CLI_SEARCH_DEFAULT)) {
+ ERR_MSG("Cannot find key matching \"%s\"", str.c_str());
+ goto done;
+ }
+ }
+
+ /* search for default key */
+ if (keys.empty() && (flags & CLI_SEARCH_DEFAULT)) {
+ if (rnp->defkey().empty()) {
+ ERR_MSG("No userid or default key for operation");
+ goto done;
+ }
+ cli_rnp_keys_matching_string(rnp, keys, rnp->defkey(), flags & ~CLI_SEARCH_DEFAULT);
+ if (keys.empty()) {
+ ERR_MSG("Default key not found");
+ }
+ }
+ res = !keys.empty();
+done:
+ if (!res) {
+ clear_key_handles(keys);
+ }
+ 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, "");
+ cfg.set_str(CFG_KR_SEC_PATH, "");
+ cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_GPG);
+ cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_GPG);
+ return true;
+ }
+
+ /* 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()) {
+ 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;
+ }
+
+ /* 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;
+ }
+ }
+
+ /* 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::append(homedir, PUBRING_GPG);
+ secpath = rnp::path::append(homedir, SECRING_GPG);
+ } else if (ks_format == RNP_KEYSTORE_GPG21) {
+ 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::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::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());
+ 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;
+}
+
+static void
+rnp_cfg_set_defkey(rnp_cfg &cfg)
+{
+ /* 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);
+ }
+}
+
+bool
+cli_cfg_set_keystore_info(rnp_cfg &cfg)
+{
+ /* detecting keystore paths and format */
+ if (!rnp_cfg_set_ks_info(cfg)) {
+ return false;
+ }
+
+ /* default key/userid */
+ rnp_cfg_set_defkey(cfg);
+ return true;
+}
+
+static bool
+is_stdinout_spec(const std::string &spec)
+{
+ 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 (is_stdinout_spec(spec)) {
+ /* input from stdin */
+ 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);
+ return NULL;
+ }
+ res = rnp_input_from_memory(&input, (const uint8_t *) envval, strlen(envval), true);
+ } else {
+ /* input from path */
+ res = rnp_input_from_path(&input, spec.c_str());
+ path = true;
+ }
+
+ if (res) {
+ return NULL;
+ }
+ if (is_path) {
+ *is_path = path;
+ }
+ return input;
+}
+
+rnp_output_t
+cli_rnp_output_to_specifier(cli_rnp_t &rnp, const std::string &spec, bool discard)
+{
+ 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 (is_stdinout_spec(spec)) {
+ res = rnp_output_to_stdout(&output);
+ } else if (!rnp_get_output_filename(spec, path, rnp)) {
+ 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;
+}
+
+bool
+cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter)
+{
+ bool secret = rnp->cfg().get_bool(CFG_SECRET);
+ int flags = secret ? CLI_SEARCH_SECRET : 0;
+ std::vector<rnp_key_handle_t> keys;
+
+ if (!cli_rnp_keys_matching_string(rnp, keys, filter, flags)) {
+ ERR_MSG("Key(s) matching '%s' not found.", filter);
+ return false;
+ }
+
+ rnp_output_t output = NULL;
+ rnp_output_t armor = NULL;
+ uint32_t base_flags = secret ? RNP_KEY_EXPORT_SECRET : RNP_KEY_EXPORT_PUBLIC;
+ bool result = false;
+
+ output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE));
+ if (!output) {
+ goto done;
+ }
+
+ /* We need single armored stream for all of the keys */
+ if (rnp_output_to_armor(output, &armor, secret ? "secret key" : "public key")) {
+ goto done;
+ }
+
+ for (auto key : keys) {
+ uint32_t flags = base_flags;
+ bool primary = false;
+
+ if (rnp_key_is_primary(key, &primary)) {
+ goto done;
+ }
+ if (primary) {
+ flags = flags | RNP_KEY_EXPORT_SUBKEYS;
+ }
+
+ if (rnp_key_export(key, armor, flags)) {
+ goto done;
+ }
+ }
+ result = !rnp_output_finish(armor);
+done:
+ rnp_output_destroy(armor);
+ rnp_output_destroy(output);
+ clear_key_handles(keys);
+ return result;
+}
+
+bool
+cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key)
+{
+ std::vector<rnp_key_handle_t> keys;
+ if (!cli_rnp_keys_matching_string(rnp, keys, key, 0)) {
+ ERR_MSG("Key matching '%s' not found.", key);
+ return false;
+ }
+ if (keys.size() > 1) {
+ ERR_MSG("Ambiguous input: too many keys found for '%s'.", key);
+ clear_key_handles(keys);
+ return false;
+ }
+ rnp_output_t output = NULL;
+ bool result = false;
+
+ output = cli_rnp_output_to_specifier(*rnp, rnp->cfg().get_str(CFG_OUTFILE));
+ if (!output) {
+ goto done;
+ }
+
+ result = !rnp_key_export_revocation(keys[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(output);
+ clear_key_handles(keys);
+ return result;
+}
+
+bool
+cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key)
+{
+ std::vector<rnp_key_handle_t> keys;
+ if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) {
+ ERR_MSG("Key matching '%s' not found.", key);
+ return false;
+ }
+ bool res = false;
+ bool revoked = false;
+ rnp_result_t ret = 0;
+
+ if (keys.size() > 1) {
+ ERR_MSG("Ambiguous input: too many keys found for '%s'.", key);
+ goto done;
+ }
+ if (rnp_key_is_revoked(keys[0], &revoked)) {
+ ERR_MSG("Error getting key revocation status.");
+ goto done;
+ }
+ if (revoked && !rnp->cfg().get_bool(CFG_FORCE)) {
+ ERR_MSG("Error: key '%s' is revoked already. Use --force to generate another "
+ "revocation signature.",
+ key);
+ goto done;
+ }
+
+ ret = rnp_key_revoke(keys[0],
+ 0,
+ rnp->cfg().get_cstr(CFG_HASH),
+ rnp->cfg().get_cstr(CFG_REV_TYPE),
+ rnp->cfg().get_cstr(CFG_REV_REASON));
+ if (ret) {
+ ERR_MSG("Failed to revoke a key: error %d", (int) ret);
+ goto done;
+ }
+ res = cli_rnp_save_keyrings(rnp);
+ /* print info about the revoked key */
+ if (res) {
+ bool subkey = false;
+ char *grip = NULL;
+ if (rnp_key_is_sub(keys[0], &subkey)) {
+ ERR_MSG("Failed to get key info");
+ goto done;
+ }
+ ret =
+ subkey ? rnp_key_get_primary_grip(keys[0], &grip) : rnp_key_get_grip(keys[0], &grip);
+ if (ret || !grip) {
+ ERR_MSG("Failed to get primary key grip.");
+ goto done;
+ }
+ clear_key_handles(keys);
+ if (!cli_rnp_keys_matching_string(rnp, keys, grip, CLI_SEARCH_SUBKEYS_AFTER)) {
+ ERR_MSG("Failed to search for revoked key.");
+ rnp_buffer_destroy(grip);
+ goto done;
+ }
+ rnp_buffer_destroy(grip);
+ for (auto handle : keys) {
+ cli_rnp_print_key_info(rnp->userio_out, rnp->ffi, handle, false, false);
+ }
+ }
+done:
+ clear_key_handles(keys);
+ return res;
+}
+
+bool
+cli_rnp_remove_key(cli_rnp_t *rnp, const char *key)
+{
+ std::vector<rnp_key_handle_t> keys;
+ if (!cli_rnp_keys_matching_string(rnp, keys, key, CLI_SEARCH_SUBKEYS)) {
+ ERR_MSG("Key matching '%s' not found.", key);
+ return false;
+ }
+ bool res = false;
+ bool secret = false;
+ bool primary = false;
+ uint32_t flags = RNP_KEY_REMOVE_PUBLIC;
+ rnp_result_t ret = 0;
+
+ if (keys.size() > 1) {
+ ERR_MSG("Ambiguous input: too many keys found for '%s'.", key);
+ goto done;
+ }
+ if (rnp_key_have_secret(keys[0], &secret)) {
+ ERR_MSG("Error getting secret key presence.");
+ goto done;
+ }
+ if (rnp_key_is_primary(keys[0], &primary)) {
+ ERR_MSG("Key error.");
+ goto done;
+ }
+
+ if (secret) {
+ flags |= RNP_KEY_REMOVE_SECRET;
+ }
+ if (primary) {
+ flags |= RNP_KEY_REMOVE_SUBKEYS;
+ }
+
+ if (secret && !rnp->cfg().get_bool(CFG_FORCE)) {
+ if (!cli_rnp_get_confirmation(
+ rnp,
+ "Key '%s' has corresponding secret key. Do you really want to delete it?",
+ key)) {
+ goto done;
+ }
+ }
+
+ ret = rnp_key_remove(keys[0], flags);
+
+ if (ret) {
+ ERR_MSG("Failed to remove the key: error %d", (int) ret);
+ goto done;
+ }
+ res = cli_rnp_save_keyrings(rnp);
+done:
+ clear_key_handles(keys);
+ return res;
+}
+
+bool
+cli_rnp_add_key(cli_rnp_t *rnp)
+{
+ const std::string &path = rnp->cfg().get_str(CFG_KEYFILE);
+ if (path.empty()) {
+ return false;
+ }
+
+ rnp_input_t input = cli_rnp_input_from_specifier(*rnp, path, NULL);
+ if (!input) {
+ ERR_MSG("failed to open key from %s", path.c_str());
+ return false;
+ }
+
+ bool res = !rnp_import_keys(
+ rnp->ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL);
+ rnp_input_destroy(input);
+
+ // set default key if we didn't have one
+ if (res && rnp->defkey().empty()) {
+ rnp->set_defkey();
+ }
+ return res;
+}
+
+static bool
+strip_extension(std::string &src)
+{
+ size_t dpos = src.find_last_of('.');
+ if (dpos == std::string::npos) {
+ return false;
+ }
+ src.resize(dpos);
+ return true;
+}
+
+static bool
+has_extension(const std::string &path, const std::string &ext)
+{
+ if (path.length() < ext.length()) {
+ return false;
+ }
+ return path.compare(path.length() - ext.length(), ext.length(), ext) == 0;
+}
+
+static std::string
+output_extension(const rnp_cfg &cfg, Operation op)
+{
+ 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;
+ }
+ 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
+extract_filename(const std::string path)
+{
+ size_t lpos = path.find_last_of("/\\");
+ if (lpos == std::string::npos) {
+ return path;
+ }
+ return path.substr(lpos + 1);
+}
+
+bool
+cli_rnp_t::init_io(Operation op, rnp_input_t *input, rnp_output_t *output)
+{
+ const std::string &in = cfg().get_str(CFG_INFILE);
+ bool is_pathin = true;
+ if (input) {
+ *input = cli_rnp_input_from_specifier(*this, in, &is_pathin);
+ if (!*input) {
+ return false;
+ }
+ }
+ /* Update CFG_SETFNAME to insert into literal packet */
+ if (!cfg().has(CFG_SETFNAME) && !is_pathin) {
+ cfg().set_str(CFG_SETFNAME, "");
+ }
+
+ if (!output) {
+ return true;
+ }
+ 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) {
+ /* 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(*this, out, discard);
+ if (!*output && input) {
+ rnp_input_destroy(*input);
+ *input = NULL;
+ }
+ return *output;
+}
+
+bool
+cli_rnp_dump_file(cli_rnp_t *rnp)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ uint32_t flags = 0;
+ uint32_t jflags = 0;
+
+ if (rnp->cfg().get_bool(CFG_GRIPS)) {
+ flags |= RNP_DUMP_GRIP;
+ jflags |= RNP_JSON_DUMP_GRIP;
+ }
+ if (rnp->cfg().get_bool(CFG_MPIS)) {
+ flags |= RNP_DUMP_MPI;
+ jflags |= RNP_JSON_DUMP_MPI;
+ }
+ if (rnp->cfg().get_bool(CFG_RAW)) {
+ flags |= RNP_DUMP_RAW;
+ jflags |= RNP_JSON_DUMP_RAW;
+ }
+
+ rnp_result_t ret = 0;
+ if (!rnp->init_io(Operation::Dump, &input, &output)) {
+ ERR_MSG("failed to open source or create output");
+ ret = 1;
+ goto done;
+ }
+
+ if (rnp->cfg().get_bool(CFG_JSON)) {
+ char *json = NULL;
+ ret = rnp_dump_packets_to_json(input, jflags, &json);
+ if (!ret) {
+ size_t len = strlen(json);
+ size_t written = 0;
+ ret = rnp_output_write(output, json, len, &written);
+ if (written < len) {
+ ret = RNP_ERROR_WRITE;
+ }
+ // add trailing empty line
+ if (!ret) {
+ ret = rnp_output_write(output, "\n", 1, &written);
+ }
+ if (written < 1) {
+ ret = RNP_ERROR_WRITE;
+ }
+ rnp_buffer_destroy(json);
+ }
+ } else {
+ ret = rnp_dump_packets_to_output(input, output, flags);
+ }
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+done:
+ return !ret;
+}
+
+bool
+cli_rnp_armor_file(cli_rnp_t *rnp)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ 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);
+ rnp_output_destroy(output);
+ return !ret;
+}
+
+bool
+cli_rnp_dearmor_file(cli_rnp_t *rnp)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ 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);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ return !ret;
+}
+
+static bool
+cli_rnp_sign(const rnp_cfg &cfg, cli_rnp_t *rnp, rnp_input_t input, rnp_output_t output)
+{
+ rnp_op_sign_t op = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ bool cleartext = cfg.get_bool(CFG_CLEARTEXT);
+ bool detached = cfg.get_bool(CFG_DETACHED);
+
+ if (cleartext) {
+ ret = rnp_op_sign_cleartext_create(&op, rnp->ffi, input, output);
+ } else if (detached) {
+ ret = rnp_op_sign_detached_create(&op, rnp->ffi, input, output);
+ } else {
+ ret = rnp_op_sign_create(&op, rnp->ffi, input, output);
+ }
+
+ if (ret) {
+ ERR_MSG("failed to initialize signing");
+ return false;
+ }
+
+ /* setup sign operation via cfg */
+ bool res = false;
+ std::vector<std::string> signers;
+ std::vector<rnp_key_handle_t> signkeys;
+
+ if (!cleartext) {
+ rnp_op_sign_set_armor(op, cfg.get_bool(CFG_ARMOR));
+ }
+
+ if (!cleartext && !detached) {
+ if (cfg.has(CFG_SETFNAME)) {
+ if (rnp_op_sign_set_file_name(op, cfg.get_str(CFG_SETFNAME).c_str())) {
+ goto done;
+ }
+ } else if (cfg.has(CFG_INFILE)) {
+ const std::string &fname = cfg.get_str(CFG_INFILE);
+ if (rnp_op_sign_set_file_name(op, extract_filename(fname).c_str())) {
+ goto done;
+ }
+ rnp_op_sign_set_file_mtime(op, rnp_filemtime(fname.c_str()));
+ }
+ if (rnp_op_sign_set_compression(op, cfg.get_cstr(CFG_ZALG), cfg.get_int(CFG_ZLEVEL))) {
+ goto done;
+ }
+ }
+
+ if (rnp_op_sign_set_hash(op, cfg.get_hashalg().c_str())) {
+ goto done;
+ }
+ rnp_op_sign_set_creation_time(op, cfg.get_sig_creation());
+ {
+ uint32_t expiration = 0;
+ if (cfg.get_expiration(CFG_EXPIRATION, expiration)) {
+ rnp_op_sign_set_expiration_time(op, expiration);
+ }
+ }
+
+ /* signing keys */
+ signers = cfg.get_list(CFG_SIGNERS);
+ if (!cli_rnp_keys_matching_strings(rnp,
+ signkeys,
+ signers,
+ CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT |
+ CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) {
+ ERR_MSG("Failed to build signing keys list");
+ goto done;
+ }
+ for (rnp_key_handle_t key : signkeys) {
+ if (rnp_op_sign_add_signature(op, key, NULL)) {
+ ERR_MSG("Failed to add signature");
+ goto done;
+ }
+ }
+
+ /* execute sign operation */
+ res = !rnp_op_sign_execute(op);
+done:
+ clear_key_handles(signkeys);
+ rnp_op_sign_destroy(op);
+ return res;
+}
+
+static bool
+cli_rnp_encrypt_and_sign(const rnp_cfg &cfg,
+ cli_rnp_t * rnp,
+ rnp_input_t input,
+ rnp_output_t output)
+{
+ rnp_op_encrypt_t op = NULL;
+
+ if (rnp_op_encrypt_create(&op, rnp->ffi, input, output)) {
+ ERR_MSG("failed to initialize encryption");
+ return false;
+ }
+
+ std::string fname;
+ std::string aalg;
+ std::vector<rnp_key_handle_t> enckeys;
+ std::vector<rnp_key_handle_t> signkeys;
+ bool res = false;
+ rnp_result_t ret;
+
+ rnp_op_encrypt_set_armor(op, cfg.get_bool(CFG_ARMOR));
+
+ if (cfg.has(CFG_SETFNAME)) {
+ if (rnp_op_encrypt_set_file_name(op, cfg.get_str(CFG_SETFNAME).c_str())) {
+ goto done;
+ }
+ } else if (cfg.has(CFG_INFILE)) {
+ const std::string &fname = cfg.get_str(CFG_INFILE);
+ if (rnp_op_encrypt_set_file_name(op, extract_filename(fname).c_str())) {
+ goto done;
+ }
+ rnp_op_encrypt_set_file_mtime(op, rnp_filemtime(fname.c_str()));
+ }
+
+ if (rnp_op_encrypt_set_compression(op, cfg.get_cstr(CFG_ZALG), cfg.get_int(CFG_ZLEVEL))) {
+ goto done;
+ }
+ if (rnp_op_encrypt_set_cipher(op, cfg.get_cstr(CFG_CIPHER))) {
+ goto done;
+ }
+ if (rnp_op_encrypt_set_hash(op, cfg.get_hashalg().c_str())) {
+ goto done;
+ }
+ aalg = cfg.has(CFG_AEAD) ? cfg.get_str(CFG_AEAD) : "None";
+ if (rnp_op_encrypt_set_aead(op, aalg.c_str())) {
+ goto done;
+ }
+ 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);
+ size_t iterations = cfg.get_int(CFG_S2K_ITER);
+ size_t msec = cfg.get_int(CFG_S2K_MSEC);
+
+ if (msec != DEFAULT_S2K_MSEC) {
+ if (rnp_calculate_iterations(halg.c_str(), msec, &iterations)) {
+ ERR_MSG("Failed to calculate S2K iterations");
+ goto done;
+ }
+ }
+
+ for (int i = 0; i < cfg.get_int(CFG_PASSWORDC, 1); i++) {
+ if (rnp_op_encrypt_add_password(
+ op, NULL, halg.c_str(), iterations, ealg.c_str())) {
+ ERR_MSG("Failed to add encrypting password");
+ goto done;
+ }
+ }
+ }
+
+ /* adding encrypting keys if pk-encryption is used */
+ if (cfg.get_bool(CFG_ENCRYPT_PK)) {
+ std::vector<std::string> keynames = cfg.get_list(CFG_RECIPIENTS);
+ if (!cli_rnp_keys_matching_strings(rnp,
+ enckeys,
+ keynames,
+ CLI_SEARCH_DEFAULT | CLI_SEARCH_SUBKEYS |
+ CLI_SEARCH_FIRST_ONLY)) {
+ ERR_MSG("Failed to build recipients key list");
+ goto done;
+ }
+ for (rnp_key_handle_t key : enckeys) {
+ if (rnp_op_encrypt_add_recipient(op, key)) {
+ ERR_MSG("Failed to add recipient");
+ goto done;
+ }
+ }
+ }
+
+ /* adding signatures if encrypt-and-sign is used */
+ if (cfg.get_bool(CFG_SIGN_NEEDED)) {
+ rnp_op_encrypt_set_creation_time(op, cfg.get_sig_creation());
+ uint32_t expiration;
+ if (cfg.get_expiration(CFG_EXPIRATION, expiration)) {
+ rnp_op_encrypt_set_expiration_time(op, expiration);
+ }
+
+ /* signing keys */
+ std::vector<std::string> keynames = cfg.get_list(CFG_SIGNERS);
+ if (!cli_rnp_keys_matching_strings(rnp,
+ signkeys,
+ keynames,
+ CLI_SEARCH_SECRET | CLI_SEARCH_DEFAULT |
+ CLI_SEARCH_SUBKEYS | CLI_SEARCH_FIRST_ONLY)) {
+ ERR_MSG("Failed to build signing keys list");
+ goto done;
+ }
+ for (rnp_key_handle_t key : signkeys) {
+ if (rnp_op_encrypt_add_signature(op, key, NULL)) {
+ ERR_MSG("Failed to add signature");
+ goto done;
+ }
+ }
+ }
+
+ /* execute encrypt or encrypt-and-sign operation */
+ ret = rnp_op_encrypt_execute(op);
+ res = (ret == RNP_SUCCESS);
+ if (ret != RNP_SUCCESS) {
+ ERR_MSG("Operation failed: %s", rnp_result_to_string(ret));
+ }
+done:
+ clear_key_handles(signkeys);
+ clear_key_handles(enckeys);
+ rnp_op_encrypt_destroy(op);
+ return res;
+}
+
+bool
+cli_rnp_setup(cli_rnp_t *rnp)
+{
+ /* unset CFG_PASSWD and empty CFG_PASSWD are different cases */
+ if (rnp->cfg().has(CFG_PASSWD)) {
+ const std::string &passwd = rnp->cfg().get_str(CFG_PASSWD);
+ if (rnp_ffi_set_pass_provider(
+ rnp->ffi, ffi_pass_callback_string, (void *) passwd.c_str())) {
+ return false;
+ }
+ }
+ rnp->pswdtries = rnp->cfg().get_pswdtries();
+ return true;
+}
+
+bool
+cli_rnp_check_weak_hash(cli_rnp_t *rnp)
+{
+ if (rnp->cfg().has(CFG_WEAK_HASH)) {
+ return true;
+ }
+
+ uint32_t security_level = 0;
+
+ if (rnp_get_security_rule(rnp->ffi,
+ RNP_FEATURE_HASH_ALG,
+ rnp->cfg().get_hashalg().c_str(),
+ rnp->cfg().time(),
+ NULL,
+ NULL,
+ &security_level)) {
+ ERR_MSG("Failed to get security rules for hash algorithm \'%s\'!",
+ rnp->cfg().get_hashalg().c_str());
+ return false;
+ }
+
+ if (security_level < RNP_SECURITY_DEFAULT) {
+ ERR_MSG("Hash algorithm \'%s\' is cryptographically weak!",
+ rnp->cfg().get_hashalg().c_str());
+ return false;
+ }
+ /* TODO: check other weak algorithms and key sizes */
+ return true;
+}
+
+bool
+cli_rnp_protect_file(cli_rnp_t *rnp)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ if (!rnp->init_io(Operation::EncryptOrSign, &input, &output)) {
+ ERR_MSG("failed to open source or create output");
+ return false;
+ }
+
+ bool res = false;
+ bool sign = rnp->cfg().get_bool(CFG_SIGN_NEEDED);
+ bool encrypt = rnp->cfg().get_bool(CFG_ENCRYPT_PK) || rnp->cfg().get_bool(CFG_ENCRYPT_SK);
+ if (sign && !encrypt) {
+ res = cli_rnp_sign(rnp->cfg(), rnp, input, output);
+ } else if (encrypt) {
+ res = cli_rnp_encrypt_and_sign(rnp->cfg(), rnp, input, output);
+ } else {
+ ERR_MSG("No operation specified");
+ }
+
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ return res;
+}
+
+/* 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;
+ char *alg = NULL;
+
+ (void) rnp_signature_get_keyid(sig, &keyid);
+ rnp::lowercase(keyid);
+ (void) rnp_signature_get_alg(sig, &alg);
+
+ fprintf(resfp,
+ "using %s key %s\n",
+ cli_rnp_normalize_key_alg(alg),
+ keyid ? keyid : "0000000000000000");
+ rnp_buffer_destroy(keyid);
+ rnp_buffer_destroy(alg);
+}
+
+static void
+cli_rnp_print_signatures(cli_rnp_t *rnp, const std::vector<rnp_op_verify_signature_t> &sigs)
+{
+ unsigned invalidc = 0;
+ unsigned unknownc = 0;
+ unsigned validc = 0;
+ std::string title = "UNKNOWN signature";
+ FILE * resfp = rnp->resfp;
+
+ for (auto sig : sigs) {
+ rnp_result_t status = rnp_op_verify_signature_get_status(sig);
+ switch (status) {
+ case RNP_SUCCESS:
+ title = "Good signature";
+ validc++;
+ break;
+ case RNP_ERROR_SIGNATURE_EXPIRED:
+ title = "EXPIRED signature";
+ invalidc++;
+ break;
+ case RNP_ERROR_SIGNATURE_INVALID:
+ title = "BAD signature";
+ invalidc++;
+ break;
+ case RNP_ERROR_KEY_NOT_FOUND:
+ title = "NO PUBLIC KEY for signature";
+ unknownc++;
+ break;
+ 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);
+
+ time_t crtime = create;
+ auto str = rnp_ctime(crtime);
+ fprintf(resfp,
+ "%s made %s%s",
+ title.c_str(),
+ rnp_y2k38_warning(crtime) ? ">=" : "",
+ str.c_str());
+ if (expiry) {
+ crtime = rnp_timeadd(crtime, expiry);
+ str = rnp_ctime(crtime);
+ fprintf(
+ resfp, "Valid until %s%s\n", rnp_y2k38_warning(crtime) ? ">=" : "", str.c_str());
+ }
+
+ rnp_signature_handle_t handle = NULL;
+ if (rnp_op_verify_signature_get_handle(sig, &handle)) {
+ ERR_MSG("Failed to obtain signature handle.");
+ continue;
+ }
+
+ cli_rnp_print_sig_key_info(resfp, handle);
+ rnp_key_handle_t key = NULL;
+
+ if ((status != RNP_ERROR_KEY_NOT_FOUND) && !rnp_signature_get_signer(handle, &key)) {
+ cli_rnp_print_key_info(resfp, rnp->ffi, key, false, false);
+ rnp_key_handle_destroy(key);
+ }
+ rnp_signature_handle_destroy(handle);
+ }
+
+ if (!sigs.size()) {
+ ERR_MSG("No signature(s) found - is this a signed file?");
+ 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("%s%u invalid signature%s, %u unknown signature%s",
+ fail,
+ invalidc,
+ si,
+ unknownc,
+ su);
+ }
+}
+
+static void
+cli_rnp_inform_of_hidden_recipient(rnp_op_verify_t op)
+{
+ size_t recipients = 0;
+ rnp_op_verify_get_recipient_count(op, &recipients);
+ if (!recipients) {
+ return;
+ }
+ for (size_t idx = 0; idx < recipients; idx++) {
+ rnp_recipient_handle_t recipient = NULL;
+ rnp_op_verify_get_recipient_at(op, idx, &recipient);
+ char *keyid = NULL;
+ rnp_recipient_get_keyid(recipient, &keyid);
+ bool hidden = keyid && !strcmp(keyid, "0000000000000000");
+ rnp_buffer_destroy(keyid);
+ if (hidden) {
+ ERR_MSG("Warning: message has hidden recipient, but it was ignored. Use "
+ "--allow-hidden to override this.");
+ break;
+ }
+ }
+}
+
+bool
+cli_rnp_process_file(cli_rnp_t *rnp)
+{
+ rnp_input_t input = NULL;
+ if (!rnp->init_io(Operation::Verify, &input, NULL)) {
+ ERR_MSG("failed to open source");
+ return false;
+ }
+
+ char *contents = NULL;
+ if (rnp_guess_contents(input, &contents)) {
+ ERR_MSG("failed to check source contents");
+ rnp_input_destroy(input);
+ return false;
+ }
+
+ /* source data for detached signature verification */
+ rnp_input_t source = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_verify_t verify = NULL;
+ rnp_result_t ret = RNP_ERROR_GENERIC;
+ bool res = false;
+ std::vector<rnp_op_verify_signature_t> sigs;
+ size_t scount = 0;
+
+ if (rnp::str_case_eq(contents, "signature")) {
+ /* detached signature */
+ std::string in = rnp->cfg().get_str(CFG_INFILE);
+ 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 (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 (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 (!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) {
+ uint32_t flags = 0;
+ if (!rnp->cfg().get_bool(CFG_NO_OUTPUT)) {
+ /* This would happen if user requested decryption instead of verification */
+ flags = flags | RNP_VERIFY_IGNORE_SIGS_ON_DECRYPT;
+ } else {
+ /* Currently CLI requires all signatures to be valid for success */
+ flags = flags | RNP_VERIFY_REQUIRE_ALL_SIGS;
+ }
+ if (rnp->cfg().get_bool(CFG_ALLOW_HIDDEN)) {
+ /* Allow hidden recipient */
+ flags = flags | RNP_VERIFY_ALLOW_HIDDEN_RECIPIENT;
+ }
+ ret = rnp_op_verify_set_flags(verify, flags);
+ }
+ }
+ if (ret) {
+ ERR_MSG("Failed to initialize verification/decryption operation.");
+ goto done;
+ }
+
+ res = !rnp_op_verify_execute(verify);
+
+ /* Check whether we had hidden recipient on verification/decryption failure */
+ if (!res && !rnp->cfg().get_bool(CFG_ALLOW_HIDDEN)) {
+ cli_rnp_inform_of_hidden_recipient(verify);
+ }
+
+ rnp_op_verify_get_signature_count(verify, &scount);
+ if (!scount) {
+ goto done;
+ }
+
+ for (size_t i = 0; i < scount; i++) {
+ rnp_op_verify_signature_t sig = NULL;
+ if (rnp_op_verify_get_signature_at(verify, i, &sig)) {
+ ERR_MSG("Failed to obtain signature info.");
+ res = false;
+ goto done;
+ }
+ try {
+ sigs.push_back(sig);
+ } catch (const std::exception &e) {
+ ERR_MSG("%s", e.what());
+ res = false;
+ goto done;
+ }
+ }
+ cli_rnp_print_signatures(rnp, sigs);
+done:
+ rnp_buffer_destroy(contents);
+ rnp_input_destroy(input);
+ rnp_input_destroy(source);
+ rnp_output_destroy(output);
+ rnp_op_verify_destroy(verify);
+ return res;
+}
+
+void
+cli_rnp_print_praise(void)
+{
+ printf("%s\n%s\n", PACKAGE_STRING, PACKAGE_BUGREPORT);
+ printf("Backend: %s\n", rnp_backend_string());
+ printf("Backend version: %s\n", rnp_backend_version());
+ printf("Supported algorithms:\n");
+ cli_rnp_print_feature(stdout, RNP_FEATURE_PK_ALG, "Public key");
+ cli_rnp_print_feature(stdout, RNP_FEATURE_SYMM_ALG, "Encryption");
+ cli_rnp_print_feature(stdout, RNP_FEATURE_AEAD_ALG, "AEAD");
+ cli_rnp_print_feature(stdout, RNP_FEATURE_PROT_MODE, "Key protection");
+ cli_rnp_print_feature(stdout, RNP_FEATURE_HASH_ALG, "Hash");
+ cli_rnp_print_feature(stdout, RNP_FEATURE_COMP_ALG, "Compression");
+ cli_rnp_print_feature(stdout, RNP_FEATURE_CURVE, "Curves");
+ printf("Please report security issues at (https://www.rnpgp.org/feedback) and\n"
+ "general bugs at https://github.com/rnpgp/rnp/issues.\n");
+}
+
+void
+cli_rnp_print_feature(FILE *fp, const char *type, const char *printed_type)
+{
+ char * result = NULL;
+ size_t count;
+ if (rnp_supported_features(type, &result) != RNP_SUCCESS) {
+ ERR_MSG("Failed to list supported features: %s", type);
+ return;
+ }
+ json_object *jso = json_tokener_parse(result);
+ if (!jso) {
+ ERR_MSG("Failed to parse JSON with features: %s", type);
+ goto done;
+ }
+ fprintf(fp, "%s: ", printed_type);
+ count = json_object_array_length(jso);
+ for (size_t idx = 0; idx < count; idx++) {
+ json_object *val = json_object_array_get_idx(jso, idx);
+ fprintf(fp, " %s%s", json_object_get_string(val), idx < count - 1 ? "," : "");
+ }
+ fputs("\n", fp);
+ fflush(fp);
+ json_object_put(jso);
+done:
+ rnp_buffer_destroy(result);
+}
diff --git a/src/rnp/fficli.h b/src/rnp/fficli.h
new file mode 100644
index 0000000..7db29dc
--- /dev/null
+++ b/src/rnp/fficli.h
@@ -0,0 +1,295 @@
+/*
+ * Copyright (c) 2019-2021, [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 FFICLI_H_
+#define FFICLI_H_
+
+#include <stddef.h>
+#include <stdbool.h>
+#include <time.h>
+#include "rnp/rnp.h"
+#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_{};
+#ifdef _WIN32
+ int subst_argc{};
+ char **subst_argv{};
+#endif
+ bool load_keyring(bool secret);
+ bool is_cv25519_subkey(rnp_key_handle_t handle);
+ bool get_protection(rnp_key_handle_t handle,
+ std::string & hash,
+ 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 */
+ bool reuse_password_for_subkey{};
+ std::string reuse_primary_fprint;
+ char * reused_password{};
+ bool hidden_msg{}; /* true if hidden recipient message was displayed */
+
+ static int ret_code(bool success);
+
+ ~cli_rnp_t();
+#ifdef _WIN32
+ void substitute_args(int *argc, char ***argv);
+#endif
+ 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()
+ {
+ return cfg_.get_str(CFG_KR_DEF_KEY);
+ }
+
+ void set_defkey();
+
+ const std::string &
+ pubpath()
+ {
+ return cfg_.get_str(CFG_KR_PUB_PATH);
+ }
+
+ const std::string &
+ secpath()
+ {
+ return cfg_.get_str(CFG_KR_SEC_PATH);
+ }
+
+ const std::string &
+ pubformat()
+ {
+ return cfg_.get_str(CFG_KR_PUB_FORMAT);
+ }
+
+ const std::string &
+ secformat()
+ {
+ return cfg_.get_str(CFG_KR_SEC_FORMAT);
+ }
+
+ rnp_cfg &
+ cfg()
+ {
+ return cfg_;
+ }
+
+ bool fix_cv25519_subkey(const std::string &key, bool checkonly = false);
+
+ bool add_new_subkey(const std::string &key);
+
+ bool set_key_expire(const std::string &key);
+
+ bool edit_key(const std::string &key);
+};
+
+typedef enum cli_search_flags_t {
+ CLI_SEARCH_SECRET = 1 << 0, /* search secret keys only */
+ CLI_SEARCH_SUBKEYS = 1 << 1, /* add subkeys as well */
+ CLI_SEARCH_FIRST_ONLY = 1 << 2, /* return only first key matching */
+ CLI_SEARCH_SUBKEYS_AFTER =
+ (1 << 3) | CLI_SEARCH_SUBKEYS, /* put subkeys after the primary key */
+ CLI_SEARCH_DEFAULT = 1 << 4 /* add default key if nothing found */
+} cli_search_flags_t;
+
+/**
+ * @brief Set keystore parameters to the rnp_cfg_t. This includes keyring paths, types and
+ * default key.
+ *
+ * @param cfg pointer to the allocated rnp_cfg_t structure
+ * @return true on success or false otherwise.
+ * @return false
+ */
+bool cli_cfg_set_keystore_info(rnp_cfg &cfg);
+
+/**
+ * @brief Create input object from the specifier, which may represent:
+ * - path
+ * - stdin (if `-` or empty string is passed)
+ * - environment variable contents, if path looks like `env:VARIABLE_NAME`
+ * @param rnp initialized CLI rnp object
+ * @param spec specifier
+ * @param is_path optional parameter. If specifier is path (not stdin, env variable), then true
+ * will be stored here, false otherwise. May be NULL if this information is not
+ * needed.
+ * @return rnp_input_t object or NULL if operation failed.
+ */
+rnp_input_t cli_rnp_input_from_specifier(cli_rnp_t & rnp,
+ const std::string &spec,
+ bool * is_path);
+
+/**
+ * @brief Create output object from the specifier, which may represent:
+ * - path
+ * - stdout (if `-` or empty string is passed)
+ *
+ * @param rnp initialized CLI rnp object
+ * @param spec specifier
+ * @param discard just discard output
+ * @return rnp_output_t or NULL if operation failed.
+ */
+rnp_output_t cli_rnp_output_to_specifier(cli_rnp_t & rnp,
+ const std::string &spec,
+ bool discard = false);
+
+bool cli_rnp_save_keyrings(cli_rnp_t *rnp);
+void cli_rnp_print_key_info(
+ FILE *fp, rnp_ffi_t ffi, rnp_key_handle_t key, bool psecret, bool psigs);
+bool cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey = false);
+bool cli_rnp_generate_key(cli_rnp_t *rnp, const char *username);
+/**
+ * @brief Find key(s) matching set of flags and search string.
+ *
+ * @param rnp initialized cli_rnp_t object.
+ * @param keys search results will be added here, leaving already existing items.
+ * @param str search string: may be part of the userid, keyid, fingerprint or grip.
+ * @param flags combination of the following flags:
+ * CLI_SEARCH_SECRET : require key to be secret,
+ * CLI_SEARCH_SUBKEYS : include subkeys to the results (see
+ * CLI_SEARCH_SUBKEYS_AFTER description).
+ * CLI_SEARCH_FIRST_ONLY : include only first key found
+ * CLI_SEARCH_SUBKEYS_AFTER : for each primary key add its subkeys after the main
+ * key. This changes behaviour of subkey search, since those will be added only
+ * if subkey is orphaned or primary key matches search.
+ * @return true if operation succeeds and at least one key is found, or false otherwise.
+ */
+bool cli_rnp_keys_matching_string(cli_rnp_t * rnp,
+ std::vector<rnp_key_handle_t> &keys,
+ const std::string & str,
+ int flags);
+/**
+ * @brief Find key(s) matching set of flags and search string(s).
+ *
+ * @param rnp initialized cli_rnp_t object.
+ * @param keys search results will be put here, overwriting vector's contents.
+ * @param strs set of search strings, may be empty.
+ * @param flags the same flags as for cli_rnp_keys_matching_string(), except additional one:
+ * CLI_SEARCH_DEFAULT : if no key is found then default key from cli_rnp_t will be
+ * searched.
+ * @return true if operation succeeds and at least one key is found for each search string, or
+ * false otherwise.
+ */
+bool cli_rnp_keys_matching_strings(cli_rnp_t * rnp,
+ std::vector<rnp_key_handle_t> & keys,
+ const std::vector<std::string> &strs,
+ int flags);
+bool cli_rnp_export_keys(cli_rnp_t *rnp, const char *filter);
+bool cli_rnp_export_revocation(cli_rnp_t *rnp, const char *key);
+bool cli_rnp_revoke_key(cli_rnp_t *rnp, const char *key);
+bool cli_rnp_remove_key(cli_rnp_t *rnp, const char *key);
+bool cli_rnp_add_key(cli_rnp_t *rnp);
+bool cli_rnp_dump_file(cli_rnp_t *rnp);
+bool cli_rnp_armor_file(cli_rnp_t *rnp);
+bool cli_rnp_dearmor_file(cli_rnp_t *rnp);
+bool cli_rnp_check_weak_hash(cli_rnp_t *rnp);
+bool cli_rnp_setup(cli_rnp_t *rnp);
+bool cli_rnp_protect_file(cli_rnp_t *rnp);
+bool cli_rnp_process_file(cli_rnp_t *rnp);
+std::string cli_rnp_escape_string(const std::string &src);
+void cli_rnp_print_praise(void);
+void cli_rnp_print_feature(FILE *fp, const char *type, const char *printed_type);
+/**
+ * @brief Convert algorithm name representation to one used by FFI.
+ * I.e. aes-128 to AES128, 3DES to TRIPLEDES, SHA-1 to SHA1 and so on.
+ *
+ * @param alg algorithm string
+ * @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<rnp_key_handle_t> &keys);
+
+const char *json_obj_get_str(json_object *obj, const char *key);
+
+#ifdef _WIN32
+bool rnp_win_substitute_cmdline_args(int *argc, char ***argv);
+void rnp_win_clear_args(int argc, char **argv);
+#endif
+
+/* TODO: we should decide what to do with functions/constants/defines below */
+#define RNP_KEYID_SIZE 8
+#define RNP_FP_SIZE 20
+#define RNP_GRIP_SIZE 20
+
+#define ERR_MSG(...) \
+ do { \
+ (void) fprintf((stderr), __VA_ARGS__); \
+ (void) fprintf((stderr), "\n"); \
+ } while (0)
+
+#define EXT_ASC (".asc")
+#define EXT_SIG (".sig")
+#define EXT_PGP (".pgp")
+#define EXT_GPG (".gpg")
+
+#define SUBDIRECTORY_GNUPG ".gnupg"
+#define SUBDIRECTORY_RNP ".rnp"
+#define PUBRING_KBX "pubring.kbx"
+#define SECRING_KBX "secring.kbx"
+#define PUBRING_GPG "pubring.gpg"
+#define SECRING_GPG "secring.gpg"
+#define PUBRING_G10 "public-keys-v1.d"
+#define SECRING_G10 "private-keys-v1.d"
+
+#endif
diff --git a/src/rnp/rnp.1.adoc b/src/rnp/rnp.1.adoc
new file mode 100644
index 0000000..ce1173e
--- /dev/null
+++ b/src/rnp/rnp.1.adoc
@@ -0,0 +1,431 @@
+= rnp(1)
+RNP
+:doctype: manpage
+:release-version: {component-version}
+:man manual: RNP Manual
+:man source: RNP {release-version}
+
+== NAME
+
+RNP - OpenPGP-compatible signatures and encryption.
+
+== SYNOPSIS
+
+*rnp* [_--homedir_ _dir_] [_OPTIONS_] _COMMAND_ [_INPUT_FILE_, ...] ...
+
+
+== DESCRIPTION
+
+The _rnp_ command-line utility is part of the _RNP_ suite and
+provides OpenPGP signing and encryption functionality
+compliant with IETF RFC 4880.
+
+_rnp_ does not allow manipulation of keys or keyrings --
+please use _rnpkeys(1)_ for that purpose.
+
+=== BASICS
+
+By default, *rnp* will apply a _COMMAND_, additionally configured with _OPTIONS_,
+to all _INPUT_FILE_(s) or _stdin_ if no _INPUT_FILE_ is given.
+There are some special cases for _INPUT_FILE_ :
+
+* _-_ (dash) substitutes to _stdin_
+* env:VARIABLE_NAME substitutes to the contents of environment variable VARIABLE_NAME
+
+Depending on the input, output may be written:
+
+* 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_, _.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*,
+unless the *--password* or *--pass-fd* option was specified.
+
+
+== COMMANDS
+
+=== INFORMATIONAL
+
+*-h*, *--help*::
+Displays a short help message. No options are expected.
+
+*-V*, *--version*::
+Displays version information. No options are expected.
+
+
+=== ENCRYPTION AND SIGNING
+
+*-e*, *--encrypt*::
+Encrypt data with public key(s), and optionally sign, if the *--sign* command is added. +
++
+You would likely want to specify one or more *--recipient*(s) or pick a *--cipher* (instead of the default).
++
+Additional options:
+
+*--recipient*:::
+Specify one or more recipients.
+
+*--cipher*:::
+Select a specific cipher.
+
+*-z 0..9*, *--zlib*, *--zip*, *--bzip*:::
+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). +
++
+Can be combined with the commands *--encrypt* and *--sign*.
++
+Options that apply to the *--encrypt* command also apply here.
++
+Additional options:
+
+*--passwords*:::
+Encryption to multiple passwords is possible with *--passwords* option. Each password would be asked via stdin/tty unless *--password* or *--pass-fd* is specified. +
+
+*-s*, *--sign*::
+Digitally sign data, using one or more secret keys you own. +
++
+Public-key or password-based encryption may be added via the *--encrypt* and *--symmetric* commands. +
++
+Additional options:
+
+*-u*, *--userid*:::
+By default, the first secret key you own will be selected for signing. Apply this option to select a different key or to use multiple keys.
+
+*--detach*:::
+By default, the signature is stored together with signed data. This option detaches the data signature to a separate file (_file.ext.sig_).
+
+*--hash*:::
+You may want to use *--hash* option to override default hash algorithm settings. As with encryption, output may be converted to ascii via the *--armor* option. +
++
+Compression options also apply here. Since the secret key is usually stored encrypted, you will be asked for the password to decrypt it via _stdin_/_tty_ unless *--password* or *--pass-fd* is specified.
+
+*--clearsign*::
+Digitally sign text data, producing human-readable output with the signature attached. +
++
+In this mode, data cannot be additionally encrypted or compressed.
++
+Other signing options, *--hash*, *-u*, *--password*, can still be used here.
+
+=== DECRYPTION AND VERIFICATION
+
+*-d*, *--decrypt*::
+Decrypt and verify data from the _INPUT_FILE_ or stdin. +
++
+If the data is signed, signature verification information will be printed to _stdout_/_tty_.
++
+Additional options:
+
+*--output*:::
+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*::
+Show detailed information about the OpenPGP data in _INPUT_FILE_ or stdin.
+Useful for curiosity, troubleshooting or debugging. +
++
+Additional options can be used:
+
+*--json*::: output JSON data instead of human-readable information
+*--grips*::: print out key fingerprints and grips
+*--mpi*::: print out all MPI values
+*--raw*::: print raw, hex-encoded packets too
+
+*--enarmor*[=_msg_|_pubkey_|_seckey_|_sign_]::
+Convert binary data to the ASCII-armored as per OpenPGP standard.
+This includes the `-----BEGIN PGP MESSAGE-----` header and footer,
+and Base64-encoded data. +
++
+Output for _file.ext_ will be written to _file.ext.asc_ (if it does not exist)
+or to _stdout_. +
++
+The following OpenPGP headers may be specified:
++
+--
+*msg* (default) ::: _-----BEGIN PGP MESSAGE-----_
+*pubkey*::: _-----BEGIN PGP PUBLIC KEY BLOCK-----_
+*seckey*::: _-----BEGIN PGP SECRET KEY BLOCK-----_
+*sign*::: _-----BEGIN PGP SIGNATURE-----_
+--
++
+Additional options:
+
+*--overwrite*:::
+Forcefully overwrite existing destination file if it exists.
+
+*--output*:::
+Specify destination file path.
+
+
+*--dearmor*::
+Attempts to convert data from an armored format to the binary format. +
++
+The _file.ext.asc_ output file would be written to _file.ext_.
+If the destination file already exists, it will prompt the user
+for a new filename.
++
+Additional options:
+
+*--overwrite*:::
+Forcefully overwrite existing destination file if it exists.
+
+*--output*:::
+Specify destination file path.
+
+
+== OPTIONS
+
+*--home*, *--homedir* _DIR_::
+Change homedir (where RNP looks for keyrings) to the specified value. +
++
+The default homedir is _~/.rnp_ .
+
+*-f*, *--keyfile* _PATH_::
+Instead of loading keyrings, use key(s) from the file specified.
+
+*-u*, *--userid* _KEY_::
+Specify one or more signing keys, searching for it via the given value _KEY_.
+See *rnpkeys(1)* on how to find valid values.
+
+*-r*, *--recipient* _KEY_::
+Add the message recipient, i.e. the public key to which message will be encrypted to.
+See *rnpkeys(1)* on how to find valid values.
+
+*--armor*, *--ascii*::
+Apply ASCII armoring to the output, so that the resulting output
+can be transferred as plain text. +
++
+See IETF RFC 4880 for more details.
+
+*--detach*, *--detached*::
+Create a detached signature.
+
+*--output* _PATH_::
+Write data processing related output to the file specified. +
++
+If not specified, the output filename will be guessed from
+the input filename/extension or the command will prompt the user
+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_.
+
+*--cipher* _ALGORITHM_::
+Set the symmetric algorithm used during encryption. +
++
+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 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.
+
+*-z* _0..9_::
+Set compression level for the compression algorithms. +
++
+*9* is the highest compression level, where *0* disables compression.
++
+The default value is *6*.
+
+*--pass-fd* _FD_::
+Specify a file descriptor to read passwords from instead of from _stdin_/_tty_. +
++
+Useful for automated or non-interactive sessions.
+
+*--password* _PASSWORD_::
+Use the specified password when it is needed. +
++
+WARNING: Not recommended for production use due to potential security issues.
+Use *--pass-fd* for batch operations instead.
+
+*--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 the current local computer time. +
++
+*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. +
++
+A specific expiration time can be specified as:
+
+*** expiration date in the ISO 8601:2019 date format (_yyyy-mm-dd_); or
+*** hours/days/months/years since creation time with the syntax of _20h_/_30d_/_1m_/_1y_;
+*** number of seconds.
+
+*--keystore-format* _GPG_|_KBX_|_G10_|_G21_::
+Set keystore format. +
++
+RNP automatically detects the keystore format. +
++
+This option allows the auto-detection behavior to be overridden.
+
+*--notty*::
+Disable use of tty. +
++
+By default RNP would detect whether TTY is attached and use it for user prompts. +
++
+This option overrides default behaviour so user input may be passed in batch mode.
+
+*--current-time* _TIME_::
+Override system's time with a specified value. +
++
+By default RNP uses system's time in all signature/key checks, however in some scenarios it could be needed to override this. +
++
+*TIME* may be specified in the same way as *--creation*.
+
+*--set-filename* _FNAME_::
+Override or set a file name, stored inside of OpenPGP message. +
++
+By default RNP will store input filename (or empty string for *stdin*/*env* input) in the resulting OpenPGP message during encryption or embedded signing.
+This option allows to override this. Special value *_CONSOLE* may be used for "for your eyes only"-message. Refer OpenPGP documentation for the details.
+
+*--allow-hidden* ::
+Allow hidden recipient support. +
++
+Sender of an encrypted message may wish to hide recipient's key by setting a Key ID field to all zeroes.
+In this case receiver has to try every available secret key, checking for a valid decrypted session key. This option is disabled by default.
+
+== EXIT STATUS
+
+_0_::
+ Success.
+
+_Non-zero_::
+ Failure.
+
+
+== EXAMPLES
+
+The following examples demonstrate method of usage of the _rnp_ command.
+
+=== EXAMPLE 1
+
+*rnp* *--homedir* _.rnp_ *--encrypt* *-r* _0x6E69636B6F6C6179_
+*--output* _document.txt.encrypted_ _document.txt_
+
+Load keyrings from the _.rnp_ folder,
+encrypt the _document.txt_ file using the
+key with keyid _0x6E69636B6F6C6179_.
+
+=== EXAMPLE 2
+
+*rnp* *--keyfile* _john-sec.asc_ *-s* *--detach* *--hash* _SHA512_ _document.txt_
+
+Generate a detached signature over the file _document.txt_, using the
+secret key stored in the file.
+Additionally override the hash algorithm to _SHA512_.
+
+=== EXAMPLE 3
+
+*rnp* *--keyfile* _john-pub.asc_ *--verify* _document.txt.sig_
+
+Verify detached signature, using the key stored in the _john-pub.asc_ file.
+The signed data is assumed to be available from the file _document.txt_.
+
+=== EXAMPLE 4
+
+*rnp* *-e* *-c* *-s* *--passwords* _3_
+*-r* _0x526F6E616C642054_
+*-r* "_john@doe.com_"
+*-u* _0x44616E69656C2057_
+_document.txt_
+
+Encrypt _document.txt_ with 2 keys (specified via _keyid_
+_0x526F6E616C642054_ and _userid_ _john@doe.com_), and 3 passwords,
+so *any* of these may be used to decrypt the resulting file.
+
+Additionally, the message will be signed with key _0x44616E69656C2057_.
+
+=== EXAMPLE 5
+
+*printf* _"Message"_ | *rnp* *--keyfile* _env:PGP_ENCRYPTION_KEY_ *-e* *-* *--armor*
+
+Encrypt message, passed via stdin, using the key, stored in environment variable *PGP_ENCRYPTION_KEY*, add ascii armoring, and print result to the stdout.
+
+== BUGS
+
+Please report _issues_ via the RNP public issue tracker at:
+https://github.com/rnpgp/rnp/issues.
+
+_Security reports_ or _security-sensitive feedback_ should be reported
+according to the instructions at:
+https://www.rnpgp.org/feedback.
+
+
+== AUTHORS
+
+*RNP* is an open source project led by Ribose and has
+received contributions from numerous individuals and
+organizations.
+
+
+== RESOURCES
+
+*Web site*: https://www.rnpgp.org
+
+*Source repository*: https://github.com/rnpgp/rnp
+
+
+== COPYING
+
+Copyright \(C) 2017-2021 Ribose.
+The RNP software suite is _freely licensed_:
+please refer to the *LICENSE* file for details.
+
+
+== SEE ALSO
+
+*rnpkeys(1)*, *librnp(3)*
diff --git a/src/rnp/rnp.cpp b/src/rnp/rnp.cpp
new file mode 100644
index 0000000..30d3ac4
--- /dev/null
+++ b/src/rnp/rnp.cpp
@@ -0,0 +1,695 @@
+/*
+ * Copyright (c) 2017-2021 [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* Command line program to perform rnp operations */
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <string>
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <sys/param.h>
+#include <unistd.h>
+#include <getopt.h>
+#endif
+#include <fcntl.h>
+#include <stdbool.h>
+#include <assert.h>
+#include <time.h>
+#include <errno.h>
+
+#include "fficli.h"
+#include "str-utils.h"
+#include "logging.h"
+
+static const char *usage =
+ "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"
+ " -r, --recipient Specify recipient's key via uid/keyid/fingerprint.\n"
+ " --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"
+ " --allow-weak-hash Allow usage of a weak hash algorithm.\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"
+ " --mpi Dump MPI values from packets.\n"
+ " --raw Dump raw packet contents as well.\n"
+ "\n"
+ "Other options:\n"
+ " --homedir path Override home directory (default is ~/.rnp/).\n"
+ " -f, --keyfile Load key(s) only from the file specified.\n"
+ " --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"
+ " --s2k-iterations Set the number of iterations for the S2K process.\n"
+ " --s2k-msec Calculate S2K iterations value based on a provided time in "
+ "milliseconds.\n"
+ " --notty Do not output anything to the TTY.\n"
+ " --current-time Override system's time.\n"
+ " --set-filename Override file name, stored inside of OpenPGP message.\n"
+ "\n"
+ "See man page for a detailed listing and explanation.\n"
+ "\n";
+
+enum optdefs {
+ /* Commands as they are get via CLI */
+ CMD_ENCRYPT = 260,
+ CMD_DECRYPT,
+ CMD_SIGN,
+ CMD_CLEARSIGN,
+ CMD_VERIFY,
+ CMD_VERIFY_CAT,
+ CMD_SYM_ENCRYPT,
+ CMD_DEARMOR,
+ CMD_ENARMOR,
+ CMD_LIST_PACKETS,
+ CMD_VERSION,
+ CMD_HELP,
+
+ /* OpenPGP data processing commands. Sign/Encrypt/Decrypt mapped to these */
+ CMD_PROTECT,
+ CMD_PROCESS,
+
+ /* Options */
+ OPT_KEY_STORE_FORMAT,
+ OPT_USERID,
+ OPT_RECIPIENT,
+ OPT_ARMOR,
+ OPT_HOMEDIR,
+ OPT_DETACHED,
+ OPT_HASH_ALG,
+ OPT_ALLOW_WEAK_HASH,
+ OPT_OUTPUT,
+ OPT_RESULTS,
+ OPT_COREDUMPS,
+ OPT_PASSWDFD,
+ OPT_PASSWD,
+ OPT_PASSWORDS,
+ OPT_EXPIRATION,
+ OPT_CREATION,
+ OPT_CIPHER,
+ OPT_NUMTRIES,
+ OPT_ZALG_ZIP,
+ OPT_ZALG_ZLIB,
+ OPT_ZALG_BZIP,
+ OPT_ZLEVEL,
+ OPT_OVERWRITE,
+ OPT_AEAD,
+ OPT_AEAD_CHUNK,
+ OPT_KEYFILE,
+ OPT_JSON,
+ OPT_GRIPS,
+ OPT_MPIS,
+ OPT_RAW,
+ OPT_NOTTY,
+ OPT_SOURCE,
+ OPT_NOWRAP,
+ OPT_CURTIME,
+ OPT_SETFNAME,
+ OPT_ALLOW_HIDDEN,
+ OPT_S2K_ITER,
+ OPT_S2K_MSEC,
+
+ /* debug */
+ OPT_DEBUG
+};
+
+#define EXIT_ERROR 2
+
+static struct option options[] = {
+ /* file manipulation commands */
+ {"encrypt", no_argument, NULL, CMD_ENCRYPT},
+ {"decrypt", no_argument, NULL, CMD_DECRYPT},
+ {"sign", no_argument, NULL, CMD_SIGN},
+ {"clearsign", no_argument, NULL, CMD_CLEARSIGN},
+ {"verify", no_argument, NULL, CMD_VERIFY},
+ {"verify-cat", no_argument, NULL, CMD_VERIFY_CAT},
+ {"symmetric", no_argument, NULL, CMD_SYM_ENCRYPT},
+ {"dearmor", no_argument, NULL, CMD_DEARMOR},
+ {"enarmor", optional_argument, NULL, CMD_ENARMOR},
+ /* file listing commands */
+ {"list-packets", no_argument, NULL, CMD_LIST_PACKETS},
+ /* debugging commands */
+ {"help", no_argument, NULL, CMD_HELP},
+ {"version", no_argument, NULL, CMD_VERSION},
+ {"debug", required_argument, NULL, OPT_DEBUG},
+ /* options */
+ {"coredumps", no_argument, NULL, OPT_COREDUMPS},
+ {"keystore-format", required_argument, NULL, OPT_KEY_STORE_FORMAT},
+ {"userid", required_argument, NULL, OPT_USERID},
+ {"recipient", required_argument, NULL, OPT_RECIPIENT},
+ {"home", required_argument, NULL, OPT_HOMEDIR},
+ {"homedir", required_argument, NULL, OPT_HOMEDIR},
+ {"keyfile", required_argument, NULL, OPT_KEYFILE},
+ {"ascii", no_argument, NULL, OPT_ARMOR},
+ {"armor", no_argument, NULL, OPT_ARMOR},
+ {"armour", no_argument, NULL, OPT_ARMOR},
+ {"detach", no_argument, NULL, OPT_DETACHED},
+ {"detached", no_argument, NULL, OPT_DETACHED},
+ {"hash", required_argument, NULL, OPT_HASH_ALG},
+ {"pass-fd", required_argument, NULL, OPT_PASSWDFD},
+ {"password", required_argument, NULL, OPT_PASSWD},
+ {"passwords", required_argument, NULL, OPT_PASSWORDS},
+ {"output", required_argument, NULL, OPT_OUTPUT},
+ {"results", required_argument, NULL, OPT_RESULTS},
+ {"creation", required_argument, NULL, OPT_CREATION},
+ {"expiration", required_argument, NULL, OPT_EXPIRATION},
+ {"expiry", required_argument, NULL, OPT_EXPIRATION},
+ {"cipher", required_argument, NULL, OPT_CIPHER},
+ {"numtries", required_argument, NULL, OPT_NUMTRIES},
+ {"zip", no_argument, NULL, OPT_ZALG_ZIP},
+ {"zlib", no_argument, NULL, OPT_ZALG_ZLIB},
+ {"bzip", no_argument, NULL, OPT_ZALG_BZIP},
+ {"bzip2", no_argument, NULL, OPT_ZALG_BZIP},
+ {"overwrite", no_argument, NULL, OPT_OVERWRITE},
+ {"aead", optional_argument, NULL, OPT_AEAD},
+ {"aead-chunk-bits", required_argument, NULL, OPT_AEAD_CHUNK},
+ {"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},
+ {"set-filename", required_argument, NULL, OPT_SETFNAME},
+ {"allow-hidden", no_argument, NULL, OPT_ALLOW_HIDDEN},
+ {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER},
+ {"s2k-msec", required_argument, NULL, OPT_S2K_MSEC},
+ {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH},
+
+ {NULL, 0, NULL, 0},
+};
+
+/* print a usage message */
+static void
+print_usage(const char *usagemsg)
+{
+ cli_rnp_print_praise();
+ puts(usagemsg);
+}
+
+/* do a command once for a specified config */
+static bool
+rnp_cmd(cli_rnp_t *rnp)
+{
+ bool ret = false;
+
+ switch (rnp->cfg().get_int(CFG_COMMAND)) {
+ case CMD_PROTECT:
+ ret = cli_rnp_protect_file(rnp);
+ break;
+ case CMD_PROCESS:
+ ret = cli_rnp_process_file(rnp);
+ break;
+ case CMD_LIST_PACKETS:
+ ret = cli_rnp_dump_file(rnp);
+ break;
+ case CMD_DEARMOR:
+ ret = cli_rnp_dearmor_file(rnp);
+ break;
+ case CMD_ENARMOR:
+ ret = cli_rnp_armor_file(rnp);
+ break;
+ case CMD_VERSION:
+ cli_rnp_print_praise();
+ ret = true;
+ break;
+ default:
+ print_usage(usage);
+ ret = true;
+ }
+
+ return ret;
+}
+
+static bool
+setcmd(rnp_cfg &cfg, int cmd, const char *arg)
+{
+ int newcmd = cmd;
+
+ /* set file processing command to one of PROTECT or PROCESS */
+ switch (cmd) {
+ case CMD_ENCRYPT:
+ cfg.set_bool(CFG_ENCRYPT_PK, true);
+ if (cfg.get_bool(CFG_ENCRYPT_SK)) {
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, false);
+ }
+ newcmd = CMD_PROTECT;
+ break;
+ case CMD_SYM_ENCRYPT:
+ cfg.set_bool(CFG_ENCRYPT_SK, true);
+ if (!cfg.get_bool(CFG_ENCRYPT_PK) && !cfg.get_bool(CFG_SIGN_NEEDED)) {
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, true);
+ }
+ newcmd = CMD_PROTECT;
+ break;
+ case CMD_CLEARSIGN:
+ cfg.set_bool(CFG_CLEARTEXT, true);
+ [[fallthrough]];
+ case CMD_SIGN:
+ cfg.set_bool(CFG_NEEDSSECKEY, true);
+ cfg.set_bool(CFG_SIGN_NEEDED, true);
+ if (cfg.get_bool(CFG_ENCRYPT_SK)) {
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, false);
+ }
+ newcmd = CMD_PROTECT;
+ break;
+ case CMD_DECRYPT:
+ /* for decryption, we probably need a seckey */
+ cfg.set_bool(CFG_NEEDSSECKEY, true);
+ newcmd = CMD_PROCESS;
+ break;
+ case CMD_VERIFY:
+ /* single verify will discard output, decrypt will not */
+ cfg.set_bool(CFG_NO_OUTPUT, true);
+ [[fallthrough]];
+ case CMD_VERIFY_CAT:
+ newcmd = CMD_PROCESS;
+ break;
+ case CMD_LIST_PACKETS:
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, true);
+ break;
+ case CMD_DEARMOR:
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, true);
+ break;
+ case CMD_ENARMOR: {
+ std::string msgt = arg ? arg : "";
+ if (msgt.empty() || (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);
+ }
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, true);
+ break;
+ }
+ case CMD_HELP:
+ case CMD_VERSION:
+ break;
+ default:
+ newcmd = CMD_HELP;
+ break;
+ }
+
+ if (cfg.has(CFG_COMMAND) && cfg.get_int(CFG_COMMAND) != newcmd) {
+ ERR_MSG("Conflicting commands!");
+ return false;
+ }
+
+ cfg.set_int(CFG_COMMAND, newcmd);
+ return true;
+}
+
+/* set an option */
+static bool
+setoption(rnp_cfg &cfg, int val, const char *arg)
+{
+ switch (val) {
+ /* redirect commands to setcmd */
+ case CMD_ENCRYPT:
+ case CMD_SIGN:
+ case CMD_CLEARSIGN:
+ case CMD_DECRYPT:
+ case CMD_SYM_ENCRYPT:
+ case CMD_VERIFY:
+ case CMD_VERIFY_CAT:
+ case CMD_LIST_PACKETS:
+ case CMD_DEARMOR:
+ case CMD_ENARMOR:
+ case CMD_HELP:
+ case CMD_VERSION:
+ return setcmd(cfg, val, arg);
+ /* options */
+ case OPT_COREDUMPS:
+#ifdef _WIN32
+ ERR_MSG("warning: --coredumps doesn't make sense on windows systems.");
+#endif
+ cfg.set_bool(CFG_COREDUMPS, true);
+ return true;
+ case OPT_KEY_STORE_FORMAT:
+ cfg.set_str(CFG_KEYSTOREFMT, arg);
+ return true;
+ case OPT_USERID:
+ cfg.add_str(CFG_SIGNERS, arg);
+ return true;
+ case OPT_RECIPIENT:
+ 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:
+ cfg.set_str(CFG_HOMEDIR, arg);
+ return true;
+ case OPT_KEYFILE:
+ cfg.set_str(CFG_KEYFILE, arg);
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, true);
+ return true;
+ case OPT_HASH_ALG:
+ return cli_rnp_set_hash(cfg, arg);
+ case OPT_ALLOW_WEAK_HASH:
+ cfg.set_bool(CFG_WEAK_HASH, true);
+ return true;
+ case OPT_PASSWDFD:
+ cfg.set_str(CFG_PASSFD, arg);
+ return true;
+ case OPT_PASSWD:
+ cfg.set_str(CFG_PASSWD, arg);
+ return true;
+ case OPT_PASSWORDS: {
+ 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:
+ cfg.set_str(CFG_OUTFILE, arg);
+ return true;
+ case OPT_RESULTS:
+ 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:
+ 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");
+ return true;
+ case OPT_ZALG_ZLIB:
+ cfg.set_str(CFG_ZALG, "ZLib");
+ return true;
+ case OPT_ZALG_BZIP:
+ cfg.set_str(CFG_ZALG, "BZip2");
+ return true;
+ case OPT_AEAD: {
+ std::string argstr = arg ? arg : "";
+ if ((argstr == "1") || rnp::str_case_eq(argstr, "eax")) {
+ argstr = "EAX";
+ } else if (argstr.empty() || (argstr == "2") || rnp::str_case_eq(argstr, "ocb")) {
+ argstr = "OCB";
+ } else {
+ ERR_MSG("Wrong AEAD algorithm: %s", argstr.c_str());
+ return false;
+ }
+ cfg.set_str(CFG_AEAD, argstr);
+ return true;
+ }
+ case OPT_AEAD_CHUNK: {
+ 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;
+ }
+ case OPT_OVERWRITE:
+ cfg.set_bool(CFG_OVERWRITE, true);
+ return true;
+ case OPT_JSON:
+ cfg.set_bool(CFG_JSON, true);
+ return true;
+ case OPT_GRIPS:
+ cfg.set_bool(CFG_GRIPS, true);
+ return true;
+ case OPT_MPIS:
+ cfg.set_bool(CFG_MPIS, true);
+ return true;
+ case OPT_RAW:
+ 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_SETFNAME:
+ cfg.set_str(CFG_SETFNAME, arg);
+ return true;
+ case OPT_ALLOW_HIDDEN:
+ cfg.set_bool(CFG_ALLOW_HIDDEN, true);
+ return true;
+ case OPT_S2K_ITER: {
+ int iterations = 0;
+ if (!rnp::str_to_int(arg, iterations) || !iterations) {
+ ERR_MSG("Wrong iterations value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_S2K_ITER, iterations);
+ return true;
+ }
+ case OPT_S2K_MSEC: {
+ int msec = 0;
+ if (!rnp::str_to_int(arg, msec) || !msec) {
+ ERR_MSG("Invalid s2k msec value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_S2K_MSEC, msec);
+ return true;
+ }
+ case OPT_DEBUG:
+ ERR_MSG("Option --debug is deprecated, ignoring.");
+ return true;
+ default:
+ return setcmd(cfg, CMD_HELP, arg);
+ }
+
+ return false;
+}
+
+static bool
+set_short_option(rnp_cfg &cfg, int ch, const char *arg)
+{
+ switch (ch) {
+ case 'V':
+ return setcmd(cfg, CMD_VERSION, arg);
+ case 'd':
+ return setcmd(cfg, CMD_DECRYPT, arg);
+ case 'e':
+ return setcmd(cfg, CMD_ENCRYPT, arg);
+ case 'c':
+ return setcmd(cfg, CMD_SYM_ENCRYPT, arg);
+ case 's':
+ return setcmd(cfg, CMD_SIGN, arg);
+ case 'v':
+ return setcmd(cfg, CMD_VERIFY, arg);
+ case 'r':
+ if (!strlen(optarg)) {
+ ERR_MSG("Recipient should not be empty");
+ } else {
+ cfg.add_str(CFG_RECIPIENTS, optarg);
+ }
+ break;
+ case 'u':
+ if (!optarg) {
+ ERR_MSG("No userid argument provided");
+ return false;
+ }
+ cfg.add_str(CFG_SIGNERS, optarg);
+ break;
+ case 'z':
+ if ((strlen(optarg) != 1) || (optarg[0] < '0') || (optarg[0] > '9')) {
+ ERR_MSG("Bad compression level: %s. Should be 0..9", optarg);
+ } else {
+ cfg.set_int(CFG_ZLEVEL, optarg[0] - '0');
+ }
+ break;
+ case 'f':
+ if (!optarg) {
+ ERR_MSG("No keyfile argument provided");
+ return false;
+ }
+ cfg.set_str(CFG_KEYFILE, optarg);
+ cfg.set_bool(CFG_KEYSTORE_DISABLED, true);
+ break;
+ case 'h':
+ [[fallthrough]];
+ default:
+ return setcmd(cfg, CMD_HELP, optarg);
+ }
+
+ return true;
+}
+
+#ifndef RNP_RUN_TESTS
+int
+main(int argc, char **argv)
+#else
+int rnp_main(int argc, char **argv);
+int
+rnp_main(int argc, char **argv)
+#endif
+{
+ if (argc < 2) {
+ print_usage(usage);
+ return EXIT_ERROR;
+ }
+
+ cli_rnp_t rnp = {};
+#if !defined(RNP_RUN_TESTS) && defined(_WIN32)
+ try {
+ rnp.substitute_args(&argc, &argv);
+ } catch (std::exception &ex) {
+ RNP_LOG("Error converting arguments ('%s')", ex.what());
+ return EXIT_ERROR;
+ }
+#endif
+
+ rnp_cfg cfg;
+ cfg.load_defaults();
+
+ /* TODO: These options should be set after initialising the context. */
+ int optindex = 0;
+ int ch;
+ while ((ch = getopt_long(argc, argv, "S:Vdecr:su:vz:f:h", options, &optindex)) != -1) {
+ /* Check for unsupported command/option */
+ if (ch == '?') {
+ print_usage(usage);
+ return EXIT_FAILURE;
+ }
+
+ bool res = ch >= CMD_ENCRYPT ? setoption(cfg, options[optindex].val, optarg) :
+ set_short_option(cfg, ch, optarg);
+ if (!res) {
+ return EXIT_ERROR;
+ }
+ }
+
+ switch (cfg.get_int(CFG_COMMAND)) {
+ case CMD_HELP:
+ print_usage(usage);
+ return EXIT_SUCCESS;
+ case CMD_VERSION:
+ cli_rnp_print_praise();
+ return EXIT_SUCCESS;
+ default:;
+ }
+
+ if (!cli_cfg_set_keystore_info(cfg)) {
+ ERR_MSG("fatal: cannot set keystore info");
+ return EXIT_ERROR;
+ }
+
+ if (!rnp.init(cfg)) {
+ ERR_MSG("fatal: cannot initialise");
+ return EXIT_ERROR;
+ }
+
+ if (!cli_rnp_check_weak_hash(&rnp)) {
+ ERR_MSG("Weak hash algorithm detected. Pass --allow-weak-hash option if you really "
+ "want to use it.");
+ return EXIT_ERROR;
+ }
+
+ bool disable_ks = rnp.cfg().get_bool(CFG_KEYSTORE_DISABLED);
+ if (!disable_ks && !rnp.load_keyrings(rnp.cfg().get_bool(CFG_NEEDSSECKEY))) {
+ ERR_MSG("fatal: failed to load keys");
+ return EXIT_ERROR;
+ }
+
+ /* load the keyfile if any */
+ if (disable_ks && !rnp.cfg().get_str(CFG_KEYFILE).empty() && !cli_rnp_add_key(&rnp)) {
+ ERR_MSG("fatal: failed to load key(s) from the file");
+ return EXIT_ERROR;
+ }
+
+ if (!cli_rnp_setup(&rnp)) {
+ return EXIT_ERROR;
+ }
+
+ /* now do the required action for each of the command line args */
+ if (optind == argc) {
+ return cli_rnp_t::ret_code(rnp_cmd(&rnp));
+ }
+ bool success = true;
+ for (int i = optind; i < argc; i++) {
+ rnp.cfg().set_str(CFG_INFILE, argv[i]);
+ success = success && rnp_cmd(&rnp);
+ }
+ return cli_rnp_t::ret_code(success);
+}
diff --git a/src/rnp/rnpcfg.cpp b/src/rnp/rnpcfg.cpp
new file mode 100644
index 0000000..e1c35a3
--- /dev/null
+++ b/src/rnp/rnpcfg.cpp
@@ -0,0 +1,572 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <limits.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#else
+#include "uniwin.h"
+#endif
+#include <sys/stat.h>
+#include <time.h>
+#include <errno.h>
+#include <stdexcept>
+#include <inttypes.h>
+
+#include "config.h"
+#include "rnpcfg.h"
+#include "defaults.h"
+#include "utils.h"
+#include "time-utils.h"
+#include <rnp/rnp.h>
+
+// must be placed after include "utils.h"
+#ifndef RNP_USE_STD_REGEX
+#include <regex.h>
+#else
+#include <regex>
+#endif
+
+typedef enum rnp_cfg_val_type_t {
+ RNP_CFG_VAL_NULL = 0,
+ RNP_CFG_VAL_INT = 1,
+ RNP_CFG_VAL_BOOL = 2,
+ RNP_CFG_VAL_STRING = 3,
+ RNP_CFG_VAL_LIST = 4
+} rnp_cfg_val_type_t;
+
+class rnp_cfg_val {
+ rnp_cfg_val_type_t type_;
+
+ public:
+ rnp_cfg_val(rnp_cfg_val_type_t t) : type_(t){};
+ rnp_cfg_val_type_t
+ type() const
+ {
+ return type_;
+ };
+
+ virtual ~rnp_cfg_val(){};
+};
+
+class rnp_cfg_int_val : public rnp_cfg_val {
+ int val_;
+
+ public:
+ rnp_cfg_int_val(int val) : rnp_cfg_val(RNP_CFG_VAL_INT), val_(val){};
+ int
+ val() const
+ {
+ return val_;
+ };
+};
+
+class rnp_cfg_bool_val : public rnp_cfg_val {
+ bool val_;
+
+ public:
+ rnp_cfg_bool_val(bool val) : rnp_cfg_val(RNP_CFG_VAL_BOOL), val_(val){};
+ bool
+ val() const
+ {
+ return val_;
+ };
+};
+
+class rnp_cfg_str_val : public rnp_cfg_val {
+ std::string val_;
+
+ public:
+ rnp_cfg_str_val(const std::string &val) : rnp_cfg_val(RNP_CFG_VAL_STRING), val_(val){};
+ const std::string &
+ val() const
+ {
+ return val_;
+ };
+};
+
+class rnp_cfg_list_val : public rnp_cfg_val {
+ std::vector<std::string> val_;
+
+ public:
+ rnp_cfg_list_val() : rnp_cfg_val(RNP_CFG_VAL_LIST), val_(){};
+ std::vector<std::string> &
+ val()
+ {
+ return val_;
+ };
+ const std::vector<std::string> &
+ val() const
+ {
+ return val_;
+ };
+};
+
+void
+rnp_cfg::load_defaults()
+{
+ set_bool(CFG_OVERWRITE, false);
+ set_str(CFG_OUTFILE, "");
+ set_str(CFG_ZALG, DEFAULT_Z_ALG);
+ set_int(CFG_ZLEVEL, DEFAULT_Z_LEVEL);
+ set_str(CFG_CIPHER, DEFAULT_SYMM_ALG);
+ set_int(CFG_NUMTRIES, MAX_PASSWORD_ATTEMPTS);
+ set_int(CFG_S2K_MSEC, DEFAULT_S2K_MSEC);
+}
+
+void
+rnp_cfg::set_str(const std::string &key, const std::string &val)
+{
+ unset(key);
+ vals_[key] = new rnp_cfg_str_val(val);
+}
+
+void
+rnp_cfg::set_str(const std::string &key, const char *val)
+{
+ unset(key);
+ vals_[key] = new rnp_cfg_str_val(val);
+}
+
+void
+rnp_cfg::set_int(const std::string &key, int val)
+{
+ unset(key);
+ vals_[key] = new rnp_cfg_int_val(val);
+}
+
+void
+rnp_cfg::set_bool(const std::string &key, bool val)
+{
+ unset(key);
+ vals_[key] = new rnp_cfg_bool_val(val);
+}
+
+void
+rnp_cfg::unset(const std::string &key)
+{
+ if (!vals_.count(key)) {
+ return;
+ }
+ delete vals_[key];
+ vals_.erase(key);
+}
+
+void
+rnp_cfg::add_str(const std::string &key, const std::string &val)
+{
+ if (!vals_.count(key)) {
+ vals_[key] = new rnp_cfg_list_val();
+ }
+ if (vals_[key]->type() != RNP_CFG_VAL_LIST) {
+ RNP_LOG("expected list val for \"%s\"", key.c_str());
+ throw std::invalid_argument("type");
+ }
+ (dynamic_cast<rnp_cfg_list_val &>(*vals_[key])).val().push_back(val);
+}
+
+bool
+rnp_cfg::has(const std::string &key) const
+{
+ return vals_.count(key);
+}
+
+const std::string &
+rnp_cfg::get_str(const std::string &key) const
+{
+ if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) {
+ return empty_str_;
+ }
+ return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val();
+}
+
+const char *
+rnp_cfg::get_cstr(const std::string &key) const
+{
+ if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_STRING)) {
+ return NULL;
+ }
+ return (dynamic_cast<const rnp_cfg_str_val &>(*vals_.at(key))).val().c_str();
+}
+
+int
+rnp_cfg::get_int(const std::string &key, int def) const
+{
+ if (!has(key)) {
+ return def;
+ }
+ const rnp_cfg_val *val = vals_.at(key);
+ switch (val->type()) {
+ case RNP_CFG_VAL_INT:
+ return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val();
+ case RNP_CFG_VAL_BOOL:
+ return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val();
+ case RNP_CFG_VAL_STRING:
+ return atoi((dynamic_cast<const rnp_cfg_str_val &>(*val)).val().c_str());
+ default:
+ return def;
+ }
+}
+
+bool
+rnp_cfg::get_bool(const std::string &key) const
+{
+ if (!has(key)) {
+ return false;
+ }
+ const rnp_cfg_val *val = vals_.at(key);
+ switch (val->type()) {
+ case RNP_CFG_VAL_INT:
+ return (dynamic_cast<const rnp_cfg_int_val &>(*val)).val();
+ case RNP_CFG_VAL_BOOL:
+ return (dynamic_cast<const rnp_cfg_bool_val &>(*val)).val();
+ case RNP_CFG_VAL_STRING: {
+ const std::string &str = (dynamic_cast<const rnp_cfg_str_val &>(*val)).val();
+ return !strcasecmp(str.c_str(), "true") || (atoi(str.c_str()) > 0);
+ }
+ default:
+ return false;
+ }
+}
+
+size_t
+rnp_cfg::get_count(const std::string &key) const
+{
+ if (!has(key) || (vals_.at(key)->type() != RNP_CFG_VAL_LIST)) {
+ return 0;
+ }
+ return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().size();
+}
+
+const std::string &
+rnp_cfg::get_str(const std::string &key, size_t idx) const
+{
+ if (get_count(key) <= idx) {
+ RNP_LOG("idx is out of bounds for \"%s\"", key.c_str());
+ throw std::invalid_argument("idx");
+ }
+ return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val().at(idx);
+}
+
+std::vector<std::string>
+rnp_cfg::get_list(const std::string &key) const
+{
+ if (!has(key)) {
+ /* it's okay to return empty list */
+ return std::vector<std::string>();
+ }
+ if (vals_.at(key)->type() != RNP_CFG_VAL_LIST) {
+ RNP_LOG("no list at the key \"%s\"", key.c_str());
+ throw std::invalid_argument("key");
+ }
+ return (dynamic_cast<const rnp_cfg_list_val &>(*vals_.at(key))).val();
+}
+
+int
+rnp_cfg::get_pswdtries() const
+{
+ const std::string &numtries = get_str(CFG_NUMTRIES);
+ int num = atoi(numtries.c_str());
+ if (numtries.empty() || (num <= 0)) {
+ return MAX_PASSWORD_ATTEMPTS;
+ } else if (numtries == "unlimited") {
+ return INFINITE_ATTEMPTS;
+ }
+ return num;
+}
+
+const std::string
+rnp_cfg::get_hashalg() const
+{
+ const std::string hash_alg = get_str(CFG_HASH);
+ if (!hash_alg.empty()) {
+ return hash_alg;
+ }
+ return DEFAULT_HASH_ALG;
+}
+
+bool
+rnp_cfg::get_expiration(const std::string &key, uint32_t &seconds) const
+{
+ if (!has(key)) {
+ return false;
+ }
+ const std::string &val = get_str(key);
+ uint64_t delta;
+ uint64_t t;
+ if (parse_date(val, t)) {
+ uint64_t now = time();
+ if (t > now) {
+ delta = t - now;
+ if (delta > UINT32_MAX) {
+ RNP_LOG("Expiration time exceeds 32-bit value");
+ return false;
+ }
+ seconds = delta;
+ return true;
+ }
+ return false;
+ }
+ const char *reg = "^([0-9]+)([hdwmy]?)$";
+#ifndef RNP_USE_STD_REGEX
+ static regex_t r;
+ static int compiled;
+ regmatch_t matches[3];
+
+ if (!compiled) {
+ compiled = 1;
+ if (regcomp(&r, reg, REG_EXTENDED | REG_ICASE)) {
+ RNP_LOG("failed to compile regexp");
+ return false;
+ }
+ }
+ if (regexec(&r, val.c_str(), ARRAY_SIZE(matches), matches, 0)) {
+ return false;
+ }
+ auto delta_str = &val.c_str()[matches[1].rm_so];
+ char mult = val.c_str()[matches[2].rm_so];
+#else
+ static std::regex re(reg, std::regex_constants::extended | std::regex_constants::icase);
+ std::smatch result;
+
+ if (!std::regex_search(val, result, re)) {
+ return false;
+ }
+ std::string delta_stdstr = result[1].str();
+ const char *delta_str = delta_stdstr.c_str();
+ char mult = result[2].str()[0];
+#endif
+ errno = 0;
+ delta = strtoul(delta_str, NULL, 10);
+ if (errno || delta > UINT_MAX) {
+ RNP_LOG("Invalid expiration '%s'.", delta_str);
+ return false;
+ }
+ switch (std::tolower(mult)) {
+ case 'h':
+ delta *= 60 * 60;
+ break;
+ case 'd':
+ delta *= 60 * 60 * 24;
+ break;
+ case 'w':
+ delta *= 60 * 60 * 24 * 7;
+ break;
+ case 'm':
+ delta *= 60 * 60 * 24 * 31;
+ break;
+ case 'y':
+ delta *= 60 * 60 * 24 * 365;
+ break;
+ }
+ if (delta > UINT32_MAX) {
+ RNP_LOG("Expiration value exceed 32 bit.");
+ return false;
+ }
+ 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
+{
+ uint64_t t = 0;
+ if (extract_timestamp(get_str(CFG_CREATION), t)) {
+ return t;
+ }
+ return time();
+}
+
+uint64_t
+rnp_cfg::time() const
+{
+ uint64_t t = 0;
+ if (extract_timestamp(get_str(CFG_CURTIME), t)) {
+ return t;
+ }
+ return ::time(NULL);
+}
+
+void
+rnp_cfg::copy(const rnp_cfg &src)
+{
+ for (const auto &it : src.vals_) {
+ if (has(it.first)) {
+ unset(it.first);
+ }
+ rnp_cfg_val *val = NULL;
+ switch (it.second->type()) {
+ case RNP_CFG_VAL_INT:
+ val = new rnp_cfg_int_val(dynamic_cast<const rnp_cfg_int_val &>(*it.second));
+ break;
+ case RNP_CFG_VAL_BOOL:
+ val = new rnp_cfg_bool_val(dynamic_cast<const rnp_cfg_bool_val &>(*it.second));
+ break;
+ case RNP_CFG_VAL_STRING:
+ val = new rnp_cfg_str_val(dynamic_cast<const rnp_cfg_str_val &>(*it.second));
+ break;
+ case RNP_CFG_VAL_LIST:
+ val = new rnp_cfg_list_val(dynamic_cast<const rnp_cfg_list_val &>(*it.second));
+ break;
+ default:
+ continue;
+ }
+ vals_[it.first] = val;
+ }
+}
+
+void
+rnp_cfg::clear()
+{
+ for (const auto &it : vals_) {
+ delete it.second;
+ }
+ vals_.clear();
+}
+
+rnp_cfg::~rnp_cfg()
+{
+ clear();
+}
+
+/**
+ * @brief Get number of days in month.
+ *
+ * @param year number of year, i.e. 2021
+ * @param month number of month, 1..12
+ * @return number of days (28..31) or 0 if month is wrong.
+ */
+static int
+days_in_month(int year, int month)
+{
+ switch (month) {
+ case 1:
+ case 3:
+ case 5:
+ case 7:
+ case 8:
+ case 10:
+ case 12:
+ return 31;
+ case 4:
+ case 6:
+ case 9:
+ case 11:
+ return 30;
+ case 2: {
+ bool leap_year = !(year % 400) || (!(year % 4) && (year % 100));
+ return leap_year ? 29 : 28;
+ }
+ default:
+ return 0;
+ }
+}
+
+bool
+rnp_cfg::parse_date(const std::string &s, uint64_t &t) const
+{
+ /* fill time zone information */
+ struct tm tm;
+ rnp_localtime(::time(NULL), tm);
+ tm.tm_hour = 0;
+ tm.tm_min = 0;
+ tm.tm_sec = 0;
+ const char *reg = "^([0-9]{4})[-/\\.]([0-9]{2})[-/\\.]([0-9]{2})$";
+#ifndef RNP_USE_STD_REGEX
+ static regex_t r;
+ static int compiled;
+
+ if (!compiled) {
+ compiled = 1;
+ if (regcomp(&r, reg, REG_EXTENDED)) {
+ RNP_LOG("failed to compile regexp");
+ return false;
+ }
+ }
+ regmatch_t matches[4];
+ if (regexec(&r, s.c_str(), ARRAY_SIZE(matches), matches, 0)) {
+ return false;
+ }
+ int year = strtol(&s[matches[1].rm_so], NULL, 10);
+ int mon = strtol(&s[matches[2].rm_so], NULL, 10);
+ int mday = strtol(&s[matches[3].rm_so], NULL, 10);
+#else
+ static std::regex re(reg, std::regex_constants::extended);
+ std::smatch result;
+
+ if (!std::regex_search(s, result, re)) {
+ return false;
+ }
+ int year = std::stoi(result[1].str());
+ int mon = std::stoi(result[2].str());
+ int mday = std::stoi(result[3].str());
+#endif
+ if (year < 1970 || mon < 1 || mon > 12 || !mday || (mday > days_in_month(year, mon))) {
+ RNP_LOG("invalid date: %s.", s.c_str());
+ return false;
+ }
+ tm.tm_year = year - 1900;
+ tm.tm_mon = mon - 1;
+ tm.tm_mday = mday;
+ /* line below is required to correctly handle DST changes */
+ tm.tm_isdst = -1;
+
+ struct tm check_tm = tm;
+ time_t built_time = rnp_mktime(&tm);
+ time_t check_time = mktime(&check_tm);
+ if (built_time != check_time) {
+ /* If date is beyond of yk2038 and we have 32-bit signed time_t, we need to reduce
+ * timestamp */
+ RNP_LOG("Warning: date %s is beyond of 32-bit time_t, so timestamp was reduced to "
+ "maximum supported value.",
+ s.c_str());
+ }
+ t = built_time;
+ return true;
+}
diff --git a/src/rnp/rnpcfg.h b/src/rnp/rnpcfg.h
new file mode 100644
index 0000000..c9478bb
--- /dev/null
+++ b/src/rnp/rnpcfg.h
@@ -0,0 +1,219 @@
+/*
+ * Copyright (c) 2017, [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 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 RNP_CFG_H_
+#define RNP_CFG_H_
+
+#include <stdbool.h>
+#include <stdint.h>
+#include <string>
+#include <vector>
+#include <unordered_map>
+
+/* cfg variables known by rnp */
+#define CFG_OVERWRITE "overwrite" /* overwrite output file if it is already exist or fail */
+#define CFG_ARMOR "armor" /* armor output data or not */
+#define CFG_ARMOR_DATA_TYPE "armor_type" /* armor data type, used with ``enarmor`` option */
+#define CFG_COMMAND "command" /* command to execute over input data */
+#define CFG_DETACHED "detached" /* produce the detached signature */
+#define CFG_CLEARTEXT "cleartext" /* cleartext signing should be used */
+#define CFG_SIGN_NEEDED "sign_needed" /* signing is needed during data protection */
+#define CFG_OUTFILE "outfile" /* name/path of the output file */
+#define CFG_NO_OUTPUT "no_output" /* do not output any data - just verify or process */
+#define CFG_INFILE "infile" /* name/path of the input file */
+#define CFG_SETFNAME "setfname" /* file name to embed into the literal data packet */
+#define CFG_RESULTS "results" /* name/path for results, not used right now */
+#define CFG_KEYSTOREFMT "keystorefmt" /* keyring format : GPG, G10, KBX */
+#define CFG_COREDUMPS "coredumps" /* enable/disable core dumps. 1 or 0. */
+#define CFG_NEEDSSECKEY "needsseckey" /* needs secret key for the ongoing operation */
+#define CFG_USERID "userid" /* userid for the ongoing operation */
+#define CFG_RECIPIENTS "recipients" /* list of encrypted data recipients */
+#define CFG_SIGNERS "signers" /* list of signers */
+#define CFG_HOMEDIR "homedir" /* home directory - folder with keyrings and so on */
+#define CFG_KEYFILE "keyfile" /* path to the file with key(s), used instead of keyring */
+#define CFG_PASSFD "pass-fd" /* password file descriptor */
+#define CFG_PASSWD "password" /* password as command-line constant */
+#define CFG_PASSWORDC "passwordc" /* number of passwords for symmetric encryption */
+#define CFG_USERINPUTFD "user-input-fd" /* user input file descriptor */
+#define CFG_NUMTRIES "numtries" /* number of password request tries, or 'unlimited' */
+#define CFG_EXPIRATION "expiration" /* signature expiration time */
+#define CFG_CREATION "creation" /* signature validity start */
+#define CFG_CIPHER "cipher" /* symmetric encryption algorithm as string */
+#define CFG_HASH "hash" /* hash algorithm used, string like 'SHA1'*/
+#define CFG_WEAK_HASH "weak-hash" /* allow weak algorithms */
+#define CFG_S2K_ITER "s2k-iter" /* number of S2K hash iterations to perform */
+#define CFG_S2K_MSEC "s2k-msec" /* number of milliseconds S2K should target */
+#define CFG_ENCRYPT_PK "encrypt_pk" /* public key should be used during encryption */
+#define CFG_ENCRYPT_SK "encrypt_sk" /* password encryption should be used */
+#define CFG_IO_RESS "ress" /* results stream */
+#define CFG_NUMBITS "numbits" /* number of bits in generated key */
+#define CFG_EXPERT "expert" /* expert key generation mode */
+#define CFG_ZLEVEL "zlevel" /* compression level: 0..9 (0 for no compression) */
+#define CFG_ZALG "zalg" /* compression algorithm: zip, zlib or bzip2 */
+#define CFG_AEAD "aead" /* if nonzero then AEAD enryption mode, int */
+#define CFG_AEAD_CHUNK "aead_chunk" /* AEAD chunk size bits, int from 0 to 56 */
+#define CFG_KEYSTORE_DISABLED \
+ "disable_keystore" /* indicates whether keystore must be initialized */
+#define CFG_FORCE "force" /* force command to succeed operation */
+#define CFG_SECRET "secret" /* indicates operation on secret key */
+#define CFG_WITH_SIGS "with-sigs" /* list keys with signatures */
+#define CFG_JSON "json" /* list packets with JSON output */
+#define CFG_GRIPS "grips" /* dump grips when dumping key packets */
+#define CFG_MPIS "mpis" /* dump MPI values when dumping packets */
+#define CFG_RAW "raw" /* dump raw packet contents */
+#define CFG_REV_TYPE "rev-type" /* revocation reason code */
+#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_ADD_SUBKEY "add-subkey" /* add subkey to existing primary */
+#define CFG_SET_KEY_EXPIRE "key-expire" /* set/update key expiration time */
+#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 */
+#define CFG_ALLOW_HIDDEN "allow-hidden" /* allow hidden recipients */
+
+/* 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"
+#define CFG_KR_SEC_PATH "kr-sec-path"
+#define CFG_KR_DEF_KEY "kr-def-key"
+
+/* key generation variables */
+#define CFG_KG_PRIMARY_ALG "kg-primary-alg"
+#define CFG_KG_PRIMARY_BITS "kg-primary-bits"
+#define CFG_KG_PRIMARY_CURVE "kg-primary-curve"
+#define CFG_KG_PRIMARY_EXPIRATION "kg-primary-expiration"
+#define CFG_KG_SUBKEY_ALG "kg-subkey-alg"
+#define CFG_KG_SUBKEY_BITS "kg-subkey-bits"
+#define CFG_KG_SUBKEY_CURVE "kg-subkey-curve"
+#define CFG_KG_SUBKEY_EXPIRATION "kg-subkey-expiration"
+#define CFG_KG_HASH "kg-hash"
+#define CFG_KG_PROT_HASH "kg-prot-hash"
+#define CFG_KG_PROT_ALG "kg-prot-alg"
+#define CFG_KG_PROT_ITERATIONS "kg-prot-iterations"
+
+/* rnp CLI config : contains all the system-dependent and specified by the user configuration
+ * options */
+class rnp_cfg_val;
+
+class rnp_cfg {
+ private:
+ std::unordered_map<std::string, rnp_cfg_val *> vals_;
+ std::string empty_str_;
+
+ /** @brief Parse date from the string in %Y-%m-%d format (using "-", "/", "." as a
+ * separator)
+ *
+ * @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 */
+ void set_str(const std::string &key, const std::string &val);
+ void set_str(const std::string &key, const char *val);
+ /** @brief set int value for the key in config */
+ void set_int(const std::string &key, int val);
+ /** @brief set bool value for the key in config */
+ void set_bool(const std::string &key, bool val);
+ /** @brief remove key and corresponding value from the config */
+ void unset(const std::string &key);
+ /** @brief add string item to the list value */
+ void add_str(const std::string &key, const std::string &val);
+ /** @brief check whether config has value for the key */
+ bool has(const std::string &key) const;
+ /** @brief get string value from the config. If it is absent then empty string will be
+ * returned */
+ const std::string &get_str(const std::string &key) const;
+ /** @brief get C string value from the config. Will return 0 instead of empty string if
+ * value is absent. */
+ const char *get_cstr(const std::string &key) const;
+ /** @brief get int value from the config. If it is absent then def will be returned */
+ int get_int(const std::string &key, int def = 0) const;
+ /** @brief get bool value from the config. If it is absent then false will be returned */
+ bool get_bool(const std::string &key) const;
+ /** @brief get number of items in the string list value. If it is absent then 0 will be
+ * returned. */
+ size_t get_count(const std::string &key) const;
+ /** @brief get string from the list value at the corresponding position. If there is no
+ * corresponding value or index too large then empty string will be returned. */
+ const std::string &get_str(const std::string &key, size_t idx) const;
+ /** @brief get all strings from the list value */
+ std::vector<std::string> get_list(const std::string &key) const;
+ /** @brief get number of the password tries */
+ int get_pswdtries() const;
+ /** @brief get hash algorithm */
+ const std::string get_hashalg() const;
+
+ /** @brief Get expiration time from the cfg variable, as value relative to the current
+ * time. As per OpenPGP standard it should fit in 32 bit value, otherwise error is
+ * returned.
+ *
+ * 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 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.
+ * Creation time may be specified in different formats:
+ * - 2017-07-12 : as the exact date
+ * - 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();
+ /* delete unneeded operators */
+ rnp_cfg &operator=(const rnp_cfg &src) = delete;
+ rnp_cfg &operator=(const rnp_cfg &&src) = delete;
+ /** @brief destructor */
+ ~rnp_cfg();
+};
+
+#endif
diff --git a/src/rnpkeys/CMakeLists.txt b/src/rnpkeys/CMakeLists.txt
new file mode 100644
index 0000000..6302903
--- /dev/null
+++ b/src/rnpkeys/CMakeLists.txt
@@ -0,0 +1,101 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+if(MSVC)
+ # remove extra ${Configuration} subfolder
+ set(ArchiveOutputDir ${CMAKE_BINARY_DIR}\\src\\rnpkeys)
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_DEBUG ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_MINSIZEREL ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELEASE ${ArchiveOutputDir})
+ set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY_RELWITHDEBINFO ${ArchiveOutputDir})
+
+ set(RuntimeOutputDir ${CMAKE_BINARY_DIR}\\src\\rnpkeys)
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_MINSIZEREL ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${RuntimeOutputDir})
+ set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELWITHDEBINFO ${RuntimeOutputDir})
+
+ find_path(GETOPT_INCLUDE_DIR
+ NAMES getopt.h
+ )
+ find_library(GETOPT_LIBRARY
+ NAMES getopt
+ )
+ find_path(DIRENT_INCLUDE_DIR
+ NAMES dirent.h
+ )
+endif()
+
+# for the headers
+find_package(JSON-C 0.11 REQUIRED)
+
+add_executable(rnpkeys
+ rnpkeys.cpp
+ tui.cpp
+ main.cpp
+ ../rnp/rnpcfg.cpp
+ ../rnp/fficli.cpp
+)
+
+if(BUILD_SHARED_LIBS)
+ target_sources(rnpkeys PRIVATE ../lib/logging.cpp $<TARGET_OBJECTS:rnp-common>)
+endif(BUILD_SHARED_LIBS)
+
+target_include_directories(rnpkeys
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+ "${JSON-C_INCLUDE_DIRS}"
+)
+if(MSVC)
+ target_include_directories(rnpkeys
+ PRIVATE
+ "${GETOPT_INCLUDE_DIR}"
+ "${DIRENT_INCLUDE_DIR}"
+ )
+endif()
+
+target_link_libraries(rnpkeys
+ PRIVATE
+ librnp
+ JSON-C::JSON-C
+)
+if(MSVC)
+ target_link_libraries(rnpkeys
+ PRIVATE
+ "${GETOPT_LIBRARY}"
+ )
+endif()
+
+include(GNUInstallDirs)
+install(TARGETS rnpkeys
+ RUNTIME
+ DESTINATION "${CMAKE_INSTALL_BINDIR}"
+ COMPONENT cli
+)
+
+# Build and install man page
+if (ENABLE_DOC)
+ add_adoc_man("${CMAKE_CURRENT_SOURCE_DIR}/rnpkeys.1.adoc" ${RNP_VERSION})
+endif()
diff --git a/src/rnpkeys/main.cpp b/src/rnpkeys/main.cpp
new file mode 100644
index 0000000..8bcb7e1
--- /dev/null
+++ b/src/rnpkeys/main.cpp
@@ -0,0 +1,136 @@
+/*
+ * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* Command line program to perform rnp operations */
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <getopt.h>
+#endif
+#include <stdio.h>
+#include <string.h>
+#include "rnpkeys.h"
+
+extern struct option options[];
+extern const char * usage;
+
+optdefs_t
+get_short_cmd(int ch)
+{
+ switch (ch) {
+ case 'V':
+ return CMD_VERSION;
+ case 'g':
+ return CMD_GENERATE_KEY;
+ case 'l':
+ return CMD_LIST_KEYS;
+ case 'h':
+ [[fallthrough]];
+ default:
+ return CMD_HELP;
+ }
+}
+
+#ifndef RNP_RUN_TESTS
+int
+main(int argc, char **argv)
+#else
+int rnpkeys_main(int argc, char **argv);
+int
+rnpkeys_main(int argc, char **argv)
+#endif
+{
+ if (argc < 2) {
+ print_usage(usage);
+ return EXIT_FAILURE;
+ }
+
+ cli_rnp_t rnp = {};
+#if !defined(RNP_RUN_TESTS) && defined(_WIN32)
+ try {
+ rnp.substitute_args(&argc, &argv);
+ } catch (std::exception &ex) {
+ RNP_LOG("Error converting arguments ('%s')", ex.what());
+ return EXIT_FAILURE;
+ }
+#endif
+
+ rnp_cfg cfg;
+ optdefs_t cmd = CMD_NONE;
+ int optindex = 0;
+ int ch;
+
+ while ((ch = getopt_long(argc, argv, "Vglh", options, &optindex)) != -1) {
+ /* Check for unsupported command/option */
+ if (ch == '?') {
+ print_usage(usage);
+ return EXIT_FAILURE;
+ }
+
+ optdefs_t newcmd = cmd;
+ if (ch >= CMD_LIST_KEYS) {
+ if (!setoption(cfg, &newcmd, options[optindex].val, optarg)) {
+ ERR_MSG("Failed to process argument --%s", options[optindex].name);
+ return EXIT_FAILURE;
+ }
+ } else {
+ newcmd = get_short_cmd(ch);
+ }
+
+ if (cmd && newcmd != cmd) {
+ ERR_MSG("Conflicting commands!");
+ return EXIT_FAILURE;
+ }
+ cmd = newcmd;
+ }
+
+ /* No initialization required for these two commands. */
+ if (cmd == CMD_HELP || cmd == CMD_VERSION) {
+ return cli_rnp_t::ret_code(rnp_cmd(&rnp, cmd, NULL));
+ }
+
+ if (!rnpkeys_init(rnp, cfg)) {
+ return EXIT_FAILURE;
+ }
+
+ if (!cli_rnp_setup(&rnp)) {
+ return EXIT_FAILURE;
+ }
+
+ /* now do the required action for each of the command line args */
+ if (optind == argc) {
+ return cli_rnp_t::ret_code(rnp_cmd(&rnp, cmd, NULL));
+ }
+ bool success = true;
+ for (int i = optind; i < argc; i++) {
+ success = success && rnp_cmd(&rnp, cmd, argv[i]);
+ }
+ return cli_rnp_t::ret_code(success);
+}
diff --git a/src/rnpkeys/rnpkeys.1.adoc b/src/rnpkeys/rnpkeys.1.adoc
new file mode 100644
index 0000000..2b09d17
--- /dev/null
+++ b/src/rnpkeys/rnpkeys.1.adoc
@@ -0,0 +1,453 @@
+= rnpkeys(1)
+RNP
+:doctype: manpage
+:release-version: {component-version}
+:man manual: RNP Manual
+:man source: RNP {release-version}
+
+== NAME
+
+RNPKEYS - OpenPGP key management utility.
+
+== SYNOPSIS
+
+*rnpkeys* [_--homedir_ _dir_] [_OPTIONS_] _COMMAND_
+
+== DESCRIPTION
+
+The _rnpkeys_ command-line utility is part of the _RNP_ suite and
+provides OpenPGP key management functionality, including:
+
+* key listing;
+* key generation;
+* key import/export; and
+* key editing.
+
+
+=== BASICS
+
+By default, *rnp* will apply a _COMMAND_, additionally configured with _OPTIONS_,
+to all _INPUT_FILE_(s) or _stdin_ if no _INPUT_FILE_ is given.
+There are some special cases for _INPUT_FILE_ :
+
+* _-_ (dash) substitutes to _stdin_
+* env:VARIABLE_NAME substitutes to the contents of environment variable VARIABLE_NAME
+
+Depending on the input, output may be written:
+
+* to the specified file with a removed or added file extension (_.pgp_, _.asc_, _.sig_); or
+* to _stdout_.
+
+Without the *--armor* option, output will be in binary.
+
+If _COMMAND_ requires public or private keys, *rnp* will look for the keyrings in *~/.rnp*. The options *--homedir* and *--keyfile* override this (see below).
+
+If _COMMAND_ needs a password, *rnp* will ask for it via *stdin* or *tty*,
+unless the *--password* or *--pass-fd* option was specified.
+
+
+By default, *rnpkeys* will use keyrings stored in the _~/.rnp_ directory.
+
+This behavior may be overridden with the _--homedir_ option.
+
+If _COMMAND_ needs a password, the command will prompt the caller
+via _stdin_ or _tty_, unless the *--password* or *--pass-fd*
+options were also used.
+
+=== SPECIFYING KEYS
+
+Most *rnpkeys* commands require a key locator or a filter,
+representing one or more keys.
+
+It may be specified in one of the following ways:
+
+*userid*::
+Or just part of the *userid*.
+For *"Alice <alice@rnpgp.com>"*, the following methods are considered identical:
+
+** _alice_
+** _alice@rnpgp_
+** _rnpgp.com_
+
+*keyid*::
+Or its right-most 8 characters. With or without _0x_ at the beginning and spaces/tabs inside. Such as:
+
+** _0x725F6F2D6D5F6120_
+** _"725F6F2D 6D5F6120"_
+** _0x6D5F6120_
+
+*key fingerprint*: The 40-character key fingerprint, such as:
+
+** _"0x416E746F 6E537669 72696465 6E6B6F20"_
+
+
+
+== COMMANDS
+
+=== INFORMATIONAL
+
+*-h*, *--help*::
+Displays a short help message. No options are expected.
+
+*-V*, *--version*::
+Displays version information. No options are expected.
+
+*-l*, *--list-keys*::
+List out keys and some brief information about each. +
++
+Additional options:
+
+*--with-sigs*:::
+Additionally display signatures of listed keys.
+
+
+=== KEY GENERATION
+
+*-g*, *--generate-key*::
+Generate a new keypair. +
++
+Without additional options, an RSA primary key pair with an RSA sub-key pair will be generated, and prompting for the encryption password afterwards.
++
+Additional options:
+
+*--numbits*:::
+Overrides the default RSA key size of *2048* bits.
+
+*--expiration* _TIME_:::
+Set key and subkey expiration time, counting from the creation time. +
++
+By default generated keys do not expire. +
++
+Expiration time can be specified as:
+
+* expiration date in the ISO 8601:2019 date format (_yyyy-mm-dd_); or
+* hours/days/months/years since creation time with the syntax of _20h_/_30d_/_1m_/_1y_;
+* number of seconds.
+
+*--expert*:::
+Select key algorithms interactively and override default settings.
+
+*--userid*:::
+Specifies the _userid_ to be used in generation.
+
+*--hash*:::
+Specify the hash algorithm used in generation.
+
+*--cipher*:::
+Specify the encryption algorithm used in generation.
+
+*--s2k-iterations*:::
+Specify the number of iterations for the S2K (string-to-key) process. +
++
+This is used during the derivation of the symmetric key, which
+encrypts a secret key from the password. +
+
+*--s2k-msec*:::
+Specify that *rnpkeys* should automatically pick a
+*--s2k-iterations* value such that the single key derivation operation
+would take _NUMBER_ of milliseconds on the current system. +
++
+For example, setting it to _2000_ would mean that each secret key
+decryption operation would take around 2 seconds (on the current machine).
+
+
+=== KEY/SIGNATURE IMPORT
+
+*--import*, *--import-keys*, *--import-sigs*::
+Import keys or signatures. +
++
+While *rnpkeys* automatically detects the input data format,
+one may still wish to specify whether the input provides keys or signatures. +
++
+By default, the import process will stop on the first discovered
+erroneous key or signature. +
++
+Additional options:
+
+*--permissive*:::
+Skip errored or unsupported packets during the import process.
+
+=== KEY/SIGNATURE EXPORT
+
+*--export-key* [*--userid*=_FILTER_] [_FILTER_]::
+Export key(s). Only export keys that match _FILTER_ if _FILTER_ is given. +
++
+If filter matches a primary key, the subkeys of the primary key are also exported.
++
+By default, key data is written to _stdout_ in ASCII-armored format.
++
+Additional options:
+
+*--output* _PATH_:::
+Specifies output to be written to a file name instead of _stdout_.
+
+*--secret*:::
+Without this option specified, the command will only export public key(s).
+This option must be provided to export secret key(s).
+
+*--export-rev* _KEY_::
+Export the revocation signature for a specified secret key. +
++
+The revocation signature can be used later in a case of key loss or compromise.
++
+Additional options:
+
+*--rev-type*:::
+Specifies type of key revocation.
+
+*--rev-reason*:::
+Specifies reason for key revocation.
+
+
+=== KEY MANIPULATION
+
+*--revoke-key* _KEY_::
+Issue revocation signature for the secret key, and save it in the keyring. +
++
+Revoked keys cannot be used further. +
++
+Additional options:
+
+*--rev-type*:::
+Specifies type of key revocation, see *options* section for the available values.
+
+*--rev-reason*:::
+Specifies reason for key revocation.
+
+
+*--remove-key* _KEY_::
+Remove the specified key. +
++
+If a primary key is specified, then all of its subkeys are also removed. +
++
+If the specified key is a secret key, then it will not be deleted without
+confirmation.
++
+Additional options:
+
+*--force*:::
+Forces removal of a secret key without prompting the user.
+
+*--edit-key* _KEY_::
+Edit or update information, associated with a key. Should be accompanied with editing option. +
++
+Currently the following options are available: +
++
+*--add-subkey*:::
+Generate and add a new subkey to the existing primary key. All additional options for the
+*--generate-key* command apply for subkey generation as well, except *--userid*.
+
+*--check-cv25519-bits*:::
+Check whether least significant/most significant bits of Curve25519 ECDH subkey are correctly set.
+RNP internally sets those bits to required values (3 least significant bits and most significant bit must be zero) during decryption,
+however other implementations (GnuPG) may require those bits to be set in key material.
+_KEY_ must specify the exact subkey via keyid or fingerprint.
+
+*--fix-cv25519-bits*:::
+Set least significant/most significant bits of Curve25519 ECDH subkey to the correct values, and save a key.
+So later export of the key would ensure compatibility with other implementations (like GnuPG).
+This operation would require the password for your secret key.
+Since version 0.16.0 of RNP generated secret key is stored with bits set to a needed value,
+however, this may be needed to fix older keys or keys generated by other implementations.
+_KEY_ must specify the exact subkey via keyid or fingerprint.
+
+*--set-expire* _TIME_:::
+Set key expiration time. See the description of the *--expiration* option for possible time formats.
+Setting argument to 0 removes key expiration, the key would never expire. It is not recommended
+due to security reasons.
+
+=== OPTIONS
+
+*--homedir* _DIR_::
+Change homedir (where RNP looks for keyrings) to the specified value. +
++
+The default homedir is _~/.rnp_ .
+
+*--output* _PATH_::
+Write data processing related output to the file specified. +
++
+Combine it with *--overwrite* to overwrite file if it already exists.
+
+*--overwrite*::
+Overwrite output file if it already exists. +
++
+
+*--userid* _USERID_::
+Use the specified _userid_ during key generation and in some
+key-searching operations.
+
+*--numbits* _BITS_::
+Specify size in bits for the generated key and subkey. +
++
+_bits_ may be in range *1024*-*16384*, as long as the public key algorithm
+does not place additional limits.
+
+*--cipher* _ALGORITHM_::
+Set the key encryption algorithm. This is only used in key generation. +
++
+The default value is _AES256_.
+
+*--hash* _ALGORITHM_::
+Use the specified hash algorithm for signatures and derivation of the encrypting key from password for secret key encryption. +
++
+The default value is _SHA256_.
+
+*--expert*::
+Use the *expert key generation* mode, allowing the selection of
+key/subkey algorithms. +
++
+The following types of keys can be generated in this mode: +
++
+--
+** *DSA* key with *ElGamal* encryption subkey
+** *DSA* key with *RSA* subkey
+** *ECDSA* key with *ECDH* subkey
+** *EdDSA* key with *x25519* subkey
+** *SM2* key with subkey
+--
++
+Specifically, for *ECDSA* and *ECDH* the underlying curve can also be specified: +
++
+--
+** _NIST P-256_, _NIST P-384_, _NIST P-521_
+** _brainpoolP256r1_, _brainpoolP384r1_, _brainpoolP512r1_
+** _secp256k1_
+--
+
+*--pass-fd* _FD_::
+Specify a file descriptor to read passwords from instead of from _stdin_/_tty_. +
++
+Useful for automated or non-interactive sessions.
+
+*--password* _PASSWORD_::
+Use the specified password when it is needed. +
++
+WARNING: Not recommended for production use due to potential security issues.
+Use *--pass-fd* for batch operations instead.
+
+*--with-sigs*::
+Print signature information when listing keys via the *-l* command.
+
+*--force*::
+Force actions to happen without prompting the user. +
++
+This applies to cases such as secret key removal, revoking an already revoked key and so on.
+
+*--permissive*::
+Skip malformed or unknown keys/signatures during key import. +
++
+By default, *rnpkeys* will stop on the first erroring packet
+and exit with an error.
+
+*--rev-type* _TYPE_::
+Use the specified type during revocation signature generation instead of the default _0_. +
++
+The following values are supported: +
++
+--
+** 0, or "no": no revocation type specified.
+** 1, or "superseded": key was superseded with another key.
+** 2, or "compromised": key was compromised and no longer valid.
+** 3, or "retired": key is retired.
+--
++
+Please refer to *IETF RFC 4880* for details.
+
+*--rev-reason* _REASON_::
+Add the specified human-readable revocation _REASON_ to the
+signature instead of an empty string.
+
+*--s2k-iterations* _NUMBER_::
+Specify the number of iterations for the S2K (string-to-key) process. +
++
+This is used during the derivation of the symmetric key, which
+encrypts a secret key from the password. +
++
+Please refer to IETF RFC 4880 for further details.
+
+*--s2k-msec* _NUMBER_::
+Specify that *rnpkeys* should automatically pick a
+*--s2k-iterations* value such that the single key derivation operation
+would take _NUMBER_ of milliseconds on the current system. +
++
+For example, setting it to _2000_ would mean that each secret key
+decryption operation would take around 2 seconds (on the current machine).
+
+*--notty*::
+Disable use of tty. +
++
+By default RNP would detect whether TTY is attached and use it for user prompts. +
++
+This option overrides default behaviour so user input may be passed in batch mode.
+
+*--current-time* _TIME_::
+Override system's time with a specified value. +
++
+By default RNP uses system's time in all signature/key checks, however in some scenarios it could be needed to override this. +
++
+*TIME* could be specified in the ISO 8601-1:2019 date format (_yyyy-mm-dd_), or in the UNIX timestamp format.
+
+== EXIT STATUS
+
+_0_::
+ Success.
+
+_Non-zero_::
+ Failure.
+
+== EXAMPLES
+
+The following examples demonstrate method of usage of the _rnpkeys_ command.
+
+=== EXAMPLE 1: IMPORT EXISTING KEYS FROM THE GNUPG
+
+Following oneliner may be used to import all public keys from the GnuPG:
+
+*gpg* *-a* *--export* | *rnpkeys* *--import* _-_
+
+To import all secret keys the following command should be used (please note, that you'll be asked for secret key password(s)):
+
+*gpg* *-a* *--export-secret-keys* | *rnpkeys* *--import* _-_
+
+=== EXAMPLE 2: GENERATE A NEW KEY
+
+This example generates a new key with specified userid and expiration.
+Also it enables "expert" mode, allowing the selection of key/subkey algorithms.
+
+*rnpkeys* *--generate* *--userid* *"john@doe.com"* *--expert* *--expiration* *1y*
+
+== BUGS
+
+Please report _issues_ via the RNP public issue tracker at:
+https://github.com/rnpgp/rnp/issues.
+
+_Security reports_ or _security-sensitive feedback_ should be reported
+according to the instructions at:
+https://www.rnpgp.org/feedback.
+
+
+== AUTHORS
+
+*RNP* is an open source project led by Ribose and has
+received contributions from numerous individuals and
+organizations.
+
+
+== RESOURCES
+
+*Web site*: https://www.rnpgp.org
+
+*Source repository*: https://github.com/rnpgp/rnp
+
+
+== COPYING
+
+Copyright \(C) 2017-2021 Ribose.
+The RNP software suite is _freely licensed_:
+please refer to the *LICENSE* file for details.
+
+
+
+== SEE ALSO
+
+*rnp(1)*, *librnp(3)*
diff --git a/src/rnpkeys/rnpkeys.cpp b/src/rnpkeys/rnpkeys.cpp
new file mode 100644
index 0000000..1a6997c
--- /dev/null
+++ b/src/rnpkeys/rnpkeys.cpp
@@ -0,0 +1,648 @@
+/*
+ * Copyright (c) 2017-2021, [Ribose Inc](https://www.ribose.com).
+ * Copyright (c) 2009 The NetBSD Foundation, Inc.
+ * All rights reserved.
+ *
+ * This code is originally derived from software contributed to
+ * The NetBSD Foundation by Alistair Crooks (agc@netbsd.org), and
+ * carried further by Ribose Inc (https://www.ribose.com).
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+ * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* Command line program to perform rnp operations */
+
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <getopt.h>
+#endif
+#include <string.h>
+#include <stdarg.h>
+#include "rnpkeys.h"
+#include "str-utils.h"
+#include <set>
+
+const char *usage =
+ "Manipulate OpenPGP keys and keyrings.\n"
+ "Usage: rnpkeys --command [options] [files]\n"
+ "Commands:\n"
+ " -h, --help This help message.\n"
+ " -V, --version Print RNP version information.\n"
+ " -g, --generate-key Generate a new keypair (default is RSA).\n"
+ " --userid Specify key's userid.\n"
+ " --expert Select key type, size, and additional parameters.\n"
+ " --numbits Override default key size (2048).\n"
+ " --expiration Set key and subkey expiration time.\n"
+ " --cipher Set cipher used to encrypt a secret key.\n"
+ " --hash Set hash which is used for key derivation.\n"
+ " --allow-weak-hash Allow usage of a weak hash algorithm.\n"
+ " -l, --list-keys List keys in the keyrings.\n"
+ " --secret List secret keys instead of public ones.\n"
+ " --with-sigs List signatures as well.\n"
+ " --import Import keys or signatures.\n"
+ " --import-keys Import keys.\n"
+ " --import-sigs Import signatures.\n"
+ " --permissive Skip erroring keys/sigs instead of failing.\n"
+ " --export-key Export a key.\n"
+ " --secret Export a secret key instead of a public.\n"
+ " --export-rev Export a key's revocation.\n"
+ " --rev-type Set revocation type.\n"
+ " --rev-reason Human-readable reason for revocation.\n"
+ " --revoke-key Revoke a key specified.\n"
+ " --remove-key Remove a key specified.\n"
+ " --edit-key Edit key properties.\n"
+ " --add-subkey Add new subkey.\n"
+ " --check-cv25519-bits Check whether Cv25519 subkey bits are correct.\n"
+ " --fix-cv25519-bits Fix Cv25519 subkey bits.\n"
+ " --set-expire Set key expiration time.\n"
+ "\n"
+ "Other options:\n"
+ " --homedir Override home directory (default is ~/.rnp/).\n"
+ " --password Password, which should be used during operation.\n"
+ " --pass-fd Read password(s) from the file descriptor.\n"
+ " --force Force operation (like secret key removal).\n"
+ " --output [file, -] Write data to the specified file or stdout.\n"
+ " --overwrite Overwrite output file without a prompt.\n"
+ " --notty Do not write anything to the TTY.\n"
+ " --current-time Override system's time.\n"
+ "\n"
+ "See man page for a detailed listing and explanation.\n"
+ "\n";
+
+struct option options[] = {
+ /* key-management commands */
+ {"list-keys", no_argument, NULL, CMD_LIST_KEYS},
+ {"export", no_argument, NULL, CMD_EXPORT_KEY},
+ {"export-key", optional_argument, NULL, CMD_EXPORT_KEY},
+ {"import", no_argument, NULL, CMD_IMPORT},
+ {"import-key", no_argument, NULL, CMD_IMPORT_KEYS},
+ {"import-keys", no_argument, NULL, CMD_IMPORT_KEYS},
+ {"import-sigs", no_argument, NULL, CMD_IMPORT_SIGS},
+ {"gen", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"gen-key", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"generate", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"generate-key", optional_argument, NULL, CMD_GENERATE_KEY},
+ {"export-rev", no_argument, NULL, CMD_EXPORT_REV},
+ {"export-revocation", no_argument, NULL, CMD_EXPORT_REV},
+ {"revoke-key", no_argument, NULL, CMD_REVOKE_KEY},
+ {"remove-key", no_argument, NULL, CMD_REMOVE_KEY},
+ {"edit-key", no_argument, NULL, CMD_EDIT_KEY},
+ /* debugging commands */
+ {"help", no_argument, NULL, CMD_HELP},
+ {"version", no_argument, NULL, CMD_VERSION},
+ {"debug", required_argument, NULL, OPT_DEBUG},
+ /* options */
+ {"coredumps", no_argument, NULL, OPT_COREDUMPS},
+ {"keystore-format", required_argument, NULL, OPT_KEY_STORE_FORMAT},
+ {"userid", required_argument, NULL, OPT_USERID},
+ {"with-sigs", no_argument, NULL, OPT_WITH_SIGS},
+ {"hash", required_argument, NULL, OPT_HASH_ALG},
+ {"home", required_argument, NULL, OPT_HOMEDIR},
+ {"homedir", required_argument, NULL, OPT_HOMEDIR},
+ {"numbits", required_argument, NULL, OPT_NUMBITS},
+ {"s2k-iterations", required_argument, NULL, OPT_S2K_ITER},
+ {"s2k-msec", required_argument, NULL, OPT_S2K_MSEC},
+ {"expiration", required_argument, NULL, OPT_EXPIRATION},
+ {"pass-fd", required_argument, NULL, OPT_PASSWDFD},
+ {"password", required_argument, NULL, OPT_PASSWD},
+ {"results", required_argument, NULL, OPT_RESULTS},
+ {"cipher", required_argument, NULL, OPT_CIPHER},
+ {"expert", no_argument, NULL, OPT_EXPERT},
+ {"output", required_argument, NULL, OPT_OUTPUT},
+ {"overwrite", no_argument, NULL, OPT_OVERWRITE},
+ {"force", no_argument, NULL, OPT_FORCE},
+ {"secret", no_argument, NULL, OPT_SECRET},
+ {"rev-type", required_argument, NULL, OPT_REV_TYPE},
+ {"rev-reason", required_argument, NULL, OPT_REV_REASON},
+ {"permissive", no_argument, NULL, OPT_PERMISSIVE},
+ {"notty", no_argument, NULL, OPT_NOTTY},
+ {"fix-cv25519-bits", no_argument, NULL, OPT_FIX_25519_BITS},
+ {"check-cv25519-bits", no_argument, NULL, OPT_CHK_25519_BITS},
+ {"add-subkey", no_argument, NULL, OPT_ADD_SUBKEY},
+ {"set-expire", required_argument, NULL, OPT_SET_EXPIRE},
+ {"current-time", required_argument, NULL, OPT_CURTIME},
+ {"allow-weak-hash", no_argument, NULL, OPT_ALLOW_WEAK_HASH},
+ {NULL, 0, NULL, 0},
+};
+
+/* list keys */
+static bool
+print_keys_info(cli_rnp_t *rnp, FILE *fp, const char *filter)
+{
+ bool psecret = rnp->cfg().get_bool(CFG_SECRET);
+ bool psigs = rnp->cfg().get_bool(CFG_WITH_SIGS);
+ int flags = CLI_SEARCH_SUBKEYS_AFTER | (psecret ? CLI_SEARCH_SECRET : 0);
+ std::vector<rnp_key_handle_t> keys;
+
+ if (!cli_rnp_keys_matching_string(rnp, keys, filter ? filter : "", flags)) {
+ fprintf(fp, "Key(s) not found.\n");
+ return false;
+ }
+ fprintf(fp, "%d key%s found\n", (int) keys.size(), (keys.size() == 1) ? "" : "s");
+ for (auto key : keys) {
+ cli_rnp_print_key_info(fp, rnp->ffi, key, psecret, psigs);
+ }
+
+ fprintf(fp, "\n");
+ /* clean up */
+ clear_key_handles(keys);
+ return true;
+}
+
+static bool
+import_keys(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname)
+{
+ std::set<std::string> new_pub_keys;
+ std::set<std::string> new_sec_keys;
+ std::set<std::string> updated_keys;
+ bool res = false;
+ bool updated = false;
+ size_t unchanged_keys = 0;
+ size_t processed_keys = 0;
+
+ uint32_t flags = RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS |
+ RNP_LOAD_SAVE_SINGLE | RNP_LOAD_SAVE_BASE64;
+
+ bool permissive = rnp->cfg().get_bool(CFG_PERMISSIVE);
+ if (permissive) {
+ flags |= RNP_LOAD_SAVE_PERMISSIVE;
+ }
+
+ do {
+ /* load keys one-by-one */
+ char * results = NULL;
+ rnp_result_t ret = rnp_import_keys(rnp->ffi, input, flags, &results);
+ if (ret == RNP_ERROR_EOF) {
+ res = true;
+ break;
+ }
+ if (ret && updated) {
+ /* some keys were imported, but then error occurred */
+ ERR_MSG("warning: not all data was processed.");
+ res = true;
+ break;
+ }
+ if (ret) {
+ ERR_MSG("failed to import key(s) from %s, stopping.", inname.c_str());
+ break;
+ }
+
+ // print information about imported key(s)
+ json_object *jso = json_tokener_parse(results);
+ rnp_buffer_destroy(results);
+ if (!jso) {
+ ERR_MSG("invalid key import resulting JSON");
+ break;
+ }
+ json_object *keys = NULL;
+ if (!json_object_object_get_ex(jso, "keys", &keys)) {
+ ERR_MSG("invalid key import JSON contents");
+ json_object_put(jso);
+ break;
+ }
+ processed_keys += json_object_array_length(keys);
+ for (size_t idx = 0; idx < (size_t) json_object_array_length(keys); idx++) {
+ json_object * keyinfo = json_object_array_get_idx(keys, idx);
+ rnp_key_handle_t key = NULL;
+ if (!keyinfo) {
+ continue;
+ }
+ std::string pub_status = json_obj_get_str(keyinfo, "public");
+ std::string sec_status = json_obj_get_str(keyinfo, "secret");
+ const char *fphex = json_obj_get_str(keyinfo, "fingerprint");
+
+ if (pub_status == "new") {
+ new_pub_keys.insert(fphex);
+ updated = true;
+ }
+ if (sec_status == "new") {
+ new_sec_keys.insert(fphex);
+ updated = true;
+ }
+ if (pub_status == "updated" || sec_status == "updated") {
+ updated_keys.insert(fphex);
+ updated = true;
+ }
+ if (pub_status == "unchanged" || sec_status == "unchanged") {
+ if (!new_pub_keys.count(fphex) && !new_sec_keys.count(fphex) &&
+ !updated_keys.count(fphex)) {
+ unchanged_keys++;
+ continue;
+ }
+ }
+ if (rnp_locate_key(rnp->ffi, "fingerprint", fphex, &key) || !key) {
+ ERR_MSG("failed to locate key with fingerprint %s", fphex);
+ continue;
+ }
+ cli_rnp_print_key_info(stdout, rnp->ffi, key, true, false);
+ rnp_key_handle_destroy(key);
+ }
+ json_object_put(jso);
+ } while (1);
+
+ // print statistics
+ ERR_MSG("Import finished: %lu key%s processed, %lu new public keys, %lu new secret keys, "
+ "%lu updated, %lu unchanged.",
+ processed_keys,
+ (processed_keys != 1) ? "s" : "",
+ new_pub_keys.size(),
+ new_sec_keys.size(),
+ updated_keys.size(),
+ unchanged_keys);
+
+ if (updated) {
+ // set default key if we didn't have one
+ if (rnp->defkey().empty()) {
+ rnp->set_defkey();
+ }
+
+ // save public and secret keyrings
+ if (!cli_rnp_save_keyrings(rnp)) {
+ ERR_MSG("failed to save keyrings");
+ }
+ }
+ return res;
+}
+
+static bool
+import_sigs(cli_rnp_t *rnp, rnp_input_t input, const std::string &inname)
+{
+ bool res = false;
+ char * results = NULL;
+ json_object *jso = NULL;
+ json_object *sigs = NULL;
+ int unknown_sigs = 0;
+ int new_sigs = 0;
+ int old_sigs = 0;
+
+ if (rnp_import_signatures(rnp->ffi, input, 0, &results)) {
+ ERR_MSG("Failed to import signatures from %s", inname.c_str());
+ goto done;
+ }
+ // print information about imported signature(s)
+ jso = json_tokener_parse(results);
+ if (!jso || !json_object_object_get_ex(jso, "sigs", &sigs)) {
+ ERR_MSG("Invalid signature import result");
+ goto done;
+ }
+
+ for (size_t idx = 0; idx < (size_t) json_object_array_length(sigs); idx++) {
+ json_object *siginfo = json_object_array_get_idx(sigs, idx);
+ if (!siginfo) {
+ continue;
+ }
+ const char *status = json_obj_get_str(siginfo, "public");
+ std::string pub_status = status ? status : "unknown";
+ status = json_obj_get_str(siginfo, "secret");
+ std::string sec_status = status ? status : "unknown";
+
+ if ((pub_status == "new") || (sec_status == "new")) {
+ new_sigs++;
+ } else if ((pub_status == "unchanged") || (sec_status == "unchanged")) {
+ old_sigs++;
+ } else {
+ unknown_sigs++;
+ }
+ }
+
+ // print status information
+ ERR_MSG("Import finished: %d new signature%s, %d unchanged, %d unknown.",
+ new_sigs,
+ (new_sigs != 1) ? "s" : "",
+ old_sigs,
+ unknown_sigs);
+
+ // save public and secret keyrings
+ if ((new_sigs > 0) && !cli_rnp_save_keyrings(rnp)) {
+ ERR_MSG("Failed to save keyrings");
+ goto done;
+ }
+ res = true;
+done:
+ json_object_put(jso);
+ rnp_buffer_destroy(results);
+ return res;
+}
+
+static bool
+import(cli_rnp_t *rnp, const std::string &spec, int cmd)
+{
+ if (spec.empty()) {
+ ERR_MSG("Import path isn't specified");
+ return false;
+ }
+ rnp_input_t input = cli_rnp_input_from_specifier(*rnp, spec, NULL);
+ if (!input) {
+ ERR_MSG("Failed to create input for %s", spec.c_str());
+ return false;
+ }
+ if (cmd == CMD_IMPORT) {
+ char *contents = NULL;
+ if (rnp_guess_contents(input, &contents)) {
+ ERR_MSG("Warning! Failed to guess content type to import. Assuming keys.");
+ }
+ cmd = (contents && !strcmp(contents, "signature")) ? CMD_IMPORT_SIGS : CMD_IMPORT_KEYS;
+ rnp_buffer_destroy(contents);
+ }
+
+ bool res = false;
+ switch (cmd) {
+ case CMD_IMPORT_KEYS:
+ res = import_keys(rnp, input, spec);
+ break;
+ case CMD_IMPORT_SIGS:
+ res = import_sigs(rnp, input, spec);
+ break;
+ default:
+ ERR_MSG("Unexpected command: %d", cmd);
+ }
+ rnp_input_destroy(input);
+ return res;
+}
+
+/* print a usage message */
+void
+print_usage(const char *usagemsg)
+{
+ cli_rnp_print_praise();
+ puts(usagemsg);
+}
+
+/* do a command once for a specified file 'f' */
+bool
+rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f)
+{
+ std::string fs;
+
+ switch (cmd) {
+ case CMD_LIST_KEYS:
+ if (!f && rnp->cfg().get_count(CFG_USERID)) {
+ fs = rnp->cfg().get_str(CFG_USERID, 0);
+ f = fs.c_str();
+ }
+ return print_keys_info(rnp, stdout, f);
+ case CMD_EXPORT_KEY: {
+ if (!f && rnp->cfg().get_count(CFG_USERID)) {
+ fs = rnp->cfg().get_str(CFG_USERID, 0);
+ f = fs.c_str();
+ }
+ if (!f) {
+ ERR_MSG("No key specified.");
+ return 0;
+ }
+ return cli_rnp_export_keys(rnp, f);
+ }
+ case CMD_IMPORT:
+ case CMD_IMPORT_KEYS:
+ case CMD_IMPORT_SIGS:
+ return import(rnp, f ? f : "", cmd);
+ case CMD_GENERATE_KEY: {
+ if (!f) {
+ size_t count = rnp->cfg().get_count(CFG_USERID);
+ if (count == 1) {
+ fs = rnp->cfg().get_str(CFG_USERID, 0);
+ f = fs.c_str();
+ } else if (count > 1) {
+ ERR_MSG("Only single userid is supported for generated keys");
+ return false;
+ }
+ }
+ return cli_rnp_generate_key(rnp, f);
+ }
+ case CMD_EXPORT_REV: {
+ if (!f) {
+ ERR_MSG("You need to specify key to generate revocation for.");
+ return false;
+ }
+ return cli_rnp_export_revocation(rnp, f);
+ }
+ case CMD_REVOKE_KEY: {
+ if (!f) {
+ ERR_MSG("You need to specify key or subkey to revoke.");
+ return false;
+ }
+ return cli_rnp_revoke_key(rnp, f);
+ }
+ case CMD_REMOVE_KEY: {
+ if (!f) {
+ ERR_MSG("You need to specify key or subkey to remove.");
+ return false;
+ }
+ return cli_rnp_remove_key(rnp, f);
+ }
+ case CMD_EDIT_KEY: {
+ if (!f) {
+ ERR_MSG("You need to specify a key or subkey to edit.");
+ return false;
+ }
+ return rnp->edit_key(f);
+ }
+ case CMD_VERSION:
+ cli_rnp_print_praise();
+ return true;
+ case CMD_HELP:
+ default:
+ print_usage(usage);
+ return true;
+ }
+}
+
+/* set the option */
+bool
+setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg)
+{
+ switch (val) {
+ case OPT_COREDUMPS:
+#ifdef _WIN32
+ ERR_MSG("warning: --coredumps doesn't make sense on windows systems.");
+#endif
+ cfg.set_bool(CFG_COREDUMPS, true);
+ return true;
+ case CMD_GENERATE_KEY:
+ cfg.set_bool(CFG_NEEDSSECKEY, true);
+ *cmd = (optdefs_t) val;
+ return true;
+ case OPT_EXPERT:
+ cfg.set_bool(CFG_EXPERT, true);
+ return true;
+ case CMD_LIST_KEYS:
+ case CMD_EXPORT_KEY:
+ case CMD_EXPORT_REV:
+ case CMD_REVOKE_KEY:
+ case CMD_REMOVE_KEY:
+ case CMD_EDIT_KEY:
+ case CMD_IMPORT:
+ case CMD_IMPORT_KEYS:
+ case CMD_IMPORT_SIGS:
+ case CMD_HELP:
+ case CMD_VERSION:
+ *cmd = (optdefs_t) val;
+ return true;
+ /* options */
+ case OPT_KEY_STORE_FORMAT:
+ cfg.set_str(CFG_KEYSTOREFMT, arg);
+ return true;
+ case OPT_USERID:
+ cfg.add_str(CFG_USERID, arg);
+ return true;
+ case OPT_HOMEDIR:
+ cfg.set_str(CFG_HOMEDIR, arg);
+ return true;
+ case OPT_NUMBITS: {
+ int bits = 0;
+ if (!rnp::str_to_int(arg, bits) || (bits < 1024) || (bits > 16384)) {
+ ERR_MSG("wrong bits value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_NUMBITS, bits);
+ return true;
+ }
+ case OPT_ALLOW_WEAK_HASH:
+ cfg.set_bool(CFG_WEAK_HASH, true);
+ return true;
+ case OPT_HASH_ALG:
+ return cli_rnp_set_hash(cfg, arg);
+ case OPT_S2K_ITER: {
+ int iterations = atoi(arg);
+ if (!iterations) {
+ ERR_MSG("Wrong iterations value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_S2K_ITER, iterations);
+ return true;
+ }
+ case OPT_EXPIRATION:
+ cfg.set_str(CFG_KG_PRIMARY_EXPIRATION, arg);
+ cfg.set_str(CFG_KG_SUBKEY_EXPIRATION, arg);
+ return true;
+ case OPT_S2K_MSEC: {
+ int msec = 0;
+ if (!rnp::str_to_int(arg, msec) || !msec) {
+ ERR_MSG("Invalid s2k msec value: %s", arg);
+ return false;
+ }
+ cfg.set_int(CFG_S2K_MSEC, msec);
+ return true;
+ }
+ case OPT_PASSWDFD:
+ cfg.set_str(CFG_PASSFD, arg);
+ return true;
+ case OPT_PASSWD:
+ cfg.set_str(CFG_PASSWD, arg);
+ return true;
+ case OPT_RESULTS:
+ cfg.set_str(CFG_IO_RESS, arg);
+ return true;
+ case OPT_CIPHER:
+ return cli_rnp_set_cipher(cfg, arg);
+ case OPT_DEBUG:
+ ERR_MSG("Option --debug is deprecated, ignoring.");
+ return true;
+ case OPT_OUTPUT:
+ if (!arg) {
+ ERR_MSG("No output filename argument provided");
+ return false;
+ }
+ cfg.set_str(CFG_OUTFILE, arg);
+ return true;
+ case OPT_OVERWRITE:
+ cfg.set_bool(CFG_OVERWRITE, true);
+ return true;
+ case OPT_FORCE:
+ cfg.set_bool(CFG_FORCE, true);
+ return true;
+ case OPT_SECRET:
+ cfg.set_bool(CFG_SECRET, true);
+ return true;
+ case OPT_WITH_SIGS:
+ cfg.set_bool(CFG_WITH_SIGS, true);
+ return true;
+ case OPT_REV_TYPE: {
+ std::string revtype = arg;
+ if (revtype == "0") {
+ revtype = "no";
+ } else if (revtype == "1") {
+ revtype = "superseded";
+ } else if (revtype == "2") {
+ revtype = "compromised";
+ } else if (revtype == "3") {
+ revtype = "retired";
+ }
+ cfg.set_str(CFG_REV_TYPE, revtype);
+ return true;
+ }
+ case OPT_REV_REASON:
+ cfg.set_str(CFG_REV_REASON, arg);
+ return true;
+ case OPT_PERMISSIVE:
+ cfg.set_bool(CFG_PERMISSIVE, true);
+ return true;
+ case OPT_NOTTY:
+ cfg.set_bool(CFG_NOTTY, true);
+ return true;
+ case OPT_FIX_25519_BITS:
+ cfg.set_bool(CFG_FIX_25519_BITS, true);
+ return true;
+ case OPT_CHK_25519_BITS:
+ cfg.set_bool(CFG_CHK_25519_BITS, true);
+ return true;
+ case OPT_CURTIME:
+ cfg.set_str(CFG_CURTIME, arg);
+ return true;
+ case OPT_ADD_SUBKEY:
+ cfg.set_bool(CFG_ADD_SUBKEY, true);
+ return true;
+ case OPT_SET_EXPIRE:
+ cfg.set_str(CFG_SET_KEY_EXPIRE, arg);
+ return true;
+ default:
+ *cmd = CMD_HELP;
+ return true;
+ }
+}
+
+bool
+rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg)
+{
+ rnp_cfg rnpcfg;
+ rnpcfg.load_defaults();
+ rnpcfg.set_int(CFG_NUMBITS, DEFAULT_RSA_NUMBITS);
+ rnpcfg.set_str(CFG_IO_RESS, "<stdout>");
+ rnpcfg.copy(cfg);
+
+ if (!cli_cfg_set_keystore_info(rnpcfg)) {
+ ERR_MSG("fatal: cannot set keystore info");
+ return false;
+ }
+ if (!rnp.init(rnpcfg)) {
+ ERR_MSG("fatal: failed to initialize rnpkeys");
+ return false;
+ }
+ if (!cli_rnp_check_weak_hash(&rnp)) {
+ ERR_MSG("Weak hash algorithm detected. Pass --allow-weak-hash option if you really "
+ "want to use it.");
+ return false;
+ }
+ /* TODO: at some point we should check for error here */
+ (void) rnp.load_keyrings(true);
+ return true;
+}
diff --git a/src/rnpkeys/rnpkeys.h b/src/rnpkeys/rnpkeys.h
new file mode 100644
index 0000000..611fcc1
--- /dev/null
+++ b/src/rnpkeys/rnpkeys.h
@@ -0,0 +1,80 @@
+#ifndef RNPKEYS_H_
+#define RNPKEYS_H_
+
+#include <stdbool.h>
+#ifdef HAVE_SYS_PARAM_H
+#include <sys/param.h>
+#else
+#include "uniwin.h"
+#endif
+#include "../rnp/fficli.h"
+#include "logging.h"
+
+#define DEFAULT_RSA_NUMBITS 2048
+
+typedef enum {
+ /* commands */
+ CMD_NONE = 0,
+ CMD_LIST_KEYS = 260,
+ CMD_EXPORT_KEY,
+ CMD_IMPORT,
+ CMD_IMPORT_KEYS,
+ CMD_IMPORT_SIGS,
+ CMD_GENERATE_KEY,
+ CMD_EXPORT_REV,
+ CMD_REVOKE_KEY,
+ CMD_REMOVE_KEY,
+ CMD_EDIT_KEY,
+ CMD_VERSION,
+ CMD_HELP,
+
+ /* options */
+ OPT_KEY_STORE_FORMAT,
+ OPT_USERID,
+ OPT_HOMEDIR,
+ OPT_NUMBITS,
+ OPT_ALLOW_WEAK_HASH,
+ OPT_HASH_ALG,
+ OPT_COREDUMPS,
+ OPT_PASSWDFD,
+ OPT_PASSWD,
+ OPT_RESULTS,
+ OPT_CIPHER,
+ OPT_EXPERT,
+ OPT_OUTPUT,
+ OPT_OVERWRITE,
+ OPT_FORCE,
+ OPT_SECRET,
+ OPT_S2K_ITER,
+ OPT_S2K_MSEC,
+ OPT_EXPIRATION,
+ OPT_WITH_SIGS,
+ OPT_REV_TYPE,
+ OPT_REV_REASON,
+ OPT_PERMISSIVE,
+ OPT_NOTTY,
+ OPT_FIX_25519_BITS,
+ OPT_CHK_25519_BITS,
+ OPT_CURTIME,
+ OPT_ADD_SUBKEY,
+ OPT_SET_EXPIRE,
+
+ /* debug */
+ OPT_DEBUG
+} optdefs_t;
+
+bool rnp_cmd(cli_rnp_t *rnp, optdefs_t cmd, const char *f);
+bool setoption(rnp_cfg &cfg, optdefs_t *cmd, int val, const char *arg);
+void print_usage(const char *usagemsg);
+
+/**
+ * @brief Initializes rnpkeys. Function allocates memory dynamically for
+ * rnp argument, which must be freed by the caller.
+ *
+ * @param rnp initialized rnp context
+ * @param cfg configuration with settings from command line
+ * @return true on success, or false otherwise.
+ */
+bool rnpkeys_init(cli_rnp_t &rnp, const rnp_cfg &cfg);
+
+#endif /* _rnpkeys_ */
diff --git a/src/rnpkeys/tui.cpp b/src/rnpkeys/tui.cpp
new file mode 100644
index 0000000..73f26dc
--- /dev/null
+++ b/src/rnpkeys/tui.cpp
@@ -0,0 +1,413 @@
+/*
+ * Copyright (c) 2017-2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+#ifdef _MSC_VER
+#include "uniwin.h"
+#else
+#include <unistd.h>
+#endif
+#include <errno.h>
+#include <iterator>
+#include "rnp/rnpcfg.h"
+#include "rnpkeys.h"
+#include "defaults.h"
+#include "file-utils.h"
+#include "logging.h"
+
+/* -----------------------------------------------------------------------------
+ * @brief Reads input from file pointer and converts it securelly to ints
+ * Partially based on ERR34-C from SEI CERT C Coding Standard
+ *
+ * @param fp pointer to opened pipe
+ * @param result[out] result read from file pointer and converted to int
+ *
+ * @returns true and value in result if integer was parsed correctly,
+ * otherwise false
+ *
+-------------------------------------------------------------------------------- */
+static bool
+rnp_secure_get_long_from_fd(FILE *fp, long &result, bool allow_empty = true)
+{
+ char buff[BUFSIZ];
+ if (!fgets(buff, sizeof(buff), fp)) {
+ RNP_LOG("EOF or read error");
+ return false;
+ }
+
+ errno = 0;
+ char *end_ptr = NULL;
+ long num_long = strtol(buff, &end_ptr, 10);
+ if (ERANGE == errno) {
+ RNP_LOG("Number out of range");
+ return false;
+ }
+ if (end_ptr == buff) {
+ return allow_empty;
+ }
+ if ('\n' != *end_ptr && '\0' != *end_ptr) {
+ RNP_LOG("Unexpected end of line");
+ return false;
+ }
+
+ result = num_long;
+ return true;
+}
+
+static bool
+is_rsa_keysize_supported(uint32_t keysize)
+{
+ return ((keysize >= 1024) && (keysize <= 4096) && !(keysize % 8));
+}
+
+static const char *
+ask_curve_name(FILE *input_fp)
+{
+ std::vector<const char *> curves;
+ static const char *const known_curves[] = {
+ "NIST P-256",
+ "NIST P-384",
+ "NIST P-521",
+ "brainpoolP256r1",
+ "brainpoolP384r1",
+ "brainpoolP512r1",
+ "secp256k1",
+ };
+ const size_t curvenum = sizeof(known_curves) / sizeof(*known_curves);
+
+ try {
+ std::copy_if(known_curves,
+ known_curves + curvenum,
+ std::back_inserter(curves),
+ [](const char *curve) {
+ bool supported = false;
+ return !rnp_supports_feature(RNP_FEATURE_CURVE, curve, &supported) &&
+ supported;
+ });
+ } catch (const std::exception &e) {
+ RNP_LOG("%s", e.what());
+ return NULL;
+ }
+ const size_t ccount = curves.size();
+ if (!ccount) {
+ return NULL;
+ }
+ bool ok = false;
+ const char *result = NULL;
+ int attempts = 0;
+ do {
+ if (attempts >= 10) {
+ printf("Too many attempts. Aborting.\n");
+ return NULL;
+ }
+ printf("Please select which elliptic curve you want:\n");
+ for (size_t i = 0; i < ccount; i++) {
+ printf("\t(%zu) %s\n", i + 1, curves[i]);
+ }
+ printf("(default %s)> ", DEFAULT_CURVE);
+ long val = 0;
+ ok = rnp_secure_get_long_from_fd(input_fp, val) && (val > 0) && (val <= (long) ccount);
+ if (ok) {
+ result = curves[val - 1];
+ }
+ attempts++;
+ } while (!ok);
+
+ return result;
+}
+
+static long
+ask_rsa_bitlen(FILE *input_fp)
+{
+ long result = 0;
+ do {
+ result = DEFAULT_RSA_NUMBITS;
+ printf("Please provide bit length of the key (between 1024 and 4096):\n(default %d)> ",
+ DEFAULT_RSA_NUMBITS);
+ } while (!rnp_secure_get_long_from_fd(input_fp, result) ||
+ !is_rsa_keysize_supported(result));
+ return result;
+}
+
+static long
+ask_elgamal_bitlen(FILE *input_fp)
+{
+ do {
+ printf(
+ "Please provide bit length of the ElGamal key (between %d and %d):\n(default %d) > ",
+ ELGAMAL_MIN_P_BITLEN,
+ ELGAMAL_MAX_P_BITLEN,
+ DEFAULT_ELGAMAL_NUMBITS);
+ long result = DEFAULT_ELGAMAL_NUMBITS;
+ if (!rnp_secure_get_long_from_fd(input_fp, result)) {
+ continue;
+ }
+ if ((result >= ELGAMAL_MIN_P_BITLEN) && (result <= ELGAMAL_MAX_P_BITLEN)) {
+ // round up to multiple of 32
+ result = ((result + 31) / 32) * 32;
+ printf("Bitlen of the key will be %lu\n", result);
+ return result;
+ }
+ } while (1);
+}
+
+static long
+ask_dsa_bitlen(FILE *input_fp)
+{
+ do {
+ printf(
+ "Please provide bit length of the DSA key (between %d and %d):\n(default %d) > ",
+ DSA_MIN_P_BITLEN,
+ DSA_MAX_P_BITLEN,
+ DSA_DEFAULT_P_BITLEN);
+ long result = DSA_DEFAULT_P_BITLEN;
+ if (!rnp_secure_get_long_from_fd(input_fp, result)) {
+ continue;
+ }
+ if ((result >= DSA_MIN_P_BITLEN) && (result <= DSA_MAX_P_BITLEN)) {
+ // round up to multiple of 64
+ result = ((result + 63) / 64) * 64;
+ printf("Bitlen of the key will be %lu\n", result);
+ return result;
+ }
+ } while (1);
+}
+
+static bool
+rnpkeys_ask_generate_params(rnp_cfg &cfg, FILE *input_fp)
+{
+ long option = 0;
+ do {
+ printf("Please select what kind of key you want:\n"
+ "\t(1) RSA (Encrypt or Sign)\n"
+ "\t(16) DSA + ElGamal\n"
+ "\t(17) DSA + RSA\n" // TODO: See #584
+ "\t(19) ECDSA + ECDH\n"
+ "\t(22) EDDSA + X25519\n"
+ "\t(99) SM2\n"
+ "> ");
+ if (!rnp_secure_get_long_from_fd(input_fp, option, false)) {
+ option = 0;
+ continue;
+ }
+ switch (option) {
+ case 1: {
+ int bits = ask_rsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 16: {
+ int bits = ask_dsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 17: {
+ int bits = ask_dsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_DSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, bits);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 19: {
+ const char *curve = ask_curve_name(input_fp);
+ if (!curve) {
+ return false;
+ }
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_ECDSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
+ cfg.set_str(CFG_KG_PRIMARY_CURVE, curve);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
+ break;
+ }
+ case 22: {
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_EDDSA);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, "Curve25519");
+ break;
+ }
+ case 99: {
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_SM2);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2);
+ if (!cfg.has(CFG_KG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3);
+ }
+ break;
+ }
+ default:
+ option = 0;
+ break;
+ }
+ } while (!option);
+
+ return true;
+}
+
+static bool
+rnpkeys_ask_generate_params_subkey(rnp_cfg &cfg, FILE *input_fp)
+{
+ long option = 0;
+ do {
+ printf("Please select subkey algorithm you want:\n"
+ "\t(1) RSA\n"
+ "\t(16) ElGamal\n"
+ "\t(17) DSA\n"
+ "\t(18) ECDH\n"
+ "\t(19) ECDSA\n"
+ "\t(22) EDDSA\n"
+ "\t(99) SM2"
+ "> ");
+ if (!rnp_secure_get_long_from_fd(input_fp, option, false)) {
+ option = 0;
+ continue;
+ }
+ switch (option) {
+ case 1: {
+ int bits = ask_rsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 16: {
+ int bits = ask_elgamal_bitlen(input_fp);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ELGAMAL);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 17: {
+ int bits = ask_dsa_bitlen(input_fp);
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_DSA);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, bits);
+ break;
+ }
+ case 18: {
+ const char *curve = ask_curve_name(input_fp);
+ if (!curve) {
+ return false;
+ }
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDH);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
+ break;
+ }
+ case 19: {
+ const char *curve = ask_curve_name(input_fp);
+ if (!curve) {
+ return false;
+ }
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_ECDSA);
+ cfg.set_str(CFG_KG_SUBKEY_CURVE, curve);
+ break;
+ }
+ case 22: {
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_EDDSA);
+ break;
+ }
+ case 99: {
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_SM2);
+ if (!cfg.has(CFG_KG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, RNP_ALGNAME_SM3);
+ }
+ break;
+ }
+ default:
+ option = 0;
+ break;
+ }
+ } while (!option);
+
+ return true;
+}
+
+bool
+cli_rnp_set_generate_params(rnp_cfg &cfg, bool subkey)
+{
+ bool res = true;
+ // hash algorithms for signing and protection
+ if (cfg.has(CFG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, cfg.get_str(CFG_HASH));
+ cfg.set_str(CFG_KG_PROT_HASH, cfg.get_str(CFG_HASH));
+ }
+
+ // key and subkey algorithms, bit length/curve
+ if (!cfg.get_bool(CFG_EXPERT)) {
+ cfg.set_str(CFG_KG_PRIMARY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_PRIMARY_BITS, cfg.get_int(CFG_NUMBITS));
+ cfg.set_str(CFG_KG_SUBKEY_ALG, RNP_ALGNAME_RSA);
+ cfg.set_int(CFG_KG_SUBKEY_BITS, cfg.get_int(CFG_NUMBITS));
+ } else {
+ FILE *input = stdin;
+ if (cfg.has(CFG_USERINPUTFD)) {
+ int inputfd = dup(cfg.get_int(CFG_USERINPUTFD));
+ if (inputfd != -1) {
+ input = rnp_fdopen(inputfd, "r");
+ if (!input) {
+ close(inputfd);
+ }
+ }
+ }
+ if (!input) {
+ return false;
+ }
+ if (subkey) {
+ res = rnpkeys_ask_generate_params_subkey(cfg, input);
+ } else {
+ res = rnpkeys_ask_generate_params(cfg, input);
+ }
+ if (input != stdin) {
+ fclose(input);
+ }
+ if (!res) {
+ return false;
+ }
+ }
+
+ // make sure hash algorithms are set
+ if (!cfg.has(CFG_KG_HASH)) {
+ cfg.set_str(CFG_KG_HASH, DEFAULT_HASH_ALG);
+ }
+ if (!cfg.has(CFG_KG_PROT_HASH)) {
+ cfg.set_str(CFG_KG_PROT_HASH, DEFAULT_HASH_ALG);
+ }
+
+ // protection symmetric algorithm
+ cfg.set_str(CFG_KG_PROT_ALG,
+ cfg.has(CFG_CIPHER) ? cfg.get_str(CFG_CIPHER) : DEFAULT_SYMM_ALG);
+ // protection iterations count
+ size_t iterations = cfg.get_int(CFG_S2K_ITER);
+ if (!iterations) {
+ res = res && !rnp_calculate_iterations(cfg.get_str(CFG_KG_PROT_HASH).c_str(),
+ cfg.get_int(CFG_S2K_MSEC),
+ &iterations);
+ }
+ cfg.set_int(CFG_KG_PROT_ITERATIONS, iterations);
+ return res;
+}
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt
new file mode 100644
index 0000000..7d2a6b0
--- /dev/null
+++ b/src/tests/CMakeLists.txt
@@ -0,0 +1,245 @@
+# Copyright (c) 2018-2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+if (BUILD_TESTING_GENERATORS)
+ add_subdirectory(data/test_key_validity)
+endif()
+
+# fixture to copy the test data directory
+add_test(
+ NAME setupTestData
+ COMMAND "${CMAKE_COMMAND}" -E copy_directory
+ "${CMAKE_CURRENT_SOURCE_DIR}/data" "${CMAKE_CURRENT_BINARY_DIR}/data"
+)
+set_tests_properties(setupTestData PROPERTIES FIXTURES_SETUP testdata)
+
+# rnp_tests
+include(GoogleTest)
+if (GTEST_SOURCES)
+ # use Googletest sources if specified
+ add_subdirectory(${GTEST_SOURCES}
+ ${CMAKE_CURRENT_BINARY_DIR}/googletest-build
+ EXCLUDE_FROM_ALL)
+ set(GTestMain gtest_main)
+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
+ FetchContent_Declare(googletest
+ GIT_REPOSITORY https://github.com/google/googletest.git
+ GIT_TAG "${GTEST_GIT_TAG}"
+ )
+ # maintain compiler/linker settings on Windows
+ set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
+ # explicitly disable unneeded gmock build
+ set(BUILD_GMOCK OFF CACHE BOOL "" FORCE)
+ FetchContent_MakeAvailable(googletest)
+ set(GTestMain gtest_main)
+endif()
+
+find_package(JSON-C 0.11 REQUIRED)
+if (CRYPTO_BACKEND_LOWERCASE STREQUAL "botan")
+ find_package(Botan2 2.14.0 REQUIRED)
+endif()
+if (CRYPTO_BACKEND_LOWERCASE STREQUAL "openssl")
+ find_package(OpenSSL 1.1.1 REQUIRED)
+endif()
+
+add_executable(rnp_tests
+ ../rnp/rnpcfg.cpp
+ ../rnp/fficli.cpp
+ ../rnp/rnp.cpp
+ ../rnpkeys/rnpkeys.cpp
+ ../rnpkeys/main.cpp
+ ../rnpkeys/tui.cpp
+ ../fuzzing/keyring.c
+ ../fuzzing/keyring_g10.cpp
+ ../fuzzing/keyring_kbx.c
+ ../fuzzing/keyimport.c
+ ../fuzzing/sigimport.c
+ ../fuzzing/dump.c
+ ../fuzzing/verify_detached.c
+ ../fuzzing/verify.c
+ cipher.cpp
+ cipher_cxx.cpp
+ cli.cpp
+ exportkey.cpp
+ 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
+ key-prefs.cpp
+ key-protect.cpp
+ key-store-search.cpp
+ key-unlock.cpp
+ key-validate.cpp
+ large-packet.cpp
+ large-mpi.cpp
+ load-g10.cpp
+ load-g23.cpp
+ load-pgp.cpp
+ log-switch.cpp
+ partial-length.cpp
+ pipe.cpp
+ rnp_tests.cpp
+ rng-randomness.cpp
+ s2k-iterations.cpp
+ streams.cpp
+ support.cpp
+ user-prefs.cpp
+ utils-hex2bin.cpp
+ utils-rnpcfg.cpp
+ issues/1030.cpp
+ issues/1115.cpp
+ issues/1171.cpp
+ issues/oss-fuzz-25489.cpp
+ fuzz_keyring.cpp
+ fuzz_keyring_g10.cpp
+ fuzz_keyring_kbx.cpp
+ fuzz_keyimport.cpp
+ fuzz_sigimport.cpp
+ fuzz_dump.cpp
+ fuzz_verify_detached.cpp
+ fuzz_verify.cpp
+)
+if(MSVC)
+ find_package(WindowsSDK)
+ GetUMWindowsSDKLibraryDir(WIN_LIBRARY_DIR)
+ message (STATUS "WIN_LIBRARY_DIR: ${WIN_LIBRARY_DIR}")
+ find_library(SHLWAPI_LIBRARY
+ PATHS
+ ${WIN_LIBRARY_DIR}
+ NAMES shlwapi)
+
+ find_path(GETOPT_INCLUDE_DIR
+ NAMES getopt.h
+ )
+ find_library(GETOPT_LIBRARY
+ NAMES getopt
+ )
+ find_path(DIRENT_INCLUDE_DIR
+ NAMES dirent.h
+ )
+ target_include_directories(rnp_tests
+ PRIVATE
+ "${GETOPT_INCLUDE_DIR}"
+ "${DIRENT_INCLUDE_DIR}"
+ )
+ target_link_libraries(rnp_tests
+ PRIVATE
+ "${SHLWAPI_LIBRARY}"
+ "${GETOPT_LIBRARY}"
+ )
+endif()
+target_include_directories(rnp_tests
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+ "${BOTAN2_INCLUDE_DIRS}"
+)
+target_link_libraries(rnp_tests
+ PRIVATE
+ librnp-static
+ JSON-C::JSON-C
+ sexp
+ ${GTestMain}
+)
+if (CRYPTO_BACKEND_LOWERCASE STREQUAL "openssl")
+ target_link_libraries(rnp_tests PRIVATE OpenSSL::Crypto)
+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"
+)
+
+# cli_tests
+# Note that we do this call early because Google Test will also do
+# this but with less strict version requirements, which will cause
+# problems for us.
+find_package(Python3 COMPONENTS Interpreter)
+find_package(GnuPG 2.2 COMPONENTS gpg gpgconf)
+function(add_cli_test suite)
+ set(_test_name cli_tests-${suite})
+ add_test(
+ NAME ${_test_name}
+ COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/cli_tests.py" -v -d "${suite}"
+ )
+ set(_env)
+ list(APPEND _env
+ "RNP_TESTS_RNP_PATH=$<TARGET_FILE:rnp>"
+ "RNP_TESTS_RNPKEYS_PATH=$<TARGET_FILE:rnpkeys>"
+ "RNP_TESTS_GPG_PATH=${GPG_EXECUTABLE}"
+ "RNP_TESTS_GPGCONF_PATH=${GPGCONF_EXECUTABLE}"
+ )
+ set_tests_properties(${_test_name} PROPERTIES
+ TIMEOUT 3000
+ FIXTURES_REQUIRED testdata
+ ENVIRONMENT "${_env}"
+ )
+endfunction()
+# get a list of test suites
+execute_process(
+ COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/cli_tests.py" -ls
+ RESULT_VARIABLE _ec
+ OUTPUT_VARIABLE suitelist
+ OUTPUT_STRIP_TRAILING_WHITESPACE
+)
+if (NOT _ec EQUAL 0)
+ message(FATAL_ERROR "Failed to retrieve test suite list for cli_tests")
+endif()
+# convert to a CMake list
+string(REGEX REPLACE ";" "\\\\;" suitelist "${suitelist}")
+string(REGEX REPLACE "\n" ";" suitelist "${suitelist}")
+# create a CTest test for each suite
+foreach(suite IN LISTS suitelist)
+ add_cli_test("${suite}")
+endforeach()
diff --git a/src/tests/cipher.cpp b/src/tests/cipher.cpp
new file mode 100644
index 0000000..25b98bf
--- /dev/null
+++ b/src/tests/cipher.cpp
@@ -0,0 +1,1013 @@
+/*
+ * 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:
+ *
+ * 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 <crypto/common.h>
+#include <crypto.h>
+#include <pgp-key.h>
+#include "rnp.h"
+#include <librepgp/stream-packet.h>
+#include <librepgp/stream-key.h>
+
+#include "rnp_tests.h"
+#include "support.h"
+#include "fingerprint.h"
+
+TEST_F(rnp_tests, hash_test_success)
+{
+ uint8_t hash_output[PGP_MAX_HASH_SIZE];
+
+ const pgp_hash_alg_t hash_algs[] = {PGP_HASH_MD5,
+ PGP_HASH_SHA1,
+ PGP_HASH_SHA256,
+ PGP_HASH_SHA384,
+ PGP_HASH_SHA512,
+ PGP_HASH_SHA224,
+ PGP_HASH_SM3,
+ PGP_HASH_SHA3_256,
+ PGP_HASH_SHA3_512,
+ PGP_HASH_UNKNOWN};
+
+ const uint8_t test_input[3] = {'a', 'b', 'c'};
+ const char * hash_alg_expected_outputs[] = {
+ "900150983CD24FB0D6963F7D28E17F72",
+ "A9993E364706816ABA3E25717850C26C9CD0D89D",
+ "BA7816BF8F01CFEA414140DE5DAE2223B00361A396177A9CB410FF61F20015AD",
+ "CB00753F45A35E8BB5A03D699AC65007272C32AB0EDED1631A8B605A43FF5BED8086072BA1"
+ "E7CC2358BAECA"
+ "134C825A7",
+ "DDAF35A193617ABACC417349AE20413112E6FA4E89A97EA20A9EEEE64B55D39A2192992A27"
+ "4FC1A836BA3C2"
+ "3A3FEEBBD454D4423643CE80E2A9AC94FA54CA49F",
+ "23097D223405D8228642A477BDA255B32AADBCE4BDA0B3F7E36C9DA7",
+ "66C7F0F462EEEDD9D1F2D46BDC10E4E24167C4875CF2F7A2297DA02B8F4BA8E0",
+ "3A985DA74FE225B2045C172D6BD390BD855F086E3E9D525B46BFE24511431532",
+ ("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({ 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
+ 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);
+
+ assert_true(bin_eq_hex(hash_output, hash_size, hash_alg_expected_outputs[i]));
+ }
+}
+
+TEST_F(rnp_tests, cipher_test_success)
+{
+ const uint8_t key[16] = {0};
+ uint8_t iv[16];
+ pgp_symm_alg_t alg = PGP_SA_AES_128;
+ pgp_crypt_t crypt;
+
+ uint8_t cfb_data[20] = {0};
+ memset(iv, 0x42, sizeof(iv));
+
+ assert_int_equal(1, pgp_cipher_cfb_start(&crypt, alg, key, iv));
+
+ assert_int_equal(0, pgp_cipher_cfb_encrypt(&crypt, cfb_data, cfb_data, sizeof(cfb_data)));
+
+ assert_true(
+ bin_eq_hex(cfb_data, sizeof(cfb_data), "BFDAA57CB812189713A950AD9947887983021617"));
+ assert_int_equal(0, pgp_cipher_cfb_finish(&crypt));
+
+ assert_int_equal(1, pgp_cipher_cfb_start(&crypt, alg, key, iv));
+ assert_int_equal(0, pgp_cipher_cfb_decrypt(&crypt, cfb_data, cfb_data, sizeof(cfb_data)));
+ assert_true(
+ bin_eq_hex(cfb_data, sizeof(cfb_data), "0000000000000000000000000000000000000000"));
+ assert_int_equal(0, pgp_cipher_cfb_finish(&crypt));
+}
+
+TEST_F(rnp_tests, pkcs1_rsa_test_success)
+{
+ uint8_t ptext[1024 / 8] = {'a', 'b', 'c', 0};
+ uint8_t dec[1024 / 8];
+ pgp_rsa_encrypted_t enc;
+ size_t dec_size;
+
+ rnp_keygen_crypto_params_t key_desc;
+ key_desc.key_alg = PGP_PKA_RSA;
+ key_desc.hash_alg = PGP_HASH_SHA256;
+ key_desc.rsa.modulus_bit_len = 1024;
+ key_desc.ctx = &global_ctx;
+ pgp_key_pkt_t seckey;
+ assert_true(pgp_generate_seckey(key_desc, seckey, true));
+ const pgp_rsa_key_t *key_rsa = &seckey.material.rsa;
+
+ assert_rnp_success(rsa_encrypt_pkcs1(&global_ctx.rng, &enc, ptext, 3, key_rsa));
+ assert_int_equal(enc.m.len, 1024 / 8);
+
+ memset(dec, 0, sizeof(dec));
+ dec_size = 0;
+ assert_rnp_success(rsa_decrypt_pkcs1(&global_ctx.rng, dec, &dec_size, &enc, key_rsa));
+ assert_true(bin_eq_hex(dec, 3, "616263"));
+ assert_int_equal(dec_size, 3);
+}
+
+TEST_F(rnp_tests, rnp_test_eddsa)
+{
+ rnp::SecurityContext ctx;
+ rnp_keygen_crypto_params_t key_desc;
+ key_desc.key_alg = PGP_PKA_EDDSA;
+ key_desc.hash_alg = PGP_HASH_SHA256;
+ key_desc.ctx = &ctx;
+
+ pgp_key_pkt_t seckey;
+ assert_true(pgp_generate_seckey(key_desc, seckey, true));
+
+ const uint8_t hash[32] = {0};
+ pgp_ec_signature_t sig = {{{0}}};
+
+ assert_rnp_success(
+ eddsa_sign(&global_ctx.rng, &sig, hash, sizeof(hash), &seckey.material.ec));
+
+ assert_rnp_success(eddsa_verify(&sig, hash, sizeof(hash), &seckey.material.ec));
+
+ // cut one byte off hash -> invalid sig
+ assert_rnp_failure(eddsa_verify(&sig, hash, sizeof(hash) - 1, &seckey.material.ec));
+
+ // swap r/s -> invalid sig
+ pgp_mpi_t tmp = sig.r;
+ sig.r = sig.s;
+ sig.s = tmp;
+ assert_rnp_failure(eddsa_verify(&sig, hash, sizeof(hash), &seckey.material.ec));
+}
+
+TEST_F(rnp_tests, rnp_test_x25519)
+{
+ rnp_keygen_crypto_params_t key_desc = {};
+ pgp_key_pkt_t seckey;
+ pgp_ecdh_encrypted_t enc = {};
+ uint8_t in[16] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16};
+ uint8_t out[16] = {};
+ size_t outlen = 0;
+ pgp_fingerprint_t fp = {};
+
+ key_desc.key_alg = PGP_PKA_ECDH;
+ key_desc.hash_alg = PGP_HASH_SHA256;
+ key_desc.ctx = &global_ctx;
+ key_desc.ecc.curve = PGP_CURVE_25519;
+
+ assert_true(pgp_generate_seckey(key_desc, seckey, true));
+ /* check for length and correctly tweaked bits */
+ assert_int_equal(seckey.material.ec.x.len, 32);
+ assert_int_equal(seckey.material.ec.x.mpi[31] & 7, 0);
+ assert_int_equal(seckey.material.ec.x.mpi[0] & 128, 0);
+ assert_int_equal(seckey.material.ec.x.mpi[0] & 64, 64);
+ assert_rnp_success(pgp_fingerprint(fp, seckey));
+ assert_rnp_success(
+ ecdh_encrypt_pkcs5(&global_ctx.rng, &enc, in, sizeof(in), &seckey.material.ec, fp));
+ assert_true(enc.mlen > 16);
+ assert_true((enc.p.mpi[0] == 0x40) && (enc.p.len == 33));
+ outlen = sizeof(out);
+ assert_rnp_success(ecdh_decrypt_pkcs5(out, &outlen, &enc, &seckey.material.ec, fp));
+ assert_true(outlen == 16);
+ assert_true(memcmp(in, out, 16) == 0);
+
+ /* negative cases */
+ enc.p.mpi[16] ^= 0xff;
+ assert_rnp_failure(ecdh_decrypt_pkcs5(out, &outlen, &enc, &seckey.material.ec, fp));
+
+ enc.p.mpi[16] ^= 0xff;
+ enc.p.mpi[0] = 0x04;
+ assert_rnp_failure(ecdh_decrypt_pkcs5(out, &outlen, &enc, &seckey.material.ec, fp));
+
+ enc.p.mpi[0] = 0x40;
+ enc.mlen--;
+ assert_rnp_failure(ecdh_decrypt_pkcs5(out, &outlen, &enc, &seckey.material.ec, fp));
+
+ enc.mlen += 2;
+ assert_rnp_failure(ecdh_decrypt_pkcs5(out, &outlen, &enc, &seckey.material.ec, fp));
+}
+
+static void
+elgamal_roundtrip(pgp_eg_key_t *key)
+{
+ const uint8_t in_b[] = {0x01, 0x02, 0x03, 0x04, 0x17};
+ pgp_eg_encrypted_t enc = {{{0}}};
+ uint8_t res[1024];
+ size_t res_len = 0;
+
+ assert_int_equal(elgamal_encrypt_pkcs1(&global_ctx.rng, &enc, in_b, sizeof(in_b), key),
+ RNP_SUCCESS);
+ assert_int_equal(elgamal_decrypt_pkcs1(&global_ctx.rng, res, &res_len, &enc, key),
+ RNP_SUCCESS);
+ assert_int_equal(res_len, sizeof(in_b));
+ assert_true(bin_eq_hex(res, res_len, "0102030417"));
+}
+
+TEST_F(rnp_tests, raw_elgamal_random_key_test_success)
+{
+ pgp_eg_key_t key;
+
+ assert_int_equal(elgamal_generate(&global_ctx.rng, &key, 1024), RNP_SUCCESS);
+ elgamal_roundtrip(&key);
+}
+
+TEST_F(rnp_tests, ecdsa_signverify_success)
+{
+ uint8_t message[64];
+ const pgp_hash_alg_t hash_alg = PGP_HASH_SHA512;
+
+ struct curve {
+ pgp_curve_t id;
+ size_t size;
+ } curves[] = {
+ {PGP_CURVE_NIST_P_256, 32}, {PGP_CURVE_NIST_P_384, 48}, {PGP_CURVE_NIST_P_521, 64}};
+
+ for (size_t i = 0; i < ARRAY_SIZE(curves); i++) {
+ // Generate test data. Mainly to make valgrind not to complain about uninitialized data
+ global_ctx.rng.get(message, sizeof(message));
+
+ pgp_ec_signature_t sig = {{{0}}};
+ rnp_keygen_crypto_params_t key_desc;
+ key_desc.key_alg = PGP_PKA_ECDSA;
+ key_desc.hash_alg = hash_alg;
+ key_desc.ecc.curve = curves[i].id;
+ key_desc.ctx = &global_ctx;
+
+ pgp_key_pkt_t seckey1;
+ pgp_key_pkt_t seckey2;
+
+ assert_true(pgp_generate_seckey(key_desc, seckey1, true));
+ assert_true(pgp_generate_seckey(key_desc, seckey2, true));
+
+ const pgp_ec_key_t *key1 = &seckey1.material.ec;
+ const pgp_ec_key_t *key2 = &seckey2.material.ec;
+
+ assert_rnp_success(
+ ecdsa_sign(&global_ctx.rng, &sig, hash_alg, message, sizeof(message), key1));
+
+ assert_rnp_success(ecdsa_verify(&sig, hash_alg, message, sizeof(message), key1));
+
+ // Fails because of different key used
+ assert_rnp_failure(ecdsa_verify(&sig, hash_alg, message, sizeof(message), key2));
+
+ // Fails because message won't verify
+ message[0] = ~message[0];
+ assert_rnp_failure(ecdsa_verify(&sig, hash_alg, message, sizeof(message), key1));
+ }
+}
+
+TEST_F(rnp_tests, ecdh_roundtrip)
+{
+ struct curve {
+ pgp_curve_t id;
+ size_t size;
+ } curves[] = {
+ {PGP_CURVE_NIST_P_256, 32}, {PGP_CURVE_NIST_P_384, 48}, {PGP_CURVE_NIST_P_521, 66}};
+
+ pgp_ecdh_encrypted_t enc;
+ uint8_t plaintext[32] = {0};
+ size_t plaintext_len = sizeof(plaintext);
+ uint8_t result[32] = {0};
+ size_t result_len = sizeof(result);
+
+ for (size_t i = 0; i < ARRAY_SIZE(curves); i++) {
+ 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 = curves[i].id;
+ key_desc.ctx = &global_ctx;
+
+ pgp_key_pkt_t ecdh_key1;
+ assert_true(pgp_generate_seckey(key_desc, ecdh_key1, true));
+
+ pgp_fingerprint_t ecdh_key1_fpr = {};
+ assert_rnp_success(pgp_fingerprint(ecdh_key1_fpr, ecdh_key1));
+
+ assert_rnp_success(ecdh_encrypt_pkcs5(&global_ctx.rng,
+ &enc,
+ plaintext,
+ plaintext_len,
+ &ecdh_key1.material.ec,
+ ecdh_key1_fpr));
+
+ assert_rnp_success(ecdh_decrypt_pkcs5(
+ result, &result_len, &enc, &ecdh_key1.material.ec, ecdh_key1_fpr));
+
+ assert_int_equal(plaintext_len, result_len);
+ assert_int_equal(memcmp(plaintext, result, result_len), 0);
+ }
+}
+
+TEST_F(rnp_tests, ecdh_decryptionNegativeCases)
+{
+ uint8_t plaintext[32] = {0};
+ size_t plaintext_len = sizeof(plaintext);
+ uint8_t result[32] = {0};
+ size_t result_len = sizeof(result);
+ 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.ctx = &global_ctx;
+
+ pgp_key_pkt_t ecdh_key1;
+ assert_true(pgp_generate_seckey(key_desc, ecdh_key1, true));
+
+ pgp_fingerprint_t ecdh_key1_fpr = {};
+ assert_rnp_success(pgp_fingerprint(ecdh_key1_fpr, ecdh_key1));
+
+ assert_rnp_success(ecdh_encrypt_pkcs5(
+ &global_ctx.rng, &enc, plaintext, plaintext_len, &ecdh_key1.material.ec, ecdh_key1_fpr));
+
+ assert_int_equal(ecdh_decrypt_pkcs5(NULL, 0, &enc, &ecdh_key1.material.ec, ecdh_key1_fpr),
+ RNP_ERROR_BAD_PARAMETERS);
+
+ assert_int_equal(ecdh_decrypt_pkcs5(result, &result_len, &enc, NULL, ecdh_key1_fpr),
+ RNP_ERROR_BAD_PARAMETERS);
+
+ assert_int_equal(
+ ecdh_decrypt_pkcs5(result, &result_len, NULL, &ecdh_key1.material.ec, ecdh_key1_fpr),
+ RNP_ERROR_BAD_PARAMETERS);
+
+ size_t mlen = enc.mlen;
+ enc.mlen = 0;
+ assert_int_equal(
+ ecdh_decrypt_pkcs5(result, &result_len, &enc, &ecdh_key1.material.ec, ecdh_key1_fpr),
+ RNP_ERROR_GENERIC);
+
+ enc.mlen = mlen - 1;
+ assert_int_equal(
+ ecdh_decrypt_pkcs5(result, &result_len, &enc, &ecdh_key1.material.ec, ecdh_key1_fpr),
+ RNP_ERROR_GENERIC);
+
+ int key_wrapping_alg = ecdh_key1.material.ec.key_wrap_alg;
+ ecdh_key1.material.ec.key_wrap_alg = PGP_SA_IDEA;
+ assert_int_equal(
+ ecdh_decrypt_pkcs5(result, &result_len, &enc, &ecdh_key1.material.ec, ecdh_key1_fpr),
+ RNP_ERROR_NOT_SUPPORTED);
+ ecdh_key1.material.ec.key_wrap_alg = (pgp_symm_alg_t) key_wrapping_alg;
+}
+
+#if defined(ENABLE_SM2)
+TEST_F(rnp_tests, sm2_roundtrip)
+{
+ uint8_t key[27] = {0};
+ uint8_t decrypted[27];
+ 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.ctx = &global_ctx;
+
+ global_ctx.rng.get(key, sizeof(key));
+
+ pgp_key_pkt_t seckey;
+ assert_true(pgp_generate_seckey(key_desc, seckey, true));
+
+ const pgp_ec_key_t *eckey = &seckey.material.ec;
+
+ pgp_hash_alg_t hashes[] = {PGP_HASH_SM3, PGP_HASH_SHA256, PGP_HASH_SHA512};
+ pgp_sm2_encrypted_t enc;
+ rnp_result_t ret;
+
+ for (size_t i = 0; i < ARRAY_SIZE(hashes); ++i) {
+ ret = sm2_encrypt(&global_ctx.rng, &enc, key, sizeof(key), hashes[i], eckey);
+ assert_int_equal(ret, RNP_SUCCESS);
+
+ memset(decrypted, 0, sizeof(decrypted));
+ decrypted_size = sizeof(decrypted);
+ ret = sm2_decrypt(decrypted, &decrypted_size, &enc, eckey);
+ assert_int_equal(ret, RNP_SUCCESS);
+ assert_int_equal(decrypted_size, sizeof(key));
+ for (size_t i = 0; i < decrypted_size; ++i) {
+ assert_int_equal(key[i], decrypted[i]);
+ }
+ }
+}
+#endif
+
+#if defined(ENABLE_SM2)
+TEST_F(rnp_tests, sm2_sm3_signature_test)
+{
+ const char *msg = "no backdoors here";
+
+ pgp_ec_key_t sm2_key;
+ pgp_ec_signature_t sig;
+
+ pgp_hash_alg_t hash_alg = PGP_HASH_SM3;
+ const size_t hash_len = rnp::Hash::size(hash_alg);
+
+ uint8_t digest[PGP_MAX_HASH_SIZE];
+
+ sm2_key.curve = PGP_CURVE_NIST_P_256;
+
+ hex2mpi(&sm2_key.p,
+ "04d9a2025f1ab59bc44e35fc53aeb8e87a79787d30cd70a1f7c49e064b8b8a2fb24d8"
+ "c82f49ee0a5b11df22cb0c3c6d9d5526d9e24d02ff8c83c06a859c26565f1");
+ hex2mpi(&sm2_key.x, "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC");
+
+ assert_int_equal(sm2_validate_key(&global_ctx.rng, &sm2_key, true), RNP_SUCCESS);
+
+ 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);
+
+ // 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);
+
+ // Check that invalid signatures are rejected
+ digest[0] ^= 1;
+ assert_int_not_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS);
+
+ digest[0] ^= 1;
+ assert_int_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS);
+
+ // Now verify a known good signature for this key/message (generated by GmSSL)
+ hex2mpi(&sig.r, "96AA39A0C4A5C454653F394E86386F2E38BE14C57D0E555F3A27A5CEF30E51BD");
+ hex2mpi(&sig.s, "62372BE4AC97DBE725AC0B279BB8FD15883858D814FD792DDB0A401DCC988E70");
+ assert_int_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS);
+}
+#endif
+
+#if defined(ENABLE_SM2)
+TEST_F(rnp_tests, sm2_sha256_signature_test)
+{
+ const char * msg = "hi chappy";
+ pgp_ec_key_t sm2_key;
+ pgp_ec_signature_t sig;
+ pgp_hash_alg_t hash_alg = PGP_HASH_SHA256;
+ const size_t hash_len = rnp::Hash::size(hash_alg);
+ uint8_t digest[PGP_MAX_HASH_SIZE];
+
+ sm2_key.curve = PGP_CURVE_SM2_P_256;
+ hex2mpi(&sm2_key.p,
+ "04d03d30dd01ca3422aeaccf9b88043b554659d3092b0a9e8cce3e8c4530a98cb79d7"
+ "05e6213eee145b748e36e274e5f101dc10d7bbc9dab9a04022e73b76e02cd");
+ hex2mpi(&sm2_key.x, "110E7973206F68C19EE5F7328C036F26911C8C73B4E4F36AE3291097F8984FFC");
+
+ assert_int_equal(sm2_validate_key(&global_ctx.rng, &sm2_key, true), RNP_SUCCESS);
+
+ 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);
+
+ // Check that invalid signatures are rejected
+ digest[0] ^= 1;
+ assert_int_not_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS);
+
+ digest[0] ^= 1;
+ assert_int_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS);
+
+ // Now verify a known good signature for this key/message (generated by GmSSL)
+ hex2mpi(&sig.r, "94DA20EA69E4FC70692158BF3D30F87682A4B2F84DF4A4829A1EFC5D9C979D3F");
+ hex2mpi(&sig.s, "EE15AF8D455B728AB80E592FCB654BF5B05620B2F4D25749D263D5C01FAD365F");
+ assert_int_equal(sm2_verify(&sig, hash_alg, digest, hash_len, &sm2_key), RNP_SUCCESS);
+}
+#endif
+
+TEST_F(rnp_tests, test_dsa_roundtrip)
+{
+ uint8_t message[PGP_MAX_HASH_SIZE];
+ pgp_key_pkt_t seckey;
+ pgp_dsa_signature_t sig;
+
+ struct key_params {
+ size_t p;
+ size_t q;
+ pgp_hash_alg_t h;
+ } keys[] = {
+ // all 1024 key-hash combinations
+ {1024, 160, PGP_HASH_SHA1},
+ {1024, 160, PGP_HASH_SHA224},
+ {1024, 160, PGP_HASH_SHA256},
+ {1024, 160, PGP_HASH_SHA384},
+ {1024, 160, PGP_HASH_SHA512},
+ // all 2048 key-hash combinations
+ {2048, 256, PGP_HASH_SHA256},
+ {2048, 256, PGP_HASH_SHA384},
+ {2048, 256, PGP_HASH_SHA512},
+ // misc
+ {1088, 224, PGP_HASH_SHA512},
+ {1024, 256, PGP_HASH_SHA256},
+ };
+
+ global_ctx.rng.get(message, sizeof(message));
+
+ for (size_t i = 0; i < ARRAY_SIZE(keys); i++) {
+ sig = {};
+ rnp_keygen_crypto_params_t key_desc;
+ key_desc.key_alg = PGP_PKA_DSA;
+ key_desc.hash_alg = keys[i].h;
+ key_desc.dsa.p_bitlen = keys[i].p;
+ key_desc.dsa.q_bitlen = keys[i].q;
+ key_desc.ctx = &global_ctx;
+
+ assert_true(pgp_generate_seckey(key_desc, seckey, true));
+ // try to prevent timeouts in travis-ci
+ printf("p: %zu q: %zu h: %s\n",
+ key_desc.dsa.p_bitlen,
+ key_desc.dsa.q_bitlen,
+ rnp::Hash::name(key_desc.hash_alg));
+ fflush(stdout);
+
+ pgp_dsa_key_t *key1 = &seckey.material.dsa;
+
+ size_t h_size = rnp::Hash::size(keys[i].h);
+ assert_int_equal(dsa_sign(&global_ctx.rng, &sig, message, h_size, key1), RNP_SUCCESS);
+ assert_int_equal(dsa_verify(&sig, message, h_size, key1), RNP_SUCCESS);
+ }
+}
+
+TEST_F(rnp_tests, test_dsa_verify_negative)
+{
+ uint8_t message[PGP_MAX_HASH_SIZE];
+ pgp_key_pkt_t sec_key1;
+ pgp_key_pkt_t sec_key2;
+ pgp_dsa_signature_t sig = {};
+
+ struct key_params {
+ size_t p;
+ size_t q;
+ pgp_hash_alg_t h;
+ } key = {1024, 160, PGP_HASH_SHA1};
+
+ global_ctx.rng.get(message, sizeof(message));
+
+ rnp_keygen_crypto_params_t key_desc;
+ key_desc.key_alg = PGP_PKA_DSA;
+ key_desc.hash_alg = key.h;
+ key_desc.dsa.p_bitlen = key.p;
+ key_desc.dsa.q_bitlen = key.q;
+ key_desc.ctx = &global_ctx;
+
+ assert_true(pgp_generate_seckey(key_desc, sec_key1, true));
+ // try to prevent timeouts in travis-ci
+ printf("p: %zu q: %zu h: %s\n",
+ key_desc.dsa.p_bitlen,
+ key_desc.dsa.q_bitlen,
+ rnp::Hash::name(key_desc.hash_alg));
+ assert_true(pgp_generate_seckey(key_desc, sec_key2, true));
+
+ pgp_dsa_key_t *key1 = &sec_key1.material.dsa;
+ pgp_dsa_key_t *key2 = &sec_key2.material.dsa;
+
+ size_t h_size = rnp::Hash::size(key.h);
+ assert_int_equal(dsa_sign(&global_ctx.rng, &sig, message, h_size, key1), RNP_SUCCESS);
+ // wrong key used
+ assert_int_equal(dsa_verify(&sig, message, h_size, key2), RNP_ERROR_SIGNATURE_INVALID);
+ // different message
+ message[0] = ~message[0];
+ assert_int_equal(dsa_verify(&sig, message, h_size, key1), RNP_ERROR_SIGNATURE_INVALID);
+}
+
+// platforms known to not have a robust response can compile with
+// -DS2K_MINIMUM_TUNING_RATIO=2 (or whatever they need)
+#ifndef S2K_MINIMUM_TUNING_RATIO
+#define S2K_MINIMUM_TUNING_RATIO 6
+#endif
+
+TEST_F(rnp_tests, s2k_iteration_tuning)
+{
+ pgp_hash_alg_t hash_alg = PGP_HASH_SHA512;
+
+ /*
+ Run trials for a while (1/4 second) to ensure dynamically clocked
+ cores spin up to full speed.
+ */
+ const size_t TRIAL_MSEC = 250;
+
+ const size_t iters_100 = pgp_s2k_compute_iters(hash_alg, 100, TRIAL_MSEC);
+ const size_t iters_10 = pgp_s2k_compute_iters(hash_alg, 10, TRIAL_MSEC);
+
+ double ratio = static_cast<double>(iters_100) / iters_10;
+ printf("s2k iteration tuning ratio: %g, (%zu:%zu)\n", ratio, iters_10, iters_100);
+ // Test roughly linear cost, often skeyed by clock idle
+ 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);
+ // encoding tests
+ assert_int_equal(pgp_s2k_encode_iterations(0), 0);
+ assert_int_equal(pgp_s2k_encode_iterations(512), 0);
+ assert_int_equal(pgp_s2k_encode_iterations(1024), 0);
+ assert_int_equal(pgp_s2k_encode_iterations(1024), 0);
+ assert_int_equal(pgp_s2k_encode_iterations(1025), 1);
+ assert_int_equal(pgp_s2k_encode_iterations(1088), 1);
+ assert_int_equal(pgp_s2k_encode_iterations(1089), 2);
+ assert_int_equal(pgp_s2k_encode_iterations(2048), 16);
+ assert_int_equal(pgp_s2k_encode_iterations(MAX_ITER - 1), 0xFF);
+ assert_int_equal(pgp_s2k_encode_iterations(MAX_ITER), 0xFF);
+ assert_int_equal(pgp_s2k_encode_iterations(MAX_ITER + 1), 0xFF);
+ assert_int_equal(pgp_s2k_encode_iterations(SIZE_MAX), 0xFF);
+ // decoding tests
+ assert_int_equal(pgp_s2k_decode_iterations(0), 1024);
+ assert_int_equal(pgp_s2k_decode_iterations(1), 1088);
+ assert_int_equal(pgp_s2k_decode_iterations(16), 2048);
+ assert_int_equal(pgp_s2k_decode_iterations(0xFF), MAX_ITER);
+}
+
+static bool
+read_key_pkt(pgp_key_pkt_t *key, const char *path)
+{
+ pgp_source_t src = {};
+ if (init_file_src(&src, path)) {
+ return false;
+ }
+ bool res = !key->parse(src);
+ src_close(&src);
+ return res;
+}
+
+#define KEYS "data/test_validate_key_material/"
+
+TEST_F(rnp_tests, test_validate_key_material)
+{
+ pgp_key_pkt_t key;
+ rnp::RNG & rng = global_ctx.rng;
+
+ /* RSA key and subkey */
+ assert_true(read_key_pkt(&key, KEYS "rsa-pub.pgp"));
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.n.mpi[key.material.rsa.n.len - 1] &= ~1;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.n.mpi[key.material.rsa.n.len - 1] |= 1;
+ key.material.rsa.e.mpi[key.material.rsa.e.len - 1] &= ~1;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key = pgp_key_pkt_t();
+
+ assert_true(read_key_pkt(&key, KEYS "rsa-sub.pgp"));
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.n.mpi[key.material.rsa.n.len - 1] &= ~1;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.n.mpi[key.material.rsa.n.len - 1] |= 1;
+ key.material.rsa.e.mpi[key.material.rsa.e.len - 1] &= ~1;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key = pgp_key_pkt_t();
+
+ assert_true(read_key_pkt(&key, KEYS "rsa-sec.pgp"));
+ key.material.validate(global_ctx);
+ assert_true(key.material.validity.valid);
+ assert_true(key.material.validity.validated);
+ assert_rnp_success(decrypt_secret_key(&key, NULL));
+ /* make sure validity is reset after decryption */
+ assert_false(key.material.validity.valid);
+ assert_false(key.material.validity.validated);
+ assert_true(key.material.secret);
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.e.mpi[key.material.rsa.e.len - 1] += 1;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.e.mpi[key.material.rsa.e.len - 1] -= 1;
+ key.material.rsa.p.mpi[key.material.rsa.p.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.p.mpi[key.material.rsa.p.len - 1] -= 2;
+ key.material.rsa.p.mpi[key.material.rsa.q.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.p.mpi[key.material.rsa.q.len - 1] -= 2;
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key = pgp_key_pkt_t();
+
+ assert_true(read_key_pkt(&key, KEYS "rsa-ssb.pgp"));
+ assert_rnp_success(decrypt_secret_key(&key, NULL));
+ assert_true(key.material.secret);
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.e.mpi[key.material.rsa.e.len - 1] += 1;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.e.mpi[key.material.rsa.e.len - 1] -= 1;
+ key.material.rsa.p.mpi[key.material.rsa.p.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.p.mpi[key.material.rsa.p.len - 1] -= 2;
+ key.material.rsa.p.mpi[key.material.rsa.q.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.rsa.p.mpi[key.material.rsa.q.len - 1] -= 2;
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key = pgp_key_pkt_t();
+
+ /* DSA-ElGamal key */
+ assert_true(read_key_pkt(&key, KEYS "dsa-sec.pgp"));
+ key.material.dsa.q.mpi[key.material.dsa.q.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.dsa.q.mpi[key.material.dsa.q.len - 1] -= 2;
+ assert_rnp_success(decrypt_secret_key(&key, NULL));
+ assert_true(key.material.secret);
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.dsa.y.mpi[key.material.dsa.y.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.dsa.y.mpi[key.material.dsa.y.len - 1] -= 2;
+ key.material.dsa.p.mpi[key.material.dsa.p.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.dsa.p.mpi[key.material.dsa.p.len - 1] -= 2;
+ /* since Botan calculates y from x on key load we do not check x vs y */
+ key.material.dsa.x = key.material.dsa.q;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key = pgp_key_pkt_t();
+
+ assert_true(read_key_pkt(&key, KEYS "eg-sec.pgp"));
+ key.material.eg.p.mpi[key.material.eg.p.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.eg.p.mpi[key.material.eg.p.len - 1] -= 2;
+ assert_rnp_success(decrypt_secret_key(&key, NULL));
+ assert_true(key.material.secret);
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.eg.p.mpi[key.material.eg.p.len - 1] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.eg.p.mpi[key.material.eg.p.len - 1] -= 2;
+ /* since Botan calculates y from x on key load we do not check x vs y */
+ key.material.eg.x = key.material.eg.p;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key = pgp_key_pkt_t();
+
+ /* ElGamal key with small subgroup */
+ assert_true(read_key_pkt(&key, KEYS "eg-sec-small-group.pgp"));
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ assert_rnp_success(decrypt_secret_key(&key, NULL));
+ key = pgp_key_pkt_t();
+
+ assert_true(read_key_pkt(&key, KEYS "eg-sec-small-group-enc.pgp"));
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ assert_rnp_success(decrypt_secret_key(&key, "password"));
+ key = pgp_key_pkt_t();
+
+ /* ECDSA key */
+ assert_true(read_key_pkt(&key, KEYS "ecdsa-p256-sec.pgp"));
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] -= 2;
+ key.material.ec.p.mpi[10] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[10] -= 2;
+ assert_rnp_success(decrypt_secret_key(&key, NULL));
+ assert_true(key.material.secret);
+ key = pgp_key_pkt_t();
+
+ /* ECDH key */
+ assert_true(read_key_pkt(&key, KEYS "ecdh-p256-sec.pgp"));
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] -= 2;
+ key.material.ec.p.mpi[10] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[10] -= 2;
+ assert_rnp_success(decrypt_secret_key(&key, NULL));
+ assert_true(key.material.secret);
+ key = pgp_key_pkt_t();
+
+ /* EDDSA key, just test for header since any value can be secret key */
+ assert_true(read_key_pkt(&key, KEYS "ed25519-sec.pgp"));
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] -= 2;
+ key = pgp_key_pkt_t();
+
+ /* x25519 key, same as the previous - botan calculates pub key from the secret one */
+ assert_true(read_key_pkt(&key, KEYS "x25519-sec.pgp"));
+ assert_rnp_success(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] += 2;
+ assert_rnp_failure(validate_pgp_key_material(&key.material, &rng));
+ key.material.ec.p.mpi[0] -= 2;
+ key = pgp_key_pkt_t();
+}
+
+TEST_F(rnp_tests, test_sm2_enabled)
+{
+ char *features = NULL;
+ bool supported = false;
+ /* check whether FFI returns value which corresponds to defines */
+#if defined(ENABLE_SM2)
+ assert_true(sm2_enabled());
+ /* SM2 */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_PK_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM2") != std::string::npos);
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "SM2", &supported));
+ assert_true(supported);
+ /* SM3 */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_HASH_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM3") != std::string::npos);
+ rnp_buffer_destroy(features);
+ supported = false;
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SM3", &supported));
+ assert_true(supported);
+ /* SM4 */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_SYMM_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM4") != std::string::npos);
+ rnp_buffer_destroy(features);
+ supported = false;
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "SM4", &supported));
+ assert_true(supported);
+ /* Curve */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_CURVE, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM2 P-256") != std::string::npos);
+ rnp_buffer_destroy(features);
+ supported = false;
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "SM2 P-256", &supported));
+ assert_true(supported);
+#else
+ assert_false(sm2_enabled());
+ /* SM2 */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_PK_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM2") == std::string::npos);
+ rnp_buffer_destroy(features);
+ supported = true;
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "SM2", &supported));
+ assert_false(supported);
+ /* SM3 */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_HASH_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM3") == std::string::npos);
+ rnp_buffer_destroy(features);
+ supported = true;
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SM3", &supported));
+ assert_false(supported);
+ /* SM4 */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_SYMM_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM4") == std::string::npos);
+ rnp_buffer_destroy(features);
+ supported = true;
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "SM4", &supported));
+ assert_false(supported);
+ /* Curve */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_CURVE, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("SM2 P-256") == std::string::npos);
+ rnp_buffer_destroy(features);
+ supported = true;
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "SM2 P-256", &supported));
+ assert_false(supported);
+#endif
+}
+
+TEST_F(rnp_tests, test_aead_enabled)
+{
+ char *features = NULL;
+ bool supported = false;
+ /* check whether FFI returns value which corresponds to defines */
+#if defined(ENABLE_AEAD)
+ bool has_eax = aead_eax_enabled();
+ bool has_ocb = aead_ocb_enabled();
+ assert_true(has_eax || has_ocb);
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_AEAD_ALG, &features));
+ assert_non_null(features);
+ assert_true((std::string(features).find("EAX") != std::string::npos) == has_eax);
+ assert_true((std::string(features).find("OCB") != std::string::npos) == has_ocb);
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "EAX", &supported));
+ assert_true(supported == has_eax);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "OCB", &supported));
+ assert_true(supported == has_ocb);
+#else
+ assert_false(aead_eax_enabled());
+ assert_false(aead_ocb_enabled());
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_AEAD_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("EAX") == std::string::npos);
+ assert_true(std::string(features).find("OCB") == std::string::npos);
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "EAX", &supported));
+ assert_false(supported);
+ 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 */
+#if defined(ENABLE_TWOFISH)
+ assert_true(twofish_enabled());
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_SYMM_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("TWOFISH") != std::string::npos);
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "TWOFISH", &supported));
+ assert_true(supported);
+#else
+ assert_false(twofish_enabled());
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_SYMM_ALG, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("TWOFISH") == std::string::npos);
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "TWOFISH", &supported));
+ assert_false(supported);
+#endif
+}
+
+TEST_F(rnp_tests, test_brainpool_enabled)
+{
+ char *features = NULL;
+ bool supported = false;
+ /* check whether FFI returns value which corresponds to defines */
+#if defined(ENABLE_BRAINPOOL)
+ assert_true(brainpool_enabled());
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_CURVE, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("brainpool") != std::string::npos);
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP256r1", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP384r1", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP512r1", &supported));
+ assert_true(supported);
+#else
+ assert_false(brainpool_enabled());
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_CURVE, &features));
+ assert_non_null(features);
+ assert_true(std::string(features).find("brainpool") == std::string::npos);
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP256r1", &supported));
+ assert_false(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP384r1", &supported));
+ assert_false(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP512r1", &supported));
+ assert_false(supported);
+#endif
+}
diff --git a/src/tests/cipher_cxx.cpp b/src/tests/cipher_cxx.cpp
new file mode 100644
index 0000000..b5f7f83
--- /dev/null
+++ b/src/tests/cipher_cxx.cpp
@@ -0,0 +1,381 @@
+/*
+ * Copyright (c) 2017-2021 [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 <fstream>
+#include <vector>
+#include <string>
+
+#include <cstring>
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include "utils.h"
+#include <vector>
+#include <string>
+#include <crypto/cipher.hpp>
+#include <crypto/mem.h>
+
+static std::vector<uint8_t>
+decode_hex(const char *hex)
+{
+ if (!hex) {
+ return {};
+ }
+ std::vector<uint8_t> data(strlen(hex) / 2);
+ if (!rnp::hex_decode(hex, data.data(), data.size())) {
+ throw std::invalid_argument("hex");
+ }
+ return data;
+}
+
+static void
+test_cipher(pgp_symm_alg_t alg,
+ pgp_cipher_mode_t mode,
+ size_t tag_size,
+ bool disable_padding,
+ const char * key_hex,
+ const char * iv_hex,
+ const char * ad_hex,
+ const char * pt_hex,
+ const char * expected_ct_hex)
+{
+ const std::vector<uint8_t> key(decode_hex(key_hex));
+ const std::vector<uint8_t> iv(decode_hex(iv_hex));
+ const std::vector<uint8_t> ad(decode_hex(ad_hex));
+ const std::vector<uint8_t> pt(decode_hex(pt_hex));
+ const std::vector<uint8_t> expected_ct(decode_hex(expected_ct_hex));
+
+ 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<uint8_t> ct;
+ // make room for padding
+ ct.resize(((pt.size() + tag_size) / block_size + 1) * block_size);
+ // set key & iv
+ assert_true(enc->set_key(key.data(), key.size()));
+ assert_true(enc->set_iv(iv.data(), iv.size()));
+ if (!ad.empty()) {
+ assert_true(enc->set_ad(ad.data(), ad.size()));
+ }
+
+ // encrypt all in one go
+ size_t output_written, input_consumed;
+ assert_true(enc->finish(
+ ct.data(), ct.size(), &output_written, pt.data(), pt.size(), &input_consumed));
+ ct.resize(output_written);
+ assert_memory_equal(ct.data(), expected_ct.data(), expected_ct.size());
+
+ // start over
+ enc.reset(Cipher::encryption(alg, mode, tag_size, disable_padding).release());
+ assert_true(enc->set_key(key.data(), key.size()));
+ assert_true(enc->set_iv(iv.data(), iv.size()));
+ if (!ad.empty()) {
+ assert_true(enc->set_ad(ad.data(), ad.size()));
+ }
+ ct.clear();
+ ct.resize(((pt.size() + tag_size) / block_size + 1) * block_size);
+ // encrypt in pieces
+ assert_memory_not_equal(ct.data(), expected_ct.data(), expected_ct.size());
+ // all except the last block
+ size_t nonfinal_bytes = rnp_round_up(pt.size(), ud) - ud;
+ output_written = 0;
+ input_consumed = 0;
+ size_t written, consumed;
+ while (input_consumed != nonfinal_bytes) {
+ assert_true(enc->update(ct.data() + output_written,
+ ct.size() - output_written,
+ &written,
+ pt.data() + input_consumed,
+ ud,
+ &consumed));
+ output_written += written;
+ input_consumed += consumed;
+ }
+ assert_true(enc->finish(ct.data() + output_written,
+ ct.size() - output_written,
+ &written,
+ pt.data() + input_consumed,
+ pt.size() - input_consumed,
+ &consumed));
+ output_written += written;
+ ct.resize(output_written);
+ assert_int_equal(ct.size(), expected_ct.size());
+ assert_memory_equal(ct.data(), expected_ct.data(), expected_ct.size());
+ enc.reset();
+
+ // decrypt
+ auto dec = Cipher::decryption(alg, mode, tag_size, disable_padding);
+ assert_true(dec->set_key(key.data(), key.size()));
+ assert_true(dec->set_iv(iv.data(), iv.size()));
+ if (!ad.empty()) {
+ assert_true(dec->set_ad(ad.data(), ad.size()));
+ }
+ // decrypt in pieces
+ std::vector<uint8_t> decrypted(ct.size());
+ // all except the last block but see below for openssl
+ nonfinal_bytes = rnp_round_up(ct.size(), ud) - ud;
+#ifdef CRYPTO_BACKEND_OPENSSL
+ /* Since ossl backend sets tag explicitly tag bytes cannot be
+ split between two blocks.
+ The issue may easily occur is (for example)
+ us = 16
+ ct.size() = 24
+ tag_size=16
+ */
+ if (ct.size() - nonfinal_bytes < tag_size) {
+ nonfinal_bytes = ct.size() - tag_size;
+ }
+#endif // CRYPTO_BACKEND_OPENSSL
+ output_written = 0;
+ input_consumed = 0;
+ while (input_consumed != nonfinal_bytes) {
+ assert_true(dec->update(decrypted.data() + output_written,
+ decrypted.size() - output_written,
+ &written,
+ (const uint8_t *) ct.data() + input_consumed,
+ // ++++ ud,
+ std::min(ud, nonfinal_bytes - input_consumed),
+ &consumed));
+ output_written += written;
+ input_consumed += consumed;
+ }
+ assert_true(dec->finish(decrypted.data() + output_written,
+ decrypted.size() - output_written,
+ &written,
+ (const uint8_t *) ct.data() + input_consumed,
+ ct.size() - input_consumed,
+ &consumed));
+ output_written += written;
+ decrypted.resize(output_written);
+ assert_int_equal(decrypted.size(), pt.size());
+ assert_memory_equal(decrypted.data(), pt.data(), pt.size());
+
+ // decrypt with a bad tag
+ if (tag_size != 0) {
+ dec.reset(Cipher::decryption(alg, mode, tag_size, disable_padding).release());
+ assert_true(dec->set_key(key.data(), key.size()));
+ assert_true(dec->set_iv(iv.data(), iv.size()));
+ if (!ad.empty()) {
+ assert_true(dec->set_ad(ad.data(), ad.size()));
+ }
+ // decrypt in pieces
+ std::vector<uint8_t> decrypted(ct.size());
+ // all except the last block but see above for openssl
+ nonfinal_bytes = rnp_round_up(ct.size(), ud) - ud;
+#ifdef CRYPTO_BACKEND_OPENSSL
+ if (ct.size() - nonfinal_bytes < tag_size) {
+ nonfinal_bytes = ct.size() - tag_size;
+ }
+#endif // CRYPTO_BACKEND_OPENSSL
+
+ output_written = 0;
+ input_consumed = 0;
+ while (input_consumed != nonfinal_bytes) {
+ assert_true(dec->update(decrypted.data() + output_written,
+ decrypted.size() - output_written,
+ &written,
+ (const uint8_t *) ct.data() + input_consumed,
+ // ++++ ud,
+ std::min(ud, nonfinal_bytes - input_consumed),
+ &consumed));
+ output_written += written;
+ input_consumed += consumed;
+ }
+ // tamper with the tag
+ ct.back() ^= 0xff;
+ assert_false(dec->finish(decrypted.data() + output_written,
+ decrypted.size() - output_written,
+ &written,
+ (const uint8_t *) ct.data() + input_consumed,
+ ct.size() - input_consumed,
+ &consumed));
+ }
+}
+
+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 -- Appendix A -- Sample Results
+ // ( The first ten test sets )
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F", // key
+ "BBAA99887766554433221100", // nounce
+ nullptr, // ad
+ nullptr, // data
+ "785407BFFFC8AD9EDCC5520AC9111EE6");
+
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221101",
+ "0001020304050607",
+ "0001020304050607",
+ "6820B3657B6F615A5725BDA0D3B4EB3A257C9AF1F8F03009");
+
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221102",
+ "0001020304050607",
+ nullptr,
+ "81017F8203F081277152FADE694A0A00");
+
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221103",
+ nullptr,
+ "0001020304050607",
+ "45DD69F8F5AAE72414054CD1F35D82760B2CD00D2F99BFA9");
+
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221104",
+ "000102030405060708090A0B0C0D0E0F",
+ "000102030405060708090A0B0C0D0E0F",
+ "571D535B60B277188BE5147170A9A22C3AD7A4FF3835B8C5701C1CCEC8FC3358");
+
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221105",
+ "000102030405060708090A0B0C0D0E0F",
+ nullptr,
+ "8CF761B6902EF764462AD86498CA6B97");
+
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221106",
+ nullptr,
+ "000102030405060708090A0B0C0D0E0F",
+ "5CE88EC2E0692706A915C00AEB8B2396F40E1C743F52436BDF06D8FA1ECA343D");
+
+ test_cipher(
+ PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221107",
+ "000102030405060708090A0B0C0D0E0F1011121314151617",
+ "000102030405060708090A0B0C0D0E0F1011121314151617",
+ "1CA2207308C87C010756104D8840CE1952F09673A448A122C92C62241051F57356D7F3C90BB0E07F");
+
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221108",
+ "000102030405060708090A0B0C0D0E0F1011121314151617",
+ nullptr,
+ "6DC225A071FC1B9F7C69F93B0F1E10DE");
+
+ test_cipher(
+ PGP_SA_AES_128,
+ PGP_CIPHER_MODE_OCB,
+ 16,
+ false,
+ "000102030405060708090A0B0C0D0E0F",
+ "BBAA99887766554433221109",
+ nullptr,
+ "000102030405060708090A0B0C0D0E0F1011121314151617",
+ "221BD0DE7FA6FE993ECCD769460A0AF2D6CDED0C395B1C3CE725F32494B9F914D85C0B1EB38357FF");
+}
+
+TEST_F(rnp_tests, test_cipher_aes_128_cbc)
+{
+ // botan test vectors
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_CBC,
+ 0,
+ false,
+ "10d6f8e78c0ccf8736e4307aaf5b07ef",
+ "3eb182d95bbd5a609aecfb59a0ca898b",
+ NULL,
+ "3a",
+ "a7d290687ae325054a8691014d6821d7");
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_CBC,
+ 0,
+ false,
+ "10d6f8e78c0ccf8736e4307aaf5b07ef",
+ "3eb182d95bbd5a609aecfb59a0ca898b",
+ NULL,
+ "3a513eb569a503b4413b31fa883ddc88",
+ "0cbaf4fa94df265fe264633a994bc25fc7654f19c282a3e2db81499c941ca2b3");
+}
+
+TEST_F(rnp_tests, test_cipher_aes_128_cbc_nopadding)
+{
+ // botan test vectors
+ test_cipher(PGP_SA_AES_128,
+ PGP_CIPHER_MODE_CBC,
+ 0,
+ true,
+ "1f8e4973953f3fb0bd6b16662e9a3c17",
+ "2fe2b333ceda8f98f4a99b40d2cd34a8",
+ NULL,
+ "45cf12964fc824ab76616ae2f4bf0822",
+ "0f61c4d44c5147c03c195ad7e2cc12b2");
+}
diff --git a/src/tests/cli.cpp b/src/tests/cli.cpp
new file mode 100644
index 0000000..a96115c
--- /dev/null
+++ b/src/tests/cli.cpp
@@ -0,0 +1,857 @@
+/*
+ * Copyright (c) 2018-2019 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp_tests.h"
+#include "support.h"
+#include "time-utils.h"
+
+#ifdef HAVE_SYS_WAIT_H
+#include <sys/wait.h>
+#else
+#ifndef WIFEXITED
+#define WIFEXITED(stat) (((*((int *) &(stat))) & 0xC0000000) == 0)
+#endif
+
+#ifndef WEXITSTATUS
+#define WEXITSTATUS(stat) (*((int *) &(stat)))
+#endif
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+#include "str-utils.h"
+#endif
+
+int rnp_main(int argc, char **argv);
+int rnpkeys_main(int argc, char **argv);
+
+static int
+call_rnp(const char *cmd, ...)
+{
+ int argc = 0;
+ int res;
+ char ** argv = (char **) calloc(32, sizeof(char *));
+ va_list args;
+
+ if (!argv) {
+ return -1;
+ }
+
+ va_start(args, cmd);
+ while (cmd) {
+ argv[argc++] = (char *) cmd;
+ cmd = va_arg(args, char *);
+ }
+ va_end(args);
+ /* reset state of getopt_long used in rnp */
+ optind = 1;
+
+ if (!strcmp(argv[0], "rnp")) {
+ res = rnp_main(argc, argv);
+ } else if (!strcmp(argv[0], "rnpkeys")) {
+ res = rnpkeys_main(argc, argv);
+ } else {
+ res = -1;
+ }
+ free(argv);
+
+ return res;
+}
+
+#define KEYS "data/keyrings"
+#define GENKEYS "data/keyrings_genkey_tmp"
+#define MKEYS "data/test_stream_key_merge/"
+#define FILES "data/test_cli"
+#define G10KEYS "data/test_stream_key_load/g10"
+
+TEST_F(rnp_tests, test_cli_rnp_keyfile)
+{
+ int ret;
+
+ /* sign with keyfile, using default key */
+ ret = call_rnp("rnp",
+ "--keyfile",
+ MKEYS "key-sec.asc",
+ "--password",
+ "password",
+ "-s",
+ FILES "/hello.txt",
+ NULL);
+ assert_int_equal(ret, 0);
+ assert_true(rnp_file_exists(FILES "/hello.txt.pgp"));
+ /* verify signed file */
+ ret =
+ call_rnp("rnp", "--keyfile", MKEYS "key-pub.asc", "-v", FILES "/hello.txt.pgp", NULL);
+ assert_int_equal(ret, 0);
+ assert_int_equal(rnp_unlink(FILES "/hello.txt.pgp"), 0);
+
+ /* sign with keyfile, using user id */
+ ret = call_rnp("rnp",
+ "-f",
+ MKEYS "key-sec.asc",
+ "-u",
+ "key-merge-uid-2",
+ "--password",
+ "password",
+ "--armor",
+ "-s",
+ FILES "/hello.txt",
+ NULL);
+ assert_int_equal(ret, 0);
+ assert_true(rnp_file_exists(FILES "/hello.txt.asc"));
+ /* verify signed file */
+ ret = call_rnp("rnp", "-f", MKEYS "key-pub.asc", "-v", FILES "/hello.txt.asc", NULL);
+ assert_int_equal(ret, 0);
+ /* verify with key without self-signature - should fail */
+ ret =
+ call_rnp("rnp", "-f", MKEYS "key-pub-just-key.pgp", "-v", FILES "/hello.txt.asc", NULL);
+ assert_int_not_equal(ret, 0);
+ assert_int_equal(rnp_unlink(FILES "/hello.txt.asc"), 0);
+
+ /* encrypt with keyfile, using default key */
+ ret = call_rnp("rnp", "--keyfile", MKEYS "key-pub.asc", "-e", FILES "/hello.txt", NULL);
+ assert_int_equal(ret, 0);
+ assert_true(rnp_file_exists(FILES "/hello.txt.pgp"));
+ /* decrypt it with raw seckey, without userids and sigs */
+ ret = call_rnp("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 */
+ ret = call_rnp("rnp",
+ "--keyfile",
+ MKEYS "key-pub.asc",
+ "-r",
+ "16CD16F267CCDD4F",
+ "--armor",
+ "-e",
+ FILES "/hello.txt",
+ NULL);
+ assert_int_not_equal(ret, 0);
+ assert_false(rnp_file_exists(FILES "/hello.txt.asc"));
+ /* now encrypt with keyfile, using the encrypting subkey */
+ ret = call_rnp("rnp",
+ "--keyfile",
+ MKEYS "key-pub.asc",
+ "-r",
+ "AF1114A47F5F5B28",
+ "--armor",
+ "-e",
+ FILES "/hello.txt",
+ NULL);
+ assert_int_equal(ret, 0);
+ assert_true(rnp_file_exists(FILES "/hello.txt.asc"));
+ /* fail to decrypt it with pubkey */
+ ret = call_rnp("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);
+}
+
+static bool
+test_cli_g10_key_sign(const char *userid)
+{
+ /* create signature */
+ int ret = call_rnp("rnp",
+ "--homedir",
+ G10KEYS,
+ "--password",
+ "password",
+ "-u",
+ userid,
+ "-s",
+ FILES "/hello.txt",
+ NULL);
+ if (ret) {
+ rnp_unlink(FILES "/hello.txt.pgp");
+ return false;
+ }
+
+ /* verify back */
+ ret = call_rnp(
+ "rnp", "--homedir", G10KEYS, "-v", FILES "/hello.txt.pgp", "--output", "-", NULL);
+ rnp_unlink(FILES "/hello.txt.pgp");
+ return !ret;
+}
+
+static bool
+test_cli_g10_key_encrypt(const char *userid)
+{
+ /* encrypt */
+ int ret =
+ call_rnp("rnp", "--homedir", G10KEYS, "-r", userid, "-e", FILES "/hello.txt", NULL);
+ if (ret) {
+ rnp_unlink(FILES "/hello.txt.pgp");
+ return false;
+ }
+
+ /* decrypt it back */
+ ret = call_rnp("rnp",
+ "--homedir",
+ G10KEYS,
+ "--password",
+ "password",
+ "-d",
+ FILES "/hello.txt.pgp",
+ "--output",
+ "-",
+ NULL);
+ rnp_unlink(FILES "/hello.txt.pgp");
+ return !ret;
+}
+
+TEST_F(rnp_tests, test_cli_g10_operations)
+{
+ int ret;
+
+ /* sign with default g10 key */
+ ret = call_rnp(
+ "rnp", "--homedir", G10KEYS, "--password", "password", "-s", FILES "/hello.txt", NULL);
+ assert_int_equal(ret, 0);
+
+ /* verify back */
+ ret = call_rnp("rnp", "--homedir", G10KEYS, "-v", FILES "/hello.txt.pgp", NULL);
+ assert_int_equal(ret, 0);
+ assert_int_equal(rnp_unlink(FILES "/hello.txt.pgp"), 0);
+
+ /* encrypt with default g10 key */
+ ret = call_rnp("rnp", "--homedir", G10KEYS, "-e", FILES "/hello.txt", NULL);
+ assert_int_equal(ret, 0);
+
+ /* decrypt it back */
+ ret = call_rnp("rnp",
+ "--homedir",
+ 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 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_true(test_cli_g10_key_encrypt("6e2f73008f8b8d6e"));
+
+#ifdef CRYPTO_BACKEND_BOTAN
+ /* GnuPG extended key format requires AEAD support that is available for BOTAN backend
+ only https://github.com/rnpgp/rnp/issues/1642 (???)
+ */
+ /* check new rsa/rsa key, key is SC while subkey is E. */
+ assert_true(test_cli_g10_key_sign("bd860a52d1899c0f"));
+ assert_true(test_cli_g10_key_encrypt("bd860a52d1899c0f"));
+ assert_false(test_cli_g10_key_sign("8e08d46a37414996"));
+ assert_true(test_cli_g10_key_encrypt("8e08d46a37414996"));
+#endif
+
+ /* check ed25519 key */
+ assert_true(test_cli_g10_key_sign("cc786278981b0728"));
+ assert_false(test_cli_g10_key_encrypt("cc786278981b0728"));
+
+ /* check ed25519/x25519 key */
+ assert_true(test_cli_g10_key_sign("941822a0fc1b30a5"));
+ assert_true(test_cli_g10_key_encrypt("941822a0fc1b30a5"));
+ assert_false(test_cli_g10_key_sign("c711187e594376af"));
+ assert_true(test_cli_g10_key_encrypt("c711187e594376af"));
+
+ /* check p256 key */
+ assert_true(test_cli_g10_key_sign("23674f21b2441527"));
+ assert_true(test_cli_g10_key_encrypt("23674f21b2441527"));
+ assert_false(test_cli_g10_key_sign("37e285e9e9851491"));
+ assert_true(test_cli_g10_key_encrypt("37e285e9e9851491"));
+
+ /* check p384 key */
+ assert_true(test_cli_g10_key_sign("242a3aa5ea85f44a"));
+ assert_true(test_cli_g10_key_encrypt("242a3aa5ea85f44a"));
+ assert_false(test_cli_g10_key_sign("e210e3d554a4fad9"));
+ assert_true(test_cli_g10_key_encrypt("e210e3d554a4fad9"));
+
+ /* check p521 key */
+ assert_true(test_cli_g10_key_sign("2092ca8324263b6a"));
+ assert_true(test_cli_g10_key_encrypt("2092ca8324263b6a"));
+ assert_false(test_cli_g10_key_sign("9853df2f6d297442"));
+ assert_true(test_cli_g10_key_encrypt("9853df2f6d297442"));
+
+ /* check bp256 key */
+ assert_true(test_cli_g10_key_sign("d0c8a3daf9e0634a") == brainpool_enabled());
+ assert_true(test_cli_g10_key_encrypt("d0c8a3daf9e0634a") == brainpool_enabled());
+ assert_false(test_cli_g10_key_sign("2edabb94d3055f76"));
+ assert_true(test_cli_g10_key_encrypt("2edabb94d3055f76") == brainpool_enabled());
+
+ /* check bp384 key */
+ assert_true(test_cli_g10_key_sign("6cf2dce85599ada2") == brainpool_enabled());
+ assert_true(test_cli_g10_key_encrypt("6cf2dce85599ada2") == brainpool_enabled());
+ assert_false(test_cli_g10_key_sign("cff1bb6f16d28191"));
+ assert_true(test_cli_g10_key_encrypt("cff1bb6f16d28191") == brainpool_enabled());
+
+ /* check bp512 key */
+ assert_true(test_cli_g10_key_sign("aa5c58d14f7b8f48") == brainpool_enabled());
+ assert_true(test_cli_g10_key_encrypt("aa5c58d14f7b8f48") == brainpool_enabled());
+ assert_false(test_cli_g10_key_sign("20cdaa1482ba79ce"));
+ assert_true(test_cli_g10_key_encrypt("20cdaa1482ba79ce") == brainpool_enabled());
+
+ /* check secp256k1 key */
+ assert_true(test_cli_g10_key_sign("3ea5bb6f9692c1a0"));
+ assert_true(test_cli_g10_key_encrypt("3ea5bb6f9692c1a0"));
+ assert_false(test_cli_g10_key_sign("7635401f90d3e533"));
+ assert_true(test_cli_g10_key_encrypt("7635401f90d3e533"));
+}
+
+TEST_F(rnp_tests, test_cli_rnpkeys_unicode)
+{
+#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
+ std::string homedir_s = std::string(m_dir) + "/unicode";
+ rnp_mkdir(homedir_s.c_str());
+ 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);
+ PROCESS_INFORMATION pi;
+ ZeroMemory(&pi, sizeof pi);
+ BOOL res = CreateProcessA(NULL, // (LPSTR) path_s.c_str(), // Module name
+ (LPSTR) cmdline_s.c_str(), // Command line
+ NULL, // Process handle not inheritable
+ NULL, // Thread handle not inheritable
+ FALSE, // Handle inheritance
+ 0, // Creation flags
+ NULL, // Use parent's environment block
+ NULL, // Use parent's starting directory
+ &si, // Pointer to STARTUPINFO structure
+ &pi); // Pointer to PROCESS_INFORMATION structure
+ assert_true(res);
+ assert_true(WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_OBJECT_0);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+
+ std::wstring homedir_ws = wstr_from_utf8(homedir_s);
+ std::wstring path_ws = wstr_from_utf8(path_s);
+ std::wstring cmdline_ws = path_ws + L" --numbits 2048 --homedir " + homedir_ws +
+ L" --password password --userid " + uid2_wide +
+ L" --generate-key";
+ STARTUPINFOW siw;
+ ZeroMemory(&siw, sizeof siw);
+ ZeroMemory(&pi, sizeof pi);
+ res = CreateProcessW(NULL,
+ (LPWSTR) cmdline_ws.c_str(), // Command line
+ NULL, // Process handle not inheritable
+ NULL, // Thread handle not inheritable
+ FALSE, // Handle inheritance
+ 0, // Creation flags
+ NULL, // Use parent's environment block
+ NULL, // Use parent's starting directory
+ &siw, // Pointer to STARTUPINFO structure
+ &pi); // Pointer to PROCESS_INFORMATION structure
+ assert_true(res);
+ assert_true(WaitForSingleObject(pi.hProcess, INFINITE) == WAIT_OBJECT_0);
+ CloseHandle(pi.hProcess);
+ CloseHandle(pi.hThread);
+ // Load the keyring and check what was actually written
+ rnp_ffi_t ffi;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_path(&input, "unicode/pubring.gpg"));
+ assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ rnp_input_destroy(input);
+
+ // convert from ACP to wide char via Windows native mechanism
+ int convertResult = MultiByteToWideChar(acp, 0, uid_acp.c_str(), uid_acp.size(), NULL, 0);
+ assert_true(convertResult > 0);
+ std::wstring uid_wide;
+ uid_wide.resize(convertResult);
+ convertResult = MultiByteToWideChar(
+ acp, 0, uid_acp.c_str(), uid_acp.size(), &uid_wide[0], (int) uid_wide.size());
+ assert_true(convertResult > 0);
+
+ // we expect to find UID in UTF-8
+ std::string uid_utf8 = wstr_to_utf8(uid_wide);
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", uid_utf8.c_str(), &key));
+ assert_non_null(key);
+
+ size_t uids = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uids));
+ assert_int_equal(uids, 1);
+
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_non_null(uid);
+
+ size_t size = 0;
+ char * data = NULL;
+ assert_rnp_success(rnp_uid_get_data(uid, (void **) &data, &size));
+ std::string uid_read(data, data + size);
+ assert_int_equal(0, uid_read.compare(uid_utf8));
+ rnp_buffer_destroy(data);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ uid_utf8 = wstr_to_utf8(uid2_wide);
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", uid_utf8.c_str(), &key));
+ assert_non_null(key);
+
+ uids = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uids));
+ assert_int_equal(uids, 1);
+
+ uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_non_null(uid);
+
+ size = 0;
+ data = NULL;
+ assert_rnp_success(rnp_uid_get_data(uid, (void **) &data, &size));
+ std::string uid2_read(data, data + size);
+ assert_int_equal(0, uid2_read.compare(uid_utf8));
+ rnp_buffer_destroy(data);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+#endif
+}
+
+TEST_F(rnp_tests, test_cli_rnp)
+{
+ int ret;
+ assert_int_equal(0, call_rnp("rnp", "--version", NULL));
+
+ /* sign with default key */
+ ret = call_rnp("rnp",
+ "--homedir",
+ KEYS "/1",
+ "--password",
+ "password",
+ "--sign",
+ FILES "/hello.txt",
+ NULL);
+ assert_int_equal(ret, 0);
+
+ /* encrypt with default key */
+ ret = call_rnp(
+ "rnp", "--homedir", KEYS "/1", "--encrypt", FILES "/hello.txt", "--overwrite", NULL);
+ assert_int_equal(ret, 0);
+
+ /* sign and verify back with g10 key */
+ ret = call_rnp("rnp",
+ "--homedir",
+ KEYS "/3",
+ "-u",
+ "4BE147BB22DF1E60",
+ "--password",
+ "password",
+ "--sign",
+ FILES "/hello.txt",
+ "--overwrite",
+ NULL);
+ assert_int_equal(ret, 0);
+ ret = call_rnp("rnp", "--homedir", KEYS "/3", "--verify", FILES "/hello.txt.pgp", NULL);
+ assert_int_equal(ret, 0);
+
+ /* encrypt and decrypt back with g10 key */
+ ret = call_rnp("rnp",
+ "--homedir",
+ KEYS "/3",
+ "-r",
+ "4BE147BB22DF1E60",
+ "--encrypt",
+ FILES "/hello.txt",
+ "--overwrite",
+ NULL);
+ assert_int_equal(ret, 0);
+ ret = call_rnp("rnp",
+ "--homedir",
+ KEYS "/3",
+ "--password",
+ "password",
+ "--decrypt",
+ FILES "/hello.txt.pgp",
+ "--output",
+ "-",
+ NULL);
+ assert_int_equal(ret, 0);
+}
+
+TEST_F(rnp_tests, test_cli_examples)
+{
+ auto examples_path = rnp::path::append(original_dir(), "../examples");
+ /* key generation example */
+ 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::path::append(examples_path, "encrypt");
+ assert_false(example_path.empty());
+ assert_int_equal(system(example_path.c_str()), 0);
+
+ /* decryption sample */
+ 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::path::append(examples_path, "sign");
+ assert_false(example_path.empty());
+ assert_int_equal(system(example_path.c_str()), 0);
+
+ /* verification sample */
+ 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;
+ assert_int_equal(0, call_rnp("rnpkeys", "--version", NULL));
+
+ /* test keys listing */
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/1", "--list-keys", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/1", "--list-keys", "--with-sigs", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/2", "--list-keys", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/2", "--list-keys", "--with-sigs", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/3", "--list-keys", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/3", "--list-keys", "--with-sigs", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/5", "--list-keys", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/5", "--list-keys", "--with-sigs", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", G10KEYS, "--list-keys", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", G10KEYS, "--list-keys", "--with-sigs", NULL);
+ assert_int_equal(ret, 0);
+
+ /* test single key listing command */
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/1", "--list-keys", "2fcadf05ffa501bb", NULL);
+ assert_int_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/1", "--list-keys", "00000000", NULL);
+ assert_int_not_equal(ret, 0);
+
+ ret = call_rnp("rnpkeys", "--homedir", KEYS "/1", "--list-keys", "zzzzzzzz", NULL);
+ assert_int_not_equal(ret, 0);
+}
+
+// check both primary key and subkey for the given userid
+static int
+key_expiration_check(rnp_key_store_t *keystore,
+ const char * userid,
+ uint32_t expectedExpiration)
+{
+ int res = -1; // not found
+ for (auto &key : keystore->keys) {
+ pgp_key_t *pk;
+ if (key.is_primary()) {
+ pk = &key;
+ } else {
+ if (!key.has_primary_fp()) {
+ return 0;
+ }
+ pk = rnp_key_store_get_key_by_fpr(keystore, key.primary_fp());
+ }
+ if (pk->uid_count() != 1) {
+ return 0;
+ }
+ auto uid = pk->get_uid(0).str;
+ if (uid != userid) {
+ continue;
+ }
+ auto expiration = key.expiration();
+ if (uid == "expiration_absolute@rnp" || uid == "expiration_beyond2038_absolute@rnp") {
+ auto diff = expectedExpiration < expiration ? expiration - expectedExpiration :
+ expectedExpiration - expiration;
+ // allow 10 minutes diff
+ if (diff < 600) {
+ res = 1;
+ } else {
+ return 0;
+ }
+ } else {
+ if (expectedExpiration == expiration) {
+ res = 1;
+ } else {
+ RNP_LOG(
+ "key_expiration_check error: userid=%s expectedExpiration=%u expiration=%u",
+ userid,
+ expectedExpiration,
+ expiration);
+ return 0;
+ }
+ }
+ }
+ return res;
+}
+
+static int
+key_generate(const char *homedir, const char *userid, const char *expiration)
+{
+ int ret = call_rnp("rnpkeys",
+ "--password",
+ "1234",
+ "--homedir",
+ homedir,
+ "--generate-key",
+ "--expiration",
+ expiration,
+ "--userid",
+ userid,
+ "--s2k-iterations",
+ "65536",
+ "--numbits",
+ "1024",
+ NULL);
+ return ret;
+}
+
+TEST_F(rnp_tests, test_cli_rnpkeys_genkey)
+{
+ assert_false(RNP_MKDIR(GENKEYS, S_IRWXU));
+ time_t basetime = time(NULL);
+ time_t rawtime = basetime + 604800;
+ time_t y2k38time = INT32_MAX;
+ uint32_t expected_diff_beyond2038_absolute;
+ if (rnp_y2k38_warning(y2k38time)) {
+ // we're on the system that doesn't support dates beyond y2k38
+ auto diff_to_y2k38 = y2k38time - basetime;
+ expected_diff_beyond2038_absolute = diff_to_y2k38;
+ } else {
+ struct tm tm2100;
+ rnp_localtime(time(NULL), tm2100);
+ tm2100.tm_hour = 0;
+ tm2100.tm_min = 0;
+ tm2100.tm_sec = 0;
+ tm2100.tm_mday = 1;
+ tm2100.tm_mon = 0;
+ tm2100.tm_year = 200;
+ /* line below is required to correctly handle DST changes */
+ tm2100.tm_isdst = -1;
+ expected_diff_beyond2038_absolute = mktime(&tm2100) - basetime;
+ }
+ struct tm timeinfo;
+ rnp_localtime(rawtime, timeinfo);
+ // clear hours, minutes and seconds
+ timeinfo.tm_hour = 0;
+ timeinfo.tm_min = 0;
+ timeinfo.tm_sec = 0;
+ rawtime = mktime(&timeinfo);
+ auto exp =
+ fmt("%d-%02d-%02d", timeinfo.tm_year + 1900, timeinfo.tm_mon + 1, timeinfo.tm_mday);
+
+ // these should fail and not go to the keystore
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_negative@rnp", "-1"), 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_unrecognized_1@rnp", "1z"), 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_unrecognized_2@rnp", "now"), 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_unrecognized_3@rnp", "00000-01-01"),
+ 0);
+ assert_int_not_equal(
+ key_generate(GENKEYS, "expiration_integer_overflow@rnp", "1234567890123456789"), 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_32bit_overflow@rnp", "4294967296"),
+ 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_overflow_day@rnp", "2037-02-29"),
+ 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_overflow_month@rnp", "2037-13-01"),
+ 0);
+ if (!rnp_y2k38_warning(y2k38time)) {
+ assert_int_not_equal(
+ key_generate(GENKEYS, "expiration_overflow_year@rnp", "2337-01-01"), 0);
+ }
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_underflow_day@rnp", "2037-02-00"),
+ 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_underflow_month@rnp", "2037-00-01"),
+ 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_underflow_year@rnp", "1800-01-01"),
+ 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_overflow@rnp", "200y"), 0);
+ assert_int_not_equal(key_generate(GENKEYS, "expiration_past@rnp", "2021-01-01"), 0);
+
+ // these should pass and go to the keystore -- 17 primary keys and 17 subkeys
+ assert_int_equal(key_generate(GENKEYS, "expiration_beyond2038_relative@rnp", "20y"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_beyond2038_absolute@rnp", "2100-01-01"),
+ 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_absolute@rnp", exp.c_str()), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_max_32bit@rnp", "4294967295"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_max_32bit_h@rnp", "1193046h"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_1sec@rnp", "1"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_1hour@rnp", "1h"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_1day@rnp", "1d"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_1week@rnp", "1w"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_1month@rnp", "1m"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_1year@rnp", "1y"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_2sec@rnp", "2"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_2hours@rnp", "2h"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_2days@rnp", "2d"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_2weeks@rnp", "2w"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_2months@rnp", "2m"), 0);
+ assert_int_equal(key_generate(GENKEYS, "expiration_2years@rnp", "2y"), 0);
+
+ auto keystore = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
+ pgp_source_t src = {};
+ assert_rnp_success(init_file_src(&src, GENKEYS "/pubring.gpg"));
+ assert_true(rnp_key_store_load_from_src(keystore, &src, NULL));
+ assert_int_equal(rnp_key_store_get_key_count(keystore), 34);
+ src_close(&src);
+ assert_int_equal(key_expiration_check(keystore, "expiration_max_32bit@rnp", 4294967295),
+ 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_max_32bit_h@rnp", 4294965600),
+ 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_1sec@rnp", 1), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_1hour@rnp", 3600), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_1day@rnp", 86400), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_1week@rnp", 604800), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_1month@rnp", 2678400), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_1year@rnp", 31536000), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_2sec@rnp", 2), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_2hours@rnp", 7200), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_2days@rnp", 172800), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_2weeks@rnp", 1209600), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_2months@rnp", 5356800), 1);
+ assert_int_equal(key_expiration_check(keystore, "expiration_2years@rnp", 63072000), 1);
+ assert_int_equal(
+ key_expiration_check(keystore, "expiration_absolute@rnp", rawtime - basetime), 1);
+ assert_int_equal(key_expiration_check(keystore,
+ "expiration_beyond2038_absolute@rnp",
+ expected_diff_beyond2038_absolute),
+ 1);
+ assert_int_equal(
+ key_expiration_check(keystore, "expiration_beyond2038_relative@rnp", 630720000), 1);
+
+ delete keystore;
+ delete_recursively(GENKEYS);
+}
+
+TEST_F(rnp_tests, test_cli_dump)
+{
+ 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.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.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.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.c_str(), KEYS "/1/");
+ assert_true(chnum < (int) sizeof(cmd));
+ status = system(cmd);
+ assert_true(WIFEXITED(status));
+ assert_int_not_equal(WEXITSTATUS(status), 0);
+}
+
+TEST_F(rnp_tests, test_cli_logname)
+{
+ char * logname = getenv("LOGNAME");
+ char * user = getenv("USER");
+ std::string testname(user ? user : "user");
+ testname.append("-test-user");
+
+ setenv("LOGNAME", testname.c_str(), 1);
+ assert_string_equal(getenv_logname(), testname.c_str());
+ if (user) {
+ unsetenv("LOGNAME");
+ assert_string_equal(getenv_logname(), user);
+ }
+
+ if (logname) {
+ setenv("LOGNAME", logname, 1);
+ } else {
+ unsetenv("LOGNAME");
+ }
+}
diff --git a/src/tests/cli_common.py b/src/tests/cli_common.py
new file mode 100644
index 0000000..12bf5d8
--- /dev/null
+++ b/src/tests/cli_common.py
@@ -0,0 +1,237 @@
+import sys
+import distutils.spawn
+import random
+import string
+import logging
+import os
+import re
+from subprocess import Popen, PIPE
+
+RNP_ROOT = None
+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):
+ return self.message + '\n' + self.log
+
+def set_workdir(dir):
+ global WORKDIR
+ WORKDIR = dir
+
+def is_windows():
+ return sys.platform.startswith('win') or sys.platform.startswith('msys')
+
+def path_for_gpg(path):
+ # GPG built for mingw/msys doesn't work with Windows paths
+ if re.match(r'^[a-z]:[\\\/].*', path.lower()):
+ path = '/' + path[0] + '/' + path[3:].replace('\\', '/')
+ return path
+
+def raise_err(msg, log = None):
+ raise CLIError(msg, log)
+
+def size_to_readable(num, suffix = 'B'):
+ for unit in ['', 'K', 'M', 'G', 'T', 'P', 'E', 'Z']:
+ if abs(num) < 1024.0:
+ return "%3.1f%s%s" % (num, unit, suffix)
+ num /= 1024.0
+ return "%.1f%s%s" % (num, 'Yi', suffix)
+
+def list_upto(lst, count):
+ return (list(lst)*(count//len(lst)+1))[:count]
+
+def pswd_pipe(password):
+ pr, pw = os.pipe()
+ with os.fdopen(pw, 'w') as fw:
+ fw.write(password)
+ fw.write('\n')
+ fw.write(password)
+ os.set_inheritable(pr, True)
+
+ if not is_windows():
+ return pr
+ # On Windows pipe is not inheritable so dup() is needed
+ prd = os.dup(pr)
+ os.close(pr)
+ return prd
+
+def random_text(path, size):
+ # Generate random text, with 50% probability good-compressible
+ if random.randint(0, 10) < 5:
+ st = ''.join(random.choice(string.ascii_letters + string.digits + " \t\r\n-,.")
+ for _ in range(size))
+ else:
+ st = ''.join(random.choice("abcdef0123456789 \t\r\n-,.") for _ in range(size))
+ with open(path, 'w+') as f:
+ f.write(st)
+
+def file_text(path, encoding = CONSOLE_ENCODING):
+ with open(path, 'rb') as f:
+ return f.read().decode(encoding).replace('\r\r', '\r')
+
+def find_utility(name, exitifnone = True):
+ path = distutils.spawn.find_executable(name)
+ if not path and exitifnone:
+ logging.error('Cannot find utility {}. Exiting.'.format(name))
+ sys.exit(1)
+
+ return path
+
+def rnp_file_path(relpath, check = True):
+ global RNP_ROOT
+ if not RNP_ROOT:
+ pypath = os.path.dirname(__file__)
+ RNP_ROOT = os.path.realpath(os.path.join(pypath, '../..'))
+
+ fpath = os.path.realpath(os.path.join(RNP_ROOT, relpath))
+
+ if check and not os.path.isfile(fpath):
+ raise NameError('rnp: file ' + relpath + ' not found')
+
+ return fpath
+
+def run_proc_windows(proc, params, stdin=None):
+ exe = os.path.basename(proc)
+ # test special quote cases
+ params = list(map(lambda st: st.replace('"', '\\"'), params))
+ # We need to escape empty parameters/ones with spaces with quotes
+ params = tuple(map(lambda st: st if (st and not any(x in st for x in [' ','\r','\t'])) else '"%s"' % st, [exe] + params))
+ logging.debug((proc + ' ' + ' '.join(params)).strip())
+ logging.debug('Working directory: ' + os.getcwd())
+ sys.stdout.flush()
+
+ stdin_path = os.path.join(WORKDIR, 'stdin.txt')
+ stdout_path = os.path.join(WORKDIR, 'stdout.txt')
+ stderr_path = os.path.join(WORKDIR, 'stderr.txt')
+ pass_path = os.path.join(WORKDIR, 'pass.txt')
+ passfd = 0
+ passfo = None
+ try:
+ idx = params.index('--pass-fd')
+ if idx < len(params):
+ passfd = int(params[idx+1])
+ passfo = os.fdopen(passfd, 'r', closefd=False)
+ except (ValueError, OSError):
+ # Ignore if pass-fd is invalid/could not be opened
+ pass
+ # We may use pipes here (ensuring we use dup to inherit handles), but those have limited buffer
+ # so we'll need to poll process
+ if stdin:
+ with open(stdin_path, "wb+") as stdinf:
+ stdinf.write(stdin.encode() if isinstance(stdin, str) else stdin)
+ stdin_fl = os.open(stdin_path, os.O_RDONLY | os.O_BINARY)
+ stdin_no = sys.stdin.fileno()
+ stdin_cp = os.dup(stdin_no)
+ else:
+ stdin_fl = None
+ stdin_no = -1
+ stdin_cp = None
+
+ stdout_fl = os.open(stdout_path, os.O_CREAT | os.O_RDWR | os.O_BINARY)
+ stdout_no = sys.stdout.fileno()
+ stdout_cp = os.dup(stdout_no)
+ stderr_fl = os.open(stderr_path, os.O_CREAT | os.O_RDWR | os.O_BINARY)
+ stderr_no = sys.stderr.fileno()
+ stderr_cp = os.dup(stderr_no)
+ if passfo:
+ 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)
+ if stdin:
+ os.dup2(stdin_fl, stdin_no)
+ os.close(stdin_fl)
+ if passfo:
+ os.dup2(pass_fl, passfd)
+ os.close(pass_fl)
+ retcode = os.spawnv(os.P_WAIT, proc, params)
+ finally:
+ os.dup2(stdout_cp, stdout_no)
+ os.close(stdout_cp)
+ os.dup2(stderr_cp, stderr_no)
+ os.close(stderr_cp)
+ if stdin:
+ os.dup2(stdin_cp, stdin_no)
+ os.close(stdin_cp)
+ if passfo:
+ os.dup2(pass_cp, passfd)
+ os.close(pass_cp)
+ passfo.close()
+ out = file_text(stdout_path).replace('\r\n', '\n')
+ err = file_text(stderr_path).replace('\r\n', '\n')
+ os.unlink(stdout_path)
+ os.unlink(stderr_path)
+ if stdin:
+ os.unlink(stdin_path)
+ if passfo:
+ os.unlink(pass_path)
+ logging.debug(err.strip())
+ logging.debug(out.strip())
+ return (retcode, out, err)
+
+if sys.version_info >= (3,):
+ def decode_string_escape(s):
+ bts = bytes(s, 'utf-8')
+ result = u''
+ candidate = bytearray()
+ utf = bytearray()
+ for b in bts:
+ if b > 0x7F:
+ if len(candidate) > 0:
+ result += candidate.decode('unicode-escape')
+ candidate.clear()
+ utf.append(b)
+ else:
+ if len(utf) > 0:
+ result += utf.decode('utf-8')
+ utf.clear()
+ candidate.append(b)
+ if len(candidate) > 0:
+ result += candidate.decode('unicode-escape')
+ if len(utf) > 0:
+ result += utf.decode('utf-8')
+ return result
+ def _decode(s):
+ return s
+else: # Python 2
+ def decode_string_escape(s):
+ return s.encode(CONSOLE_ENCODING).decode('decode_string_escape')
+ def _decode(x):
+ return x.decode(CONSOLE_ENCODING)
+
+def run_proc(proc, params, stdin=None):
+ # On Windows we need to use spawnv() for handle inheritance in pswd_pipe()
+ if is_windows():
+ return run_proc_windows(proc, params, stdin)
+ paramline = u' '.join(map(_decode, params))
+ logging.debug((proc + ' ' + paramline).strip())
+ param_bytes = list(map(lambda x: x.encode(CONSOLE_ENCODING), params))
+ process = Popen([proc] + param_bytes, stdout=PIPE, stderr=PIPE,
+ stdin=PIPE if stdin else None, close_fds=False,
+ universal_newlines=True)
+ output, errout = process.communicate(stdin)
+ retcode = process.poll()
+ logging.debug(errout.strip())
+ logging.debug(output.strip())
+
+ return (retcode, output, errout)
+
+def run_proc_fast(proc, params):
+ with open(os.devnull, 'w') as devnull:
+ proc = Popen([proc] + params, stdout=devnull, stderr=devnull)
+ return proc.wait()
diff --git a/src/tests/cli_perf.py b/src/tests/cli_perf.py
new file mode 100755
index 0000000..9015292
--- /dev/null
+++ b/src/tests/cli_perf.py
@@ -0,0 +1,316 @@
+#!/usr/bin/env python
+
+import sys
+import tempfile
+import shutil
+import inspect
+import os
+import logging
+from timeit import default_timer as perf_timer
+from argparse import ArgumentParser
+from cli_common import (
+ find_utility,
+ run_proc,
+ run_proc_fast,
+ pswd_pipe,
+ rnp_file_path,
+ size_to_readable,
+ raise_err
+)
+
+RNP = ''
+RNPK = ''
+GPG = ''
+WORKDIR = ''
+RNPDIR = ''
+GPGDIR = ''
+RMWORKDIR = False
+SMALL_ITERATIONS = 100
+LARGE_ITERATIONS = 5
+LARGESIZE = 1024*1024*100
+SMALLSIZE = 0
+SMALLFILE = 'smalltest.txt'
+LARGEFILE = 'largetest.txt'
+PASSWORD = 'password'
+
+def setup(workdir):
+ # Searching for rnp and gnupg
+ global RNP, GPG, RNPK, WORKDIR, RNPDIR, GPGDIR, SMALLSIZE, RMWORKDIR
+ logging.basicConfig(stream=sys.stdout, format="%(message)s")
+ logging.getLogger().setLevel(logging.INFO)
+
+ RNP = rnp_file_path('src/rnp/rnp')
+ RNPK = rnp_file_path('src/rnpkeys/rnpkeys')
+ GPG = find_utility('gpg')
+ if workdir:
+ WORKDIR = workdir
+ else:
+ WORKDIR = tempfile.mkdtemp(prefix = 'rnpptmp')
+ RMWORKDIR = True
+
+ logging.debug('Setting up test in {} ...'.format(WORKDIR))
+
+ # Creating working directory and populating it with test files
+ RNPDIR = os.path.join(WORKDIR, '.rnp')
+ GPGDIR = os.path.join(WORKDIR, '.gpg')
+ os.mkdir(RNPDIR, 0o700)
+ os.mkdir(GPGDIR, 0o700)
+
+ # Generating key
+ pipe = pswd_pipe(PASSWORD)
+ params = ['--homedir', RNPDIR, '--pass-fd', str(pipe), '--userid', 'performance@rnp',
+ '--generate-key']
+ # Run key generation
+ run_proc(RNPK, params)
+ os.close(pipe)
+
+
+ # Importing keys to GnuPG so it can build trustdb and so on
+ run_proc(GPG, ['--batch', '--passphrase', '', '--homedir', GPGDIR, '--import',
+ os.path.join(RNPDIR, 'pubring.gpg'), os.path.join(RNPDIR, 'secring.gpg')])
+
+ # Generating small file for tests
+ SMALLSIZE = 3312
+ st = 'lorem ipsum dol ' * (SMALLSIZE//16+1)
+ with open(os.path.join(WORKDIR, SMALLFILE), 'w+') as small_file:
+ small_file.write(st)
+
+ # Generating large file for tests
+ print('Generating large file of size {}'.format(size_to_readable(LARGESIZE)))
+
+ st = '0123456789ABCDEF' * (1024//16)
+ with open(os.path.join(WORKDIR, LARGEFILE), 'w') as fd:
+ for i in range(0, LARGESIZE // 1024):
+ fd.write(st)
+
+def run_iterated(iterations, func, src, dst, *args):
+ runtime = 0
+
+ for i in range(0, iterations):
+ tstart = perf_timer()
+ func(src, dst, *args)
+ runtime += perf_timer() - tstart
+ os.remove(dst)
+
+ res = runtime / iterations
+ #print '{} average run time: {}'.format(func.__name__, res)
+ return res
+
+def rnp_symencrypt_file(src, dst, cipher, zlevel = 6, zalgo = 'zip', armor = False):
+ params = ['--homedir', RNPDIR, '--password', PASSWORD, '--cipher', cipher,
+ '-z', str(zlevel), '--' + zalgo, '-c', src, '--output', dst]
+ if armor:
+ params += ['--armor']
+ ret = run_proc_fast(RNP, params)
+ if ret != 0:
+ raise_err('rnp symmetric encryption failed')
+
+def rnp_decrypt_file(src, dst):
+ ret = run_proc_fast(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--decrypt', src,
+ '--output', dst])
+ if ret != 0:
+ raise_err('rnp decryption failed')
+
+def gpg_symencrypt_file(src, dst, cipher = 'AES', zlevel = 6, zalgo = 1, armor = False):
+ params = ['--homedir', GPGDIR, '-c', '-z', str(zlevel), '--s2k-count', '524288',
+ '--compress-algo', str(zalgo), '--batch', '--passphrase', PASSWORD,
+ '--cipher-algo', cipher, '--output', dst, src]
+ if armor:
+ params.insert(2, '--armor')
+ ret = run_proc_fast(GPG, params)
+ if ret != 0:
+ raise_err('gpg symmetric encryption failed for cipher ' + cipher)
+
+def gpg_decrypt_file(src, dst, keypass):
+ ret = run_proc_fast(GPG, ['--homedir', GPGDIR, '--pinentry-mode=loopback', '--batch',
+ '--yes', '--passphrase', keypass, '--trust-model', 'always',
+ '-o', dst, '-d', src])
+ if ret != 0:
+ raise_err('gpg decryption failed')
+
+def print_test_results(fsize, rnptime, gpgtime, operation):
+ if not rnptime or not gpgtime:
+ logging.info('{}:TEST FAILED'.format(operation))
+
+ if fsize == SMALLSIZE:
+ rnpruns = 1.0 / rnptime
+ gpgruns = 1.0 / gpgtime
+ runstr = '{:.2f} runs/sec vs {:.2f} runs/sec'.format(rnpruns, gpgruns)
+
+ if rnpruns >= gpgruns:
+ percents = (rnpruns - gpgruns) / gpgruns * 100
+ logging.info('{:<30}: RNP is {:>3.0f}% FASTER then GnuPG ({})'.format(
+ operation, percents, runstr))
+ else:
+ percents = (gpgruns - rnpruns) / gpgruns * 100
+ logging.info('{:<30}: RNP is {:>3.0f}% SLOWER then GnuPG ({})'.format(
+ operation, percents, runstr))
+ else:
+ rnpspeed = fsize / 1024.0 / 1024.0 / rnptime
+ gpgspeed = fsize / 1024.0 / 1024.0 / gpgtime
+ spdstr = '{:.2f} MB/sec vs {:.2f} MB/sec'.format(rnpspeed, gpgspeed)
+
+ if rnpspeed >= gpgspeed:
+ percents = (rnpspeed - gpgspeed) / gpgspeed * 100
+ logging.info('{:<30}: RNP is {:>3.0f}% FASTER then GnuPG ({})'.format(
+ operation, percents, spdstr))
+ else:
+ percents = (gpgspeed - rnpspeed) / gpgspeed * 100
+ logging.info('{:<30}: RNP is {:>3.0f}% SLOWER then GnuPG ({})'.format(
+ operation, percents, spdstr))
+
+def get_file_params(filetype):
+ if filetype == 'small':
+ infile, outfile, iterations, fsize = (SMALLFILE, SMALLFILE + '.gpg',
+ SMALL_ITERATIONS, SMALLSIZE)
+ else:
+ infile, outfile, iterations, fsize = (LARGEFILE, LARGEFILE + '.gpg',
+ LARGE_ITERATIONS, LARGESIZE)
+
+ infile = os.path.join(WORKDIR, infile)
+ rnpout = os.path.join(WORKDIR, outfile + '.rnp')
+ gpgout = os.path.join(WORKDIR, outfile + '.gpg')
+ return (infile, rnpout, gpgout, iterations, fsize)
+
+
+class Benchmark(object):
+ rnphome = ['--homedir', RNPDIR]
+ gpghome = ['--homedir', GPGDIR]
+
+ def small_file_symmetric_encryption(self):
+ # Running each operation iteratively for a small and large file(s), calculating the average
+ # 1. Encryption
+ '''
+ Small file symmetric encryption
+ '''
+ infile, rnpout, gpgout, iterations, fsize = get_file_params('small')
+ for armor in [False, True]:
+ tmrnp = run_iterated(iterations, rnp_symencrypt_file, infile, rnpout,
+ 'AES128', 0, 'zip', armor)
+ tmgpg = run_iterated(iterations, gpg_symencrypt_file, infile, gpgout,
+ 'AES128', 0, 1, armor)
+ testname = 'ENCRYPT-SMALL-{}'.format('ARMOR' if armor else 'BINARY')
+ print_test_results(fsize, tmrnp, tmgpg, testname)
+
+ def large_file_symmetric_encryption(self):
+ '''
+ Large file symmetric encryption
+ '''
+ infile, rnpout, gpgout, iterations, fsize = get_file_params('large')
+ for cipher in ['AES128', 'AES192', 'AES256', 'TWOFISH', 'BLOWFISH', 'CAST5', 'CAMELLIA128', 'CAMELLIA192', 'CAMELLIA256']:
+ tmrnp = run_iterated(iterations, rnp_symencrypt_file, infile, rnpout,
+ cipher, 0, 'zip', False)
+ tmgpg = run_iterated(iterations, gpg_symencrypt_file, infile, gpgout,
+ cipher, 0, 1, False)
+ testname = 'ENCRYPT-{}-BINARY'.format(cipher)
+ print_test_results(fsize, tmrnp, tmgpg, testname)
+
+ def large_file_armored_encryption(self):
+ '''
+ Large file armored encryption
+ '''
+ infile, rnpout, gpgout, iterations, fsize = get_file_params('large')
+ tmrnp = run_iterated(iterations, rnp_symencrypt_file, infile, rnpout,
+ 'AES128', 0, 'zip', True)
+ tmgpg = run_iterated(iterations, gpg_symencrypt_file, infile, gpgout, 'AES128', 0, 1, True)
+ print_test_results(fsize, tmrnp, tmgpg, 'ENCRYPT-LARGE-ARMOR')
+
+ def small_file_symmetric_decryption(self):
+ '''
+ Small file symmetric decryption
+ '''
+ infile, rnpout, gpgout, iterations, fsize = get_file_params('small')
+ inenc = infile + '.enc'
+ for armor in [False, True]:
+ gpg_symencrypt_file(infile, inenc, 'AES', 0, 1, armor)
+ tmrnp = run_iterated(iterations, rnp_decrypt_file, inenc, rnpout)
+ tmgpg = run_iterated(iterations, gpg_decrypt_file, inenc, gpgout, PASSWORD)
+ testname = 'DECRYPT-SMALL-{}'.format('ARMOR' if armor else 'BINARY')
+ print_test_results(fsize, tmrnp, tmgpg, testname)
+ os.remove(inenc)
+
+ def large_file_symmetric_decryption(self):
+ '''
+ Large file symmetric decryption
+ '''
+ infile, rnpout, gpgout, iterations, fsize = get_file_params('large')
+ inenc = infile + '.enc'
+ for cipher in ['AES128', 'AES192', 'AES256', 'TWOFISH', 'BLOWFISH', 'CAST5',
+ 'CAMELLIA128', 'CAMELLIA192', 'CAMELLIA256']:
+ gpg_symencrypt_file(infile, inenc, cipher, 0, 1, False)
+ tmrnp = run_iterated(iterations, rnp_decrypt_file, inenc, rnpout)
+ tmgpg = run_iterated(iterations, gpg_decrypt_file, inenc, gpgout, PASSWORD)
+ testname = 'DECRYPT-{}-BINARY'.format(cipher)
+ print_test_results(fsize, tmrnp, tmgpg, testname)
+ os.remove(inenc)
+
+ def large_file_armored_decryption(self):
+ '''
+ Large file armored decryption
+ '''
+ infile, rnpout, gpgout, iterations, fsize = get_file_params('large')
+ inenc = infile + '.enc'
+ gpg_symencrypt_file(infile, inenc, 'AES128', 0, 1, True)
+ tmrnp = run_iterated(iterations, rnp_decrypt_file, inenc, rnpout)
+ tmgpg = run_iterated(iterations, gpg_decrypt_file, inenc, gpgout, PASSWORD)
+ print_test_results(fsize, tmrnp, tmgpg, 'DECRYPT-LARGE-ARMOR')
+ os.remove(inenc)
+
+ # 3. Signing
+ #print '\n#3. Signing\n'
+ # 4. Verification
+ #print '\n#4. Verification\n'
+ # 5. Cleartext signing
+ #print '\n#5. Cleartext signing and verification\n'
+ # 6. Detached signature
+ #print '\n#6. Detached signing and verification\n'
+
+# Usage ./cli_perf.py [working_directory]
+#
+# It's better to use RAMDISK to perform tests
+# in order to speed up disk reads/writes
+#
+# On linux:
+# mkdir -p /tmp/working
+# sudo mount -t tmpfs -o size=512m tmpfs /tmp/working
+# ./cli_perf.py -w /tmp/working
+# sudo umount /tmp/working
+
+
+if __name__ == '__main__':
+
+ # parse options
+ parser = ArgumentParser(description="RNP benchmarking")
+ parser.add_argument("-b", "--bench", dest="benchmarks",
+ help="Name of the comma-separated benchmarks to run", metavar="benchmarks")
+ parser.add_argument("-w", "--workdir", dest="workdir",
+ help="Working directory to use", metavar="workdir")
+ parser.add_argument("-l", "--list", help="Print list of available benchmarks and exit",
+ action="store_true")
+ args = parser.parse_args()
+
+ # get list of benchamrks to run
+ bench_methods = [ x[0] for x in inspect.getmembers(Benchmark,
+ predicate=lambda x: inspect.ismethod(x) or inspect.isfunction(x))]
+ print(bench_methods)
+
+ if args.list:
+ for name in bench_methods:
+ logging.info(("\t " + name))
+ sys.exit(0)
+
+ if args.benchmarks:
+ bench_methods = filter(lambda x: x in args.benchmarks.split(","), bench_methods)
+
+ # setup operations
+ setup(args.workdir)
+
+ for name in bench_methods:
+ method = getattr(Benchmark, name)
+ logging.info(("\n" + name + "(): " + inspect.getdoc(method)))
+ method(Benchmark())
+
+ try:
+ shutil.rmtree(WORKDIR)
+ except Exception:
+ logging.info(("Cleanup failed"))
diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py
new file mode 100755
index 0000000..e6f5ed7
--- /dev/null
+++ b/src/tests/cli_tests.py
@@ -0,0 +1,5048 @@
+#!/usr/bin/env python
+
+import logging
+import os
+import os.path
+import re
+import shutil
+import sys
+import tempfile
+import time
+import unittest
+from platform import architecture
+
+from cli_common import (file_text, find_utility, is_windows, list_upto,
+ path_for_gpg, pswd_pipe, raise_err, random_text,
+ run_proc, decode_string_escape, CONSOLE_ENCODING,
+ set_workdir)
+from gnupg import GnuPG as GnuPG
+from rnp import Rnp as Rnp
+
+WORKDIR = ''
+RNP = ''
+RNPK = ''
+GPG = ''
+GPGCONF = ''
+RNPDIR = ''
+GPGHOME = None
+PASSWORD = 'password'
+RMWORKDIR = True
+GPG_AEAD = False
+GPG_AEAD_EAX = False
+GPG_AEAD_OCB = False
+GPG_NO_OLD = False
+GPG_BRAINPOOL = False
+TESTS_SUCCEEDED = []
+TESTS_FAILED = []
+TEST_WORKFILES = []
+
+# Supported features
+RNP_TWOFISH = True
+RNP_BRAINPOOL = True
+RNP_AEAD_EAX = True
+RNP_AEAD_OCB = True
+RNP_AEAD_OCB_AES = False
+RNP_AEAD = True
+RNP_IDEA = True
+RNP_BLOWFISH = True
+RNP_CAST5 = True
+RNP_RIPEMD160 = True
+
+if sys.version_info >= (3,):
+ unichr = chr
+
+def escape_regex(str):
+ return '^' + ''.join((c, "[\\x{:02X}]".format(ord(c)))[0 <= ord(c) <= 0x20 \
+ or c in ['[',']','(',')','|','"','$','.','*','^','$','\\','+','?','{','}']] for c in str) + '$'
+
+UNICODE_LATIN_CAPITAL_A_GRAVE = unichr(192)
+UNICODE_LATIN_SMALL_A_GRAVE = unichr(224)
+UNICODE_LATIN_CAPITAL_A_MACRON = unichr(256)
+UNICODE_LATIN_SMALL_A_MACRON = unichr(257)
+UNICODE_GREEK_CAPITAL_HETA = unichr(880)
+UNICODE_GREEK_SMALL_HETA = unichr(881)
+UNICODE_GREEK_CAPITAL_OMEGA = unichr(937)
+UNICODE_GREEK_SMALL_OMEGA = unichr(969)
+UNICODE_CYRILLIC_CAPITAL_A = unichr(0x0410)
+UNICODE_CYRILLIC_SMALL_A = unichr(0x0430)
+UNICODE_CYRILLIC_CAPITAL_YA = unichr(0x042F)
+UNICODE_CYRILLIC_SMALL_YA = unichr(0x044F)
+UNICODE_SEQUENCE_1 = UNICODE_LATIN_CAPITAL_A_GRAVE + UNICODE_LATIN_SMALL_A_MACRON \
+ + UNICODE_GREEK_CAPITAL_HETA + UNICODE_GREEK_SMALL_OMEGA \
+ + UNICODE_CYRILLIC_CAPITAL_A + UNICODE_CYRILLIC_SMALL_YA
+UNICODE_SEQUENCE_2 = UNICODE_LATIN_SMALL_A_GRAVE + UNICODE_LATIN_CAPITAL_A_MACRON \
+ + UNICODE_GREEK_SMALL_HETA + UNICODE_GREEK_CAPITAL_OMEGA \
+ + UNICODE_CYRILLIC_SMALL_A + UNICODE_CYRILLIC_CAPITAL_YA
+WEIRD_USERID_UNICODE_1 = unichr(160) + unichr(161) \
+ + UNICODE_SEQUENCE_1 + unichr(40960) + u'@rnp'
+WEIRD_USERID_UNICODE_2 = unichr(160) + unichr(161) \
+ + UNICODE_SEQUENCE_2 + unichr(40960) + u'@rnp'
+WEIRD_USERID_SPECIAL_CHARS = '\\}{][)^*.+(\t\n|$@rnp'
+WEIRD_USERID_SPACE = ' '
+WEIRD_USERID_QUOTE = '"'
+WEIRD_USERID_SPACE_AND_QUOTE = ' "'
+WEIRD_USERID_QUOTE_AND_SPACE = '" '
+WEIRD_USERID_TOO_LONG = 'x' * 125 + '@rnp' # totaling 129 (MAX_USER_ID + 1)
+
+# Key userids
+KEY_ENCRYPT = 'encryption@rnp'
+KEY_SIGN_RNP = 'signing@rnp'
+KEY_SIGN_GPG = 'signing@gpg'
+KEY_ENC_RNP = 'enc@rnp'
+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 <alice@rnp>'
+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'
+
+# Misc
+GPG_LOOPBACK = '--pinentry-mode=loopback'
+
+# Regexps
+RE_RSA_KEY = r'(?s)^' \
+r'# .*' \
+r':public key packet:\s+' \
+r'version 4, algo 1, created \d+, expires 0\s+' \
+r'pkey\[0\]: \[(\d{4}) bits\]\s+' \
+r'pkey\[1\]: \[17 bits\]\s+' \
+r'keyid: ([0-9A-F]{16})\s+' \
+r'# .*' \
+r':user ID packet: "(.+)"\s+' \
+r'# .*' \
+r':signature packet: algo 1, keyid \2\s+' \
+r'.*' \
+r'# .*' \
+r':public sub key packet:' \
+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 ([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+$'
+
+RE_MULTIPLE_KEY_LIST = r'(?s)^\s*(\d+) (?:key|keys) found.*$'
+RE_MULTIPLE_KEY_5 = r'(?s)^\s*' \
+r'10 keys found.*' \
+r'.+uid\s+0@rnp-multiple' \
+r'.+uid\s+1@rnp-multiple' \
+r'.+uid\s+2@rnp-multiple' \
+r'.+uid\s+3@rnp-multiple' \
+r'.+uid\s+4@rnp-multiple.*$'
+
+RE_MULTIPLE_SUBKEY_3 = r'(?s)^\s*' \
+r'3 keys found.*$'
+
+RE_MULTIPLE_SUBKEY_8 = r'(?s)^\s*' \
+r'8 keys found.*$'
+
+RE_GPG_SINGLE_RSA_KEY = r'(?s)^\s*' \
+r'.+-+\s*' \
+r'pub\s+rsa.+' \
+r'\s+([0-9A-F]{40})\s*' \
+r'uid\s+.+rsakey@gpg.*'
+
+RE_GPG_GOOD_SIGNATURE = r'(?s)^.*' \
+r'gpg: Signature made .*' \
+r'gpg: Good signature from "(.*)".*'
+
+RE_RNP_GOOD_SIGNATURE = r'(?s)^.*' \
+r'Good signature made .*' \
+r'using .* key .*' \
+r'pub .*' \
+r'uid\s+(.*)\s*' \
+r'Signature\(s\) verified successfully.*$'
+
+RE_RNP_ENCRYPTED_KEY = r'(?s)^.*' \
+r'Secret key packet.*' \
+r'secret key material:.*' \
+r'encrypted secret key data:.*' \
+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':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:.*' \
+r':type 33, len 21.*' \
+r'issuer fingerprint:.*' \
+r':type 2, len 4.*' \
+r'signature creation time:.*' \
+r':type 29.*' \
+r'reason for revocation: (.*)' \
+r'message: (.*)' \
+r'unhashed subpackets:.*' \
+r':type 16, len 8.*' \
+r'issuer key ID: .*$'
+
+RE_GPG_REVOCATION_IMPORT = r'(?s)^.*' \
+r'key 0451409669FFDE3C: "Alice <alice@rnp>" revocation certificate imported.*' \
+r'Total number processed: 1.*' \
+r'new key revocations: 1.*$'
+
+RE_SIG_1_IMPORT = r'(?s)^.*Import finished: 1 new signature, 0 unchanged, 0 unknown.*'
+
+RE_KEYSTORE_INFO = r'(?s)^.*fatal: cannot set keystore info'
+
+RNP_TO_GPG_ZALGS = { 'zip' : '1', 'zlib' : '2', 'bzip2' : '3' }
+# These are mostly identical
+RNP_TO_GPG_CIPHERS = {'AES' : 'aes128', 'AES192' : 'aes192', 'AES256' : 'aes256',
+ 'TWOFISH' : 'twofish', 'CAMELLIA128' : 'camellia128',
+ 'CAMELLIA192' : 'camellia192', 'CAMELLIA256' : 'camellia256',
+ 'IDEA' : 'idea', '3DES' : '3des', 'CAST5' : 'cast5',
+ 'BLOWFISH' : 'blowfish'}
+
+# Error messages
+RNP_DATA_DIFFERS = 'rnp decrypted data differs'
+GPG_DATA_DIFFERS = 'gpg decrypted data differs'
+KEY_GEN_FAILED = 'key generation failed'
+KEY_LIST_FAILED = 'key list failed'
+KEY_LIST_WRONG = 'wrong key list output'
+PKT_LIST_FAILED = 'packet listing failed'
+ALICE_IMPORT_FAIL = 'Alice key import failed'
+ENC_FAILED = 'encryption failed'
+DEC_FAILED = 'decryption failed'
+DEC_DIFFERS = 'Decrypted data differs'
+GPG_IMPORT_FAILED = 'gpg key import failed'
+
+def check_packets(fname, regexp):
+ ret, output, err = run_proc(GPG, ['--homedir', '.',
+ '--list-packets', path_for_gpg(fname)])
+ if ret != 0:
+ logging.error(err)
+ return None
+ else:
+ result = re.match(regexp, output)
+ if not result:
+ logging.debug('Wrong packets:')
+ logging.debug(output)
+ return result
+
+def clear_keyrings():
+ shutil.rmtree(RNPDIR, ignore_errors=True)
+ os.mkdir(RNPDIR, 0o700)
+
+ run_proc(GPGCONF, ['--homedir', GPGHOME, '--kill', 'gpg-agent'])
+ while os.path.isdir(GPGDIR):
+ try:
+ shutil.rmtree(GPGDIR)
+ except Exception:
+ time.sleep(0.1)
+ os.mkdir(GPGDIR, 0o700)
+
+def allow_y2k38_on_32bit(filename):
+ if architecture()[0] == '32bit':
+ return [filename, filename + '_y2k38']
+ else:
+ return [filename]
+
+def compare_files(src, dst, message):
+ if file_text(src) != file_text(dst):
+ raise_err(message)
+
+def compare_file(src, string, message):
+ if file_text(src) != string:
+ raise_err(message)
+
+def compare_file_any(srcs, string, message):
+ for src in srcs:
+ if file_text(src) == string:
+ return
+ raise_err(message)
+
+def compare_file_ex(src, string, message, symbol='?'):
+ ftext = file_text(src)
+ if len(ftext) != len(string):
+ raise_err(message)
+ for i in range(0, len(ftext)):
+ if (ftext[i] != symbol[0]) and (ftext[i] != string[i]):
+ raise_err(message)
+
+def remove_files(*args):
+ for fpath in args:
+ try:
+ os.remove(fpath)
+ except Exception:
+ # Ignore if file cannot be removed
+ pass
+
+def reg_workfiles(mainname, *exts):
+ global TEST_WORKFILES
+ res = []
+ for ext in exts:
+ fpath = os.path.join(WORKDIR, mainname + ext)
+ if fpath in TEST_WORKFILES:
+ logging.warn('Warning! Path {} is already in TEST_WORKFILES'.format(fpath))
+ else:
+ TEST_WORKFILES += [fpath]
+ res += [fpath]
+ return res
+
+def clear_workfiles():
+ global TEST_WORKFILES
+ remove_files(*TEST_WORKFILES)
+ TEST_WORKFILES = []
+
+def rnp_genkey_rsa(userid, bits=2048, pswd=PASSWORD):
+ pipe = pswd_pipe(pswd)
+ ret, _, err = run_proc(RNPK, ['--numbits', str(bits), '--homedir', RNPDIR, '--pass-fd', str(pipe),
+ '--notty', '--s2k-iterations', '50000', '--userid', userid, '--generate-key'])
+ os.close(pipe)
+ if ret != 0:
+ raise_err('rsa key generation failed', err)
+
+def rnp_params_insert_z(params, pos, z):
+ if z:
+ if len(z) > 0 and z[0] != None:
+ params[pos:pos] = ['--' + z[0]]
+ if len(z) > 1 and z[1] != None:
+ params[pos:pos] = ['-z', str(z[1])]
+
+def rnp_params_insert_aead(params, pos, aead):
+ if aead != None:
+ params[pos:pos] = ['--aead=' + aead[0]] if len(aead) > 0 and aead[0] != None else ['--aead']
+ if len(aead) > 1 and aead[1] != None:
+ params[pos + 1:pos + 1] = ['--aead-chunk-bits=' + str(aead[1])]
+
+def rnp_encrypt_file_ex(src, dst, recipients=None, passwords=None, aead=None, cipher=None,
+ z=None, armor=False, s2k_iter=False, s2k_msec=False):
+ params = ['--homedir', RNPDIR, src, '--output', dst]
+ # Recipients. None disables PK encryption, [] to use default key. Otherwise list of ids.
+ if recipients != None:
+ params[2:2] = ['--encrypt']
+ for userid in reversed(recipients):
+ params[2:2] = ['-r', escape_regex(userid)]
+ # Passwords to encrypt to. None or [] disables password encryption.
+ if passwords:
+ if recipients is None:
+ params[2:2] = ['-c']
+ if s2k_iter != False:
+ params += ['--s2k-iterations', str(s2k_iter)]
+ if s2k_msec != False:
+ params += ['--s2k-msec', str(s2k_msec)]
+ pipe = pswd_pipe('\n'.join(passwords))
+ params[2:2] = ['--pass-fd', str(pipe), '--passwords', str(len(passwords))]
+
+ # Cipher or None for default
+ if cipher: params[2:2] = ['--cipher', cipher]
+ # Armor
+ if armor: params += ['--armor']
+ rnp_params_insert_aead(params, 2, aead)
+ rnp_params_insert_z(params, 2, z)
+ ret, _, err = run_proc(RNP, params)
+ if passwords: os.close(pipe)
+ if ret != 0:
+ raise_err('rnp encryption failed with ' + cipher, err)
+
+def rnp_encrypt_and_sign_file(src, dst, recipients, encrpswd, signers, signpswd,
+ aead=None, cipher=None, z=None, armor=False):
+ params = ['--homedir', RNPDIR, '--sign', '--encrypt', src, '--output', dst]
+ pipe = pswd_pipe('\n'.join(encrpswd + signpswd))
+ params[2:2] = ['--pass-fd', str(pipe)]
+
+ # Encrypting passwords if any
+ if encrpswd:
+ params[2:2] = ['--passwords', str(len(encrpswd))]
+ # Adding recipients. If list is empty then default will be used.
+ for userid in reversed(recipients):
+ params[2:2] = ['-r', escape_regex(userid)]
+ # Adding signers. If list is empty then default will be used.
+ for signer in reversed(signers):
+ params[2:2] = ['-u', escape_regex(signer)]
+ # Cipher or None for default
+ if cipher: params[2:2] = ['--cipher', cipher]
+ # Armor
+ if armor: params += ['--armor']
+ rnp_params_insert_aead(params, 2, aead)
+ rnp_params_insert_z(params, 2, z)
+
+ ret, _, err = run_proc(RNP, params)
+ os.close(pipe)
+ if ret != 0:
+ raise_err('rnp encrypt-and-sign failed', err)
+
+def rnp_decrypt_file(src, dst, password = PASSWORD):
+ pipe = pswd_pipe(password)
+ ret, out, err = run_proc(
+ RNP, ['--homedir', RNPDIR, '--pass-fd', str(pipe), '--decrypt', src, '--output', dst])
+ os.close(pipe)
+ if ret != 0:
+ raise_err('rnp decryption failed', out + err)
+
+def rnp_sign_file_ex(src, dst, signers, passwords, options = None):
+ pipe = pswd_pipe('\n'.join(passwords))
+ params = ['--homedir', RNPDIR, '--pass-fd', str(pipe), src]
+ if dst: params += ['--output', dst]
+ if 'cleartext' in options:
+ params[4:4] = ['--clearsign']
+ else:
+ params[4:4] = ['--sign']
+ if 'armor' in options: params += ['--armor']
+ if 'detached' in options: params += ['--detach']
+
+ for signer in reversed(signers):
+ params[4:4] = ['--userid', escape_regex(signer)]
+
+ ret, _, err = run_proc(RNP, params)
+ os.close(pipe)
+ if ret != 0:
+ raise_err('rnp signing failed', err)
+
+
+def rnp_sign_file(src, dst, signers, passwords, armor=False):
+ options = []
+ if armor: options += ['armor']
+ rnp_sign_file_ex(src, dst, signers, passwords, options)
+
+
+def rnp_sign_detached(src, signers, passwords, armor=False):
+ options = ['detached']
+ if armor: options += ['armor']
+ rnp_sign_file_ex(src, None, signers, passwords, options)
+
+
+def rnp_sign_cleartext(src, dst, signers, passwords):
+ rnp_sign_file_ex(src, dst, signers, passwords, ['cleartext'])
+
+
+def rnp_verify_file(src, dst, signer=None):
+ params = ['--homedir', RNPDIR, '--verify-cat', src, '--output', dst]
+ ret, out, err = run_proc(RNP, params)
+ if ret != 0:
+ raise_err('rnp verification failed', err + out)
+ # Check RNP output
+ match = re.match(RE_RNP_GOOD_SIGNATURE, err)
+ if not match:
+ raise_err('wrong rnp verification output', err)
+ if signer and (match.group(1).strip() != signer.strip()):
+ raise_err('rnp verification failed, wrong signer')
+
+
+def rnp_verify_detached(sig, signer=None):
+ ret, out, err = run_proc(RNP, ['--homedir', RNPDIR, '--verify', sig])
+ if ret != 0:
+ raise_err('rnp detached verification failed', err + out)
+ # Check RNP output
+ match = re.match(RE_RNP_GOOD_SIGNATURE, err)
+ if not match:
+ raise_err('wrong rnp detached verification output', err)
+ if signer and (match.group(1).strip() != signer.strip()):
+ raise_err('rnp detached verification failed, wrong signer'.format())
+
+
+def rnp_verify_cleartext(src, signer=None):
+ params = ['--homedir', RNPDIR, '--verify', src]
+ ret, out, err = run_proc(RNP, params)
+ if ret != 0:
+ raise_err('rnp verification failed', err + out)
+ # Check RNP output
+ match = re.match(RE_RNP_GOOD_SIGNATURE, err)
+ if not match:
+ raise_err('wrong rnp verification output', err)
+ if signer and (match.group(1).strip() != signer.strip()):
+ raise_err('rnp verification failed, wrong signer')
+
+
+def gpg_import_pubring(kpath=None):
+ if not kpath:
+ kpath = os.path.join(RNPDIR, PUBRING)
+ ret, _, err = run_proc(
+ GPG, ['--display-charset', CONSOLE_ENCODING, '--batch', '--homedir', GPGHOME, '--import', kpath])
+ if ret != 0:
+ raise_err(GPG_IMPORT_FAILED, err)
+
+
+def gpg_import_secring(kpath=None, password = PASSWORD):
+ if not kpath:
+ kpath = os.path.join(RNPDIR, SECRING)
+ ret, _, err = run_proc(
+ GPG, ['--display-charset', CONSOLE_ENCODING, '--batch', '--passphrase', password, '--homedir', GPGHOME, '--import', kpath])
+ if ret != 0:
+ raise_err('gpg secret key import failed', err)
+
+
+def gpg_export_secret_key(userid, password, keyfile):
+ ret, _, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, GPG_LOOPBACK,
+ '--yes', '--passphrase', password, '--output',
+ path_for_gpg(keyfile), '--export-secret-key', userid])
+
+ if ret != 0:
+ raise_err('gpg secret key export failed', err)
+
+def gpg_params_insert_z(params, pos, z):
+ if z:
+ if len(z) > 0 and z[0] != None:
+ params[pos:pos] = ['--compress-algo', RNP_TO_GPG_ZALGS[z[0]]]
+ if len(z) > 1 and z[1] != None:
+ params[pos:pos] = ['-z', str(z[1])]
+
+def gpg_encrypt_file(src, dst, cipher=None, z=None, armor=False):
+ src = path_for_gpg(src)
+ dst = path_for_gpg(dst)
+ params = ['--homedir', GPGHOME, '-e', '-r', KEY_ENCRYPT, '--batch',
+ '--trust-model', 'always', '--output', dst, src]
+ if z: gpg_params_insert_z(params, 3, z)
+ if cipher: params[3:3] = ['--cipher-algo', RNP_TO_GPG_CIPHERS[cipher]]
+ if armor: params[2:2] = ['--armor']
+ if GPG_NO_OLD: params[2:2] = ['--allow-old-cipher-algos']
+
+ ret, out, err = run_proc(GPG, params)
+ if ret != 0:
+ raise_err('gpg encryption failed for cipher ' + cipher, err)
+
+def gpg_symencrypt_file(src, dst, cipher=None, z=None, armor=False, aead=None):
+ src = path_for_gpg(src)
+ dst = path_for_gpg(dst)
+ params = ['--homedir', GPGHOME, '-c', '--s2k-count', '65536', '--batch',
+ '--passphrase', PASSWORD, '--output', dst, src]
+ if z: gpg_params_insert_z(params, 3, z)
+ if cipher: params[3:3] = ['--cipher-algo', RNP_TO_GPG_CIPHERS[cipher]]
+ if GPG_NO_OLD: params[3:3] = ['--allow-old-cipher-algos']
+ if armor: params[2:2] = ['--armor']
+ if aead != None:
+ if len(aead) > 0 and aead[0] != None:
+ params[3:3] = ['--aead-algo', aead[0]]
+ if len(aead) > 1 and aead[1] != None:
+ params[3:3] = ['--chunk-size', str(aead[1] + 6)]
+ params[3:3] = ['--rfc4880bis', '--force-aead']
+
+ ret, out, err = run_proc(GPG, params)
+ if ret != 0:
+ raise_err('gpg symmetric encryption failed for cipher ' + cipher, err)
+
+
+def gpg_decrypt_file(src, dst, keypass):
+ src = path_for_gpg(src)
+ dst = path_for_gpg(dst)
+ ret, out, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, GPG_LOOPBACK, '--batch',
+ '--yes', '--passphrase', keypass, '--trust-model',
+ 'always', '-o', dst, '-d', src])
+ if ret != 0:
+ raise_err('gpg decryption failed', err)
+
+
+def gpg_verify_file(src, dst, signer=None):
+ src = path_for_gpg(src)
+ dst = path_for_gpg(dst)
+ ret, out, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch',
+ '--yes', '--trust-model', 'always', '-o', dst, '--verify', src])
+ if ret != 0:
+ raise_err('gpg verification failed', err)
+ # Check GPG output
+ match = re.match(RE_GPG_GOOD_SIGNATURE, err)
+ if not match:
+ raise_err('wrong gpg verification output', err)
+ if signer and (match.group(1) != signer):
+ raise_err('gpg verification failed, wrong signer')
+
+
+def gpg_verify_detached(src, sig, signer=None):
+ src = path_for_gpg(src)
+ sig = path_for_gpg(sig)
+ ret, _, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch', '--yes', '--trust-model',
+ 'always', '--verify', sig, src])
+ if ret != 0:
+ raise_err('gpg detached verification failed', err)
+ # Check GPG output
+ match = re.match(RE_GPG_GOOD_SIGNATURE, err)
+ if not match:
+ raise_err('wrong gpg detached verification output', err)
+ if signer and (match.group(1) != signer):
+ raise_err('gpg detached verification failed, wrong signer')
+
+
+def gpg_verify_cleartext(src, signer=None):
+ src = path_for_gpg(src)
+ ret, _, err = run_proc(
+ GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch', '--yes', '--trust-model', 'always', '--verify', src])
+ if ret != 0:
+ raise_err('gpg cleartext verification failed', err)
+ # Check GPG output
+ match = re.match(RE_GPG_GOOD_SIGNATURE, err)
+ if not match:
+ raise_err('wrong gpg verification output', err)
+ if signer and (match.group(1) != signer):
+ raise_err('gpg verification failed, wrong signer')
+
+
+def gpg_sign_file(src, dst, signer, z=None, armor=False):
+ src = path_for_gpg(src)
+ dst = path_for_gpg(dst)
+ params = ['--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--yes',
+ '--passphrase', PASSWORD, '--trust-model', 'always', '-u', signer, '-o',
+ dst, '-s', src]
+ if z: gpg_params_insert_z(params, 3, z)
+ if armor: params.insert(2, '--armor')
+ ret, _, err = run_proc(GPG, params)
+ if ret != 0:
+ raise_err('gpg signing failed', err)
+
+
+def gpg_sign_detached(src, signer, armor=False, textsig=False):
+ src = path_for_gpg(src)
+ params = ['--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--yes',
+ '--passphrase', PASSWORD, '--trust-model', 'always', '-u', signer,
+ '--detach-sign', src]
+ if armor: params.insert(2, '--armor')
+ if textsig: params.insert(2, '--text')
+ ret, _, err = run_proc(GPG, params)
+ if ret != 0:
+ raise_err('gpg detached signing failed', err)
+
+
+def gpg_sign_cleartext(src, dst, signer):
+ src = path_for_gpg(src)
+ dst = path_for_gpg(dst)
+ params = ['--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--yes', '--passphrase',
+ PASSWORD, '--trust-model', 'always', '-u', signer, '-o', dst, '--clearsign', src]
+ ret, _, err = run_proc(GPG, params)
+ if ret != 0:
+ raise_err('gpg cleartext signing failed', err)
+
+
+def gpg_agent_clear_cache():
+ run_proc(GPGCONF, ['--homedir', GPGHOME, '--kill', 'gpg-agent'])
+
+'''
+ Things to try here later on:
+ - different symmetric algorithms
+ - different file sizes (block len/packet len tests)
+ - different public key algorithms
+ - different compression levels/algorithms
+'''
+
+
+def gpg_to_rnp_encryption(filesize, cipher=None, z=None):
+ '''
+ Encrypts with GPG and decrypts with RNP
+ '''
+ src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [False, True]:
+ # Encrypt cleartext file with GPG
+ gpg_encrypt_file(src, dst, cipher, z, armor)
+ # Decrypt encrypted file with RNP
+ rnp_decrypt_file(dst, dec)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+ remove_files(dst, dec)
+ clear_workfiles()
+
+
+def file_encryption_rnp_to_gpg(filesize, z=None):
+ '''
+ Encrypts with RNP and decrypts with GPG and RNP
+ '''
+ # TODO: Would be better to do "with reg_workfiles() as src,dst,enc ... and
+ # do cleanup at the end"
+ src, dst, enc = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [False, True]:
+ # Encrypt cleartext file with RNP
+ rnp_encrypt_file_ex(src, enc, [KEY_ENCRYPT], None, None, None, z, armor)
+ # Decrypt encrypted file with GPG
+ gpg_decrypt_file(enc, dst, PASSWORD)
+ compare_files(src, dst, GPG_DATA_DIFFERS)
+ remove_files(dst)
+ # Decrypt encrypted file with RNP
+ rnp_decrypt_file(enc, dst)
+ compare_files(src, dst, RNP_DATA_DIFFERS)
+ remove_files(enc, dst)
+ clear_workfiles()
+
+'''
+ Things to try later:
+ - different public key algorithms
+ - decryption with generated by GPG and imported keys
+'''
+
+
+def rnp_sym_encryption_gpg_to_rnp(filesize, cipher = None, z = None):
+ src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [False, True]:
+ # Encrypt cleartext file with GPG
+ gpg_symencrypt_file(src, dst, cipher, z, armor)
+ # Decrypt encrypted file with RNP
+ rnp_decrypt_file(dst, dec)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+ remove_files(dst, dec)
+ clear_workfiles()
+
+
+def rnp_sym_encryption_rnp_to_gpg(filesize, cipher = None, z = None, s2k_iter = False, s2k_msec = False):
+ src, dst, enc = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [False, True]:
+ # Encrypt cleartext file with RNP
+ rnp_encrypt_file_ex(src, enc, None, [PASSWORD], None, cipher, z, armor, s2k_iter, s2k_msec)
+ # Decrypt encrypted file with GPG
+ gpg_decrypt_file(enc, dst, PASSWORD)
+ compare_files(src, dst, GPG_DATA_DIFFERS)
+ remove_files(dst)
+ # Decrypt encrypted file with RNP
+ rnp_decrypt_file(enc, dst)
+ compare_files(src, dst, RNP_DATA_DIFFERS)
+ remove_files(enc, dst)
+ clear_workfiles()
+
+def rnp_sym_encryption_rnp_aead(filesize, cipher = None, z = None, aead = None, usegpg = False):
+ src, dst, enc = reg_workfiles('cleartext', '.txt', '.rnp', '.enc')
+ # Generate random file of required size
+ random_text(src, filesize)
+ # Encrypt cleartext file with RNP
+ rnp_encrypt_file_ex(src, enc, None, [PASSWORD], aead, cipher, z)
+ # Decrypt encrypted file with RNP
+ rnp_decrypt_file(enc, dst)
+ compare_files(src, dst, RNP_DATA_DIFFERS)
+ remove_files(dst)
+
+ if usegpg:
+ # Decrypt encrypted file with GPG
+ gpg_decrypt_file(enc, dst, PASSWORD)
+ compare_files(src, dst, GPG_DATA_DIFFERS)
+ remove_files(dst, enc)
+ # Encrypt cleartext file with GPG
+ gpg_symencrypt_file(src, enc, cipher, z, False, aead)
+ # Decrypt encrypted file with RNP
+ rnp_decrypt_file(enc, dst)
+ compare_files(src, dst, RNP_DATA_DIFFERS)
+
+ clear_workfiles()
+
+def rnp_signing_rnp_to_gpg(filesize):
+ src, sig, ver = reg_workfiles('cleartext', '.txt', '.sig', '.ver')
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [False, True]:
+ # Sign file with RNP
+ rnp_sign_file(src, sig, [KEY_SIGN_RNP], [PASSWORD], armor)
+ # Verify signed file with RNP
+ rnp_verify_file(sig, ver, KEY_SIGN_RNP)
+ compare_files(src, ver, 'rnp verified data differs')
+ remove_files(ver)
+ # Verify signed message with GPG
+ gpg_verify_file(sig, ver, KEY_SIGN_RNP)
+ compare_files(src, ver, 'gpg verified data differs')
+ remove_files(sig, ver)
+ clear_workfiles()
+
+
+def rnp_detached_signing_rnp_to_gpg(filesize):
+ src, sig, asc = reg_workfiles('cleartext', '.txt', EXT_SIG, EXT_ASC)
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [True, False]:
+ # Sign file with RNP
+ rnp_sign_detached(src, [KEY_SIGN_RNP], [PASSWORD], armor)
+ sigpath = asc if armor else sig
+ # Verify signature with RNP
+ rnp_verify_detached(sigpath, KEY_SIGN_RNP)
+ # Verify signed message with GPG
+ gpg_verify_detached(src, sigpath, KEY_SIGN_RNP)
+ remove_files(sigpath)
+ clear_workfiles()
+
+
+def rnp_cleartext_signing_rnp_to_gpg(filesize):
+ src, asc = reg_workfiles('cleartext', '.txt', EXT_ASC)
+ # Generate random file of required size
+ random_text(src, filesize)
+ # Sign file with RNP
+ rnp_sign_cleartext(src, asc, [KEY_SIGN_RNP], [PASSWORD])
+ # Verify signature with RNP
+ rnp_verify_cleartext(asc, KEY_SIGN_RNP)
+ # Verify signed message with GPG
+ gpg_verify_cleartext(asc, KEY_SIGN_RNP)
+ clear_workfiles()
+
+
+def rnp_signing_gpg_to_rnp(filesize, z=None):
+ src, sig, ver = reg_workfiles('cleartext', '.txt', '.sig', '.ver')
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [True, False]:
+ # Sign file with GPG
+ gpg_sign_file(src, sig, KEY_SIGN_GPG, z, armor)
+ # Verify file with RNP
+ rnp_verify_file(sig, ver, KEY_SIGN_GPG)
+ compare_files(src, ver, 'rnp verified data differs')
+ remove_files(sig, ver)
+ clear_workfiles()
+
+
+def rnp_detached_signing_gpg_to_rnp(filesize, textsig=False):
+ src, sig, asc = reg_workfiles('cleartext', '.txt', EXT_SIG, EXT_ASC)
+ # Generate random file of required size
+ random_text(src, filesize)
+ for armor in [True, False]:
+ # Sign file with GPG
+ gpg_sign_detached(src, KEY_SIGN_GPG, armor, textsig)
+ sigpath = asc if armor else sig
+ # Verify file with RNP
+ rnp_verify_detached(sigpath, KEY_SIGN_GPG)
+ clear_workfiles()
+
+def rnp_cleartext_signing_gpg_to_rnp(filesize):
+ src, asc = reg_workfiles('cleartext', '.txt', EXT_ASC)
+ # Generate random file of required size
+ random_text(src, filesize)
+ # Sign file with GPG
+ gpg_sign_cleartext(src, asc, KEY_SIGN_GPG)
+ # Verify signature with RNP
+ rnp_verify_cleartext(asc, KEY_SIGN_GPG)
+ # Verify signed message with GPG
+ gpg_verify_cleartext(asc, KEY_SIGN_GPG)
+ clear_workfiles()
+
+def gpg_check_features():
+ global GPG_AEAD, GPG_AEAD_EAX, GPG_AEAD_OCB, GPG_NO_OLD, GPG_BRAINPOOL
+ _, out, _ = run_proc(GPG, ["--version"])
+ # AEAD
+ GPG_AEAD_EAX = re.match(r'(?s)^.*AEAD:.*EAX.*', out) is not None
+ GPG_AEAD_OCB = re.match(r'(?s)^.*AEAD:.*OCB.*', out) is not None
+ # Version 2.3.0-beta1598 and up drops support of 64-bit block algos
+ match = re.match(r'(?s)^.*gpg \(GnuPG\) (\d+)\.(\d+)\.(\d+)(-beta(\d+))?.*$', out)
+ if not match:
+ raise_err('Failed to parse GnuPG version.')
+ ver = [int(match.group(1)), int(match.group(2)), int(match.group(3))]
+ beta = int(match.group(5)) if match.group(5) else 0
+ if not beta:
+ GPG_NO_OLD = ver >= [2, 3, 0]
+ else:
+ GPG_NO_OLD = ver == [2, 3, 0] and (beta >= 1598)
+ # Version 2.4.0 and up doesn't support EAX and doesn't has AEAD in output
+ if ver >= [2, 4, 0]:
+ GPG_AEAD_OCB = True
+ GPG_AEAD_EAX = False
+ GPG_AEAD = GPG_AEAD_OCB or GPG_AEAD_EAX
+ # Check whether Brainpool curves are supported
+ _, out, _ = run_proc(GPG, ["--with-colons", "--list-config", "curve"])
+ GPG_BRAINPOOL = re.match(r'(?s)^.*brainpoolP256r1.*', out) is not None
+ print('GPG_AEAD_EAX: ' + str(GPG_AEAD_EAX))
+ print('GPG_AEAD_OCB: ' + str(GPG_AEAD_OCB))
+ print('GPG_NO_OLD: ' + str(GPG_NO_OLD))
+ print('GPG_BRAINPOOL: ' + str(GPG_BRAINPOOL))
+
+def rnp_check_features():
+ global RNP_TWOFISH, RNP_BRAINPOOL, RNP_AEAD, RNP_AEAD_EAX, RNP_AEAD_OCB, RNP_AEAD_OCB_AES, RNP_IDEA, RNP_BLOWFISH, RNP_CAST5, RNP_RIPEMD160
+ ret, out, _ = run_proc(RNP, ['--version'])
+ if ret != 0:
+ raise_err('Failed to get RNP version.')
+ # AEAD
+ RNP_AEAD_EAX = re.match(r'(?s)^.*AEAD:.*EAX.*', out) is not None
+ RNP_AEAD_OCB = re.match(r'(?s)^.*AEAD:.*OCB.*', out) is not None
+ RNP_AEAD = RNP_AEAD_EAX or RNP_AEAD_OCB
+ RNP_AEAD_OCB_AES = RNP_AEAD_OCB and re.match(r'(?s)^.*Backend.*OpenSSL.*', out) is not None
+ # Twofish
+ RNP_TWOFISH = re.match(r'(?s)^.*Encryption:.*TWOFISH.*', out) is not None
+ # Brainpool curves
+ RNP_BRAINPOOL = re.match(r'(?s)^.*Curves:.*brainpoolP256r1.*brainpoolP384r1.*brainpoolP512r1.*', out) is not None
+ # IDEA encryption algorithm
+ RNP_IDEA = re.match(r'(?s)^.*Encryption:.*IDEA.*', out) is not None
+ RNP_BLOWFISH = re.match(r'(?s)^.*Encryption:.*BLOWFISH.*', out) is not None
+ RNP_CAST5 = re.match(r'(?s)^.*Encryption:.*CAST5.*', out) is not None
+ RNP_RIPEMD160 = re.match(r'(?s)^.*Hash:.*RIPEMD160.*', out) is not None
+ print('RNP_TWOFISH: ' + str(RNP_TWOFISH))
+ print('RNP_BLOWFISH: ' + str(RNP_BLOWFISH))
+ print('RNP_IDEA: ' + str(RNP_IDEA))
+ print('RNP_CAST5: ' + str(RNP_CAST5))
+ print('RNP_RIPEMD160: ' + str(RNP_RIPEMD160))
+ print('RNP_BRAINPOOL: ' + str(RNP_BRAINPOOL))
+ print('RNP_AEAD_EAX: ' + str(RNP_AEAD_EAX))
+ print('RNP_AEAD_OCB: ' + str(RNP_AEAD_OCB))
+ print('RNP_AEAD_OCB_AES: ' + str(RNP_AEAD_OCB_AES))
+
+def setup(loglvl):
+ # Setting up directories.
+ global RMWORKDIR, WORKDIR, RNPDIR, RNP, RNPK, GPG, GPGDIR, GPGHOME, GPGCONF
+ logging.basicConfig(stream=sys.stderr, format="%(message)s")
+ logging.getLogger().setLevel(loglvl)
+ WORKDIR = tempfile.mkdtemp(prefix='rnpctmp')
+ set_workdir(WORKDIR)
+ RMWORKDIR = True
+
+ logging.info('Running in ' + WORKDIR)
+
+ RNPDIR = os.path.join(WORKDIR, '.rnp')
+ RNP = os.getenv('RNP_TESTS_RNP_PATH') or 'rnp'
+ RNPK = os.getenv('RNP_TESTS_RNPKEYS_PATH') or 'rnpkeys'
+ shutil.rmtree(RNPDIR, ignore_errors=True)
+ os.mkdir(RNPDIR, 0o700)
+
+ os.environ["RNP_LOG_CONSOLE"] = "1"
+
+ GPGDIR = os.path.join(WORKDIR, '.gpg')
+ GPGHOME = path_for_gpg(GPGDIR) if is_windows() else GPGDIR
+ GPG = os.getenv('RNP_TESTS_GPG_PATH') or find_utility('gpg')
+ GPGCONF = os.getenv('RNP_TESTS_GPGCONF_PATH') or find_utility('gpgconf')
+ gpg_check_features()
+ rnp_check_features()
+ shutil.rmtree(GPGDIR, ignore_errors=True)
+ os.mkdir(GPGDIR, 0o700)
+
+def data_path(subpath):
+ ''' Constructs path to the tests data file/dir'''
+ return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', subpath)
+
+def key_path(file_base_name, secret):
+ ''' Constructs path to the .gpg file'''
+ path=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/cli_EncryptSign',
+ file_base_name)
+ return ''.join([path, '-sec' if secret else '', '.gpg'])
+
+def rnp_supported_ciphers(aead = False):
+ ciphers = ['AES', 'AES192', 'AES256']
+ if aead and RNP_AEAD_OCB_AES:
+ return ciphers
+ ciphers += ['CAMELLIA128', 'CAMELLIA192', 'CAMELLIA256']
+ if RNP_TWOFISH:
+ ciphers += ['TWOFISH']
+ # AEAD supports only 128-bit block ciphers
+ if aead:
+ return ciphers
+ ciphers += ['3DES']
+ if RNP_IDEA:
+ ciphers += ['IDEA']
+ if RNP_BLOWFISH:
+ ciphers += ['BLOWFISH']
+ if RNP_CAST5:
+ ciphers += ['CAST5']
+ return ciphers
+
+class TestIdMixin(object):
+
+ @property
+ def test_id(self):
+ return "".join(self.id().split('.')[1:3])
+
+class KeyLocationChooserMixin(object):
+ def __init__(self):
+ # If set it will try to import a key from provided location
+ # otherwise it will try to generate a key
+ self.__op_key_location = None
+ self.__op_key_gen_cmd = None
+
+ @property
+ def operation_key_location(self):
+ return self.__op_key_location
+
+ @operation_key_location.setter
+ def operation_key_location(self, key):
+ if (type(key) is not tuple): raise RuntimeError("Key must be tuple(pub,sec)")
+ self.__op_key_location = key
+ self.__op_key_gen_cmd = None
+
+ @property
+ def operation_key_gencmd(self):
+ return self.__op_key_gen_cmd
+
+ @operation_key_gencmd.setter
+ def operation_key_gencmd(self, cmd):
+ self.__op_key_gen_cmd = cmd
+ self.__op_key_location = None
+
+'''
+ Things to try here later on:
+ - different public key algorithms
+ - different key protection levels/algorithms
+ - armored import/export
+'''
+class Keystore(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ clear_keyrings()
+
+ @classmethod
+ def tearDownClass(cls):
+ clear_keyrings()
+
+ def tearDown(self):
+ clear_workfiles()
+
+ def _rnpkey_generate_rsa(self, bits= None):
+ # Setup command line params
+ if bits:
+ params = ['--numbits', str(bits)]
+ else:
+ params = []
+ bits = 2048
+
+ userid = str(bits) + '@rnptest'
+ # Open pipe for password
+ pipe = pswd_pipe(PASSWORD)
+ params = params + ['--homedir', RNPDIR, '--pass-fd', str(pipe),
+ '--userid', userid, '--s2k-iterations', '50000', '--generate-key']
+ # Run key generation
+ ret, _, _ = run_proc(RNPK, params)
+ os.close(pipe)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Check packets using the gpg
+ match = check_packets(os.path.join(RNPDIR, PUBRING), RE_RSA_KEY)
+ self.assertTrue(match, 'generated key check failed')
+ keybits = int(match.group(1))
+ self.assertLessEqual(keybits, bits, 'too much bits')
+ self.assertGreater(keybits, bits - 8, 'too few bits')
+ keyid = match.group(2)
+ self.assertEqual(match.group(3), userid, 'wrong user id')
+ # List keys using the rnpkeys
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, KEY_LIST_FAILED)
+ match = re.match(RE_RSA_KEY_LIST, out)
+ # Compare key ids
+ self.assertTrue(match, 'wrong RSA key list output')
+ self.assertEqual(match.group(3)[-16:], match.group(2), 'wrong fp')
+ self.assertEqual(match.group(2), keyid.lower(), 'wrong keyid')
+ self.assertEqual(match.group(1), str(bits), 'wrong key bits in list')
+ # 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_IMPORT_FAILED)
+ # Cleanup and return
+ clear_keyrings()
+
+ def test_generate_default_rsa_key(self):
+ self._rnpkey_generate_rsa()
+
+ def test_rnpkeys_keygen_invalid_parameters(self):
+ # Pass invalid numbits
+ ret, _, err = run_proc(RNPK, ['--numbits', 'wrong', '--homedir', RNPDIR, '--password', 'password',
+ '--userid', 'wrong', '--generate-key'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*wrong bits value: wrong.*')
+ # Too small
+ ret, _, err = run_proc(RNPK, ['--numbits', '768', '--homedir', RNPDIR, '--password', 'password',
+ '--userid', '768', '--generate-key'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*wrong bits value: 768.*')
+ # Wrong hash algorithm
+ ret, _, err = run_proc(RNPK, ['--hash', 'BAD_HASH', '--homedir', RNPDIR, '--password', 'password',
+ '--userid', 'bad_hash', '--generate-key'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Unsupported hash algorithm: BAD_HASH.*')
+ # Wrong S2K iterations
+ ret, _, err = run_proc(RNPK, ['--s2k-iterations', 'WRONG_ITER', '--homedir', RNPDIR, '--password', 'password',
+ '--userid', 'wrong_iter', '--generate-key'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Wrong iterations value: WRONG_ITER.*')
+ # Wrong S2K msec
+ ret, _, err = run_proc(RNPK, ['--s2k-msec', 'WRONG_MSEC', '--homedir', RNPDIR, '--password', 'password',
+ '--userid', 'wrong_msec', '--generate-key'])
+ self.assertNotEqual(ret, 0)
+ 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 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
+ '''
+ clear_keyrings()
+ # Generate 5 keys with different user ids
+ for i in range(0, 5):
+ # generate the next key
+ pipe = pswd_pipe(PASSWORD)
+ userid = str(i) + '@rnp-multiple'
+ ret, _, _ = run_proc(RNPK, ['--numbits', '2048', '--homedir', RNPDIR, '--s2k-msec', '100',
+ '--cipher', 'AES-128', '--pass-fd', str(pipe), '--userid', userid,
+ '--generate-key'])
+ os.close(pipe)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # list keys using the rnpkeys, checking whether it reports correct key
+ # number
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, KEY_LIST_FAILED)
+ match = re.match(RE_MULTIPLE_KEY_LIST, out)
+ self.assertTrue(match, KEY_LIST_WRONG)
+ self.assertEqual(match.group(1), str((i + 1) * 2), 'wrong key count')
+
+ # Checking the 5 keys output
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, KEY_LIST_FAILED)
+ self.assertRegex(out, RE_MULTIPLE_KEY_5, KEY_LIST_WRONG)
+
+ # Cleanup and return
+ clear_keyrings()
+
+ def test_generate_key_with_gpg_import_to_rnp(self):
+ '''
+ Generate key with GnuPG and import it to rnp
+ '''
+ # Generate key in GnuPG
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--passphrase',
+ '', '--quick-generate-key', 'rsakey@gpg', 'rsa'])
+ self.assertEqual(ret, 0, 'gpg key generation failed')
+ # Getting fingerprint of the generated key
+ ret, out, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--list-keys'])
+ match = re.match(RE_GPG_SINGLE_RSA_KEY, out)
+ self.assertTrue(match, 'wrong gpg key list output')
+ keyfp = match.group(1)
+ # Exporting generated public key
+ ret, out, err = run_proc(
+ GPG, ['--batch', '--homedir', GPGHOME, '--armor', '--export', keyfp])
+ self.assertEqual(ret, 0, 'gpg : public key export failed')
+ pubpath = os.path.join(RNPDIR, keyfp + '-pub.asc')
+ with open(pubpath, 'w+') as f:
+ f.write(out)
+ # Exporting generated secret key
+ ret, out, err = run_proc(
+ GPG, ['--batch', '--homedir', GPGHOME, '--armor', '--export-secret-key', keyfp])
+ self.assertEqual(ret, 0, 'gpg : secret key export failed')
+ secpath = os.path.join(RNPDIR, keyfp + '-sec.asc')
+ with open(secpath, 'w+') as f:
+ f.write(out)
+ # Importing public key to rnp
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', pubpath])
+ self.assertEqual(ret, 0, 'rnp : public key import failed')
+ # Importing secret key to rnp
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', secpath])
+ self.assertEqual(ret, 0, 'rnp : secret key import failed')
+
+ def test_generate_with_rnp_import_to_gpg(self):
+ '''
+ Generate key with RNP and export it and then import to GnuPG
+ '''
+ # Open pipe for password
+ pipe = pswd_pipe(PASSWORD)
+ # Run key generation
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
+ '--userid', 'rsakey@rnp', '--generate-key'])
+ os.close(pipe)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Export key
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'rsakey@rnp'])
+ self.assertEqual(ret, 0, 'key export failed')
+ pubpath = os.path.join(RNPDIR, 'rnpkey-pub.asc')
+ with open(pubpath, 'w+') as f:
+ f.write(out)
+ # Import key with GPG
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import',
+ path_for_gpg(pubpath)])
+ self.assertEqual(ret, 0, 'gpg : public key import failed')
+
+ def test_generate_to_kbx(self):
+ '''
+ Generate KBX with RNP and ensurethat the key can be read with GnuPG
+ '''
+ clear_keyrings()
+ pipe = pswd_pipe(PASSWORD)
+ kbx_userid_tracker = 'kbx_userid_tracker@rnp'
+ # Run key generation
+ ret, out, err = run_proc(RNPK, ['--gen-key', '--keystore-format', 'GPG21',
+ '--userid', kbx_userid_tracker, '--homedir',
+ RNPDIR, '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Read KBX with GPG
+ ret, out, err = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys'])
+ self.assertEqual(ret, 0, 'gpg : failed to read KBX')
+ self.assertTrue(kbx_userid_tracker in out, 'gpg : failed to read expected key from KBX')
+ clear_keyrings()
+
+ def test_generate_protection_pass_fd(self):
+ '''
+ Generate key with RNP, using the --pass-fd parameter, and make sure key is encrypted
+ '''
+ clear_keyrings()
+ # Open pipe for password
+ pipe = pswd_pipe(PASSWORD)
+ # Run key generation
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
+ '--userid', KEY_ENC_RNP, '--generate-key'])
+ os.close(pipe)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Check packets using the gpg
+ params = ['--homedir', RNPDIR, '--list-packets', os.path.join(RNPDIR, SECRING)]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, RE_RNP_ENCRYPTED_KEY, 'wrong encrypted secret key listing')
+
+ def test_generate_protection_password(self):
+ '''
+ Generate key with RNP, using the --password parameter, and make sure key is encrypted
+ '''
+ clear_keyrings()
+ params = ['--homedir', RNPDIR, '--password', 'password', '--userid', KEY_ENC_RNP, '--generate-key']
+ ret, _, _ = run_proc(RNPK, params)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Check packets using the gpg
+ params = ['--homedir', RNPDIR, '--list-packets', os.path.join(RNPDIR, SECRING)]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, RE_RNP_ENCRYPTED_KEY, 'wrong encrypted secret key listing')
+
+ def test_generate_unprotected_key(self):
+ '''
+ Generate key with RNP, using the --password parameter, and make sure key is encrypted
+ '''
+ clear_keyrings()
+ params = ['--homedir', RNPDIR, '--password=', '--userid', KEY_ENC_RNP, '--generate-key']
+ ret, _, _ = run_proc(RNPK, params)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Check packets using the gpg
+ params = ['--homedir', RNPDIR, '--list-packets', os.path.join(RNPDIR, SECRING)]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(out, RE_RNP_ENCRYPTED_KEY, 'wrong unprotected secret key listing')
+
+ def test_generate_preferences(self):
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe), '--userid',
+ 'eddsa_25519_prefs', '--generate-key', '--expert'], '22\n')
+ os.close(pipe)
+ self.assertEqual(ret, 0)
+ ret, out, _ = run_proc(RNP, ['--list-packets', os.path.join(RNPDIR, PUBRING)])
+ self.assertRegex(out, r'.*preferred symmetric algorithms: AES-256, AES-192, AES-128 \(9, 8, 7\).*')
+ self.assertRegex(out, r'.*preferred hash algorithms: SHA256, SHA384, SHA512, SHA224 \(8, 9, 10, 11\).*')
+
+ def test_import_signatures(self):
+ clear_keyrings()
+ RE_SIG_2_UNCHANGED = r'(?s)^.*Import finished: 0 new signatures, 2 unchanged, 0 unknown.*'
+ # Import command without the path parameter
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs'])
+ self.assertNotEqual(ret, 0, 'Sigs import without file failed')
+ self.assertRegex(err, r'(?s)^.*Import path isn\'t specified.*', 'Sigs import without file wrong output')
+ # Import command with invalid path parameter
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', data_path('test_key_validity/alice-rev-no-file.pgp')])
+ self.assertNotEqual(ret, 0, 'Sigs import with invalid path failed')
+ self.assertRegex(err, r'(?s)^.*Failed to create input for .*', 'Sigs import with invalid path wrong output')
+ # Try to import signature to empty keyring
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', data_path('test_key_validity/alice-rev.pgp')])
+ self.assertEqual(ret, 0, 'Alice key rev import failed')
+ self.assertRegex(err, r'(?s)^.*Import finished: 0 new signatures, 0 unchanged, 1 unknown.*', 'Alice key rev import wrong output')
+ # Import Basil's key
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/basil-pub.asc')])
+ self.assertEqual(ret, 0, 'Basil key import failed')
+ # Try to import Alice's signatures with Basil's key only
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sigs.pgp')])
+ self.assertEqual(ret, 0, 'Alice sigs import failed')
+ self.assertRegex(err, r'(?s)^.*Import finished: 0 new signatures, 0 unchanged, 2 unknown.*', 'Alice sigs import wrong output')
+ # Import Alice's key without revocation/direct-key signatures
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_PUB)])
+ self.assertEqual(ret, 0, ALICE_IMPORT_FAIL)
+ # Import key revocation signature
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', data_path('test_key_validity/alice-rev.pgp')])
+ self.assertEqual(ret, 0, 'Alice key rev import failed')
+ self.assertRegex(err, RE_SIG_1_IMPORT, 'Alice key rev import wrong output')
+ # Import direct-key signature
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-revoker-sig.pgp')])
+ self.assertEqual(ret, 0, 'Alice direct-key sig import failed')
+ self.assertRegex(err, RE_SIG_1_IMPORT, 'Alice direct-key sig import wrong output')
+ # Try to import two signatures again
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sigs.pgp')])
+ self.assertEqual(ret, 0, 'Alice sigs reimport failed')
+ self.assertRegex(err, RE_SIG_2_UNCHANGED, 'Alice sigs file reimport wrong output')
+ # Import two signatures again via stdin
+ stext = file_text(data_path('test_key_validity/alice-sigs.asc'))
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', '-'], stext)
+ self.assertEqual(ret, 0, 'Alice sigs stdin reimport failed')
+ self.assertRegex(err, RE_SIG_2_UNCHANGED, 'Alice sigs stdin reimport wrong output')
+ # Import two signatures via env variable
+ os.environ["SIG_FILE"] = stext
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', 'env:SIG_FILE'])
+ self.assertEqual(ret, 0, 'Alice sigs env reimport failed')
+ self.assertRegex(err, RE_SIG_2_UNCHANGED, 'Alice sigs var reimport wrong output')
+ # Try to import malformed signatures
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sigs-malf.pgp')])
+ self.assertNotEqual(ret, 0, 'Alice malformed sigs import failed')
+ self.assertRegex(err, r'(?s)^.*Failed to import signatures from .*', 'Alice malformed sigs wrong output')
+
+ def test_export_revocation(self):
+ clear_keyrings()
+ OUT_NO_REV = 'no-revocation.pgp'
+ OUT_ALICE_REV = 'alice-revocation.pgp'
+ # Import Alice's public key and be unable to export revocation
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_PUB)])
+ self.assertEqual(ret, 0, ALICE_IMPORT_FAIL)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'alice'])
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*Revoker secret key not found.*', 'Wrong pubkey revocation export output')
+ # Import Alice's secret key and subkey
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
+ self.assertEqual(ret, 0, 'Alice secret key import failed')
+ # Attempt to export revocation without specifying key
+ pipe = pswd_pipe(PASSWORD)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*You need to specify key to generate revocation for.*', 'Wrong no key revocation export output')
+ # Attempt to export revocation for unknown key
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'basil'])
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*Key matching \'basil\' not found.*', 'Wrong unknown key revocation export output')
+ # Attempt to export revocation for subkey
+ pipe = pswd_pipe(PASSWORD)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'DD23CEB7FEBEFF17'])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*Key matching \'DD23CEB7FEBEFF17\' not found.*', 'Wrong subkey revocation export output')
+ # Attempt to export revocation with too broad search
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/basil-sec.asc')])
+ self.assertEqual(ret, 0, 'Basil secret key import failed')
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'rnp', '--pass-fd', str(pipe),
+ '--output', OUT_NO_REV, '--force'])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0, 'Failed to fail to export revocation')
+ self.assertFalse(os.path.isfile(OUT_NO_REV), 'Failed to fail to export revocation')
+ self.assertRegex(err, r'(?s)^.*Ambiguous input: too many keys found for \'rnp\'.*', 'Wrong revocation export output')
+ # Finally successfully export revocation
+ pipe = pswd_pipe(PASSWORD)
+ 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)
+ self.assertTrue(match, 'Wrong revocation signature contents')
+ self.assertEqual(match.group(1).strip(), '0 (No reason)', 'Wrong revocation signature reason')
+ self.assertEqual(match.group(2).strip(), '', 'Wrong revocation signature message')
+ # Make sure it can be imported back
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', OUT_ALICE_REV])
+ self.assertEqual(ret, 0, 'Failed to import revocation back')
+ self.assertRegex(err, RE_SIG_1_IMPORT, 'Revocation import wrong output')
+ # Make sure file will not be overwritten with --force parameter
+ with open(OUT_ALICE_REV, 'w+') as f:
+ f.truncate(10)
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe), '--output', OUT_ALICE_REV, '--force', '--notty'], '\n\n')
+ os.close(pipe)
+ self.assertNotEqual(ret, 0, 'Revocation was overwritten with --force')
+ self.assertEqual(10, os.stat(OUT_ALICE_REV).st_size, 'Revocation was overwritten with --force')
+ # Make sure file will not be overwritten without --overwrite parameter
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe), '--output', OUT_ALICE_REV, '--notty'], '\n\n')
+ os.close(pipe)
+ self.assertNotEqual(ret, 0, 'Revocation was overwritten without --overwrite and --force')
+ self.assertTrue(os.path.isfile(OUT_ALICE_REV), 'Revocation was overwritten without --overwrite')
+ self.assertEqual(10, os.stat(OUT_ALICE_REV).st_size, 'Revocation was overwritten without --overwrite')
+ # Make sure file will be overwritten with --overwrite parameter
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, err = 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.assertGreater(os.stat(OUT_ALICE_REV).st_size, 10)
+ # Create revocation with wrong code - 'no longer valid' (which is usable only for userid)
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'alice', '--rev-type', 'no longer valid',
+ '--pass-fd', str(pipe), '--output', OUT_NO_REV, '--force'])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0, 'Failed to use wrong revocation reason')
+ self.assertFalse(os.path.isfile(OUT_NO_REV))
+ self.assertRegex(err, r'(?s)^.*Wrong key revocation code: 32.*', 'Wrong revocation export output')
+ # Create revocation without rev-code parameter
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'alice', '--pass-fd', str(pipe),
+ '--output', OUT_NO_REV, '--force', '--rev-type'])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0, 'Failed to use rev-type without parameter')
+ self.assertFalse(os.path.isfile(OUT_NO_REV), 'Failed to use rev-type without parameter')
+ # Create another revocation with custom code/reason
+ revcodes = {"0" : "0 (No reason)", "1" : "1 (Superseded)", "2" : "2 (Compromised)",
+ "3" : "3 (Retired)", "no" : "0 (No reason)", "superseded" : "1 (Superseded)",
+ "compromised" : "2 (Compromised)", "retired" : "3 (Retired)"}
+ for revcode in revcodes:
+ revreason = 'Custom reason: ' + revcode
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe),
+ '--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)
+ self.assertEqual(match.group(1).strip(), revcodes[revcode], 'Wrong revocation signature revcode')
+ self.assertEqual(match.group(2).strip(), revreason, 'Wrong revocation signature reason')
+ # Make sure it is also imported back
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', OUT_ALICE_REV])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, RE_SIG_1_IMPORT, 'Revocation import wrong output')
+ # Now let's import it with GnuPG
+ gpg_import_pubring(data_path(KEY_ALICE_PUB))
+ ret, _, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', OUT_ALICE_REV])
+ self.assertEqual(ret, 0, 'gpg signature revocation import failed')
+ self.assertRegex(err, RE_GPG_REVOCATION_IMPORT, 'Wrong gpg revocation import output')
+
+ os.remove(OUT_ALICE_REV)
+ clear_keyrings()
+
+ def test_import_keys(self):
+ clear_keyrings()
+ # try to import non-existing file
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('thiskeyfiledoesnotexist')])
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)^.*Failed to create input for .*thiskeyfiledoesnotexist.*')
+ # try malformed file
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sigs-malf.pgp')])
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)^.*failed to import key\(s\) from .*test_key_validity/alice-sigs-malf.pgp, stopping\..*')
+ self.assertRegex(err, r'(?s)^.*Import finished: 0 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*')
+ # try --import
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 2 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 2 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_stream_key_merge/key-both.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 3 new public keys, 3 new secret keys, 0 updated, 0 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_stream_key_merge/key-both.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 6 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 1 new public keys, 0 new secret keys, 1 updated, 0 unchanged\..*')
+ clear_keyrings()
+ # try --import-key
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 2 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 2 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_stream_key_merge/key-both.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 3 new public keys, 3 new secret keys, 0 updated, 0 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_stream_key_merge/key-both.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 6 unchanged\..*')
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 1 new public keys, 0 new secret keys, 1 updated, 0 unchanged\..*')
+ clear_keyrings()
+
+ def test_export_keys(self):
+ PUB_KEY = r'(?s)^.*' \
+ r'-----BEGIN PGP PUBLIC KEY BLOCK-----.*' \
+ r'-----END PGP PUBLIC KEY BLOCK-----.*$'
+ PUB_KEY_PKTS = r'(?s)^.*' \
+ r'Public key packet.*' \
+ r'keyid: 0x0451409669ffde3c.*' \
+ r'Public subkey packet.*' \
+ r'keyid: 0xdd23ceb7febeff17.*$'
+ SEC_KEY = r'(?s)^.*' \
+ r'-----BEGIN PGP PRIVATE KEY BLOCK-----.*' \
+ r'-----END PGP PRIVATE KEY BLOCK-----.*$'
+ SEC_KEY_PKTS = r'(?s)^.*' \
+ r'Secret key packet.*' \
+ r'keyid: 0x0451409669ffde3c.*' \
+ r'Secret subkey packet.*' \
+ r'keyid: 0xdd23ceb7febeff17.*$'
+ KEY_OVERWRITE = r'(?s)^.*' \
+ r'File \'.*alice-key.pub.asc\' already exists.*' \
+ 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(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\.$')
+ # Attempt to export wrong key
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'boris'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Key\(s\) matching \'boris\' not found\.$')
+ # Export it to the stdout
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, PUB_KEY)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', '-'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, PUB_KEY)
+ # Export key via --userid parameter
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--userid', 'alice'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, PUB_KEY)
+ # Export with empty --userid parameter
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--userid'])
+ self.assertNotEqual(ret, 0)
+ # Export it to the file
+ kpub, ksec, kren = reg_workfiles('alice-key', '.pub.asc', '.sec.asc', '.pub.ren-asc')
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub])
+ self.assertEqual(ret, 0)
+ self.assertRegex(file_text(kpub), PUB_KEY)
+ # Try to export again to the same file without additional parameters
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--notty'], '\n\n')
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(out, KEY_OVERWRITE)
+ self.assertRegex(err, r'(?s)^.*Operation failed: file \'.*alice-key.pub.asc\' already exists.*$')
+ # Try to export with --force parameter
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--force', '--notty'], '\n\n')
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(out, KEY_OVERWRITE)
+ self.assertRegex(err, r'(?s)^.*Operation failed: file \'.*alice-key.pub.asc\' already exists.*$')
+ # 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 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)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--notty'], 'y\n')
+ self.assertEqual(ret, 0)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', kpub])
+ self.assertEqual(ret, 0)
+ # Enter new filename in overwrite prompt
+ with open(kpub, 'w+') as f:
+ f.truncate(10)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--notty'], 'n\n' + kren + '\n')
+ self.assertEqual(ret, 0)
+ self.assertEqual(os.path.getsize(kpub), 10)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', kren])
+ self.assertEqual(ret, 0)
+ # Attempt to export secret key
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--secret', 'alice'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Key\(s\) matching \'alice\' not found\.$')
+ # Import Alice's secret key and subkey
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
+ self.assertEqual(ret, 0)
+ # Make sure secret key is not exported when public is requested
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', ksec])
+ self.assertEqual(ret, 0)
+ self.assertRegex(file_text(ksec), PUB_KEY)
+ ret, out, _ = run_proc(RNP, ['--list-packets', ksec])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, PUB_KEY_PKTS)
+ # Make sure secret key is correctly exported
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--secret', 'alice', '--output', ksec, '--overwrite'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(file_text(ksec), SEC_KEY)
+ ret, out, _ = run_proc(RNP, ['--list-packets', ksec])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, SEC_KEY_PKTS)
+ clear_keyrings()
+
+ def test_userid_escape(self):
+ clear_keyrings()
+ tracker_beginning = 'tracker'
+ tracker_end = '@rnp'
+ tracker_1 = tracker_beginning + ''.join(map(chr, range(1,0x10))) + tracker_end
+ tracker_2 = tracker_beginning + ''.join(map(chr, range(0x10,0x20))) + tracker_end
+ #Run key generation
+ rnp_genkey_rsa(tracker_1, 1024)
+ rnp_genkey_rsa(tracker_2, 1024)
+ #Read with rnpkeys
+ ret, out_rnp, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, 'rnpkeys : failed to read keystore')
+ #Read with GPG
+ ret, out_gpg, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys'])
+ self.assertEqual(ret, 0, 'gpg : failed to read keystore')
+ tracker_rnp = re.findall(r'' + tracker_beginning + '.*' + tracker_end + '', out_rnp)
+ tracker_gpg = re.findall(r'' + tracker_beginning + '.*' + tracker_end + '', out_gpg)
+ self.assertEqual(len(tracker_rnp), 2, 'failed to find expected rnp userids')
+ self.assertEqual(len(tracker_gpg), 2, 'failed to find expected gpg userids')
+ self.assertEqual(tracker_rnp, tracker_gpg, 'userids from rnpkeys and gpg don\'t match')
+ clear_keyrings()
+
+ def test_key_revoke(self):
+ clear_keyrings()
+ # Import Alice's public key and be unable to revoke
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_PUB)])
+ self.assertEqual(ret, 0, ALICE_IMPORT_FAIL)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke-key', 'alice'])
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*Revoker secret key not found.*Failed to revoke a key.*')
+ # Import Alice's secret key and subkey
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
+ self.assertEqual(ret, 0)
+ # Attempt to revoke without specifying a key
+ pipe = pswd_pipe(PASSWORD)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*You need to specify key or subkey to revoke.*')
+ # Attempt to revoke unknown key
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'basil'])
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*Key matching \'basil\' not found.*')
+ # Attempt to revoke with too broad search
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/basil-sec.asc')])
+ self.assertEqual(ret, 0)
+ pipe = pswd_pipe(PASSWORD)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'rnp', '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertRegex(err, r'(?s)^.*Ambiguous input: too many keys found for \'rnp\'.*')
+ # Revoke a primary key
+ pipe = pswd_pipe(PASSWORD)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '0451409669FFDE3C', '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertEqual(ret, 0)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*pub.*0451409669ffde3c.*\[REVOKED\].*73edcc9119afc8e2dbbdcde50451409669ffde3c.*')
+ # Try again without the '--force' parameter
+ pipe = pswd_pipe(PASSWORD)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '0451409669FFDE3C', '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*Error: key \'0451409669FFDE3C\' is revoked already. Use --force to generate another revocation signature.*')
+ # Try again with --force parameter
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '0451409669FFDE3C', '--pass-fd', str(pipe), "--force", "--rev-type", "3", "--rev-reason", "Custom"])
+ os.close(pipe)
+ self.assertEqual(ret, 0)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*pub.*0451409669ffde3c.*\[REVOKED\].*73edcc9119afc8e2dbbdcde50451409669ffde3c.*')
+ # Revoke a subkey
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'DD23CEB7FEBEFF17', '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertEqual(ret, 0)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*sub.*dd23ceb7febeff17.*\[REVOKED\].*a4bbb77370217bca2307ad0ddd23ceb7febeff17.*')
+ # Try again without the '--force' parameter
+ pipe = pswd_pipe(PASSWORD)
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'DD23CEB7FEBEFF17', '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0)
+ self.assertEqual(len(out), 0)
+ self.assertRegex(err, r'(?s)^.*Error: key \'DD23CEB7FEBEFF17\' is revoked already. Use --force to generate another revocation signature.*', err)
+ # Try again with --force parameter
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'DD23CEB7FEBEFF17', '--pass-fd', str(pipe), "--force", "--rev-type", "2", "--rev-reason", "Other"])
+ os.close(pipe)
+ self.assertEqual(ret, 0)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*sub.*dd23ceb7febeff17.*\[REVOKED\].*a4bbb77370217bca2307ad0ddd23ceb7febeff17.*')
+
+ def _test_userid_genkey(self, userid_beginning, weird_part, userid_end, weird_part2=''):
+ clear_keyrings()
+ USERS = [userid_beginning + weird_part + userid_end]
+ if weird_part2:
+ USERS.append(userid_beginning + weird_part2 + userid_end)
+ # Run key generation
+ for userid in USERS:
+ rnp_genkey_rsa(userid, 1024)
+ # Read with GPG
+ ret, out, err = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys', '--charset', CONSOLE_ENCODING])
+ self.assertEqual(ret, 0, 'gpg : failed to read keystore')
+ tracker_escaped = re.findall(r'' + userid_beginning + '.*' + userid_end + '', out)
+ tracker_gpg = list(map(decode_string_escape, tracker_escaped))
+ self.assertEqual(tracker_gpg, USERS, 'gpg : failed to find expected userids from keystore')
+ # Read with rnpkeys
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, 'rnpkeys : failed to read keystore')
+ tracker_escaped = re.findall(r'' + userid_beginning + '.*' + userid_end + '', out)
+ tracker_rnp = list(map(decode_string_escape, tracker_escaped))
+ self.assertEqual(tracker_rnp, USERS, 'rnpkeys : failed to find expected userids from keystore')
+ clear_keyrings()
+
+ def test_userid_unicode_genkeys(self):
+ self._test_userid_genkey('track', WEIRD_USERID_UNICODE_1, 'end', WEIRD_USERID_UNICODE_2)
+
+ def test_userid_special_chars_genkeys(self):
+ self._test_userid_genkey('track', WEIRD_USERID_SPECIAL_CHARS, 'end')
+ self._test_userid_genkey('track', WEIRD_USERID_SPACE, 'end')
+ self._test_userid_genkey('track', WEIRD_USERID_QUOTE, 'end')
+ self._test_userid_genkey('track', WEIRD_USERID_SPACE_AND_QUOTE, 'end')
+
+ def test_userid_too_long_genkeys(self):
+ clear_keyrings()
+ userid = WEIRD_USERID_TOO_LONG
+ # Open pipe for password
+ pipe = pswd_pipe(PASSWORD)
+ # Run key generation
+ ret, _, _ = run_proc(RNPK, ['--gen-key', '--userid', userid,
+ '--homedir', RNPDIR, '--pass-fd', str(pipe)])
+ os.close(pipe)
+ self.assertNotEqual(ret, 0, 'should have failed on too long id')
+
+ def test_key_remove(self):
+ if RNP_CAST5:
+ MSG_KEYS_NOT_FOUND = r'Key\(s\) not found\.'
+ clear_keyrings()
+ # Import public keyring
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(PUBRING_1)])
+ self.assertEqual(ret, 0)
+ # Remove without parameters
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key'])
+ self.assertNotEqual(ret, 0)
+ # Remove all imported public keys with subkeys
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a', '2fcadf05ffa501bb'])
+ self.assertEqual(ret, 0)
+ # Check that keyring is empty
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Invalid no-keys output')
+ # Import secret keyring
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('keyrings/1/secring.gpg')])
+ self.assertEqual(ret, 0, 'Secret keyring import failed')
+ # Remove all secret keys with subkeys
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a', '2fcadf05ffa501bb', '--force'])
+ self.assertEqual(ret, 0, 'Failed to remove 2 secret keys')
+ # Check that keyring is empty
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Failed to remove secret keys')
+ # Import public keyring
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(PUBRING_1)])
+ self.assertEqual(ret, 0, 'Public keyring import failed')
+ # Remove all subkeys
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key',
+ '326ef111425d14a5', '54505a936a4a970e', '8a05b89fad5aded1', '1d7e8a5393c997a8', '1ed63ee56fadc34d'])
+ self.assertEqual(ret, 0, 'Failed to remove 5 keys')
+ # Check that subkeys are removed
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'2 keys found', 'Failed to remove subkeys')
+ self.assertFalse(re.search('326ef111425d14a5|54505a936a4a970e|8a05b89fad5aded1|1d7e8a5393c997a8|1ed63ee56fadc34d', out))
+ # Remove remaining public keys
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a', '2fcadf05ffa501bb'])
+ self.assertEqual(ret, 0, 'Failed to remove public keys')
+ # Try to remove again
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'Key matching \'7bc6709b15c23a4a\' not found\.', 'Unexpected result')
+ # Check that keyring is empty
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Failed to list empty keyring')
+ # Import public keyring
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(PUBRING_1)])
+ self.assertEqual(ret, 0, 'Public keyring import failed')
+ # Try to remove by uid substring, should match multiple keys and refuse to remove
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', 'uid0'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'Ambiguous input: too many keys found for \'uid0\'\.', 'Unexpected result')
+ # Remove keys by uids
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', 'key0-uid0', 'key1-uid1'])
+ self.assertEqual(ret, 0, 'Failed to remove keys')
+ # Check that keyring is empty
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Failed to remove keys')
+
+ def test_additional_subkeys_default(self):
+ '''
+ Generate default key (primary + sub) then add more subkeys.
+ '''
+ # Open pipe for password
+ pipe = pswd_pipe(PASSWORD)
+ # Run key generation
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
+ '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
+ os.close(pipe)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Edit generated key, generate & add one more subkey with default parameters
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
+ '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertEqual(ret, 0, 'Failed to add new subkey')
+ # list keys, check result
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, KEY_LIST_FAILED)
+ self.assertRegex(out, RE_MULTIPLE_SUBKEY_3, KEY_LIST_WRONG)
+ clear_keyrings()
+
+ def test_additional_subkeys_invalid_parameters(self):
+ # Run primary key generation
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
+ '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Attempt to generate subkey for non-existing key
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
+ '--edit-key', '--add-subkey', 'unknown'])
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'Secret keys matching \'unknown\' not found.')
+ # Attempt to generate subkey using the invalid password
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'wrong',
+ '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)Failed to unlock primary key.*Subkey generation failed')
+ # Attempt to generate subkey using the invalid password, asked via tty
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key',
+ '--add-subkey', 'primary_for_many_subs@rnp'], 'password2\n')
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)Failed to unlock primary key.*Subkey generation failed')
+ # Attempt to generate ECDH subkey with invalid curve
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, '--edit-key', '--add-subkey',
+ 'primary_for_many_subs@rnp', '--expert'],
+ '\n\n0\n101\n18\n-10\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n')
+ self.assertEqual(ret, 1)
+ self.assertRegex(out, r'(?s)Too many attempts. Aborting.')
+ self.assertRegex(err, r'(?s)Subkey generation setup failed')
+ # Attempt to generate ECDSA subkey with invalid curve
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, '--edit-key', '--add-subkey',
+ 'primary_for_many_subs@rnp', '--expert'],
+ '19\n-10\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n')
+ self.assertEqual(ret, 1)
+ self.assertRegex(out, r'(?s)Too many attempts. Aborting.')
+ self.assertRegex(err, r'(?s)Subkey generation setup failed')
+ # Pass invalid numbits
+ ret, _, err = run_proc(RNPK, ['--numbits', 'wrong', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--userid', 'wrong', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*wrong bits value: wrong.*')
+ # Too small
+ ret, _, err = run_proc(RNPK, ['--numbits', '768', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--userid', '768', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*wrong bits value: 768.*')
+ # ElGamal too large and wrong numbits
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'wrong', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
+ '--expert'], '16\n2048zzz\n99999999999999999999999999\n2048\n')
+ self.assertRegex(err, r'(?s)Unexpected end of line.*Number out of range.*')
+ self.assertEqual(ret, 1)
+ # Wrong hash algorithm
+ ret, _, err = run_proc(RNPK, ['--hash', 'BAD_HASH', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--userid', 'bad_hash', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Unsupported hash algorithm: BAD_HASH.*')
+ # Wrong S2K iterations
+ ret, _, err = run_proc(RNPK, ['--s2k-iterations', 'WRONG_ITER', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--userid', 'wrong_iter', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Wrong iterations value: WRONG_ITER.*')
+ # Wrong S2K msec
+ ret, _, err = run_proc(RNPK, ['--s2k-msec', 'WRONG_MSEC', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--userid', 'wrong_msec', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertNotEqual(ret, 0)
+ 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', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: WRONG_AES.*Failed to process argument --cipher.*')
+ # Ambiguous primary key
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
+ '--userid', 'primary_for_many_subs2@rnp', '--generate-key'])
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
+ '--edit-key', '--add-subkey', 'primary_for_many'])
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'Ambiguous input: too many keys found for \'primary_for_many\'')
+
+ clear_keyrings()
+
+ def test_additional_subkeys_expert_mode(self):
+ # Run primary key generation
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
+ '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
+ # RSA subkey
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
+ '--expert'], '\n\n0\n101\n1\n1023\n4097\n3072\n')
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # ElGamal subkey
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
+ '--expert'], '\n\n0\n101\n16\n1023\n4097\n1025\n')
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # DSA subkey
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
+ '--expert'], '\n\n0\n101\n17\n1023\n3073\n1025\n')
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # ECDH subkey
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
+ '--expert'], '\n\n0\n101\n18\n0\n8\n1\n')
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # ECDSA subkey
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
+ '--expert'], '\n\n0\n101\n19\n0\n8\n1\n')
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # EDDSA subkey
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
+ '--expert'], '\n\n0\n101\n22\n')
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # list keys, check result
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, KEY_LIST_FAILED)
+ self.assertRegex(out, RE_MULTIPLE_SUBKEY_8, KEY_LIST_WRONG)
+
+ clear_keyrings()
+
+ def test_additional_subkeys_reuse_password(self):
+ pipe = pswd_pipe('primarypassword')
+ # Primary key with password
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
+ '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
+ os.close(pipe)
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Provide password to add subkey, reuse password for subkey, say "yes"
+ stdinstr = 'primarypassword\ny\n'
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'],
+ stdinstr)
+ self.assertEqual(ret, 0, 'Failed to add new subkey')
+ self.assertRegex(out, r'Would you like to use the same password to protect subkey')
+ # Do not reuse same password for subkey, say "no"
+ stdinstr = 'primarypassword\nN\nsubkeypassword\nsubkeypassword\n'
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'],
+ stdinstr)
+ self.assertEqual(ret, 0)
+ # Primary key with empty password
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
+ '--userid', 'primary_with_empty_password@rnp', '--generate-key'])
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ # Set empty password for generated subkey
+ stdinstr = '\n\ny\n'
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_with_empty_password@rnp'],
+ stdinstr)
+ self.assertEqual(ret, 0)
+ # Set password for generated subkey
+ stdinstr = 'subkeypassword\nsubkeypassword\n'
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_with_empty_password@rnp'],
+ stdinstr)
+ self.assertEqual(ret, 0)
+ clear_keyrings()
+
+ def test_edit_key_single_option(self):
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_25519_NOTWEAK_SEC)])
+ self.assertEqual(ret, 0)
+ # Try to pass multiple --edit-key sub-options at once
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--check-cv25519-bits', '--fix-cv25519-bits',
+ '--add-subkey', '--set-expire', '0', '3176fc1486aa2528'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Only one key edit option can be executed at a time..*$')
+ clear_keyrings()
+
+ def test_set_expire(self):
+ kpath = os.path.join(RNPDIR, PUBRING)
+ # Primary key with empty password
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
+ '--userid', 'primary_with_empty_password@rnp', '--generate-key'])
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+
+ # Wrong expiration argument
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '-1', 'primary_with_empty_password@rnp'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'Failed to set key expiration.')
+
+ ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
+ self.assertEqual(ret, 0)
+ matches = re.findall(r'(key expiration time: 63072000 seconds \(730 days\))', out)
+ self.assertEqual(len(matches), 2)
+
+ # Non-existing key argument
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '0', 'wrongkey'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'Secret keys matching \'wrongkey\' not found.')
+
+ # Remove expiration date
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '0', 'primary_with_empty_password@rnp'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(out, r'(?s)^.*\[EXPIRES .*', 'Failed to remove expiration!')
+
+ ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
+ self.assertEqual(ret, 0)
+ matches = re.findall(r'(key expiration time: 63072000 seconds \(730 days\))', out)
+ self.assertEqual(len(matches), 1)
+
+ # Expires in 10 seconds
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10', 'primary_with_empty_password@rnp'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')
+
+ ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*key expiration time: 10 seconds \(0 days\).*')
+
+ # Expires in 10 hours
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10h', 'primary_with_empty_password@rnp'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')
+ ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*key expiration time: 36000 seconds \(0 days\).*')
+
+ # Expires in 10 months
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10m', 'primary_with_empty_password@rnp'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')
+ ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*key expiration time: 26784000 seconds \(310 days\).*')
+
+ # Expires in 10 years
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10y', 'primary_with_empty_password@rnp'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')
+ ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*key expiration time: 315360000 seconds \(3650 days\).*')
+
+ # Additional primary for ambiguous key uid
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
+ '--userid', 'primary2@rnp', '--generate-key'])
+ self.assertEqual(ret, 0, KEY_GEN_FAILED)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '0', 'primary'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'Ambiguous input: too many keys found for \'primary\'')
+
+ clear_keyrings()
+
+class Misc(unittest.TestCase):
+
+ @classmethod
+ def setUpClass(cls):
+ rnp_genkey_rsa(KEY_ENCRYPT)
+ rnp_genkey_rsa(KEY_SIGN_GPG)
+ gpg_import_pubring()
+ gpg_import_secring()
+
+ @classmethod
+ def tearDownClass(cls):
+ clear_keyrings()
+
+ def tearDown(self):
+ clear_workfiles()
+
+ def test_encryption_unicode(self):
+ if sys.version_info >= (3,):
+ filename = UNICODE_SEQUENCE_1
+ else:
+ filename = UNICODE_SEQUENCE_1.encode(CONSOLE_ENCODING)
+
+ src, dst, dec = reg_workfiles(filename, '.txt', '.rnp', '.dec')
+ # Generate random file of required size
+ random_text(src, 128000)
+
+ rnp_encrypt_file_ex(src, dst, [KEY_ENCRYPT])
+ rnp_decrypt_file(dst, dec)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+
+ remove_files(src, dst, dec)
+
+ def test_encryption_no_mdc(self):
+ src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
+ # Generate random file of required size
+ random_text(src, 64000)
+ # Encrypt cleartext file with GPG
+ params = ['--homedir', GPGHOME, '-c', '-z', '0', '--disable-mdc', '--s2k-count',
+ '65536', '--batch', '--passphrase', PASSWORD, '--output',
+ path_for_gpg(dst), path_for_gpg(src)]
+ ret, _, _ = run_proc(GPG, params)
+ self.assertEqual(ret, 0, 'gpg symmetric encryption failed')
+ # Decrypt encrypted file with RNP
+ rnp_decrypt_file(dst, dec)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+
+ def test_encryption_s2k(self):
+ src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
+ random_text(src, 1000)
+
+ ciphers = rnp_supported_ciphers(False)
+ hashes = ['SHA1', 'RIPEMD160', 'SHA256', 'SHA384', 'SHA512', 'SHA224']
+ s2kmodes = [0, 1, 3]
+
+ if not RNP_RIPEMD160:
+ hashes.remove('RIPEMD160')
+
+ 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]
+
+ if s2k is not None:
+ params.insert(7, '--s2k-mode')
+ params.insert(8, str(s2k))
+
+ if iterations is not None:
+ params.insert(9, '--s2k-count')
+ params.insert(10, str(iterations))
+
+ if GPG_NO_OLD:
+ params.insert(3, '--allow-old-cipher-algos')
+
+ ret, _, _ = run_proc(GPG, params)
+ self.assertEqual(ret, 0, 'gpg symmetric encryption failed')
+ rnp_decrypt_file(dst, dec)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+ remove_files(dst, dec)
+
+ for i in range(0, 20):
+ rnp_encryption_s2k_gpg(ciphers[i % len(ciphers)], hashes[
+ i % len(hashes)], s2kmodes[i % len(s2kmodes)])
+
+ def test_armor(self):
+ src_beg, dst_beg, dst_mid, dst_fin = reg_workfiles('beg', '.src', '.dst',
+ '.mid.dst', '.fin.dst')
+ armor_types = [('msg', 'MESSAGE'), ('pubkey', 'PUBLIC KEY BLOCK'),
+ ('seckey', 'PRIVATE KEY BLOCK'), ('sign', 'SIGNATURE')]
+
+ random_text(src_beg, 1000)
+ # Wrong armor type
+ ret, _, err = run_proc(RNP, ['--enarmor=wrong', src_beg, '--output', dst_beg])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Wrong enarmor argument: wrong.*$')
+
+ # Default armor type
+ ret, _, _ = run_proc(RNP, ['--enarmor', src_beg, '--output', dst_beg])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Wrong enarmor argument: wrong.*$')
+ txt = file_text(dst_beg).strip('\r\n')
+ self.assertTrue(txt.startswith('-----BEGIN PGP MESSAGE-----'), 'wrong armor header')
+ self.assertTrue(txt.endswith('-----END PGP MESSAGE-----'), 'wrong armor trailer')
+ remove_files(dst_beg)
+
+ for data_type, header in armor_types:
+ prefix = '-----BEGIN PGP ' + header + '-----'
+ suffix = '-----END PGP ' + header + '-----'
+
+ ret, _, _ = run_proc(RNP, ['--enarmor=' + data_type, src_beg, '--output', dst_beg])
+ self.assertEqual(ret, 0)
+ txt = file_text(dst_beg).strip('\r\n')
+
+ self.assertTrue(txt.startswith(prefix), 'wrong armor header')
+ self.assertTrue(txt.endswith(suffix), 'wrong armor trailer')
+
+ ret, _, _ = run_proc(RNP, ['--dearmor', dst_beg, '--output', dst_mid])
+ self.assertEqual(ret, 0)
+ ret, _, _ = run_proc(RNP, ['--enarmor=' + data_type, dst_mid, '--output', dst_fin])
+ self.assertEqual(ret, 0)
+
+ 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(KEYRING_DIR_1)
+ KEYRING_2 = data_path('keyrings/2')
+ KEYRING_3 = data_path(KEYRING_DIR_3)
+ KEYRING_5 = data_path('keyrings/5')
+ path = data_path('test_cli_rnpkeys') + '/'
+
+ if RNP_CAST5:
+ _, 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, ['--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'])
+ compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_sigs_sec'), out, 'keyring 1 sec sig listing failed')
+
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_2, '--list-keys'])
+ compare_file(path + 'keyring_2_list_keys', out, 'keyring 2 key listing failed')
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_2, '-l', '--with-sigs'])
+ compare_file(path + 'keyring_2_list_sigs', out, 'keyring 2 sig listing failed')
+
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_3, '--list-keys'])
+ compare_file_any(allow_y2k38_on_32bit(path + 'keyring_3_list_keys'), out, 'keyring 3 key listing failed')
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_3, '-l', '--with-sigs'])
+ compare_file_any(allow_y2k38_on_32bit(path + 'keyring_3_list_sigs'), out, 'keyring 3 sig listing failed')
+
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_5, '--list-keys'])
+ compare_file(path + 'keyring_5_list_keys', out, 'keyring 5 key listing failed')
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_5, '-l', '--with-sigs'])
+ compare_file(path + 'keyring_5_list_sigs', out, 'keyring 5 sig listing failed')
+
+ _, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
+ '--list-keys'])
+ if RNP_BRAINPOOL:
+ self.assertEqual(file_text(path + 'test_stream_key_load_keys'), out, 'g10 keyring key listing failed')
+ else:
+ self.assertEqual(file_text(path + 'test_stream_key_load_keys_no_bp'), out, 'g10 keyring key listing failed')
+ _, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
+ '-l', '--with-sigs'])
+ if RNP_BRAINPOOL:
+ self.assertEqual(file_text(path + 'test_stream_key_load_sigs'), out, 'g10 keyring sig listing failed')
+ else:
+ self.assertEqual(file_text(path + 'test_stream_key_load_sigs_no_bp'), out, 'g10 keyring sig listing failed')
+ # Below are disabled until we have some kind of sorting which doesn't depend on
+ # readdir order
+ #_, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
+ # '-l', '--secret'])
+ #compare_file(path + 'test_stream_key_load_keys_sec', out,
+ # 'g10 sec keyring key listing failed')
+ #_, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
+ # '-l', '--secret', '--with-sigs'])
+ #compare_file(path + 'test_stream_key_load_sigs_sec', out,
+ # 'g10 sec keyring sig listing failed')
+
+ if RNP_CAST5:
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', '2fcadf05ffa501bb'])
+ compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb'), out, 'list key 2fcadf05ffa501bb failed')
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l',
+ '--with-sigs', '2fcadf05ffa501bb'])
+ compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb_sig'), out, 'list sig 2fcadf05ffa501bb failed')
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l',
+ '--secret', '2fcadf05ffa501bb'])
+ compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb_sec'), out, 'list sec 2fcadf05ffa501bb failed')
+
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', '00000000'])
+ compare_file(path + 'getkey_00000000', out, 'list key 00000000 failed')
+ _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', 'zzzzzzzz'])
+ 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')
+ else:
+ self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys_no_bp')), out, 'g10 key listing failed')
+ ret, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10), '--secret', '--list-keys'])
+ self.assertEqual(ret, 0)
+ if RNP_BRAINPOOL:
+ self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys_sec')), out, 'g10 secret key listing failed')
+ else:
+ self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys_sec_no_bp')), out, 'g10 secret key listing failed')
+
+ def test_rnpkeys_g10_def_key(self):
+ RE_SIG = r'(?s)^.*' \
+ r'Good signature made .*' \
+ r'using (.*) key (.*)' \
+ r'pub .*' \
+ r'b54fdebbb673423a5d0aa54423674f21b2441527.*' \
+ r'uid\s+(ecc-p256)\s*' \
+ r'Signature\(s\) verified successfully.*$'
+
+ src, dst = reg_workfiles('cleartext', '.txt', '.rnp')
+ random_text(src, 1000)
+ # Sign file with rnp using the default g10 key
+ params = ['--homedir', data_path('test_cli_g10_defkey/g10'),
+ '--password', PASSWORD, '--output', dst, '-s', src]
+ ret, _, err = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'rnp signing failed')
+ # Verify signed file
+ params = ['--homedir', data_path('test_cli_g10_defkey/g10'), '-v', dst]
+ ret, _, err = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'verification failed')
+ self.assertRegex(err, RE_SIG, 'wrong rnp g10 verification output')
+
+ 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, '--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, '--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, '--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, '--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, '--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
+ clear_keyrings()
+ # Import Alice's secret key and subkey
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
+ self.assertEqual(ret, 0, 'Alice secret key import failed')
+ # Export key
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export', 'Alice'])
+ self.assertEqual(ret, 0, 'key export failed')
+ pubpath = os.path.join(RNPDIR, 'Alice-export-test.asc')
+ with open(pubpath, 'w+') as f:
+ f.write(out)
+ # List exported key packets
+ params = ['--list-packets', pubpath]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, PKT_LIST_FAILED)
+ compare_file_ex(data_path('test_single_export_subkeys/list_key_export_single.txt'), out,
+ 'exported packets mismatch')
+
+ def test_rnp_permissive_key_import(self):
+ # Import keys while skipping bad packets, see #1160
+ clear_keyrings()
+ # Try to import without --permissive option, should fail.
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_key_edge_cases/pubring-malf-cert.pgp')])
+ self.assertNotEqual(ret, 0, 'Imported bad packets without --permissive option set!')
+ # Import with --permissive
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', '--permissive',data_path('test_key_edge_cases/pubring-malf-cert.pgp')])
+ self.assertEqual(ret, 0, 'Failed to import keys with --permissive option')
+
+ # List imported keys and sigs
+ params = ['--homedir', RNPDIR, '--list-keys', '--with-sigs']
+ 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)
+ self.assertEqual(ret, 0, PKT_LIST_FAILED)
+ compare_file_ex(data_path('test_list_packets/list_standard.txt'), out,
+ 'standard listing mismatch')
+ # List packets with mpi values
+ params = ['--mpi', '--list-packets', KEY_P256]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'packet listing with mpi failed')
+ compare_file_ex(data_path('test_list_packets/list_mpi.txt'), out, 'mpi listing mismatch')
+ # List packets with grip/fingerprint values
+ params = ['--list-packets', KEY_P256, '--grips']
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'packet listing with grips failed')
+ compare_file_ex(data_path('test_list_packets/list_grips.txt'), out,
+ 'grips listing mismatch')
+ # List packets with raw packet contents
+ params = ['--list-packets', KEY_P256, '--raw']
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'packet listing with raw packets failed')
+ compare_file_ex(data_path('test_list_packets/list_raw.txt'), out, 'raw listing mismatch')
+ # List packets with all options enabled
+ params = ['--list-packets', KEY_P256, '--grips', '--raw', '--mpi']
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'packet listing with all options failed')
+ compare_file_ex(data_path('test_list_packets/list_all.txt'), out, 'all listing mismatch')
+
+ # List packets with JSON output
+ params = ['--json', '--list-packets', KEY_P256]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'json packet listing failed')
+ compare_file_ex(data_path('test_list_packets/list_json.txt'), out, 'json listing mismatch')
+ # List packets with mpi values, JSON output
+ params = ['--json', '--mpi', '--list-packets', KEY_P256]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'json mpi packet listing failed')
+ compare_file_ex(data_path('test_list_packets/list_json_mpi.txt'), out,
+ 'json mpi listing mismatch')
+ # List packets with grip/fingerprint values, JSON output
+ params = ['--json', '--grips', '--list-packets', KEY_P256]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'json grips packet listing failed')
+ compare_file_ex(data_path('test_list_packets/list_json_grips.txt'), out,
+ 'json grips listing mismatch')
+ # List packets with raw packet values, JSON output
+ params = ['--json', '--raw', '--list-packets', KEY_P256]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'json raw packet listing failed')
+ compare_file_ex(data_path('test_list_packets/list_json_raw.txt'), out,
+ 'json raw listing mismatch')
+ # List packets with all values, JSON output
+ params = ['--json', '--raw', '--list-packets', KEY_P256, '--mpi', '--grips']
+ ret, out, err = run_proc(RNP, params)
+ self.assertEqual(ret, 0, 'json all listing failed')
+ compare_file_ex(data_path('test_list_packets/list_json_all.txt'), out,
+ 'json all listing mismatch')
+ # List packets with notations
+ params = ['--list-packets', data_path('test_key_edge_cases/key-critical-notations.pgp')]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*notation data: critical text = critical value.*$')
+ self.assertRegex(out, r'(?s)^.*notation data: critical binary = 0x000102030405060708090a0b0c0d0e0f \(16 bytes\).*$')
+ # List packets with notations via JSON
+ params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-critical-notations.pgp')]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*\"human\":true.*\"name\":\"critical text\".*\"value\":\"critical value\".*$')
+ self.assertRegex(out, r'(?s)^.*\"human\":false.*\"name\":\"critical binary\".*\"value\":\"000102030405060708090a0b0c0d0e0f\".*$')
+ # 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.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.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)
+ compare_file_ex(data_path('test_key_edge_cases/key-empty-uid.txt'), out,
+ 'key-empty-uid listing mismatch')
+
+ # List empty uid with raw packet contents
+ params = ['--list-packets', '--raw', KEY_EMPTY_UID]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, PKT_LIST_FAILED)
+ compare_file_ex(data_path('test_key_edge_cases/key-empty-uid-raw.txt'), out,
+ 'key-empty-uid-raw listing mismatch')
+
+ # List empty uid packet contents to JSON
+ params = ['--list-packets', '--json', KEY_EMPTY_UID]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, PKT_LIST_FAILED)
+ compare_file_ex(data_path('test_key_edge_cases/key-empty-uid.json'), out,
+ 'key-empty-uid json listing mismatch')
+
+ # List experimental subpackets
+ params = ['--list-packets', data_path('test_key_edge_cases/key-subpacket-101-110.pgp')]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, PKT_LIST_FAILED)
+ compare_file_ex(data_path('test_key_edge_cases/key-subpacket-101-110.txt'), out,
+ 'key-subpacket-101-110 listing mismatch')
+
+ # List experimental subpackets JSON
+ params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-subpacket-101-110.pgp')]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, PKT_LIST_FAILED)
+ compare_file_ex(data_path('test_key_edge_cases/key-subpacket-101-110.json'), out,
+ 'key-subpacket-101-110 json listing mismatch')
+
+ # List malformed signature
+ params = ['--list-packets', data_path('test_key_edge_cases/key-malf-sig.pgp')]
+ ret, out, _ = run_proc(RNP, params)
+ self.assertEqual(ret, 0, PKT_LIST_FAILED)
+ compare_file_ex(data_path('test_key_edge_cases/key-malf-sig.txt'), out,
+ 'key-malf-sig listing mismatch')
+
+ # List malformed signature JSON
+ params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-malf-sig.pgp')]
+ ret, out, _ = run_proc(RNP, params)
+ 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):
+ if RNP_CAST5:
+ 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(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.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.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.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, _, 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, _, 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, _, 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'])
+ self.assertEqual(ret, 0, 'invalid exit code of \'rnpkeys --help\'')
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--unknown-option', '--help'])
+ self.assertNotEqual(ret, 0, 'rnp should return non-zero exit code for unknown command line options')
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--unknown-option', '--help'])
+ self.assertNotEqual(ret, 0, 'rnpkeys should return non-zero exit code for unknown command line options')
+
+ def test_input_from_specifier(self):
+ KEY_LIST = r'(?s)^.*' \
+ r'1 key found.*' \
+ r'pub .*255/EdDSA.*0451409669ffde3c.*' \
+ r'73edcc9119afc8e2dbbdcde50451409669ffde3c.*$'
+ NO_KEY_LIST = r'(?s)^.*' \
+ r'Key\(s\) not found.*$'
+ WRONG_VAR = r'(?s)^.*' \
+ r'Failed to get value of the environment variable \'SOMETHING_UNSET\'.*' \
+ r'Failed to create input for env:SOMETHING_UNSET.*$'
+ WRONG_DATA = r'(?s)^.*' \
+ r'failed to import key\(s\) from env:KEY_FILE, stopping.*$'
+ PGP_MSG = r'(?s)^.*' \
+ r'-----BEGIN PGP MESSAGE-----.*' \
+ r'-----END PGP MESSAGE-----.*$'
+ ENV_KEY = 'env:KEY_FILE'
+
+ clear_keyrings()
+ # Import key from the stdin
+ ktext = file_text(data_path(KEY_ALICE_SEC))
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', '-'], ktext)
+ self.assertEqual(ret, 0, 'failed to import key from stdin')
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, KEY_LIST_FAILED)
+ self.assertRegex(out, KEY_LIST, KEY_LIST_WRONG)
+ # Cleanup and import key from the env variable
+ clear_keyrings()
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertNotEqual(ret, 0, 'no key list failed')
+ self.assertRegex(out, NO_KEY_LIST, KEY_LIST_WRONG)
+ # Pass unset variable
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', 'env:SOMETHING_UNSET'])
+ self.assertNotEqual(ret, 0, 'key import from env must fail')
+ self.assertRegex(err, WRONG_VAR, 'wrong output')
+ # Pass incorrect value in environment variable
+ os.environ['KEY_FILE'] = "something"
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', ENV_KEY])
+ self.assertNotEqual(ret, 0, 'key import failed')
+ self.assertRegex(err, WRONG_DATA, 'wrong output')
+ # Now import the correct key
+ os.environ['KEY_FILE'] = ktext
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', ENV_KEY])
+ self.assertEqual(ret, 0, 'key import failed')
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0, KEY_LIST_FAILED)
+ self.assertRegex(out, KEY_LIST, KEY_LIST_WRONG)
+
+ # Sign message from the stdin, using the env keyfile
+ ret, out, _ = run_proc(RNP, ['-s', '-', '--password', 'password', '--armor', '--keyfile', ENV_KEY], 'Message to sign')
+ self.assertEqual(ret, 0, 'Message signing failed')
+ self.assertRegex(out, PGP_MSG, 'wrong signing output')
+ os.environ['SIGN_MSG'] = out
+ # Verify message from the env variable
+ ret, out, _ = run_proc(RNP, ['-d', 'env:SIGN_MSG', '--keyfile', ENV_KEY])
+ self.assertEqual(ret, 0, 'Message verification failed')
+ self.assertEqual(out, 'Message to sign', 'wrong verification output')
+
+ def test_output_to_specifier(self):
+ src, enc, encasc, dec = reg_workfiles('source', '.txt', EXT_PGP, EXT_ASC, '.dec')
+ with open(src, 'w+') as f:
+ f.write('Hello world')
+ # Encrypt file and make sure result is stored with .pgp extension
+ ret, out, _ = run_proc(RNP, ['-c', src, '--password', 'password'])
+ self.assertEqual(ret, 0, ENC_FAILED)
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', enc, '--output', dec, '--password', 'password'])
+ self.assertEqual(ret, 0, DEC_FAILED)
+ self.assertEqual(file_text(src), file_text(dec), DEC_DIFFERS)
+ remove_files(enc, dec)
+ # Encrypt file with armor and make sure result is stored with .asc extension
+ ret, _, _ = run_proc(RNP, ['-c', src, '--armor', '--password', 'password'])
+ self.assertEqual(ret, 0, ENC_FAILED)
+ ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', encasc, '--output', '-', '--password', 'password'])
+ self.assertEqual(ret, 0, DEC_FAILED)
+ self.assertEqual(file_text(src), out, DEC_DIFFERS)
+ remove_files(encasc)
+ # Encrypt file and write result to the stdout
+ ret, out, _ = run_proc(RNP, ['-c', src, '--armor', '--output', '-', '--password', 'password'])
+ self.assertEqual(ret, 0, ENC_FAILED)
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', '--output', dec, '--password', 'password', '-'], out)
+ self.assertEqual(ret, 0, DEC_FAILED)
+ self.assertEqual(file_text(src), file_text(dec), DEC_DIFFERS)
+ remove_files(dec)
+ # Encrypt file and write armored result to the stdout
+ ret, out, _ = run_proc(RNP, ['-c', src, '--armor','--output', '-', '--password', 'password'])
+ self.assertEqual(ret, 0, ENC_FAILED)
+ ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', '--output', '-', '--password', 'password', '-'], out)
+ self.assertEqual(ret, 0, DEC_FAILED)
+ self.assertEqual(file_text(src), out, DEC_DIFFERS)
+ # Encrypt stdin and write result to the stdout
+ srctxt = file_text(src)
+ ret, out, _ = run_proc(RNP, ['-c', '--armor', '--password', 'password'], srctxt)
+ self.assertEqual(ret, 0, ENC_FAILED)
+ ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', '--password', 'password'], out)
+ self.assertEqual(ret, 0, DEC_FAILED)
+ self.assertEqual(out, srctxt, DEC_DIFFERS)
+ # Encrypt stdin and attempt to write to non-existing dir
+ ret, _, err = run_proc(RNP, ['-c', '--armor', '--password', 'password', '--output', 'nonexisting/output.pgp'], srctxt)
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*init_file_dest.*failed to create file.*output.pgp.*Error 2.*$')
+ self.assertNotRegex(err, r'(?s)^.*failed to initialize encryption.*$')
+ self.assertRegex(err, r'(?s)^.*failed to open source or create output.*$')
+ # Sign stdin and then verify it using non-existing directory for output
+ ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '--armor', '--password', 'password', '-s'], srctxt)
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*BEGIN PGP MESSAGE.*END PGP MESSAGE.*$')
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '-v', '--output', 'nonexisting/output.pgp'], out)
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)^.*init_file_dest.*failed to create file.*output.pgp.*Error 2.*$')
+
+ def test_literal_filename(self):
+ EMPTY_FNAME = r'(?s)^.*literal data packet.*mode b.*created 0, name="".*$'
+ HELLO_FNAME = r'(?s)^.*literal data packet.*mode b.*created 0, name="hello".*$'
+ src, enc, dec = reg_workfiles('source', '.txt', EXT_PGP, '.dec')
+ with open(src, 'w+') as f:
+ f.write('Literal filename check')
+ # Encrypt file and make sure it's name is stored in literal data packet
+ ret, out, _ = run_proc(RNP, ['-c', src, '--password', 'password'])
+ self.assertEqual(ret, 0)
+ ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*literal data packet.*mode b.*created \d+.*name="source.txt".*$')
+ remove_files(enc)
+ # Encrypt file, overriding it's name
+ ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', src, '--password', 'password'])
+ self.assertEqual(ret, 0)
+ ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, HELLO_FNAME)
+ remove_files(enc)
+ # Encrypt file, using empty name
+ ret, out, _ = run_proc(RNP, ['--set-filename', '', '-c', src, '--password', 'password'])
+ self.assertEqual(ret, 0)
+ ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, EMPTY_FNAME)
+ remove_files(enc)
+ # Encrypt stdin, making sure empty name is stored
+ ret, out, _ = run_proc(RNP, ['-c', '--password', 'password', '--output', enc], 'Data from stdin')
+ self.assertEqual(ret, 0)
+ ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, EMPTY_FNAME)
+ remove_files(enc)
+ # Encrypt stdin, setting the file name
+ ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', '--password', 'password', '--output', enc], 'Data from stdin')
+ self.assertEqual(ret, 0)
+ ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, HELLO_FNAME)
+ remove_files(enc)
+ # Encrypt env, making sure empty name is stored
+ ret, out, _ = run_proc(RNP, ['-c', 'env:HOME', '--password', 'password', '--output', enc])
+ self.assertEqual(ret, 0)
+ ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, EMPTY_FNAME)
+ remove_files(enc)
+ # Encrypt env, setting the file name
+ ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', 'env:HOME', '--password', 'password', '--output', enc])
+ self.assertEqual(ret, 0)
+ ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, HELLO_FNAME)
+ remove_files(enc)
+
+ 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.*$'
+ 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\'.*$'
+ PUB_IMPORT= r'(?s)^.*pub\s+255/EdDSA 0451409669ffde3c .* \[SC\].*$'
+ 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:
+ self.assertFalse(os.path.isdir(RNPDIR), '.rnp directory should not exists')
+ src, enc, dec = reg_workfiles('source', '.txt', EXT_PGP, '.dec')
+ random_text(src, 2000)
+ # Run symmetric encryption/decryption without .rnp home directory
+ 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.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.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.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, 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.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 <alice@rnp>')
+ 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)
+ ret, out, err = run_proc(RNP, ['-c', src, '--password', 'password'])
+ self.assertEqual(ret, 0, 'Symmetric encryption failed')
+ self.assertNotRegex(err, EMPTY_KEYRING, 'Invalid encryption output')
+ ret, out, err = run_proc(RNP, ['-d', enc, '--password', 'password', '--output', dec])
+ self.assertEqual(ret, 0, 'Symmetric decryption failed')
+ self.assertRegex(err, EMPTY_KEYRING, 'wrong decryption output')
+ self.assertIn(WORKDIR, err, 'wrong decryption output')
+ compare_files(src, dec, DEC_DIFFERS)
+ remove_files(enc, dec)
+ # Import key with empty keyrings in .rnp home directory
+ ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)])
+ self.assertEqual(ret, 0, 'Public key import failed')
+ self.assertRegex(err, EMPTY_KEYRING, 'No empty keyring msg in key import output')
+ self.assertIn(WORKDIR, err, 'No workdir in empty keyring key import output')
+ self.assertRegex(out, PUB_IMPORT, 'Wrong pubkey 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, EMPTY_KEYRING, 'No empty keyring in key import output')
+ self.assertRegex(err, EMPTY_SECRING, 'No empty secring in key import output')
+ self.assertIn(WORKDIR, err, 'wrong key import output')
+ self.assertRegex(out, SEC_IMPORT, 'wrong secret key import output')
+ finally:
+ os.environ['HOME'] = home
+ shutil.rmtree(RNPDIR, ignore_errors=True)
+ os.rename(RNPDIR + '-old', RNPDIR)
+ clear_workfiles()
+
+ def test_alg_aliases(self):
+ 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.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)^.*rnp(|\.exe): option( .--cipher.|) requires an argument.*')
+ # 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
+ ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password', '--hash'])
+ self.assertNotEqual(ret, 0)
+ # Encrypt file using the unknown hash algorithm
+ ret, _, err = run_proc(RNP, ['-c', src, '--hash', 'bad', '--password', 'password'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Unsupported hash algorithm: bad.*$')
+ # Encrypt file using the AES algorithm instead of AES-128
+ ret, _, err = run_proc(RNP, ['-c', src, '--cipher', 'AES', '--password', 'password'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, r'(?s)^.*Warning, unsupported encryption algorithm: AES.*$')
+ self.assertNotRegex(err, r'(?s)^.*Unsupported encryption algorithm: AES.*$')
+ # Make sure AES-128 was used
+ ret, out, _ = run_proc(RNP, ['--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out,r'(?s)^.*Symmetric-key encrypted session key packet.*symmetric algorithm: 7 \(AES-128\).*$')
+ remove_files(enc)
+ # Encrypt file using the 3DES instead of tripledes
+ ret, _, err = run_proc(RNP, ['-c', src, '--cipher', '3DES', '--password', 'password'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, r'(?s)^.*Warning, unsupported encryption algorithm: 3DES.*$')
+ self.assertNotRegex(err, r'(?s)^.*Unsupported encryption algorithm: 3DES.*$')
+ # Make sure 3DES was used
+ ret, out, _ = run_proc(RNP, ['--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out,r'(?s)^.*Symmetric-key encrypted session key packet.*symmetric algorithm: 2 \(TripleDES\).*$')
+ remove_files(enc)
+ if RNP_RIPEMD160:
+ # Use ripemd-160 hash instead of RIPEMD160
+ ret, _, err = run_proc(RNP, ['-c', src, '--hash', 'ripemd-160', '--password', 'password'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, r'(?s)^.*Unsupported hash algorithm: ripemd-160.*$')
+ # Make sure RIPEMD160 was used
+ ret, out, _ = run_proc(RNP, ['--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out,r'(?s)^.*Symmetric-key encrypted session key packet.*s2k hash algorithm: 3 \(RIPEMD160\).*$')
+ remove_files(enc)
+
+ def test_core_dumps(self):
+ CORE_DUMP = r'(?s)^.*warning: core dumps may be enabled, sensitive data may be leaked to disk.*$'
+ NO_CORE_DUMP = r'(?s)^.*warning: --coredumps doesn\'t make sense on windows systems.*$'
+ # Check rnpkeys for the message
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, CORE_DUMP)
+ # Check rnp for the message
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--armor', '--password', 'password', '-c'], 'message')
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, CORE_DUMP)
+ # Enable coredumps for rnpkeys
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys', '--coredumps'])
+ self.assertEqual(ret, 0)
+ if is_windows():
+ self.assertNotRegex(err, CORE_DUMP)
+ self.assertRegex(err, NO_CORE_DUMP)
+ else:
+ self.assertRegex(err, CORE_DUMP)
+ self.assertNotRegex(err, NO_CORE_DUMP)
+ # Enable coredumps for rnp
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--armor', '--password', 'password', '-c', '--coredumps'], 'message')
+ self.assertEqual(ret, 0)
+ if is_windows():
+ self.assertNotRegex(err, CORE_DUMP)
+ self.assertRegex(err, NO_CORE_DUMP)
+ else:
+ self.assertRegex(err, CORE_DUMP)
+ self.assertNotRegex(err, NO_CORE_DUMP)
+
+ def test_backend_version(self):
+ BOTAN_BACKEND_VERSION = r'(?s)^.*.' \
+ 'Backend: Botan.*' \
+ 'Backend version: ([a-zA-z\.0-9]+).*$'
+ OPENSSL_BACKEND_VERSION = r'(?s)^.*' \
+ 'Backend: OpenSSL.*' \
+ 'Backend version: ([a-zA-z\.0-9]+).*$'
+ # Run without parameters and make sure it matches
+ ret, out, _ = run_proc(RNP, [])
+ self.assertNotEqual(ret, 0)
+ match = re.match(BOTAN_BACKEND_VERSION, out) or re.match(OPENSSL_BACKEND_VERSION, out)
+ self.assertTrue(match)
+ # Run with version parameters
+ ret, out, err = run_proc(RNP, ['--version'])
+ self.assertEqual(ret, 0)
+ match = re.match(BOTAN_BACKEND_VERSION, out)
+ backend_prog = 'botan'
+ if not match:
+ match = re.match(OPENSSL_BACKEND_VERSION, out)
+ backend_prog = 'openssl'
+ openssl_root = os.getenv('OPENSSL_ROOT_DIR')
+ else:
+ openssl_root = None
+ self.assertTrue(match)
+ # check there is no unexpected output
+ self.assertNotRegex(err, r'(?is)^.*Unsupported.*$')
+ self.assertNotRegex(err, r'(?is)^.*pgp_sa_to_openssl_string.*$')
+
+ # In case when there are several openssl installations
+ # testing environment is supposed to point to the right one
+ # through OPENSSL_ROOT_DIR environment variable
+ if openssl_root is not None:
+ backen_prog_ext = shutil.which(backend_prog, path = openssl_root + '/bin')
+ else:
+ # In all other cases
+ # check that botan or openssl executable binary exists in PATH
+ backen_prog_ext = shutil.which(backend_prog)
+
+ if backen_prog_ext is not None:
+ ret, out, _ = run_proc(backen_prog_ext, ['version'])
+ self.assertEqual(ret, 0)
+ self.assertIn(match.group(1), out)
+
+ def test_help_message(self):
+ # rnp help message
+ # short -h option
+ ret, out, _ = run_proc(RNP, ['-h'])
+ self.assertEqual(ret, 0)
+ short_h = out
+ # long --help option
+ ret, out, _ = run_proc(RNP, ['--help'])
+ self.assertEqual(ret, 0)
+ long_h = out
+ self.assertEqual(short_h, long_h)
+ # rnpkeys help message
+ # short -h option
+ ret, out, _ = run_proc(RNPK, ['-h'])
+ self.assertEqual(ret, 0)
+ short_h = out
+ # long --help options
+ ret, out, _ = run_proc(RNPK, ['--help'])
+ self.assertEqual(ret, 0)
+ long_h = out
+ self.assertEqual(short_h, long_h)
+
+ def test_wrong_mpi_bit_count(self):
+ WRONG_MPI_BITS = r'(?s)^.*Warning! Wrong mpi bit count: got [0-9]+, but actual is [0-9]+.*$'
+ # Make sure message is not displayed on normal keys
+ ret, _, err = run_proc(RNP, ['--list-packets', data_path(PUBRING_1)])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, WRONG_MPI_BITS)
+ # Make sure message is displayed on wrong mpi
+ ret, _, err = run_proc(RNP, ['--list-packets', data_path('test_key_edge_cases/alice-wrong-mpi-bit-count.pgp')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, WRONG_MPI_BITS)
+
+ def test_eddsa_small_x(self):
+ os.rename(RNPDIR, RNPDIR + '-old')
+ home = os.environ['HOME']
+ os.environ['HOME'] = WORKDIR
+ try:
+ self.assertFalse(os.path.isdir(RNPDIR), '.rnp directory should not exists')
+ src, sig, ver = reg_workfiles('source', '.txt', EXT_PGP, '.dec')
+ random_text(src, 2000)
+ # load just public key and verify pre-signed message
+ ret, _, _ = run_proc(RNPK, ['--import', data_path('test_key_edge_cases/key-eddsa-small-x-pub.asc')])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--verify', data_path('test_messages/message.txt.sign-small-eddsa-x')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Good signature made .*using EdDSA key 7bc55b9bdce36e18.*$')
+ # load secret key and sign message
+ ret, out, _ = run_proc(RNPK, ['--import', data_path('test_key_edge_cases/key-eddsa-small-x-sec.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*sec.*255/EdDSA.*7bc55b9bdce36e18.*eddsa_small_x.*ssb.*c6c35ea115368a0b.*$')
+ ret, _, _ = run_proc(RNP, ['--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ # verify back
+ ret, _, err = run_proc(RNP, ['--verify', sig, '--output', ver])
+ self.assertEqual(ret, 0)
+ self.assertEqual(file_text(src), file_text(ver))
+ self.assertRegex(err, r'(?s)^.*Good signature made .*using EdDSA key 7bc55b9bdce36e18.*$')
+ # verify back with GnuPG
+ os.remove(ver)
+ gpg_import_pubring(data_path('test_key_edge_cases/key-eddsa-small-x-pub.asc'))
+ gpg_verify_file(sig, ver, 'eddsa_small_x')
+ finally:
+ os.environ['HOME'] = home
+ shutil.rmtree(RNPDIR, ignore_errors=True)
+ os.rename(RNPDIR + '-old', RNPDIR)
+ clear_workfiles()
+
+ def test_cv25519_bit_fix(self):
+ RE_NOT_25519 = r'(?s)^.*Error: specified key is not Curve25519 ECDH subkey.*$'
+ # Import and tweak non-protected secret key
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_25519_NOTWEAK_SEC)])
+ self.assertEqual(ret, 0)
+ # Check some --edit-key invalid options combinations
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*You need to specify a key or subkey to edit.*$')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '3176fc1486aa2528'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*You should specify one of the editing options for --edit-key.*$')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--check-cv25519-bits'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*You need to specify a key or subkey to edit.*$')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--check-cv25519-bits', 'key'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Secret keys matching \'key\' not found.*$')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--check-cv25519-bits', 'eddsa-25519-non-tweaked'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, RE_NOT_25519)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--check-cv25519-bits', '3176fc1486aa2528'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, RE_NOT_25519)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--check-cv25519-bits', '950ee0cd34613dba'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*Warning: Cv25519 key bits need fixing.*$')
+ # Tweak bits
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--fix-cv25519-bits', '3176fc1486aa2528'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, RE_NOT_25519)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--fix-cv25519-bits', '950ee0cd34613dba'])
+ self.assertEqual(ret, 0)
+ # Make sure bits are correctly tweaked and key may be used to decrypt and imported to GnuPG
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--check-cv25519-bits', '950ee0cd34613dba'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*Cv25519 key bits are set correctly and do not require fixing.*$')
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', data_path(MSG_ES_25519)])
+ self.assertEqual(ret, 0)
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', os.path.join(RNPDIR, SECRING)])
+ self.assertEqual(ret, 0)
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '-d', data_path(MSG_ES_25519)])
+ 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)
+ # Make sure protected secret key works the same way
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_edge_cases/key-25519-non-tweaked-sec-prot.asc')])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'wrong', '--edit-key', '--check-cv25519-bits', '950ee0cd34613dba'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Error: failed to unlock key. Did you specify valid password\\?.*$')
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'password', '--notty', '--edit-key', '--check-cv25519-bits', '950ee0cd34613dba'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*Warning: Cv25519 key bits need fixing.*$')
+ # Tweak bits
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'wrong', '--edit-key', '--fix-cv25519-bits', '950ee0cd34613dba'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Error: failed to unlock key. Did you specify valid password\\?.*$')
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'password', '--edit-key', '--fix-cv25519-bits', '950ee0cd34613dba'])
+ self.assertEqual(ret, 0)
+ # Make sure key is protected with the same options
+ ret, out, _ = run_proc(RNP, ['--list-packets', os.path.join(RNPDIR, SECRING)])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*Secret subkey packet.*254.*AES-256.*3.*SHA256.*58720256.*0x950ee0cd34613dba.*$')
+ # Make sure bits are correctly tweaked and key may be used to decrypt and imported to GnuPG
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--password', 'password', '--edit-key', '--check-cv25519-bits', '950ee0cd34613dba'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*Cv25519 key bits are set correctly and do not require fixing.*$')
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', 'password', '-d', data_path(MSG_ES_25519)])
+ self.assertEqual(ret, 0)
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--batch', '--passphrase', 'password', '--import', os.path.join(RNPDIR, SECRING)])
+ self.assertEqual(ret, 0)
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--passphrase', 'password',
+ '--trust-model', 'always', '-d', data_path(MSG_ES_25519)])
+ 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)
+
+ 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')
+ srceax = data_path('test_messages/message.aead-last-zero-chunk.enc')
+ srcocb = data_path('test_messages/message.aead-last-zero-chunk.enc-ocb')
+ eax_size = os.path.getsize(srceax)
+ ocb_size = os.path.getsize(srcocb)
+ self.assertEqual(eax_size - 1, ocb_size)
+ # 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_EAX and RNP_BRAINPOOL:
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-d', srceax, '--output', dec])
+ self.assertEqual(ret, 0)
+ self.assertEqual(file_text(srctxt), file_text(dec))
+ os.remove(dec)
+ if RNP_AEAD_OCB and RNP_BRAINPOOL:
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-d', srcocb, '--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)
+ if GPG_AEAD_EAX:
+ gpg_decrypt_file(srceax, dec, PASSWORD)
+ self.assertEqual(file_text(srctxt), file_text(dec))
+ os.remove(dec)
+ if GPG_AEAD_OCB:
+ gpg_decrypt_file(srcocb, dec, PASSWORD)
+ self.assertEqual(file_text(srctxt), file_text(dec))
+ os.remove(dec)
+ if RNP_AEAD_EAX and RNP_BRAINPOOL:
+ # Encrypt with RNP
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-z', '0', '-r', 'alice', '--aead=eax',
+ '--set-filename', 'cleartext-z0.txt', '--aead-chunk-bits=1', '-e', srctxt, '--output', enc])
+ self.assertEqual(ret, 0)
+ self.assertEqual(os.path.getsize(enc), eax_size)
+ # 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_EAX and GPG_BRAINPOOL:
+ # Decrypt with GnuPG
+ gpg_decrypt_file(enc, dec, PASSWORD)
+ self.assertEqual(file_text(srctxt), file_text(dec))
+ os.remove(enc)
+ if RNP_AEAD_OCB and RNP_BRAINPOOL:
+ # Encrypt with RNP
+ ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '-z', '0', '-r', 'alice', '--aead=ocb',
+ '--set-filename', 'cleartext-z0.txt', '--aead-chunk-bits=1', '-e', srctxt, '--output', enc])
+ self.assertEqual(ret, 0)
+ self.assertEqual(os.path.getsize(enc), ocb_size)
+ # 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_OCB 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):
+ if RNP_CAST5:
+ # 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.*')
+ # 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.* \[EXPIRED 2017.*$')
+ self.assertNotRegex(out, r'(?s)^.*\[INVALID\].*$')
+ 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)^.*Failed to add recipient.*$')
+ 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
+ RNPG10 = RNPDIR + '/g10'
+ #os.mkdir(RNPG10, 0o700)
+ kring = shutil.copytree(data_path(KEYRING_DIR_3), RNPG10)
+ ret, _, err = run_proc(RNPK, ['--homedir', kring, '--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', kring, '--keystore-format', 'GPG21', '--list-keys'])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*2 keys found')
+ shutil.rmtree(RNPG10, ignore_errors=True)
+
+ 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()
+
+ def test_subkey_binding_on_uid(self):
+ RNP2 = RNPDIR + '2'
+ os.mkdir(RNP2, 0o700)
+
+ # Import key with deleted subkey packet (so subkey binding is attached to the uid)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNP2, '--import', data_path('test_key_edge_cases/alice-uid-binding.pgp')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*pub.*0451409669ffde3c.*alice@rnp.*$')
+ # List keys - make sure rnp doesn't attempt to validate wrong sig
+ ret, out, err = run_proc(RNPK, ['--homedir', RNP2, '--list-keys', '--with-sigs'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, r'(?s)^.*wrong lbits.*$')
+ self.assertRegex(err, r'(?s)^.*Invalid binding signature key type.*$')
+ self.assertRegex(out, r'(?s)^.*sig.*alice@rnp.*.*sig.*alice@rnp.*invalid.*$')
+
+ shutil.rmtree(RNP2, ignore_errors=True)
+
+ def test_key_locate(self):
+ seckey = data_path(SECRING_1)
+ pubkey = data_path(PUBRING_1)
+ src, sig = reg_workfiles('cleartext', '.txt', '.sig')
+ random_text(src, 1200)
+ # Try non-existing key
+ ret, _, err = run_proc(RNP, ['--keyfile', seckey, '-u', 'alice', '--sign', src, '--output', sig])
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)^.*Cannot find key matching "alice".*')
+ # Match via partial uid
+ ret, _, _ = run_proc(RNP, ['--keyfile', seckey, '-u', 'key0', '--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-v', sig])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Good signature made.*7bc6709b15c23a4a.*Signature\(s\) verified successfully.*')
+ remove_files(sig)
+ R_GOOD_SIG = r'(?s)^.*Good signature made.*2fcadf05ffa501bb.*Signature\(s\) verified successfully.*'
+ # Match via keyid with hex prefix
+ ret, _, _ = run_proc(RNP, ['--keyfile', seckey, '-u', '0x2fcadf05ffa501bb', '--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-v', sig])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, R_GOOD_SIG)
+ remove_files(sig)
+ # Match via keyid with spaces/tabs
+ ret, _, _ = run_proc(RNP, ['--keyfile', seckey, '-u', '0X 2FCA DF05\tFFA5\t01BB', '--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-v', sig])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, R_GOOD_SIG)
+ remove_files(sig)
+ # Match via half of the keyid
+ ret, _, _ = run_proc(RNP, ['--keyfile', seckey, '-u', 'FFA501BB', '--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-v', sig])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, R_GOOD_SIG)
+ remove_files(sig)
+ # Match via fingerprint
+ ret, _, _ = run_proc(RNP, ['--keyfile', seckey, '-u', 'be1c4ab9 51F4C2F6 b604c7f8 2FCADF05 ffa501bb', '--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-v', sig])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, R_GOOD_SIG)
+ remove_files(sig)
+ # Match via grip
+ ret, _, _ = run_proc(RNP, ['--keyfile', seckey, '-u', '0xb2a7f6c34aa2c15484783e9380671869a977a187', '--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-v', sig])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, R_GOOD_SIG)
+ remove_files(sig)
+ # Match via regexp
+ ret, _, _ = run_proc(RNP, ['--keyfile', seckey, '-u', 'key[12].uid.', '--password', PASSWORD, '--sign', src, '--output', sig])
+ self.assertEqual(ret, 0)
+ ret, _, err = run_proc(RNP, ['--keyfile', pubkey, '-v', sig])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, R_GOOD_SIG)
+ remove_files(sig)
+ clear_workfiles()
+
+ def test_conflicting_commands(self):
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--generate-key', '--import', '--revoke-key', '--list-keys'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Conflicting commands!*')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '-g', '-l'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Conflicting commands!*')
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--sign', '--verify', '--decrypt', '--list-packets'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Conflicting commands!*')
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '-s', '-v'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Conflicting commands!*')
+
+ def test_hidden_recipient(self):
+ seckey = data_path(SECRING_1)
+ msg1 = data_path('test_messages/message.txt.enc-hidden-1')
+ msg2 = data_path('test_messages/message.txt.enc-hidden-2')
+ pswd = 'password\n'
+ pswds = 'password\npassword\npassword\npassword\n'
+ R_MSG = r'(?s)^.*This is test message to be signed.*'
+ H_MSG1 = r'(?s)^.*Warning: message has hidden recipient, but it was ignored. Use --allow-hidden to override this.*'
+ H_MSG2 = r'(?s)^.*This message has hidden recipient. Will attempt to use all secret keys for decryption.*'
+ # Try to decrypt message without valid key
+ ret, out, err = run_proc(RNP, ['--keyfile', data_path(KEY_ALICE_SUB_SEC), '--notty', '-d', msg1], pswd)
+ self.assertEqual(ret, 1)
+ self.assertNotRegex(out, R_MSG)
+ self.assertNotRegex(out, r'(?s)^.*Enter password for key.*')
+ self.assertRegex(err, H_MSG1)
+ # Try to decrypt message with first recipient hidden, it must not be asked for
+ ret, out, _ = run_proc(RNP, ['--keyfile', seckey, '--notty', '-d', msg1], pswd)
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, R_MSG)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x326EF111425D14A5 to decrypt.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*')
+ # Try to decrypt message with first recipient hidden, providing wrong password
+ ret, out, err = run_proc(RNP, ['--keyfile', seckey, '--notty', '-d', msg1], '123\n')
+ self.assertEqual(ret, 1)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x326EF111425D14A5 to decrypt.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*')
+ self.assertRegex(err, H_MSG1)
+ # Try to decrypt message with second recipient hidden
+ ret, out, _ = run_proc(RNP, ['--keyfile', seckey, '--notty', '-d', msg2], pswd)
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, R_MSG)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x8A05B89FAD5ADED1 to decrypt.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*')
+ # Try to decrypt message with second recipient hidden, wrong password
+ ret, out, err = run_proc(RNP, ['--keyfile', seckey, '--notty', '-d', msg2], '123\n')
+ self.assertEqual(ret, 1)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x8A05B89FAD5ADED1 to decrypt.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*')
+ self.assertRegex(err, H_MSG1)
+ # Allow hidden recipient, specifying valid password
+ ret, out, err = run_proc(RNP, ['--keyfile', seckey, '--notty', '--allow-hidden', '-d', msg1], pswds)
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, H_MSG2)
+ self.assertRegex(out, R_MSG)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x1ED63EE56FADC34D.*0x8A05B89FAD5ADED1.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*Enter password.*')
+ # Allow hidden recipient, specifying all wrong passwords
+ ret, out, err = run_proc(RNP, ['--keyfile', seckey, '--notty', '--allow-hidden', '-d', msg1], '1\n1\n1\n1\n')
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, H_MSG2)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x1ED63EE56FADC34D.*0x8A05B89FAD5ADED1.*0x326EF111425D14A5.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*Enter password.*Enter password.*')
+ # Allow hidden recipient, specifying invalid password for first recipient and valid password for hidden, message 2
+ ret, out, err = run_proc(RNP, ['--keyfile', seckey, '--notty', '--allow-hidden', '-d', msg2], '1\npassword\npassword\n')
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, H_MSG2)
+ self.assertRegex(out, R_MSG)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x8A05B89FAD5ADED1.*0x54505A936A4A970E.*0x326EF111425D14A5.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*Enter password.*Enter password.*')
+ # Allow hidden recipient, specifying invalid password for all, message 2
+ ret, out, err = run_proc(RNP, ['--keyfile', seckey, '--notty', '--allow-hidden', '-d', msg2], '1\n1\n1\n1\n')
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, H_MSG2)
+ self.assertRegex(out, r'(?s)^.*Enter password for key 0x8A05B89FAD5ADED1.*0x54505A936A4A970E.*0x326EF111425D14A5.*')
+ self.assertNotRegex(out, r'(?s)^.*Enter password.*Enter password.*Enter password.*Enter password.*')
+
+ def test_allow_weak_hash(self):
+ RNP2 = RNPDIR + '2'
+ os.mkdir(RNP2, 0o700)
+ # rnpkeys, force weak hashes for key generation
+ ret, _, err = run_proc(RNPK, ['--homedir', RNP2, '-g', '--password=', '--hash', 'MD5'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Hash algorithm \'MD5\' is cryptographically weak!.*Weak hash algorithm detected. Pass --allow-weak-hash option if you really want to use it\..*')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNP2, '-g', '--password=', '--hash', 'MD5', '--allow-weak-hash'])
+ self.assertEqual(ret, 0)
+
+ ret, _, err = run_proc(RNPK, ['--homedir', RNP2, '-g', '--password=', '--hash', 'SHA1'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Hash algorithm \'SHA1\' is cryptographically weak!.*Weak hash algorithm detected. Pass --allow-weak-hash option if you really want to use it\..*')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNP2, '-g', '--password=', '--hash', 'SHA1', '--allow-weak-hash'])
+ self.assertEqual(ret, 0)
+
+ # check non-weak hash
+ ret, _, err = run_proc(RNPK, ['--homedir', RNP2, '-g', '--password=', '--hash', 'SHA3-512'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, r'(?s)^.*Hash algorithm \'SHA3\-512\' is cryptographically weak!.*')
+ ret, _, err = run_proc(RNPK, ['--homedir', RNP2, '-g', '--password=', '--hash', 'SHA3-512', '--allow-weak-hash'])
+ self.assertEqual(ret, 0)
+
+ # rnp, force weak hashes for signature
+ src, sig = reg_workfiles('cleartext', '.txt', '.sig')
+ random_text(src, 120)
+
+ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--sign', src, '--output', sig, '--hash', 'MD5'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Hash algorithm \'MD5\' is cryptographically weak!.*Weak hash algorithm detected. Pass --allow-weak-hash option if you really want to use it\..*')
+ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--sign', src, '--output', sig, '--hash', 'MD5', '--allow-weak-hash'])
+ self.assertEqual(ret, 0)
+ remove_files(sig)
+
+ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--sign', src, '--output', sig, '--hash', 'SHA1'])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Hash algorithm \'SHA1\' is cryptographically weak!.*Weak hash algorithm detected. Pass --allow-weak-hash option if you really want to use it\..*')
+ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--sign', src, '--output', sig, '--hash', 'SHA1', '--allow-weak-hash'])
+ self.assertEqual(ret, 0)
+ remove_files(sig)
+
+ # check non-weak hash
+ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--sign', src, '--output', sig, '--hash', 'SHA3-512'])
+ self.assertEqual(ret, 0)
+ self.assertNotRegex(err, r'(?s)^.*Hash algorithm \'SHA3\-512\' is cryptographically weak!.*')
+ remove_files(sig)
+ ret, _, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--sign', src, '--output', sig, '--hash', 'SHA3-512', '--allow-weak-hash'])
+ self.assertEqual(ret, 0)
+ remove_files(sig)
+
+ clear_workfiles()
+ shutil.rmtree(RNP2, ignore_errors=True)
+
+class Encryption(unittest.TestCase):
+ '''
+ Things to try later:
+ - different public key algorithms
+ - different hash algorithms where applicable
+
+ TODO:
+ Tests in this test case should be split into many algorithm-specific tests
+ (potentially auto generated)
+ Reason being - if you have a problem with BLOWFISH size 1000000, you don't want
+ to wait until everything else gets
+ tested before your failing BLOWFISH
+ '''
+ # Ciphers list tro try during encryption. None will use default
+ CIPHERS = [None]
+ SIZES = [20, 40, 120, 600, 1000, 5000, 20000, 250000]
+ # Compression parameters to try during encryption(s)
+ Z = [[None, 0], ['zip'], ['zlib'], ['bzip2'], [None, 1], [None, 9]]
+ # Number of test runs - each run picks next encryption algo and size, wrapping on array
+ RUNS = 20
+
+ @classmethod
+ def setUpClass(cls):
+ # Generate keypair in RNP
+ rnp_genkey_rsa(KEY_ENCRYPT)
+ # Add some other keys to the keyring
+ rnp_genkey_rsa('dummy1@rnp', 1024)
+ rnp_genkey_rsa('dummy2@rnp', 1024)
+ gpg_import_pubring()
+ gpg_import_secring()
+ Encryption.CIPHERS += rnp_supported_ciphers(False)
+ 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
+ def tearDownClass(cls):
+ clear_keyrings()
+
+ def tearDown(self):
+ clear_workfiles()
+
+ # Encrypt cleartext file with GPG and decrypt it with RNP,
+ # using different ciphers and file sizes
+ def test_file_encryption__gpg_to_rnp(self):
+ for size, cipher in zip(Encryption.SIZES_R, Encryption.CIPHERS_R):
+ gpg_to_rnp_encryption(size, cipher)
+
+ # Encrypt with RNP and decrypt with GPG
+ def test_file_encryption__rnp_to_gpg(self):
+ for size in Encryption.SIZES:
+ file_encryption_rnp_to_gpg(size)
+
+ def test_sym_encryption__gpg_to_rnp(self):
+ # Encrypt cleartext with GPG and decrypt with RNP
+ for size, cipher, z in zip(Encryption.SIZES_R, Encryption.CIPHERS_R, Encryption.Z_R):
+ rnp_sym_encryption_gpg_to_rnp(size, cipher, z)
+
+ def test_sym_encryption__rnp_to_gpg(self):
+ # Encrypt cleartext with RNP and decrypt with GPG
+ for size, cipher, z in zip(Encryption.SIZES_R, Encryption.CIPHERS_R, Encryption.Z_R):
+ rnp_sym_encryption_rnp_to_gpg(size, cipher, z, 1024)
+
+ def test_sym_encryption_s2k_iter(self):
+ src, enc = reg_workfiles('cleartext', '.txt', '.gpg')
+ # Generate random file of required size
+ random_text(src, 20)
+ def s2k_iter_run(input_iterations, expected_iterations):
+ # Encrypt cleartext file with RNP
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--output', enc, '--password', PASSWORD, '-c', '--s2k-iterations', str(input_iterations), src])
+ if ret != 0:
+ raise_err('rnp encryption failed', err)
+ ret, out, _ = run_proc(RNP, ['--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*s2k iterations: [0-9]+ \(encoded as [0-9]+\).*')
+ matches = re.findall(r'(?s)^.*s2k iterations: ([0-9]+) \(encoded as [0-9]+\).*', out)
+ if int(matches[0]) != expected_iterations:
+ raise_err('unexpected iterations number', matches[0])
+ remove_files(enc)
+
+ for iters in [1024, 1088, 0x3e00000]:
+ s2k_iter_run(iters, iters)
+ clear_workfiles()
+
+ def test_sym_encryption_s2k_msec(self):
+ src, enc = reg_workfiles('cleartext', '.txt', '.gpg')
+ # Generate random file of required size
+ random_text(src, 20)
+ def s2k_msec_iters(msec):
+ # Encrypt cleartext file with RNP
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--output', enc, '--password', PASSWORD, '-c', '--s2k-msec', str(msec), src])
+ if ret != 0:
+ raise_err('rnp encryption failed', err)
+ ret, out, _ = run_proc(RNP, ['--list-packets', enc])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, r'(?s)^.*s2k iterations: [0-9]+ \(encoded as [0-9]+\).*')
+ matches = re.findall(r'(?s)^.*s2k iterations: ([0-9]+) \(encoded as [0-9]+\).*', out)
+ remove_files(enc)
+ return int(matches[0])
+
+ iters1msec = s2k_msec_iters(1)
+ iters10msec = s2k_msec_iters(10)
+ iters100msec = s2k_msec_iters(100)
+
+ disable_test = os.getenv('DISABLE_TEST_S2K_MSEC')
+ if disable_test is None:
+ self.assertGreaterEqual(iters10msec, iters1msec)
+ self.assertGreaterEqual(iters100msec, iters10msec)
+ clear_workfiles()
+
+ def test_sym_encryption_wrong_s2k(self):
+ src, dst, enc = reg_workfiles('cleartext', '.txt', '.rnp', '.enc')
+ random_text(src, 1001)
+ # Wrong S2K iterations
+ ret, _, err = run_proc(RNP, ['--s2k-iterations', 'WRONG_ITER', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--output', enc, '-c', src])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Wrong iterations value: WRONG_ITER.*')
+ # Wrong S2K msec
+ ret, _, err = run_proc(RNP, ['--s2k-msec', 'WRONG_MSEC', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--output', enc, '-c', src])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Invalid s2k msec value: WRONG_MSEC.*')
+ # Overflow
+ ret, _, err = run_proc(RNP, ['--s2k-iterations', '999999999999', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--output', enc, '-c', src])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Wrong iterations value: 999999999999.*')
+ self.assertNotRegex(err, r'(?s)^.*std::out_of_range.*')
+
+ ret, _, err = run_proc(RNP, ['--s2k-msec', '999999999999', '--homedir', RNPDIR, '--password', PASSWORD,
+ '--output', enc, '-c', src])
+ self.assertNotEqual(ret, 0)
+ self.assertRegex(err, r'(?s)^.*Invalid s2k msec value: 999999999999.*')
+ self.assertNotRegex(err, r'(?s)^.*std::out_of_range.*')
+
+ remove_files(src, dst, enc)
+
+ def test_sym_encryption__rnp_aead(self):
+ if not RNP_AEAD:
+ print('AEAD is not available for RNP - skipping.')
+ return
+ CIPHERS = rnp_supported_ciphers(True)
+ AEADS = [None, 'eax', 'ocb']
+ if not RNP_AEAD_EAX:
+ AEADS.remove('eax')
+ AEAD_C = list_upto(CIPHERS, Encryption.RUNS)
+ AEAD_M = list_upto(AEADS, Encryption.RUNS)
+ AEAD_B = list_upto([None, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 12, 14, 16], Encryption.RUNS)
+
+ # 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])
+ if RNP_AEAD_EAX:
+ self.assertEqual(ret, 0)
+ rnp_decrypt_file(enc, dst)
+ remove_files(dst, enc)
+ else:
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)^.*Invalid AEAD algorithm: EAX')
+ # Check non-AES OCB mode
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', enc, '--cipher', 'CAMELLIA192', '--aead=ocb', '--aead-chunk-bits', '2', '-z', '0', '-c', src])
+ if RNP_AEAD_OCB_AES:
+ self.assertEqual(ret, 1)
+ self.assertRegex(err, r'(?s)^.*Only AES-OCB is supported by the OpenSSL backend')
+ else:
+ self.assertEqual(ret, 0)
+ rnp_decrypt_file(enc, dst)
+ remove_files(dst, enc)
+ # Check default (AES) OCB
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', enc, '--aead=ocb', '--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)
+ if RNP_AEAD_EAX:
+ 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(dst, enc)
+ if RNP_AEAD_OCB:
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--password', PASSWORD, '--output', enc, '--aead-chunk-bits', '2', '-z', '0', '-c', src])
+ self.assertEqual(ret, 0)
+ rnp_decrypt_file(enc, dst)
+ remove_files(src, dst, enc)
+
+ def fill_aeads(self, runs):
+ aead = [None, [None]]
+ if RNP_AEAD_EAX:
+ aead += [['eax']]
+ if RNP_AEAD_OCB:
+ aead += [['ocb']]
+ return list_upto(aead, runs)
+
+ def gpg_supports(self, aead):
+ if (aead == ['eax']) and not GPG_AEAD_EAX:
+ return False
+ if (aead == ['ocb']) and not GPG_AEAD_OCB:
+ return False
+ if (aead == [None]) and not GPG_AEAD_OCB:
+ return False
+ return True
+
+ 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
+ for uid, pswd in zip(USERIDS, KEYPASS):
+ rnp_genkey_rsa(uid, 1024, pswd)
+
+ gpg_import_pubring()
+ gpg_import_secring()
+
+ KEYPSWD = tuple((t1, t2) for t1 in range(len(USERIDS) + 1)
+ for t2 in range(len(PASSWORDS) + 1))
+ KEYPSWD = list_upto(KEYPSWD, Encryption.RUNS)
+ AEADS = self.fill_aeads(Encryption.RUNS)
+
+ src, dst, dec = reg_workfiles('cleartext', '.txt', '.rnp', '.dec')
+ # Generate random file of required size
+ random_text(src, 65500)
+
+ for kpswd, aead in zip(KEYPSWD, AEADS):
+ keynum, pswdnum = kpswd
+ if (keynum == 0) and (pswdnum == 0):
+ continue
+ uids = USERIDS[:keynum] if keynum else None
+ pswds = PASSWORDS[:pswdnum] if pswdnum else None
+
+ rnp_encrypt_file_ex(src, dst, uids, pswds, aead)
+
+ # Decrypt file with each of the keys, we have different password for each key
+ # For CFB mode there is ~5% probability that GnuPG will attempt to decrypt
+ # message's SESK with a wrong password, see T3795 on dev.gnupg.org
+ first_pass = aead is None and ((pswdnum > 1) or ((pswdnum == 1) and (keynum > 0)))
+ try_gpg = self.gpg_supports(aead)
+ for pswd in KEYPASS[:keynum]:
+ if not first_pass and try_gpg:
+ gpg_decrypt_file(dst, dec, pswd)
+ gpg_agent_clear_cache()
+ remove_files(dec)
+ rnp_decrypt_file(dst, dec, '\n'.join([pswd] * 5))
+ remove_files(dec)
+
+ # Decrypt file with each of the passwords (with gpg only first password is checked)
+ if first_pass and try_gpg:
+ gpg_decrypt_file(dst, dec, PASSWORDS[0])
+ gpg_agent_clear_cache()
+ remove_files(dec)
+
+ for pswd in PASSWORDS[:pswdnum]:
+ if not first_pass and try_gpg:
+ gpg_decrypt_file(dst, dec, pswd)
+ gpg_agent_clear_cache()
+ remove_files(dec)
+ rnp_decrypt_file(dst, dec, '\n'.join([pswd] * 5))
+ remove_files(dec)
+
+ remove_files(dst, dec)
+
+ clear_workfiles()
+
+ def test_encryption_and_signing(self):
+ USERIDS = ['enc-sign1@rnp', 'enc-sign2@rnp', 'enc-sign3@rnp']
+ KEYPASS = ['encsign1pass', 'encsign2pass', 'encsign3pass']
+ PASSWORDS = ['password1', 'password2', 'password3']
+ AEAD_C = list_upto(rnp_supported_ciphers(True), Encryption.RUNS)
+ # Generate multiple keys and import to GnuPG
+ for uid, pswd in zip(USERIDS, KEYPASS):
+ rnp_genkey_rsa(uid, 1024, pswd)
+
+ gpg_import_pubring()
+ gpg_import_secring()
+
+ SIGNERS = list_upto(range(1, len(USERIDS) + 1), Encryption.RUNS)
+ KEYPSWD = tuple((t1, t2) for t1 in range(1, len(USERIDS) + 1)
+ for t2 in range(len(PASSWORDS) + 1))
+ KEYPSWD = list_upto(KEYPSWD, Encryption.RUNS)
+ AEADS = self.fill_aeads(Encryption.RUNS)
+ ZS = list_upto([None, [None, 0]], Encryption.RUNS)
+
+ src, dst, dec = reg_workfiles('cleartext', '.txt', '.rnp', '.dec')
+ # Generate random file of required size
+ random_text(src, 65500)
+
+ for i in range(0, Encryption.RUNS):
+ signers = USERIDS[:SIGNERS[i]]
+ signpswd = KEYPASS[:SIGNERS[i]]
+ keynum, pswdnum = KEYPSWD[i]
+ recipients = USERIDS[:keynum]
+ passwords = PASSWORDS[:pswdnum]
+ aead = AEADS[i]
+ z = ZS[i]
+ cipher = AEAD_C[i]
+ first_pass = aead is None and ((pswdnum > 1) or ((pswdnum == 1) and (keynum > 0)))
+ try_gpg = self.gpg_supports(aead)
+
+ rnp_encrypt_and_sign_file(src, dst, recipients, passwords, signers,
+ signpswd, aead, cipher, z)
+ # Decrypt file with each of the keys, we have different password for each key
+ for pswd in KEYPASS[:keynum]:
+ if not first_pass and try_gpg:
+ gpg_decrypt_file(dst, dec, pswd)
+ gpg_agent_clear_cache()
+ remove_files(dec)
+ rnp_decrypt_file(dst, dec, '\n'.join([pswd] * 5))
+ remove_files(dec)
+
+ # GPG decrypts only with first password, see T3795
+ if first_pass and try_gpg:
+ gpg_decrypt_file(dst, dec, PASSWORDS[0])
+ gpg_agent_clear_cache()
+ remove_files(dec)
+
+ # Decrypt file with each of the passwords
+ for pswd in PASSWORDS[:pswdnum]:
+ if not first_pass and try_gpg:
+ gpg_decrypt_file(dst, dec, pswd)
+ gpg_agent_clear_cache()
+ remove_files(dec)
+ rnp_decrypt_file(dst, dec, '\n'.join([pswd] * 5))
+ remove_files(dec)
+
+ remove_files(dst, dec)
+
+ def test_encryption_weird_userids_special_1(self):
+ uid = WEIRD_USERID_SPECIAL_CHARS
+ pswd = 'encSpecial1Pass'
+ rnp_genkey_rsa(uid, 1024, pswd)
+ # Encrypt
+ src = data_path(MSG_TXT)
+ dst, dec = reg_workfiles('weird_userids_special_1', '.rnp', '.dec')
+ rnp_encrypt_file_ex(src, dst, [uid], None, None)
+ # Decrypt
+ rnp_decrypt_file(dst, dec, pswd)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+ clear_workfiles()
+
+ def test_encryption_weird_userids_special_2(self):
+ USERIDS = [WEIRD_USERID_SPACE, WEIRD_USERID_QUOTE, WEIRD_USERID_SPACE_AND_QUOTE, WEIRD_USERID_QUOTE_AND_SPACE]
+ KEYPASS = ['encSpecial2Pass1', 'encSpecial2Pass2', 'encSpecial2Pass3', 'encSpecial2Pass4']
+ # Generate multiple keys
+ for uid, pswd in zip(USERIDS, KEYPASS):
+ rnp_genkey_rsa(uid, 1024, pswd)
+ # Encrypt to all recipients
+ src = data_path(MSG_TXT)
+ dst, dec = reg_workfiles('weird_userids_special_2', '.rnp', '.dec')
+ rnp_encrypt_file_ex(src, dst, list(map(lambda uid: uid, USERIDS)), None, None)
+ # Decrypt file with each of the passwords
+ for pswd in KEYPASS:
+ multiple_pass_attempts = (pswd + '\n') * len(KEYPASS)
+ rnp_decrypt_file(dst, dec, multiple_pass_attempts)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+ remove_files(dec)
+ # Cleanup
+ clear_workfiles()
+
+ def test_encryption_weird_userids_unicode(self):
+ USERIDS_1 = [
+ WEIRD_USERID_UNICODE_1, WEIRD_USERID_UNICODE_2]
+ USERIDS_2 = [
+ WEIRD_USERID_UNICODE_1, WEIRD_USERID_UNICODE_2]
+ # The idea is to generate keys with USERIDS_1 and encrypt with USERIDS_2
+ # (that differ only in case)
+ # But currently Unicode case-insensitive search is not working,
+ # so we're encrypting with exactly the same recipient
+ KEYPASS = ['encUnicodePass1', 'encUnicodePass2']
+ # Generate multiple keys
+ for uid, pswd in zip(USERIDS_1, KEYPASS):
+ rnp_genkey_rsa(uid, 1024, pswd)
+ # Encrypt to all recipients
+ src = data_path('test_messages') + '/message.txt'
+ dst, dec = reg_workfiles('weird_unicode', '.rnp', '.dec')
+ rnp_encrypt_file_ex(src, dst, list(map(lambda uid: uid, USERIDS_2)), None, None)
+ # Decrypt file with each of the passwords
+ for pswd in KEYPASS:
+ multiple_pass_attempts = (pswd + '\n') * len(KEYPASS)
+ rnp_decrypt_file(dst, dec, multiple_pass_attempts)
+ compare_files(src, dec, RNP_DATA_DIFFERS)
+ remove_files(dec)
+ # Cleanup
+ clear_workfiles()
+
+ def test_encryption_x25519(self):
+ # Make sure that we support import and decryption using both tweaked and non-tweaked keys
+ KEY_IMPORT = r'(?s)^.*' \
+ r'sec.*255/EdDSA.*3176fc1486aa2528.*' \
+ r'uid.*eddsa-25519-non-tweaked.*' \
+ r'ssb.*255/ECDH.*950ee0cd34613dba.*$'
+ BITS_MSG = r'(?s)^.*Warning: bits of 25519 secret key are not tweaked.*$'
+
+ ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_25519_NOTWEAK_SEC)])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, KEY_IMPORT)
+ ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '-d', data_path(MSG_ES_25519)])
+ self.assertEqual(ret, 0)
+ self.assertRegex(err, BITS_MSG)
+ self.assertRegex(err, r'(?s)^.*Signature\(s\) verified successfully.*$')
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', 'eddsa-25519-non-tweaked', '--force'])
+ self.assertEqual(ret, 0)
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_edge_cases/key-25519-tweaked-sec.asc')])
+ self.assertEqual(ret, 0)
+ self.assertRegex(out, KEY_IMPORT)
+ 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 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'])
+ self.assertEqual(ret, 0)
+ # Make sure GPG imports tweaked key and successfully decrypts message
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', data_path('test_key_edge_cases/key-25519-tweaked-sec.asc')])
+ self.assertEqual(ret, 0)
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '-d', data_path(MSG_ES_25519)])
+ self.assertEqual(ret, 0)
+ # Generate
+ pipe = pswd_pipe(PASSWORD)
+ ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe), '--userid',
+ 'eddsa_25519', '--generate-key', '--expert'], '22\n')
+ os.close(pipe)
+ self.assertEqual(ret, 0)
+ # Export
+ ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export', '--secret', 'eddsa_25519'])
+ self.assertEqual(ret, 0)
+ # Import key with GPG
+ ret, out, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import'], out)
+ self.assertEqual(ret, 0)
+ src, dst, dec = reg_workfiles('cleartext', '.txt', '.rnp', '.dec')
+ # Generate random file of required size
+ random_text(src, 1000)
+ # Encrypt and sign with RNP
+ ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '-es', '-r', 'eddsa_25519', '-u',
+ 'eddsa_25519', '--password', PASSWORD, src, '--output', dst, '--armor'])
+ # Decrypt and verify with RNP
+ rnp_decrypt_file(dst, dec, 'password')
+ self.assertEqual(file_text(src), file_text(dec))
+ remove_files(dec)
+ # Decrypt and verify with GPG
+ gpg_decrypt_file(dst, dec, 'password')
+ self.assertEqual(file_text(src), file_text(dec))
+ remove_files(dst, dec)
+ # Encrypt and sign with GnuPG
+ ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--always-trust', '-r', 'eddsa_25519',
+ '-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.*OCB.*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)
+ rnp_genkey_rsa(KEY_SIGN_GPG)
+ gpg_import_pubring()
+ gpg_import_secring()
+
+ @classmethod
+ def tearDownClass(cls):
+ clear_keyrings()
+
+ def tearDown(self):
+ clear_workfiles()
+
+ def test_rnp_compression(self):
+ runs = 30
+ levels = list_upto([None, 0, 2, 4, 6, 9], runs)
+ algosrnp = list_upto([None, 'zip', 'zlib', 'bzip2'], runs)
+ sizes = list_upto([20, 1000, 5000, 15000, 250000], runs)
+
+ for level, algo, size in zip(levels, algosrnp, sizes):
+ z = [algo, level]
+ gpg_to_rnp_encryption(size, None, z)
+ file_encryption_rnp_to_gpg(size, z)
+ rnp_signing_gpg_to_rnp(size, z)
+
+class SignDefault(unittest.TestCase):
+ '''
+ Things to try later:
+ - different public key algorithms
+ - different hash algorithms where applicable
+ - cleartext signing/verification
+ - detached signing/verification
+ '''
+ # Message sizes to be tested
+ SIZES = [20, 1000, 5000, 20000, 150000, 1000000]
+
+ @classmethod
+ def setUpClass(cls):
+ # Generate keypair in RNP
+ rnp_genkey_rsa(KEY_SIGN_RNP)
+ rnp_genkey_rsa(KEY_SIGN_GPG)
+ gpg_import_pubring()
+ gpg_import_secring()
+
+ @classmethod
+ def tearDownClass(cls):
+ clear_keyrings()
+
+ # TODO: This script should generate one test case per message size.
+ # Not sure how to do it yet
+ def test_rnp_to_gpg_default_key(self):
+ for size in Sign.SIZES:
+ rnp_signing_rnp_to_gpg(size)
+ rnp_detached_signing_rnp_to_gpg(size)
+ rnp_cleartext_signing_rnp_to_gpg(size)
+
+ def test_gpg_to_rnp_default_key(self):
+ for size in Sign.SIZES:
+ rnp_signing_gpg_to_rnp(size)
+ rnp_detached_signing_gpg_to_rnp(size)
+ rnp_detached_signing_gpg_to_rnp(size, True)
+ rnp_cleartext_signing_gpg_to_rnp(size)
+
+ def test_rnp_multiple_signers(self):
+ USERIDS = ['sign1@rnp', 'sign2@rnp', 'sign3@rnp']
+ KEYPASS = ['sign1pass', 'sign2pass', 'sign3pass']
+
+ # Generate multiple keys and import to GnuPG
+ for uid, pswd in zip(USERIDS, KEYPASS):
+ rnp_genkey_rsa(uid, 1024, pswd)
+
+ gpg_import_pubring()
+ gpg_import_secring()
+
+ src, dst, sig, ver = reg_workfiles('cleartext', '.txt', '.rnp', EXT_SIG, '.ver')
+ # Generate random file of required size
+ random_text(src, 128000)
+
+ for keynum in range(1, len(USERIDS) + 1):
+ # Normal signing
+ rnp_sign_file(src, dst, USERIDS[:keynum], KEYPASS[:keynum])
+ gpg_verify_file(dst, ver)
+ remove_files(ver)
+ rnp_verify_file(dst, ver)
+ remove_files(dst, ver)
+
+ # Detached signing
+ rnp_sign_detached(src, USERIDS[:keynum], KEYPASS[:keynum])
+ gpg_verify_detached(src, sig)
+ rnp_verify_detached(sig)
+ remove_files(sig)
+
+ # Cleartext signing
+ rnp_sign_cleartext(src, dst, USERIDS[:keynum], KEYPASS[:keynum])
+ gpg_verify_cleartext(dst)
+ rnp_verify_cleartext(dst)
+ remove_files(dst)
+
+ clear_workfiles()
+
+ def test_sign_weird_userids(self):
+ USERIDS = [WEIRD_USERID_SPECIAL_CHARS, WEIRD_USERID_SPACE, WEIRD_USERID_QUOTE,
+ WEIRD_USERID_SPACE_AND_QUOTE, WEIRD_USERID_QUOTE_AND_SPACE,
+ WEIRD_USERID_UNICODE_1, WEIRD_USERID_UNICODE_2]
+ KEYPASS = ['signUnicodePass1', 'signUnicodePass2', 'signUnicodePass3', 'signUnicodePass4',
+ 'signUnicodePass5', 'signUnicodePass6', 'signUnicodePass7']
+
+ # Generate multiple keys
+ for uid, pswd in zip(USERIDS, KEYPASS):
+ rnp_genkey_rsa(uid, 1024, pswd)
+
+ gpg_import_pubring()
+ gpg_import_secring()
+
+ src, dst, sig, ver = reg_workfiles('cleartext', '.txt', '.rnp', EXT_SIG, '.ver')
+ # Generate random file of required size
+ random_text(src, 128000)
+
+ for keynum in range(1, len(USERIDS) + 1):
+ # Normal signing
+ rnp_sign_file(src, dst, USERIDS[:keynum], KEYPASS[:keynum])
+ gpg_verify_file(dst, ver)
+ remove_files(ver)
+ rnp_verify_file(dst, ver)
+ remove_files(dst, ver)
+
+ # Detached signing
+ rnp_sign_detached(src, USERIDS[:keynum], KEYPASS[:keynum])
+ gpg_verify_detached(src, sig)
+ rnp_verify_detached(sig)
+ remove_files(sig)
+
+ # Cleartext signing
+ rnp_sign_cleartext(src, dst, USERIDS[:keynum], KEYPASS[:keynum])
+ gpg_verify_cleartext(dst)
+ 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')
+ random_text(src, 0x1337)
+
+ if not self.operation_key_location and not self.operation_key_gencmd:
+ raise RuntimeError("key not found")
+
+ if self.operation_key_location:
+ self.assertTrue(e1.import_key(self.operation_key_location[0]))
+ self.assertTrue(e1.import_key(self.operation_key_location[1], True))
+ else:
+ self.assertTrue(e1.generate_key_batch(self.operation_key_gencmd))
+
+ self.assertTrue(e1.export_key(keyfile, False))
+ self.assertTrue(e2.import_key(keyfile))
+ self.assertEqual(e2.encrypt(e1.userid, enc_out, src), not failenc)
+ self.assertEqual(e1.decrypt(dec_out, enc_out), not faildec)
+ clear_workfiles()
+
+ def setUp(self):
+ KeyLocationChooserMixin.__init__(self)
+ self.rnp = Rnp(RNPDIR, RNP, RNPK)
+ self.gpg = GnuPG(GPGHOME, GPG)
+ self.rnp.password = self.gpg.password = PASSWORD
+ self.rnp.userid = self.gpg.userid = self.test_id + AT_EXAMPLE
+
+ @classmethod
+ def tearDownClass(cls):
+ clear_keyrings()
+
+class EncryptElgamal(Encrypt):
+
+ GPG_GENERATE_DSA_ELGAMAL_PATTERN = """
+ Key-Type: dsa
+ Key-Length: {0}
+ Key-Usage: sign
+ Subkey-Type: ELG-E
+ Subkey-Length: {1}
+ Subkey-Usage: encrypt
+ Name-Real: Test Testovich
+ Expire-Date: 1y
+ Preferences: aes256 sha256 sha384 sha512 sha1 zlib
+ Name-Email: {2}
+ """
+
+ RNP_GENERATE_DSA_ELGAMAL_PATTERN = "16\n{0}\n"
+
+ @staticmethod
+ def key_pfx(sign_key_size, enc_key_size):
+ return "GnuPG_dsa_elgamal_%d_%d" % (sign_key_size, enc_key_size)
+
+ 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 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
+ self._encrypt_decrypt(self.rnp, self.gpg)
+
+ def test_encrypt_P1024_1024(self): self.do_test_encrypt(1024, 1024)
+ def test_encrypt_P1024_2048(self): self.do_test_encrypt(1024, 2048)
+ def test_encrypt_P2048_2048(self): self.do_test_encrypt(2048, 2048)
+ def test_encrypt_P3072_3072(self): self.do_test_encrypt(3072, 3072)
+ def test_decrypt_P1024_1024(self): self.do_test_decrypt(1024, 1024)
+ def test_decrypt_P2048_2048(self): self.do_test_decrypt(2048, 2048)
+ 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 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)
+
+ def test_generate_elgamal_key1024_in_rnp_and_decrypt(self):
+ cmd = EncryptElgamal.RNP_GENERATE_DSA_ELGAMAL_PATTERN.format(1024)
+ self.operation_key_gencmd = cmd
+ self._encrypt_decrypt(self.rnp, self.gpg)
+
+
+class EncryptEcdh(Encrypt):
+
+ GPG_GENERATE_ECDH_ECDSA_PATTERN = """
+ Key-Type: ecdsa
+ Key-Curve: {0}
+ Key-Usage: sign auth
+ Subkey-Type: ecdh
+ Subkey-Usage: encrypt
+ Subkey-Curve: {0}
+ Name-Real: Test Testovich
+ Expire-Date: 1y
+ Preferences: aes256 sha256 sha384 sha512 sha1 zlib
+ Name-Email: {1}"""
+
+ RNP_GENERATE_ECDH_ECDSA_PATTERN = "19\n{0}\n"
+
+ def test_encrypt_nistP256(self):
+ self.operation_key_gencmd = EncryptEcdh.GPG_GENERATE_ECDH_ECDSA_PATTERN.format(
+ "nistp256", self.rnp.userid)
+ self._encrypt_decrypt(self.gpg, self.rnp)
+
+ def test_encrypt_nistP384(self):
+ self.operation_key_gencmd = EncryptEcdh.GPG_GENERATE_ECDH_ECDSA_PATTERN.format(
+ "nistp384", self.rnp.userid)
+ self._encrypt_decrypt(self.gpg, self.rnp)
+
+ def test_encrypt_nistP521(self):
+ self.operation_key_gencmd = EncryptEcdh.GPG_GENERATE_ECDH_ECDSA_PATTERN.format(
+ "nistp521", self.rnp.userid)
+ self._encrypt_decrypt(self.gpg, self.rnp)
+
+ def test_decrypt_nistP256(self):
+ self.operation_key_gencmd = EncryptEcdh.RNP_GENERATE_ECDH_ECDSA_PATTERN.format(1)
+ self._encrypt_decrypt(self.rnp, self.gpg)
+
+ def test_decrypt_nistP384(self):
+ self.operation_key_gencmd = EncryptEcdh.RNP_GENERATE_ECDH_ECDSA_PATTERN.format(2)
+ self._encrypt_decrypt(self.rnp, self.gpg)
+
+ def test_decrypt_nistP521(self):
+ self.operation_key_gencmd = EncryptEcdh.RNP_GENERATE_ECDH_ECDSA_PATTERN.format(3)
+ self._encrypt_decrypt(self.rnp, self.gpg)
+
+class Sign(unittest.TestCase, TestIdMixin, KeyLocationChooserMixin):
+ SIZES = [20, 1000, 5000, 20000, 150000, 1000000]
+
+ def _sign_verify(self, e1, e2, failsign = False, failver = False):
+ '''
+ Helper function for Sign verification
+ 1. e1 creates/loads key
+ 2. e1 exports key
+ 3. e2 imports key
+ 2. e1 signs message
+ 3. e2 verifies message
+
+ eX == entityX
+ '''
+ keyfile, src, output = reg_workfiles(self.test_id, '.gpg', '.in', '.out')
+ random_text(src, 0x1337)
+
+ if not self.operation_key_location and not self.operation_key_gencmd:
+ print(self.operation_key_gencmd)
+ raise RuntimeError("key not found")
+
+ if self.operation_key_location:
+ self.assertTrue(e1.import_key(self.operation_key_location[0]))
+ self.assertTrue(e1.import_key(self.operation_key_location[1], True))
+ else:
+ self.assertTrue(e1.generate_key_batch(self.operation_key_gencmd))
+ self.assertTrue(e1.export_key(keyfile, False))
+ self.assertTrue(e2.import_key(keyfile))
+ self.assertEqual(e1.sign(output, src), not failsign)
+ self.assertEqual(e2.verify(output), not failver)
+ clear_workfiles()
+
+ def setUp(self):
+ KeyLocationChooserMixin.__init__(self)
+ self.rnp = Rnp(RNPDIR, RNP, RNPK)
+ self.gpg = GnuPG(GPGHOME, GPG)
+ self.rnp.password = self.gpg.password = PASSWORD
+ self.rnp.userid = self.gpg.userid = self.test_id + AT_EXAMPLE
+
+ @classmethod
+ def tearDownClass(cls):
+ clear_keyrings()
+
+class SignECDSA(Sign):
+ # {0} must be replaced by ID of the curve 3,4 or 5 (NIST-256,384,521)
+ #CURVES = ["NIST P-256", "NIST P-384", "NIST P-521"]
+ GPG_GENERATE_ECDSA_PATTERN = """
+ Key-Type: ecdsa
+ Key-Curve: {0}
+ Key-Usage: sign auth
+ Name-Real: Test Testovich
+ Expire-Date: 1y
+ Preferences: twofish sha512 zlib
+ Name-Email: {1}"""
+
+ # {0} must be replaced by ID of the curve 1,2 or 3 (NIST-256,384,521)
+ RNP_GENERATE_ECDSA_PATTERN = "19\n{0}\n"
+
+ def test_sign_P256(self):
+ cmd = SignECDSA.RNP_GENERATE_ECDSA_PATTERN.format(1)
+ self.operation_key_gencmd = cmd
+ self._sign_verify(self.rnp, self.gpg)
+
+ def test_sign_P384(self):
+ cmd = SignECDSA.RNP_GENERATE_ECDSA_PATTERN.format(2)
+ self.operation_key_gencmd = cmd
+ self._sign_verify(self.rnp, self.gpg)
+
+ def test_sign_P521(self):
+ cmd = SignECDSA.RNP_GENERATE_ECDSA_PATTERN.format(3)
+ self.operation_key_gencmd = cmd
+ self._sign_verify(self.rnp, self.gpg)
+
+ def test_verify_P256(self):
+ cmd = SignECDSA.GPG_GENERATE_ECDSA_PATTERN.format("nistp256", self.rnp.userid)
+ self.operation_key_gencmd = cmd
+ self._sign_verify(self.gpg, self.rnp)
+
+ def test_verify_P384(self):
+ cmd = SignECDSA.GPG_GENERATE_ECDSA_PATTERN.format("nistp384", self.rnp.userid)
+ self.operation_key_gencmd = cmd
+ self._sign_verify(self.gpg, self.rnp)
+
+ def test_verify_P521(self):
+ cmd = SignECDSA.GPG_GENERATE_ECDSA_PATTERN.format("nistp521", self.rnp.userid)
+ self.operation_key_gencmd = cmd
+ self._sign_verify(self.gpg, self.rnp)
+
+ def test_hash_truncation(self):
+ '''
+ Signs message hashed with SHA512 with a key of size 256. Implementation
+ truncates leftmost 256 bits of a hash before signing (see FIPS 186-4, 6.4)
+ '''
+ cmd = SignECDSA.RNP_GENERATE_ECDSA_PATTERN.format(1)
+ rnp = self.rnp.copy()
+ rnp.hash = 'SHA512'
+ self.operation_key_gencmd = cmd
+ self._sign_verify(rnp, self.gpg)
+
+class SignDSA(Sign):
+ # {0} must be replaced by ID of the curve 3,4 or 5 (NIST-256,384,521)
+ #CURVES = ["NIST P-256", "NIST P-384", "NIST P-521"]
+ GPG_GENERATE_DSA_PATTERN = """
+ Key-Type: dsa
+ Key-Length: {0}
+ Key-Usage: sign auth
+ Name-Real: Test Testovich
+ Expire-Date: 1y
+ Preferences: twofish sha256 sha384 sha512 sha1 zlib
+ Name-Email: {1}"""
+
+ # {0} must be replaced by ID of the curve 1,2 or 3 (NIST-256,384,521)
+ RNP_GENERATE_DSA_PATTERN = "17\n{0}\n"
+
+ @staticmethod
+ def key_pfx(p): return "GnuPG_dsa_elgamal_%d_%d" % (p, p)
+
+ 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 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, 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)
+ def test_sign_P2112_Q256(self): self.do_test_sign(2112)
+
+ def test_verify_P1024_Q160(self): self.do_test_verify(1024)
+ def test_verify_P2048_Q256(self): self.do_test_verify(2048)
+ def test_verify_P3072_Q256(self): self.do_test_verify(3072)
+ def test_verify_P2112_Q256(self): self.do_test_verify(2112)
+
+ def test_sign_P1088_Q224(self):
+ self.operation_key_gencmd = SignDSA.RNP_GENERATE_DSA_PATTERN.format(1088)
+ self._sign_verify(self.rnp, self.gpg)
+
+ def test_verify_P1088_Q224(self):
+ self.operation_key_gencmd = SignDSA.GPG_GENERATE_DSA_PATTERN.format("1088", self.rnp.userid)
+ self._sign_verify(self.gpg, self.rnp)
+
+ def test_hash_truncation(self):
+ '''
+ Signs message hashed with SHA512 with a key of size 160 bits. Implementation
+ truncates leftmost 160 bits of a hash before signing (see FIPS 186-4, 4.2)
+ '''
+ rnp = self.rnp.copy()
+ rnp.hash = 'SHA512'
+ self.operation_key_gencmd = SignDSA.RNP_GENERATE_DSA_PATTERN.format(1024)
+ self._sign_verify(rnp, self.gpg)
+
+class EncryptSignRSA(Encrypt, Sign):
+
+ GPG_GENERATE_RSA_PATTERN = """
+ Key-Type: rsa
+ Key-Length: {0}
+ Key-Usage: sign auth
+ Subkey-Type: rsa
+ Subkey-Length: {0}
+ Subkey-Usage: encrypt
+ Name-Real: Test Testovich
+ Expire-Date: 1y
+ Preferences: twofish sha256 sha384 sha512 sha1 zlib
+ Name-Email: {1}"""
+
+ RNP_GENERATE_RSA_PATTERN = "1\n{0}\n"
+
+ @staticmethod
+ def key_pfx(p): return "GnuPG_rsa_%d_%d" % (p, p)
+
+ def do_encrypt_verify(self, key_size):
+ pfx = EncryptSignRSA.key_pfx(key_size)
+ self.operation_key_location = tuple((key_path(pfx, False), key_path(pfx, True)))
+ self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE
+ self._encrypt_decrypt(self.gpg, self.rnp)
+ self._sign_verify(self.gpg, self.rnp)
+
+ def do_rnp_decrypt_sign(self, key_size):
+ pfx = EncryptSignRSA.key_pfx(key_size)
+ self.operation_key_location = tuple((key_path(pfx, False), key_path(pfx, True)))
+ self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE
+ self._encrypt_decrypt(self.rnp, self.gpg)
+ self._sign_verify(self.rnp, self.gpg)
+
+ def test_rnp_encrypt_verify_1024(self): self.do_encrypt_verify(1024)
+ def test_rnp_encrypt_verify_2048(self): self.do_encrypt_verify(2048)
+ def test_rnp_encrypt_verify_4096(self): self.do_encrypt_verify(4096)
+
+ def test_rnp_decrypt_sign_1024(self): self.do_rnp_decrypt_sign(1024)
+ def test_rnp_decrypt_sign_2048(self): self.do_rnp_decrypt_sign(2048)
+ def test_rnp_decrypt_sign_4096(self): self.do_rnp_decrypt_sign(4096)
+
+ def setUp(self):
+ Encrypt.setUp(self)
+
+ @classmethod
+ def tearDownClass(cls):
+ Encrypt.tearDownClass()
+
+def test_suites(tests):
+ if hasattr(tests, '__iter__'):
+ for x in tests:
+ for y in test_suites(x):
+ yield y
+ else:
+ yield tests.__class__.__name__
+
+# Main thinghy
+
+if __name__ == '__main__':
+ main = unittest.main
+ if not hasattr(main, 'USAGE'):
+ main.USAGE = ''
+ main.USAGE += ''.join([
+ "\nRNP test client specific flags:\n",
+ " -w,\t\t Don't remove working directory\n",
+ " -d,\t\t Enable debug messages\n"])
+
+ LEAVE_WORKING_DIRECTORY = ("-w" in sys.argv)
+ if LEAVE_WORKING_DIRECTORY:
+ # -w must be removed as unittest doesn't expect it
+ sys.argv.remove('-w')
+ else:
+ LEAVE_WORKING_DIRECTORY = os.getenv('RNP_KEEP_TEMP') is not None
+
+ LVL = logging.INFO
+ if "-d" in sys.argv:
+ sys.argv.remove('-d')
+ LVL = logging.DEBUG
+
+ # list suites
+ if '-ls' in sys.argv:
+ tests = unittest.defaultTestLoader.loadTestsFromModule(sys.modules[__name__])
+ for suite in set(test_suites(tests)):
+ print(suite)
+ sys.exit(0)
+
+ setup(LVL)
+ res = main(exit=False)
+
+ if not LEAVE_WORKING_DIRECTORY:
+ try:
+ if RMWORKDIR:
+ shutil.rmtree(WORKDIR)
+ else:
+ shutil.rmtree(RNPDIR)
+ shutil.rmtree(GPGDIR)
+ except Exception:
+ # Ignore exception if something cannot be deleted
+ pass
+
+ sys.exit(not res.result.wasSuccessful())
diff --git a/src/tests/data/.gitattributes b/src/tests/data/.gitattributes
new file mode 100644
index 0000000..8b392e8
--- /dev/null
+++ b/src/tests/data/.gitattributes
@@ -0,0 +1,19 @@
+# for test_ffi_op_verify_sig_count
+test_messages/message-32k-crlf.txt -text
+test_messages/message-trailing-cr.txt -text
+# for test_stream_signatures
+test_stream_signatures/source*.txt -text
+# for cleartext tests
+test_messages/message.txt -text
+# for test_ffi_enarmor_dearmor
+test_stream_key_load/rsa-rsa-pub.asc -text
+# for Misc.test_rnpkeys_lists
+test_cli_rnpkeys/* -text
+# for Misc.test_rnp_single_export
+test_single_export_subkeys/list_key_export_single.txt -text
+# for Misc.test_rnp_list_packets
+test_list_packets/* -text
+# for Misc.test_rnp_list_packets_edge_cases
+test_key_edge_cases/* -text
+# for test_ffi_dearmor_edge_cases
+test_stream_armor/blank_line_with_whitespace.asc -text
diff --git a/src/tests/data/.keepme b/src/tests/data/.keepme
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/src/tests/data/.keepme
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024-sec.gpg
new file mode 100644
index 0000000..9f0e0ed
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024-sec.gpg
@@ -0,0 +1,28 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQHpBGFe77URBADp3o2K++kVaHiAHBoSZvNPOYBtJIEFN8wWzDUkOa5CDKAyzj8X
+T70444ePqvloTHMbNfM3EF7Qe0sZDEbVLHJqsOFQwUF7JeHlBQL7H2w/W90tIf4f
+kyaqYO4uK2YvW7y5UpqfiET2hukoLq404s16znkeVKHZ5zsvGqBYkzV2jwCg20CG
+UHyy/TrR1EmOeNzQDbbkU+UEANb0v/MFMkP7M9ZFgR3wQL2CUvSzN7OOME8ifMFS
+y22QQq7latxbeuUDjDjGNhXGtSIUmgHCwc6nzHvNpQ+cm/NWrZvyOv3pLOHkU1tI
+/0esSCIw7egwApygCoBgUH27w8lFjBWOXNY10kCOyzTVXYeLcyCYOq8+Bh9KmMOb
+xj6qA/oCggr9jYCjBgbrm1m3iuOd9vttqfTb0Ab13l3UKQI1px+jX42AIALSa5uH
+iYbEk4nYVLgBTNFY8bI3BMKESQVMJeS3oXyRAxTAsWvD98qXV0DR15wH2Rq2HHNa
+wmltcovRrpzfsCaFKr50HVBC9mVeECg2o66sbcjkGQx6L6Te5/4HAwJFb51WJw6M
+svM/C8nNxRC/zFiXH9ACYyvnDRBO+mctMbvf9euTeCMk7zffR9oMtd7hUOTeFeyd
+LCltvoxeiBcuefCWtDhUZXN0IFRlc3RvdmljaCA8R251UEdfZHNhX2VsZ2FtYWxf
+MTAyNF8xMDI0QGV4YW1wbGUuY29tPohyBBMRAgAyFiEE11CMZvArgEeh6W0AlOdk
+hnqNnMEFAmFe77UCGwMCCwkFFQgJCgICFgICHgUCF4AACgkQlOdkhnqNnMEsGACg
+hYrQ4OqnJupTXd82lNb8Bq5I678An2r6SmdeVn9Z/skLDVn4coGaw83UnQFfBGFe
+77UQBADwCH6poAGVGQl2RPG44jIH4G9FT+zHwfgi59sc/g7xKNIROLy3SmXp0KNb
+9I9OebQPPWVost/wW5rHlMKT9ecnHmsS6N8+kFvu+zp19Q+OKkp9ybg6kagjj1tW
+H0ONGwGOP7qjSb71XG/YtUZlQ6qfjmTdIvpB9DymsM7SLXQckwADBQP+MHWOFQmN
+YSdJq0bZPtQbC+SbXtuiyqAmp/8/0OTW4ZTGiDCzjC9AWl5gV1SX7zKiN5+HLWI2
+wfXKEqWQnto31PP0Bs1X9c/3hdL+6ylbnc4IuIScNuQN4E7KtLKtjhSKdhKlvDZR
+eZSkRfz74efmf5+nzgbI2XMjfRKRZdOVwgH+BwMCP2K49baJwJjz1WsGK3uY53+4
+WYCwmluNNpkDzMCpNkxM7ACQ4EWvBayu2UzSrgK95fK+g/gRU6vASUnuiItLK2sn
+Ve0CqdP/PUYbpDWfiGAEGBECACAWIQTXUIxm8CuAR6HpbQCU52SGeo2cwQUCYV7v
+tQIbDAAKCRCU52SGeo2cwTORAJ9dUmdbapJCw8DJPKo3zrj5K2uXPwCbBAEh7kdn
+MZK669R2Snwp1hYOINU=
+=sBQ1
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024.gpg
new file mode 100644
index 0000000..cb65fb0
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_1024.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048-sec.gpg
new file mode 100644
index 0000000..35a14a7
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048-sec.gpg
@@ -0,0 +1,33 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQHpBGFe77YRBAC7YuR+mUtDv8tgj+xheX0e1hTpnevRmA3yGb1EOYHEucfOk0k/
+KjFFHzouUP8eLS6CCiKq0PAWCNuHrgTxxSGURWVTic3/vMpjWiPcSqxsB5YkvqCT
+rdM+CgzsEL22FglNd/yL/3EYuvO1UGF9TtIhOs/92AG/x5/pJyJKehr+GwCgs0Zi
+Aa2J/FmhsvmMWFD8WlK6cJ0D/0X07TPbJW3PXGbovnBc/F5k8tcfLOyYuj1Pv/nW
+DUFuovJt88GoUd3lDTdO51NUK+La32hMLnvIgagEjr1wBzfMsbSmmkfEwRXWQLMn
+Lp9XPkEaVvAovW7xUleF1SL1bSrRRnmWjmc+sylR0I7nlhNLqbjoYBb/IaVyU2jc
+zhk1A/93rntBVw5DXZuOspx+800UfIn/IQnjMYUYMywa3C2NjZvZL7yGVbn2tUFz
+xBSQxaj3gvjzYPI5z1VP3hLY/99zQjzksNjdQDn+HuKWb+4rtW0TzE6G2yEkclJK
+IxnK8Rw4pNAYEaZCDyf8L3FCd2ts7v2yU1IHYEJW8ibhN+D18v4HAwKqm9e9T2qs
+5fPlJ2vhBUVfKyl93F4+aY/jZFF8laKLDPbVSwwfMhjM+MqewiknxGp1k/5Df2Wl
+XM83Xob7T5Dx8KJTtDhUZXN0IFRlc3RvdmljaCA8R251UEdfZHNhX2VsZ2FtYWxf
+MTAyNF8yMDQ4QGV4YW1wbGUuY29tPohyBBMRAgAyFiEEzCEZP/BL1q3fLdXBm00p
+ZANQVKUFAmFe77YCGwMCCwkFFQgJCgICFgICHgUCF4AACgkQm00pZANQVKVrgQCg
+nvFdnU1/NeKhVI/+tw6b2vloyt8AnjWYdVSGjHT/zd8jYBlMbMf0tyOnnQJrBGFe
+77YQCAC+5tS9jY7IEv62gjNmOvruZL8wb6xEZ7/6F4K/IQfKNexOP/xPPOYXsNbS
+GE/FBHLt808Owpkp56wtu7y5uI4yAA8wB5WnIlFeVurijUNiHcg4VNVf7aObyTqA
+uJWiEQBXlDQHbKQJ9WyMAubzdsvRK1kHlW0ZavCAu3dQ9SG2JEaDP5ijr9yWM8Bp
+Y+hdbzta17/5+w/N1Eve2haWwzdi7VA9S8670HetBZ6BMUcCERHYGkZ9qdauvunc
+CDl0oMsaZedwofuftI54AJwVnBcyaYWB1C4xVGLucLDpLM39rRV/1HCzS/FeBXNE
+SjZBpY7Dgk+P862A/C5qH+1P/BZnAAQLB/4wv5MPzjuevr5rUgoxz7plrc9aVcgR
+CPdBN0NadiFKqYbxI0wxRbMMjc+dX2dSd671z8Dn6pMrdriDaWuuwxSdXmMCElmK
+IBfpUZx1L36gOgZbw2utG/A412cLa5J7lyzyluN3Gc1qnXu7f6PYwHQkb6DlcOAX
+ORpByOOio1M0TiidGdN6jcZIvOSrZrXX1d6IPfbO5gqp3F4orNHleG1QaYXZrJJS
+WMq9PmFrvgzlcPp9FJXh7Yji6ca44oyJaSpAJrsVowV2dmcWohtpzYve6JGrmsCK
+cGhlo8CHuUIG4bHKYeH7++gBJxJZRBrKiYkgZa+vcSJdf82BPIJBFzk9/gcDAoFW
+HN4aiWy986alVKUOHUpmoPmqUunVyhrjRj8JF1ul5k52/NjsnjW4pufKdkJ1CqVQ
+7xNbAC9mTHk9Kn8LkJdTI8sULUBJYARmrKX8ez8QNjJaZSCKYpX3A4hgBBgRAgAg
+FiEEzCEZP/BL1q3fLdXBm00pZANQVKUFAmFe77YCGwwACgkQm00pZANQVKWDAQCf
+U/uklCOD9vcVLVp9fkA7lOpky04AnipIycqWKmIY3LuHahP1AFP0uNjE
+=lQoQ
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048.gpg
new file mode 100644
index 0000000..067b2bd
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1024_2048.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234-sec.gpg
new file mode 100644
index 0000000..40c60b7
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234-sec.gpg
@@ -0,0 +1,32 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQJZBGFe77kRBQDED0dCShzpj1tWOXXig8N6AAaYng6zoXtPOkB0+K+Uq0Qu+OwZ
+ZBk8ELDMJgF5gGxGsCKu9ALFeaHubVDkAtWcgj0iq+cZKILmuWW83+HEBCTb1pet
+SIYMXFbhGMiJSNlw1Is+OvOiluqB+TKj5ZDF4VWVFxwG92izNRjQMkBNcnTP/ZzF
+aJXmjfkakvwzdCqAQyb5RtSZXz/HuUzs4/gfAODuZNFduli0EEgjRxdLQJ8WmSjG
+XWmvQgKePq6XBP4+PyS2svI0KECmVTH5nlxFX9Fkfq7TQOrIg8dM2a9qHnFw8eq4
+Ibww71eCPVnsTZo+AEiBE8rfohraHPt1Pc158+MIVgvDCczysmQXB6RDUoV62tWS
+zxDIVDmFju5MXDP8+3Nu0ShObwdGjH2XSr4et3cjS5KVIzdoHeV5S1B8qFDCXhn1
+Z3Zs+WuT80ygUqu/SWzSsQtea7oOCQWgcJjBBQCIX6YFAQjC3aSPtEPnyb/xjDQj
+Y45wuV71Dv7Wxttwki8gRUahxADDfA8rVxgjDQLfIBfcZSy3RRFAwL/hpLxlb61R
+Sf1197ZI1fd3T0AMyjPmOxEzrOu0mKPVxzwplWcOpvEkpPWk4yOMdXq/FmSdL8hX
+hX5sQzt01PuTCcTU+Y4B53BrMdqPaxYZYlr+K56e+R6dFN9j3i9/WfyQbnjc/gcD
+AmXE+lFwAxT4872MR669qt1ItwzlrwzuAKa/zLpcRK2vlA69oZgDwU7834r2c/LI
+WSrIDRxP6A9tub+f5kVJ+S9gwT4Pg3AaLMUAdLQ4VGVzdCBUZXN0b3ZpY2ggPEdu
+dVBHX2RzYV9lbGdhbWFsXzEyMzRfMTIzNEBleGFtcGxlLmNvbT6IggQTEQsAMhYh
+BPEWW0sV4oPJZ6LLdbW4bfCx3VtoBQJhXu+5AhsDAgsJBRUICQoCAhYCAh4FAheA
+AAoJELW4bfCx3Vtoi+IA3jbZGv4V54s0UU3Pc2xqe34W6FKcQ74ylrFYLCQA30Pz
+Llno8IO0fMEkjODxpXlfqFnE16Wm9npfs6OdAZsEYV7vuRAE4IvJb4BHL375yZKP
+vHTbFcaFuf+ElqN2iXVEZTPj/uop1Gp5JrM3OZDoMa/gTdAXWmk+AWYy3mLfp8dB
+n0gHNWyKDEFg8XqGiQSsYDxjkIHyKC/m6S0i+m3EetQwikAt6Uu/iY8HMWQcQ7Kk
+n3DFnd6DD0lS+nn2J1y/xYQacPv7i3xhp0+vnTbE6dNerwmNPQfMJPB+FNvf+l0H
+LwAECwTggpUOZ79yqWJyoPR5OhdCEjLx9LtJYdzu9asGWLZgztwDKwabqYo/1Qcr
+2iPd8rZr1YJrG6L9nfRL+tENO4s1m2P7w/bJ8D/hpVnFSPNXyLRWf2L9+hVtfPKi
+hJ2ysokhjUGYBjXjBZSxboHkXYQpb/w1ZJKwhpZl5Sw2XKKrQC+n9LeD77jmSlQr
+TNZ86o0xfcGzbWENPLhB875a/gcDAivOpDfD8X+T85Az4N8N01QZoQ0QdX1GSiFV
+CRIFWg3FKuoB+5ice2CtdfmKARtGPAnQmclo2uYQDQzEQGlu+J2vWLXQN3M6hIV+
+tQXzn/8ydHyIcAQYEQsAIBYhBPEWW0sV4oPJZ6LLdbW4bfCx3VtoBQJhXu+5AhsM
+AAoJELW4bfCx3Vto8FAA4ICXEA1I6ZDZ5AHKDqAsJEDKgEY8taTJa0IbZwEA4Jbg
+z+B3rt8NNrHs5AkFBQJptxbIu1gegrJAlPg=
+=79Pf
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234.gpg
new file mode 100644
index 0000000..8effa91
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_1234_1234.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048-sec.gpg
new file mode 100644
index 0000000..136125f
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048-sec.gpg
@@ -0,0 +1,43 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+Zj2jRtKWBB8x/2SXisZVTzJb
+=W2Y0
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048.gpg
new file mode 100644
index 0000000..612deb1
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2048_2048.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112-sec.gpg
new file mode 100644
index 0000000..7b219f8
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112-sec.gpg
@@ -0,0 +1,44 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+Tgi2eoTVV8l2CGmV
+=8SKT
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112.gpg
new file mode 100644
index 0000000..73470fc
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_2112_2112.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072-sec.gpg
new file mode 100644
index 0000000..e667164
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072-sec.gpg
@@ -0,0 +1,56 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=tIv0
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072.gpg b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072.gpg
new file mode 100644
index 0000000..58fe2aa
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_dsa_elgamal_3072_3072.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024-sec.gpg
new file mode 100644
index 0000000..c2b319b
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024-sec.gpg
@@ -0,0 +1,35 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=2vB7
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024.gpg
new file mode 100644
index 0000000..3cd970e
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_1024_1024.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048-sec.gpg
new file mode 100644
index 0000000..7f207d2
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048-sec.gpg
@@ -0,0 +1,59 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=iOlL
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048.gpg
new file mode 100644
index 0000000..794ec9b
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_2048_2048.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072-sec.gpg
new file mode 100644
index 0000000..238841b
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072-sec.gpg
@@ -0,0 +1,83 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=uNhW
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072.gpg
new file mode 100644
index 0000000..f51f91e
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_3072_3072.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096-sec.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096-sec.gpg
new file mode 100644
index 0000000..b7e72a0
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096-sec.gpg
@@ -0,0 +1,107 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=juL3
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096.gpg b/src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096.gpg
new file mode 100644
index 0000000..5383b43
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/GnuPG_rsa_4096_4096.gpg
Binary files differ
diff --git a/src/tests/data/cli_EncryptSign/regenerate_keys b/src/tests/data/cli_EncryptSign/regenerate_keys
new file mode 100755
index 0000000..2ee2543
--- /dev/null
+++ b/src/tests/data/cli_EncryptSign/regenerate_keys
@@ -0,0 +1,126 @@
+#!/usr/bin/env python
+
+import tempfile
+import sys
+
+sys.path.append("../../")
+import git
+from gnupg import GnuPG as GnuPG
+from rnp import Rnp as Rnp
+
+PASSWORD = "password"
+USERID_PFX = '@example.com'
+
+def find_exe(basedir, exename):
+ import os
+ for root, _, files in os.walk(basedir):
+ fpath = os.path.join(root, exename)
+ if exename in files and os.access(fpath, os.X_OK):
+ return os.path.abspath(fpath)
+
+ return None
+
+class KeyFormatter(object):
+ def __init__(self, ktype, pattern):
+ self.pattern = pattern
+ self.key_type = ktype
+
+ def key_type(self): return self.key_type
+
+ def key_size(self, sign_key_size, enc_key_size):
+ self.sign_key_size = sign_key_size
+ self.enc_key_size = enc_key_size
+ return self
+
+ def format(self, gen_obj):
+ raise NotImplementedError("not implemented in base")
+
+class RnpRsaKeyFormatter(KeyFormatter):
+ RNP_GENERATE_RSA_PATTERN = "1\n{0}\n"
+
+ def __init__(self):
+ super(RnpRsaKeyFormatter, self).__init__('rsa', RnpRsaKeyFormatter.RNP_GENERATE_RSA_PATTERN)
+
+ def format(self, gen_obj):
+ return self.pattern.format(self.sign_key_size)
+
+class GpgRsaKeyFormatter(KeyFormatter):
+ GPG_GENERATE_RSA_PATERN = """
+ Key-Type: rsa
+ Key-Length: {0}
+ Key-Usage: sign auth
+ Subkey-Type: rsa
+ Subkey-Length: {1}
+ Subkey-Usage: encrypt
+ Name-Real: Test Testovich
+ Preferences: aes256 aes512 sha256 sha384 sha512 sha1 zlib
+ Name-Email: {2}"""
+
+ def __init__(self):
+ super(GpgRsaKeyFormatter, self).__init__('rsa', GpgRsaKeyFormatter.GPG_GENERATE_RSA_PATERN)
+
+ def format(self, gen_obj):
+ return self.pattern.format(self.sign_key_size, self.enc_key_size, gen_obj.userid)
+
+class GpgDsaKeyFormatter(KeyFormatter):
+ GPG_GENERATE_DSA_ELGAMAL_PATERN = """
+ Key-Type: dsa
+ Key-Length: {0}
+ Key-Usage: sign
+ Subkey-Type: ELG-E
+ Subkey-Length: {1}
+ Subkey-Usage: encrypt
+ Name-Real: Test Testovich
+ Preferences: aes256 aes512 sha256 sha384 sha512 sha1 zlib
+ Name-Email: {2}
+ """
+
+ def __init__(self):
+ super(GpgDsaKeyFormatter, self).__init__('dsa_elgamal', GpgDsaKeyFormatter.GPG_GENERATE_DSA_ELGAMAL_PATERN)
+
+ def format(self, gen_obj):
+ return self.pattern.format(self.sign_key_size, self.enc_key_size, gen_obj.userid)
+
+class RnpDsaKeyFormatter(KeyFormatter):
+ RNP_GENERATE_DSA_ELGAMAL_PATTERN = "16\n{0}\n"
+
+ def __init__(self):
+ super(RnpDsaKeyFormatter, self).__init__('dsa_elgamal', RnpDsaKeyFormatter.RNP_GENERATE_DSA_ELGAMAL_PATTERN)
+
+ def format(self, gen_obj):
+ return self.pattern.format(self.sign_key_size)
+
+def keygen(obj, formatter):
+ key_name_pfx = obj.__class__.__name__
+ enc_key_size = formatter.enc_key_size
+ sign_key_size = formatter.sign_key_size
+
+ key_name = '_'.join([key_name_pfx, formatter.key_type, str(sign_key_size), str(enc_key_size)])
+ obj.userid = key_name+USERID_PFX
+
+ # generate and export
+ if not obj.generate_key_batch(formatter.format(obj)): raise RuntimeError("Generation failed")
+ if not obj.export_key(key_name+"-sec.gpg", True): raise RuntimeError("Secret key export failed")
+ if not obj.export_key(key_name+".gpg", False): raise RuntimeError("Public key export failed")
+
+
+# Lists of tuples (sign key size, encryption key size)
+DSA_ELGAMAL_KEY_SIZES = [(1024, 1024), (1024, 2048), (1234, 1234), (2048, 2048), (2112, 2112), (3072, 3072)]
+RSA_KEY_SIZES = [(1024, 1024), (2048, 2048), (3072, 3072), (4096, 4096)]
+
+
+topdir = git.Repo(".", search_parent_directories=True).working_tree_dir
+rnp = Rnp(tempfile.mkdtemp(prefix="rnp-regen-rnp"), find_exe(topdir, "rnp"), find_exe(topdir, "rnpkeys"))
+rnp.password = PASSWORD
+gpg = GnuPG(tempfile.mkdtemp(prefix="rnp-regen-gpg"), "/usr/bin/gpg")
+gpg.password = PASSWORD
+
+# Generate RSA
+for key in RSA_KEY_SIZES:
+ keygen(rnp, RnpRsaKeyFormatter().key_size(key[0], key[1]))
+ keygen(gpg, GpgRsaKeyFormatter().key_size(key[0], key[1]))
+
+# Generate DSA/ElGamal
+for key in DSA_ELGAMAL_KEY_SIZES:
+ keygen(rnp, RnpDsaKeyFormatter().key_size(key[0], key[1]))
+ keygen(gpg, GpgDsaKeyFormatter().key_size(key[0], key[1]))
diff --git a/src/tests/data/issue1188/armored_revocation_signature.pgp b/src/tests/data/issue1188/armored_revocation_signature.pgp
new file mode 100644
index 0000000..e61dcd0
--- /dev/null
+++ b/src/tests/data/issue1188/armored_revocation_signature.pgp
@@ -0,0 +1,12 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+wsD2BCABCAAgFiEEjfYTgEZi8VK4srNzBBUFBnoZpfIFAl796EQCHQAACgkQBBUFBnoZpfKpMwv8
+CLJNFLb2H5I302hprJPAvFo4VynMZqM9HVRfRQ8sdWJ/qM3cT90L/rS8kAO8Qs/MYtAYQ8Wc07XS
+1/oaljqxVL8ARL0+Bw5OdMy93FW9ot5DbQolGF9L1MN3+T7j9ChbyXOtpiO3JuzIbEZTUyDGn85J
+yMtHaFnOx5V3pGqMUgR8eaPnsg00zUpfFHrzGku5SPKAe0Rf5HfO28fOwEacNYLp21rRQZarAZ7j
+YXF/A/aIWzWxXdu7hzToqmtkWNiL3wKBCjx/xsTRHLq2o2XjllN6WXtDiPjeeCJT912vy0WwS2ih
+yXiq9qIE33nYXz/CPanJUCPajgC1AmWM+LUgHK/fuNULPUQLhzek8iw9GHnqoS6Ywl30WL9sbpA1
+NS3oQSLY3HVYE2p/jMXXNeLwTiunSwiVqfDL5ki6VMI5b/GXEb1omQT9AkWqyh+xAmm7+OJQ3fhy
+bfI3Nv1l+YwuWHZgjb3bHwjOXCgPX7GblCxEMTbl2sHohjYcTgBT/3Gg
+=Yqti
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/keyrings/1/info.txt b/src/tests/data/keyrings/1/info.txt
new file mode 100644
index 0000000..ba007cf
--- /dev/null
+++ b/src/tests/data/keyrings/1/info.txt
@@ -0,0 +1 @@
+password: password
diff --git a/src/tests/data/keyrings/1/pubring.gpg b/src/tests/data/keyrings/1/pubring.gpg
new file mode 100644
index 0000000..da9e1ad
--- /dev/null
+++ b/src/tests/data/keyrings/1/pubring.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/1/pubring.gpg.asc b/src/tests/data/keyrings/1/pubring.gpg.asc
new file mode 100644
index 0000000..18bcc43
--- /dev/null
+++ b/src/tests/data/keyrings/1/pubring.gpg.asc
@@ -0,0 +1,67 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+Aw==
+=hqEl
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/keyrings/1/secring-cast5.gpg b/src/tests/data/keyrings/1/secring-cast5.gpg
new file mode 100644
index 0000000..70ae8bd
--- /dev/null
+++ b/src/tests/data/keyrings/1/secring-cast5.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/1/secring.gpg b/src/tests/data/keyrings/1/secring.gpg
new file mode 100644
index 0000000..8925451
--- /dev/null
+++ b/src/tests/data/keyrings/1/secring.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/2/info.txt b/src/tests/data/keyrings/2/info.txt
new file mode 100644
index 0000000..8fd1956
--- /dev/null
+++ b/src/tests/data/keyrings/2/info.txt
@@ -0,0 +1 @@
+These are pgp263-test.*.asc from gnupg's tests.
diff --git a/src/tests/data/keyrings/2/pubring.gpg b/src/tests/data/keyrings/2/pubring.gpg
new file mode 100644
index 0000000..3016ff7
--- /dev/null
+++ b/src/tests/data/keyrings/2/pubring.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/2/secring.gpg b/src/tests/data/keyrings/2/secring.gpg
new file mode 100644
index 0000000..3756414
--- /dev/null
+++ b/src/tests/data/keyrings/2/secring.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/3/info.txt b/src/tests/data/keyrings/3/info.txt
new file mode 100644
index 0000000..ba007cf
--- /dev/null
+++ b/src/tests/data/keyrings/3/info.txt
@@ -0,0 +1 @@
+password: password
diff --git a/src/tests/data/keyrings/3/private-keys-v1.d/63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59.key b/src/tests/data/keyrings/3/private-keys-v1.d/63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59.key
new file mode 100644
index 0000000..b496177
--- /dev/null
+++ b/src/tests/data/keyrings/3/private-keys-v1.d/63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59.key
Binary files differ
diff --git a/src/tests/data/keyrings/3/private-keys-v1.d/7EAB41A2F46257C36F2892696F5A2F0432499AD3.key b/src/tests/data/keyrings/3/private-keys-v1.d/7EAB41A2F46257C36F2892696F5A2F0432499AD3.key
new file mode 100644
index 0000000..830770f
--- /dev/null
+++ b/src/tests/data/keyrings/3/private-keys-v1.d/7EAB41A2F46257C36F2892696F5A2F0432499AD3.key
Binary files differ
diff --git a/src/tests/data/keyrings/3/pubring.kbx b/src/tests/data/keyrings/3/pubring.kbx
new file mode 100644
index 0000000..9aabee6
--- /dev/null
+++ b/src/tests/data/keyrings/3/pubring.kbx
Binary files differ
diff --git a/src/tests/data/keyrings/4/info.txt b/src/tests/data/keyrings/4/info.txt
new file mode 100644
index 0000000..fc2efd5
--- /dev/null
+++ b/src/tests/data/keyrings/4/info.txt
@@ -0,0 +1,2 @@
+PGP 2.6.3i keyrings and exported public/secret key files.
+password: password
diff --git a/src/tests/data/keyrings/4/pubring.pgp b/src/tests/data/keyrings/4/pubring.pgp
new file mode 100755
index 0000000..6800faf
--- /dev/null
+++ b/src/tests/data/keyrings/4/pubring.pgp
Binary files differ
diff --git a/src/tests/data/keyrings/4/rsav3-p.asc b/src/tests/data/keyrings/4/rsav3-p.asc
new file mode 100755
index 0000000..340b7aa
--- /dev/null
+++ b/src/tests/data/keyrings/4/rsav3-p.asc
@@ -0,0 +1,12 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: 2.6.3i
+
+mQCNA1nlTiAAAAEEAPBKZRxjg3YJNoGI7nJNNkkboVVpqxSLUO5ftEvto4hxyq7O
+DCs3pij3kZwCgdhYMrjEUMw22EommT+851gpE1lkZQUGbop00ggQE+1esgaOMaSZ
+aGZQV4NM6TmUiRHNxUio7Cbr0bsmwT1y/ui/HKuVfleyZv2yVn0LwQ6TNATJAAUR
+tBByc2F2M0ByaWJvc2UuY29tiQCVAwUQWeVOIX0LwQ6TNATJAQGl8AP/VfKUJCUk
+GZgDkSrcWX3sUPDjTb/sUEWXr0aRkhxdTHxaBdOIMyogJEkwTCKcubFkq9W6JqUk
+BP88FCs7JWtLFZKmp7FBR6GJplepwcxssycOfHGpoVkUfxC/vaLxR9xafQue1bo6
+THrohx7SEIRzxsBKHKled/nL22bpS8y3HF4=
+=sD9Y
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/keyrings/4/rsav3-s.asc b/src/tests/data/keyrings/4/rsav3-s.asc
new file mode 100755
index 0000000..54be0da
--- /dev/null
+++ b/src/tests/data/keyrings/4/rsav3-s.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SECRET KEY BLOCK-----
+Version: 2.6.3i
+
+lQHgA1nlTiAAAAEEAPBKZRxjg3YJNoGI7nJNNkkboVVpqxSLUO5ftEvto4hxyq7O
+DCs3pij3kZwCgdhYMrjEUMw22EommT+851gpE1lkZQUGbop00ggQE+1esgaOMaSZ
+aGZQV4NM6TmUiRHNxUio7Cbr0bsmwT1y/ui/HKuVfleyZv2yVn0LwQ6TNATJAAUR
+AaMOdxpGT9XyA/0GrjC7dV8VnBiR1BWy1+AvK61kqK4ive1X/Upfoa6F7PIS35E5
+dmeoWbijekUTl+/uj3oMUHe1W7w9c2kR/6XOsbWzSW525eTVeI3tJ8vZEwK2Ebgn
+eQDigGiwgAqYsuTwnCh9wTRnN0bhPI3lVl1Y3l0PceezY/dFpc1mFci4dQIAn6q8
+xlhdDlD4GxRijLl0DsbWOnNYw4oF3JWpSPeQYy38Q174X3TA15O1/rF74ztM2Mzg
+AKC23ANM7NLfK+J0pwIAQPh5OQ9f992dyPBct3yp16gtGMfcE9xcaiJXjWtaKVnL
+F74rVku6zf/2e3h9HQvQkDdWu94gOSMJjbwioy7/bwH/2TI63PWfg6LvpKV9V5Ce
+WxwW2pYk8z/wn8DbJ629dCwd/oobuXJoSYrM3atFCNzjHlKKAs1Qq6KgDPEj8yLl
+/qHitBByc2F2M0ByaWJvc2UuY29t
+=BXSD
+-----END PGP SECRET KEY BLOCK-----
diff --git a/src/tests/data/keyrings/4/secring.pgp b/src/tests/data/keyrings/4/secring.pgp
new file mode 100755
index 0000000..4f3d339
--- /dev/null
+++ b/src/tests/data/keyrings/4/secring.pgp
Binary files differ
diff --git a/src/tests/data/keyrings/5/pubring.gpg b/src/tests/data/keyrings/5/pubring.gpg
new file mode 100644
index 0000000..55cb6e1
--- /dev/null
+++ b/src/tests/data/keyrings/5/pubring.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/5/secring.gpg b/src/tests/data/keyrings/5/secring.gpg
new file mode 100644
index 0000000..cde0d78
--- /dev/null
+++ b/src/tests/data/keyrings/5/secring.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/6/pubring.gpg b/src/tests/data/keyrings/6/pubring.gpg
new file mode 100644
index 0000000..3da286d
--- /dev/null
+++ b/src/tests/data/keyrings/6/pubring.gpg
Binary files differ
diff --git a/src/tests/data/keyrings/6/secring.gpg b/src/tests/data/keyrings/6/secring.gpg
new file mode 100644
index 0000000..2ba81dd
--- /dev/null
+++ b/src/tests/data/keyrings/6/secring.gpg
Binary files differ
diff --git a/src/tests/data/test_cli/hello.txt b/src/tests/data/test_cli/hello.txt
new file mode 100644
index 0000000..d2e8786
--- /dev/null
+++ b/src/tests/data/test_cli/hello.txt
@@ -0,0 +1,2 @@
+Hello world!
+This is a sample file to use for signing.
diff --git a/src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.key b/src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.key
new file mode 100644
index 0000000..04e5007
--- /dev/null
+++ b/src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.key
Binary files differ
diff --git a/src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.key b/src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.key
new file mode 100644
index 0000000..96bea9b
--- /dev/null
+++ b/src/tests/data/test_cli_g10_defkey/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.key
Binary files differ
diff --git a/src/tests/data/test_cli_g10_defkey/g10/pubring.kbx b/src/tests/data/test_cli_g10_defkey/g10/pubring.kbx
new file mode 100644
index 0000000..b9bd49f
--- /dev/null
+++ b/src/tests/data/test_cli_g10_defkey/g10/pubring.kbx
Binary files differ
diff --git a/src/tests/data/test_cli_rnpkeys/g10_list_keys b/src/tests/data/test_cli_rnpkeys/g10_list_keys
new file mode 100644
index 0000000..c4c4b07
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/g10_list_keys
@@ -0,0 +1,72 @@
+23 keys found
+
+pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+pub 255/EdDSA cc786278981b0728 2018-04-03 [SC]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+
+pub 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+sub 256/ECDH 37e285e9e9851491 2018-04-03 [E]
+ 40e608afbc8d62cdcc08904f37e285e9e9851491
+
+pub 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC]
+ ab25cba042dd924c3acc3ed3242a3aa5ea85f44a
+uid ecc-p384
+sub 384/ECDH e210e3d554a4fad9 2018-04-03 [E]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+
+pub 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC]
+ 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a
+uid ecc-p521
+sub 521/ECDH 9853df2f6d297442 2018-04-03 [E]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
+pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E]
+ 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e
+
+pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+uid ecc-bp256
+sub 256/ECDH 2edabb94d3055f76 2018-04-03 [E]
+ 08192b478f740360b74c82cc2edabb94d3055f76
+
+pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SC]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384
+sub 384/ECDH cff1bb6f16d28191 2018-04-03 [E]
+ 76969ce7033d990931df92b2cff1bb6f16d28191
+
+pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SC]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+uid ecc-bp512
+sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E]
+ 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce
+
+pub 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC]
+ 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5
+uid eddsa-x25519
+sub 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+
+pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+sub 256/ECDH 7635401f90d3e533 2018-04-03 [E]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+sub 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+
diff --git a/src/tests/data/test_cli_rnpkeys/g10_list_keys_no_bp b/src/tests/data/test_cli_rnpkeys/g10_list_keys_no_bp
new file mode 100644
index 0000000..0632260
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/g10_list_keys_no_bp
@@ -0,0 +1,72 @@
+23 keys found
+
+pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+pub 255/EdDSA cc786278981b0728 2018-04-03 [SC]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+
+pub 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+sub 256/ECDH 37e285e9e9851491 2018-04-03 [E]
+ 40e608afbc8d62cdcc08904f37e285e9e9851491
+
+pub 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC]
+ ab25cba042dd924c3acc3ed3242a3aa5ea85f44a
+uid ecc-p384
+sub 384/ECDH e210e3d554a4fad9 2018-04-03 [E]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+
+pub 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC]
+ 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a
+uid ecc-p521
+sub 521/ECDH 9853df2f6d297442 2018-04-03 [E]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
+pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E]
+ 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e
+
+pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [INVALID]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+uid ecc-bp256 [INVALID]
+sub 256/ECDH 2edabb94d3055f76 2018-04-03 [INVALID]
+ 08192b478f740360b74c82cc2edabb94d3055f76
+
+pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [INVALID]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384 [INVALID]
+sub 384/ECDH cff1bb6f16d28191 2018-04-03 [INVALID]
+ 76969ce7033d990931df92b2cff1bb6f16d28191
+
+pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [INVALID]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+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
+sub 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+
+pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+sub 256/ECDH 7635401f90d3e533 2018-04-03 [E]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+sub 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+
diff --git a/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec b/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec
new file mode 100644
index 0000000..9cc47c0
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec
@@ -0,0 +1,72 @@
+23 keys found
+
+sec 2048/DSA c8a10a7d78273e10 2018-04-03 [SC]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+ssb 3072/ElGamal 02a5715c3537717e 2018-04-03 [E]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+sec 255/EdDSA cc786278981b0728 2018-04-03 [SC]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+
+sec 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+ssb 256/ECDH 37e285e9e9851491 2018-04-03 [E]
+ 40e608afbc8d62cdcc08904f37e285e9e9851491
+
+sec 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC]
+ ab25cba042dd924c3acc3ed3242a3aa5ea85f44a
+uid ecc-p384
+ssb 384/ECDH e210e3d554a4fad9 2018-04-03 [E]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+
+sec 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC]
+ 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a
+uid ecc-p521
+ssb 521/ECDH 9853df2f6d297442 2018-04-03 [E]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
+sec 3072/RSA 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+ssb 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E]
+ 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e
+
+sec 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+uid ecc-bp256
+ssb 256/ECDH 2edabb94d3055f76 2018-04-03 [E]
+ 08192b478f740360b74c82cc2edabb94d3055f76
+
+sec 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SC]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384
+ssb 384/ECDH cff1bb6f16d28191 2018-04-03 [E]
+ 76969ce7033d990931df92b2cff1bb6f16d28191
+
+sec 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SC]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+uid ecc-bp512
+ssb 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E]
+ 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce
+
+sec 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC]
+ 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5
+uid eddsa-x25519
+ssb 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+
+sec 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+ssb 256/ECDH 7635401f90d3e533 2018-04-03 [E]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+sec 2048/RSA bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+ssb 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+
diff --git a/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_bp b/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_bp
new file mode 100644
index 0000000..a2eb7c5
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/g10_list_keys_sec_no_bp
@@ -0,0 +1,72 @@
+23 keys found
+
+sec 2048/DSA c8a10a7d78273e10 2018-04-03 [SC]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+ssb 3072/ElGamal 02a5715c3537717e 2018-04-03 [E]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+sec 255/EdDSA cc786278981b0728 2018-04-03 [SC]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+
+sec 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+ssb 256/ECDH 37e285e9e9851491 2018-04-03 [E]
+ 40e608afbc8d62cdcc08904f37e285e9e9851491
+
+sec 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC]
+ ab25cba042dd924c3acc3ed3242a3aa5ea85f44a
+uid ecc-p384
+ssb 384/ECDH e210e3d554a4fad9 2018-04-03 [E]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+
+sec 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC]
+ 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a
+uid ecc-p521
+ssb 521/ECDH 9853df2f6d297442 2018-04-03 [E]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
+sec 3072/RSA 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+ssb 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E]
+ 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e
+
+sec 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [INVALID]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+uid ecc-bp256 [INVALID]
+ssb 256/ECDH 2edabb94d3055f76 2018-04-03 [INVALID]
+ 08192b478f740360b74c82cc2edabb94d3055f76
+
+sec 384/ECDSA 6cf2dce85599ada2 2018-04-03 [INVALID]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384 [INVALID]
+ssb 384/ECDH cff1bb6f16d28191 2018-04-03 [INVALID]
+ 76969ce7033d990931df92b2cff1bb6f16d28191
+
+sec 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [INVALID]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+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
+ssb 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+
+sec 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+ssb 256/ECDH 7635401f90d3e533 2018-04-03 [E]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+sec 2048/RSA bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+ssb 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+
diff --git a/src/tests/data/test_cli_rnpkeys/getkey_00000000 b/src/tests/data/test_cli_rnpkeys/getkey_00000000
new file mode 100644
index 0000000..570c3b2
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_00000000
@@ -0,0 +1 @@
+Key(s) not found.
diff --git a/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb
new file mode 100644
index 0000000..09f192d
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb
@@ -0,0 +1,12 @@
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec
new file mode 100644
index 0000000..d69e11d
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec
@@ -0,0 +1,12 @@
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec_y2k38 b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec_y2k38
new file mode 100644
index 0000000..849e9d2
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sec_y2k38
@@ -0,0 +1,12 @@
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig
new file mode 100644
index 0000000..47a9277
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig
@@ -0,0 +1,17 @@
+3 keys found
+
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig_y2k38 b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig_y2k38
new file mode 100644
index 0000000..c9db774
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_sig_y2k38
@@ -0,0 +1,17 @@
+3 keys found
+
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_y2k38 b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_y2k38
new file mode 100644
index 0000000..b82d8ec
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_2fcadf05ffa501bb_y2k38
@@ -0,0 +1,12 @@
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/getkey_zzzzzzzz b/src/tests/data/test_cli_rnpkeys/getkey_zzzzzzzz
new file mode 100644
index 0000000..570c3b2
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/getkey_zzzzzzzz
@@ -0,0 +1 @@
+Key(s) not found.
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys
new file mode 100644
index 0000000..686501b
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys
@@ -0,0 +1,24 @@
+7 keys found
+
+pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC]
+ e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a
+uid key0-uid0
+uid key0-uid1
+uid key0-uid2
+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 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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec
new file mode 100644
index 0000000..e6e4c0f
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec
@@ -0,0 +1,24 @@
+7 keys found
+
+sec 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC]
+ e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a
+uid key0-uid0
+uid key0-uid1
+uid key0-uid2
+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 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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k38 b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k38
new file mode 100644
index 0000000..197714b
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_sec_y2k38
@@ -0,0 +1,24 @@
+7 keys found
+
+sec 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC]
+ e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a
+uid key0-uid0
+uid key0-uid1
+uid key0-uid2
+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 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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+ssb 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k38 b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k38
new file mode 100644
index 0000000..dae5347
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_keys_y2k38
@@ -0,0 +1,24 @@
+7 keys found
+
+pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC]
+ e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a
+uid key0-uid0
+uid key0-uid1
+uid key0-uid2
+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 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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+sub 1024/ElGamal 326ef111425d14a5 2017-07-20 [E]
+ 57f8ed6e5c197db63c60ffaf326ef111425d14a5
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs
new file mode 100644
index 0000000..eeb0055
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs
@@ -0,0 +1,35 @@
+7 keys found
+
+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 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
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec
new file mode 100644
index 0000000..a93d26e
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec
@@ -0,0 +1,35 @@
+7 keys found
+
+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 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
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k38 b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k38
new file mode 100644
index 0000000..f10150b
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_sec_y2k38
@@ -0,0 +1,35 @@
+7 keys found
+
+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 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
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k38 b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k38
new file mode 100644
index 0000000..15b0dae
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_1_list_sigs_y2k38
@@ -0,0 +1,35 @@
+7 keys found
+
+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 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
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/keyring_2_list_keys b/src/tests/data/test_cli_rnpkeys/keyring_2_list_keys
new file mode 100644
index 0000000..3709223
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_2_list_keys
@@ -0,0 +1,6 @@
+1 key found
+
+pub 888/RSA dc70c124a50283f1 2001-11-08 [ESCA]
+ c80aa54aa5c6ac73a373687134abe4bd
+uid pgp2.6.3-test-key
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs b/src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs
new file mode 100644
index 0000000..e6d0aff
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_2_list_sigs
@@ -0,0 +1,7 @@
+1 key found
+
+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/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys b/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys
new file mode 100644
index 0000000..7772579
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys
@@ -0,0 +1,8 @@
+2 keys found
+
+pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES 2069-09-28]
+ 4f2e62b74e6a4cd333bc19004be147bb22df1e60
+uid test1
+sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES 2069-09-28]
+ 10793e367ee867c32e358f2aa49bae05c16e8bc8
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k38 b/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k38
new file mode 100644
index 0000000..f4b7e1a
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_3_list_keys_y2k38
@@ -0,0 +1,8 @@
+2 keys found
+
+pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES >=2038-01-19]
+ 4f2e62b74e6a4cd333bc19004be147bb22df1e60
+uid test1
+sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES >=2038-01-19]
+ 10793e367ee867c32e358f2aa49bae05c16e8bc8
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs b/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs
new file mode 100644
index 0000000..b67ec9c
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs
@@ -0,0 +1,10 @@
+2 keys found
+
+pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES 2069-09-28]
+ 4f2e62b74e6a4cd333bc19004be147bb22df1e60
+uid test1
+sig 4be147bb22df1e60 2019-10-11 test1
+sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES 2069-09-28]
+ 10793e367ee867c32e358f2aa49bae05c16e8bc8
+sig 4be147bb22df1e60 2019-10-11 test1
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k38 b/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k38
new file mode 100644
index 0000000..02a80b8
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_3_list_sigs_y2k38
@@ -0,0 +1,10 @@
+2 keys found
+
+pub 2048/RSA 4be147bb22df1e60 2017-09-30 [SC] [EXPIRES >=2038-01-19]
+ 4f2e62b74e6a4cd333bc19004be147bb22df1e60
+uid test1
+sig 4be147bb22df1e60 2019-10-11 test1
+sub 2048/RSA a49bae05c16e8bc8 2017-09-30 [E] [EXPIRES >=2038-01-19]
+ 10793e367ee867c32e358f2aa49bae05c16e8bc8
+sig 4be147bb22df1e60 2019-10-11 test1
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_5_list_keys b/src/tests/data/test_cli_rnpkeys/keyring_5_list_keys
new file mode 100644
index 0000000..bee0e12
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_5_list_keys
@@ -0,0 +1,8 @@
+2 keys found
+
+pub 256/ECDSA 0e33fd46ff10f19c 2017-11-22 [SC]
+ b6b5e497a177551ecb8862200e33fd46ff10f19c
+uid test0
+sub 256/ECDH 074131bc8d16c5c9 2017-11-22 [E]
+ 481e6a41b10ecd71a477db02074131bc8d16c5c9
+
diff --git a/src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs b/src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs
new file mode 100644
index 0000000..6ff4929
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/keyring_5_list_sigs
@@ -0,0 +1,10 @@
+2 keys found
+
+pub 256/ECDSA 0e33fd46ff10f19c 2017-11-22 [SC]
+ 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/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt b/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt
new file mode 100644
index 0000000..9c0ab4f
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt
@@ -0,0 +1,34 @@
+7 keys found
+
+pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC]
+ e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a
+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 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
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES 2083-05-11]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt_y2k38 b/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt_y2k38
new file mode 100644
index 0000000..c5193ba
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt_y2k38
@@ -0,0 +1,34 @@
+7 keys found
+
+pub 1024/RSA 7bc6709b15c23a4a 2017-07-20 [SC]
+ e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a
+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 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
+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 54505a936a4a970e 2017-07-20 [E] [EXPIRES >=2038-01-19]
+ a3e94de61a8cb229413d348e54505a936a4a970e
+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/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys
new file mode 100644
index 0000000..c4c4b07
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys
@@ -0,0 +1,72 @@
+23 keys found
+
+pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+pub 255/EdDSA cc786278981b0728 2018-04-03 [SC]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+
+pub 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+sub 256/ECDH 37e285e9e9851491 2018-04-03 [E]
+ 40e608afbc8d62cdcc08904f37e285e9e9851491
+
+pub 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC]
+ ab25cba042dd924c3acc3ed3242a3aa5ea85f44a
+uid ecc-p384
+sub 384/ECDH e210e3d554a4fad9 2018-04-03 [E]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+
+pub 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC]
+ 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a
+uid ecc-p521
+sub 521/ECDH 9853df2f6d297442 2018-04-03 [E]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
+pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E]
+ 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e
+
+pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+uid ecc-bp256
+sub 256/ECDH 2edabb94d3055f76 2018-04-03 [E]
+ 08192b478f740360b74c82cc2edabb94d3055f76
+
+pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SC]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384
+sub 384/ECDH cff1bb6f16d28191 2018-04-03 [E]
+ 76969ce7033d990931df92b2cff1bb6f16d28191
+
+pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SC]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+uid ecc-bp512
+sub 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E]
+ 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce
+
+pub 255/EdDSA 941822a0fc1b30a5 2018-10-15 [SC]
+ 4c9738a6f2be4e1a796c9b7b941822a0fc1b30a5
+uid eddsa-x25519
+sub 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+
+pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+sub 256/ECDH 7635401f90d3e533 2018-04-03 [E]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+sub 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+
diff --git a/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_bp b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_bp
new file mode 100644
index 0000000..0632260
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_no_bp
@@ -0,0 +1,72 @@
+23 keys found
+
+pub 2048/DSA c8a10a7d78273e10 2018-04-03 [SC]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+sub 3072/ElGamal 02a5715c3537717e 2018-04-03 [E]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+pub 255/EdDSA cc786278981b0728 2018-04-03 [SC]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+
+pub 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+sub 256/ECDH 37e285e9e9851491 2018-04-03 [E]
+ 40e608afbc8d62cdcc08904f37e285e9e9851491
+
+pub 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC]
+ ab25cba042dd924c3acc3ed3242a3aa5ea85f44a
+uid ecc-p384
+sub 384/ECDH e210e3d554a4fad9 2018-04-03 [E]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+
+pub 521/ECDSA 2092ca8324263b6a 2018-04-03 [SC]
+ 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a
+uid ecc-p521
+sub 521/ECDH 9853df2f6d297442 2018-04-03 [E]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
+pub 3072/RSA 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+sub 3072/RSA 6e2f73008f8b8d6e 2018-04-03 [E]
+ 20fe5b1ab68c2d7210fb08aa6e2f73008f8b8d6e
+
+pub 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [INVALID]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+uid ecc-bp256 [INVALID]
+sub 256/ECDH 2edabb94d3055f76 2018-04-03 [INVALID]
+ 08192b478f740360b74c82cc2edabb94d3055f76
+
+pub 384/ECDSA 6cf2dce85599ada2 2018-04-03 [INVALID]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384 [INVALID]
+sub 384/ECDH cff1bb6f16d28191 2018-04-03 [INVALID]
+ 76969ce7033d990931df92b2cff1bb6f16d28191
+
+pub 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [INVALID]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+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
+sub 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+
+pub 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+sub 256/ECDH 7635401f90d3e533 2018-04-03 [E]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+pub 2048/RSA bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+sub 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+
diff --git a/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec
new file mode 100644
index 0000000..d30584b
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_keys_sec
@@ -0,0 +1,66 @@
+21 keys found
+
+sec 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384
+ssb 256/ECDH 7635401f90d3e533 2018-04-03 [E] [EXPIRES 2019-01-28]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+sec 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+
+sec 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+
+sec 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+uid ecc-bp512
+ssb 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+ssb 384/ECDH e210e3d554a4fad9 2018-04-03 [E] [EXPIRES 2019-01-28]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+ssb 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E] [EXPIRES 2019-01-28]
+ 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce
+
+sec 255/EdDSA cc786278981b0728 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+
+sec 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+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 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 02a5715c3537717e 2018-04-03 [E] [EXPIRES 2019-01-28]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+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
+uid ecc-p521
+
+sec 256/ECDSA 23674f21b2441527 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+ssb 521/ECDH 9853df2f6d297442 2018-04-03 [E] [EXPIRES 2019-01-28]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
diff --git a/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs
new file mode 100644
index 0000000..cc7dcc1
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs
@@ -0,0 +1,95 @@
+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 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
+
+pub 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ 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 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+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 bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2
+sub 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2
+
diff --git a/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_bp b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_bp
new file mode 100644
index 0000000..8581e0a
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_no_bp
@@ -0,0 +1,95 @@
+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 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
+
+pub 256/ECDSA 23674f21b2441527 2018-04-03 [SC]
+ 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 2fb9179118898e8b 2018-04-03 [SC]
+ 6bc04a5a3ddb35766b9a40d82fb9179118898e8b
+uid rsa-rsa
+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 [INVALID]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+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 [INVALID]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+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 [INVALID]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+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 bd860a52d1899c0f 2021-12-24 [SC]
+ 5aa9362aea07de23a726762cbd860a52d1899c0f
+uid rsa-rsa-2
+sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2
+sub 2048/RSA 8e08d46a37414996 2021-12-24 [E]
+ ca3e4420cf3d3b62d9ee7c6e8e08d46a37414996
+sig bd860a52d1899c0f 2021-12-24 rsa-rsa-2
+
diff --git a/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec
new file mode 100644
index 0000000..7d66df0
--- /dev/null
+++ b/src/tests/data/test_cli_rnpkeys/test_stream_key_load_sigs_sec
@@ -0,0 +1,77 @@
+21 keys found
+
+sec 384/ECDSA 6cf2dce85599ada2 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 5b8a254c823ced98decd10ed6cf2dce85599ada2
+uid ecc-bp384
+sig 6cf2dce85599ada2 2018-04-03 ecc-bp384
+ssb 256/ECDH 7635401f90d3e533 2018-04-03 [E] [EXPIRES 2019-01-28]
+ c263ec4ce2b3772746ed53227635401f90d3e533
+
+sec 256/ECDSA 3ea5bb6f9692c1a0 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 81f772b57d4ebfe7000a66233ea5bb6f9692c1a0
+uid ecc-p256k1
+sig 3ea5bb6f9692c1a0 2018-04-03 ecc-p256k1
+
+sec 2048/DSA c8a10a7d78273e10 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 091c44ce9cfbc3ff7ec7a64dc8a10a7d78273e10
+uid dsa-eg
+sig c8a10a7d78273e10 2018-04-03 dsa-eg
+
+sec 512/ECDSA aa5c58d14f7b8f48 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 4c59ab9272aa6a1f60b85bd0aa5c58d14f7b8f48
+uid ecc-bp512
+sig aa5c58d14f7b8f48 2018-04-03 ecc-bp512
+ssb 255/ECDH c711187e594376af 2018-10-15 [E]
+ cfdb2a1f8325cc949ce0b597c711187e594376af
+ssb 384/ECDH e210e3d554a4fad9 2018-04-03 [E] [EXPIRES 2019-01-28]
+ cbc2ac55dcd8e4e34fb2f816e210e3d554a4fad9
+ssb 512/ECDH 20cdaa1482ba79ce 2018-04-03 [E] [EXPIRES 2019-01-28]
+ 270a7cd0dc6c2e01dce8603620cdaa1482ba79ce
+
+sec 255/EdDSA cc786278981b0728 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 21fc68274aae3b5de39a4277cc786278981b0728
+uid ecc-25519
+sig cc786278981b0728 2018-04-03 ecc-25519
+
+sec 256/ECDSA d0c8a3daf9e0634a 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 0633c5f72a198f51e650e4abd0c8a3daf9e0634a
+uid ecc-bp256
+sig d0c8a3daf9e0634a 2018-04-03 ecc-bp256
+
+sec 384/ECDSA 242a3aa5ea85f44a 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ 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 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 02a5715c3537717e 2018-04-03 [E] [EXPIRES 2019-01-28]
+ 3409f96f0c57242540702dba02a5715c3537717e
+
+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]
+ 4fb39ff6fa4857a4bd7ef5b42092ca8324263b6a
+uid ecc-p521
+sig 2092ca8324263b6a 2018-04-03 ecc-p521
+
+sec 256/ECDSA 23674f21b2441527 2018-04-03 [SC] [EXPIRES 2019-01-28]
+ b54fdebbb673423a5d0aa54423674f21b2441527
+uid ecc-p256
+sig 23674f21b2441527 2018-04-03 ecc-p256
+ssb 521/ECDH 9853df2f6d297442 2018-04-03 [E] [EXPIRES 2019-01-28]
+ a9297c86dd0de109e1ebae9c9853df2f6d297442
+
diff --git a/src/tests/data/test_ffi_json/generate-bad-pk-alg.json b/src/tests/data/test_ffi_json/generate-bad-pk-alg.json
new file mode 100644
index 0000000..dded359
--- /dev/null
+++ b/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/src/tests/data/test_ffi_json/generate-eddsa-wrong-prefs.json b/src/tests/data/test_ffi_json/generate-eddsa-wrong-prefs.json
new file mode 100644
index 0000000..5fb0292
--- /dev/null
+++ b/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/src/tests/data/test_ffi_json/generate-pair-dsa-elg.json b/src/tests/data/test_ffi_json/generate-pair-dsa-elg.json
new file mode 100644
index 0000000..2df60c4
--- /dev/null
+++ b/src/tests/data/test_ffi_json/generate-pair-dsa-elg.json
@@ -0,0 +1,11 @@
+{
+ "primary": {
+ "type": "DSA",
+ "length": 1024,
+ "userid": "test0"
+ },
+ "sub": {
+ "type": "Elgamal",
+ "length": 1536
+ }
+}
diff --git a/src/tests/data/test_ffi_json/generate-pair.json b/src/tests/data/test_ffi_json/generate-pair.json
new file mode 100644
index 0000000..0fbd14e
--- /dev/null
+++ b/src/tests/data/test_ffi_json/generate-pair.json
@@ -0,0 +1,25 @@
+{
+ "primary": {
+ "type": "ECDSA",
+ "curve": "NIST P-256",
+ "userid": "test0",
+ "usage": "sign",
+ "expiration": 0,
+ "hash": "SHA256",
+ "preferences" : {
+ "hashes": ["SHA512", "SHA256"],
+ "ciphers": ["AES256", "AES128"],
+ "compression": ["Zlib"],
+ "key server": "hkp://pgp.mit.edu"
+ },
+ "protection" : {
+ "cipher": "AES256",
+ "hash": "SHA256",
+ "iterations": 65536
+ }
+ },
+ "sub": {
+ "type": "RSA",
+ "length": 1024
+ }
+}
diff --git a/src/tests/data/test_ffi_json/generate-primary.json b/src/tests/data/test_ffi_json/generate-primary.json
new file mode 100644
index 0000000..2d6b077
--- /dev/null
+++ b/src/tests/data/test_ffi_json/generate-primary.json
@@ -0,0 +1,15 @@
+{
+ "primary": {
+ "type": "ECDSA",
+ "curve": "NIST P-256",
+ "userid": "test0",
+ "usage": ["sign"],
+ "expiration": 0,
+ "preferences" : {
+ "hashes": ["SHA512", "SHA256"],
+ "ciphers": ["AES256", "AES128"],
+ "compression": ["Zlib"],
+ "key server": "hkp://pgp.mit.edu"
+ }
+ }
+}
diff --git a/src/tests/data/test_ffi_json/generate-sub.json b/src/tests/data/test_ffi_json/generate-sub.json
new file mode 100644
index 0000000..549647e
--- /dev/null
+++ b/src/tests/data/test_ffi_json/generate-sub.json
@@ -0,0 +1,11 @@
+{
+ "sub": {
+ "primary": {
+ "grip": "PLACEHOLDER"
+ },
+ "type": "RSA",
+ "length": 1024,
+ "usage": ["encrypt"],
+ "hash": "SHA256"
+ }
+}
diff --git a/src/tests/data/test_forged_keys/dsa-eg-pub-forged-key.pgp b/src/tests/data/test_forged_keys/dsa-eg-pub-forged-key.pgp
new file mode 100644
index 0000000..1f05351
--- /dev/null
+++ b/src/tests/data/test_forged_keys/dsa-eg-pub-forged-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/dsa-eg-pub-forged-material.pgp b/src/tests/data/test_forged_keys/dsa-eg-pub-forged-material.pgp
new file mode 100644
index 0000000..78d5316
--- /dev/null
+++ b/src/tests/data/test_forged_keys/dsa-eg-pub-forged-material.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/dsa-eg-pub-forged-subkey.pgp b/src/tests/data/test_forged_keys/dsa-eg-pub-forged-subkey.pgp
new file mode 100644
index 0000000..406a39a
--- /dev/null
+++ b/src/tests/data/test_forged_keys/dsa-eg-pub-forged-subkey.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/dsa-eg-pub.pgp b/src/tests/data/test_forged_keys/dsa-eg-pub.pgp
new file mode 100644
index 0000000..a573094
--- /dev/null
+++ b/src/tests/data/test_forged_keys/dsa-eg-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-25519-pub-forged-key.pgp b/src/tests/data/test_forged_keys/ecc-25519-pub-forged-key.pgp
new file mode 100644
index 0000000..39b3ea0
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-25519-pub-forged-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-25519-pub-forged-material.pgp b/src/tests/data/test_forged_keys/ecc-25519-pub-forged-material.pgp
new file mode 100644
index 0000000..5ed9042
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-25519-pub-forged-material.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-25519-pub-future-cert-malf-bind.pgp b/src/tests/data/test_forged_keys/ecc-25519-pub-future-cert-malf-bind.pgp
new file mode 100644
index 0000000..f65fc45
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-25519-pub-future-cert-malf-bind.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-25519-pub-future-cert.pgp b/src/tests/data/test_forged_keys/ecc-25519-pub-future-cert.pgp
new file mode 100644
index 0000000..b7967c1
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-25519-pub-future-cert.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-25519-pub.pgp b/src/tests/data/test_forged_keys/ecc-25519-pub.pgp
new file mode 100644
index 0000000..a9c18ca
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-25519-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-expired-key.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-expired-key.pgp
new file mode 100644
index 0000000..dabab38
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-expired-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-expired-subkey.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-expired-subkey.pgp
new file mode 100644
index 0000000..5dce619
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-expired-subkey.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-forged-key.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-forged-key.pgp
new file mode 100644
index 0000000..1d49e4d
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-forged-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-forged-material.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-forged-material.pgp
new file mode 100644
index 0000000..aa39f74
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-forged-material.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-forged-subkey.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-forged-subkey.pgp
new file mode 100644
index 0000000..373ad30
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-forged-subkey.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-no-binding.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-no-binding.pgp
new file mode 100644
index 0000000..1110b23
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-no-binding.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-no-cert-malf-binding.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-no-cert-malf-binding.pgp
new file mode 100644
index 0000000..511ae24
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-no-cert-malf-binding.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub-no-certification.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub-no-certification.pgp
new file mode 100644
index 0000000..d081b14
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub-no-certification.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-pub.pgp b/src/tests/data/test_forged_keys/ecc-p256-pub.pgp
new file mode 100644
index 0000000..7e627eb
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-sec-expired-key.pgp b/src/tests/data/test_forged_keys/ecc-p256-sec-expired-key.pgp
new file mode 100644
index 0000000..0106277
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-sec-expired-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-sec-expired-subkey.pgp b/src/tests/data/test_forged_keys/ecc-p256-sec-expired-subkey.pgp
new file mode 100644
index 0000000..c6df1eb
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-sec-expired-subkey.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/ecc-p256-sec.pgp b/src/tests/data/test_forged_keys/ecc-p256-sec.pgp
new file mode 100644
index 0000000..54839ac
--- /dev/null
+++ b/src/tests/data/test_forged_keys/ecc-p256-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/eddsa-2012-md5-pub.pgp b/src/tests/data/test_forged_keys/eddsa-2012-md5-pub.pgp
new file mode 100644
index 0000000..27144d4
--- /dev/null
+++ b/src/tests/data/test_forged_keys/eddsa-2012-md5-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/eddsa-2012-md5-sec.pgp b/src/tests/data/test_forged_keys/eddsa-2012-md5-sec.pgp
new file mode 100644
index 0000000..bcf0e1f
--- /dev/null
+++ b/src/tests/data/test_forged_keys/eddsa-2012-md5-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/eddsa-2024-pub.pgp b/src/tests/data/test_forged_keys/eddsa-2024-pub.pgp
new file mode 100644
index 0000000..fe8ab88
--- /dev/null
+++ b/src/tests/data/test_forged_keys/eddsa-2024-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/eddsa-2024-sec.pgp b/src/tests/data/test_forged_keys/eddsa-2024-sec.pgp
new file mode 100644
index 0000000..1b40ec3
--- /dev/null
+++ b/src/tests/data/test_forged_keys/eddsa-2024-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-key.pgp b/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-key.pgp
new file mode 100644
index 0000000..bb6cddb
--- /dev/null
+++ b/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-material.pgp b/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-material.pgp
new file mode 100644
index 0000000..d7dcf4c
--- /dev/null
+++ b/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-material.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-subkey.pgp b/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-subkey.pgp
new file mode 100644
index 0000000..7b89eb1
--- /dev/null
+++ b/src/tests/data/test_forged_keys/rsa-rsa-pub-forged-subkey.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/rsa-rsa-pub-future-key.pgp b/src/tests/data/test_forged_keys/rsa-rsa-pub-future-key.pgp
new file mode 100644
index 0000000..b736c46
--- /dev/null
+++ b/src/tests/data/test_forged_keys/rsa-rsa-pub-future-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/rsa-rsa-pub.pgp b/src/tests/data/test_forged_keys/rsa-rsa-pub.pgp
new file mode 100644
index 0000000..0ec631a
--- /dev/null
+++ b/src/tests/data/test_forged_keys/rsa-rsa-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/rsa-rsa-sec-future-key.pgp b/src/tests/data/test_forged_keys/rsa-rsa-sec-future-key.pgp
new file mode 100644
index 0000000..b760b9e
--- /dev/null
+++ b/src/tests/data/test_forged_keys/rsa-rsa-sec-future-key.pgp
Binary files differ
diff --git a/src/tests/data/test_forged_keys/rsa-rsa-sec.pgp b/src/tests/data/test_forged_keys/rsa-rsa-sec.pgp
new file mode 100644
index 0000000..40613b8
--- /dev/null
+++ b/src/tests/data/test_forged_keys/rsa-rsa-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_fuzz_dump/clusterfuzz-testcase-minimized-fuzz_dump-5757362284265472 b/src/tests/data/test_fuzz_dump/clusterfuzz-testcase-minimized-fuzz_dump-5757362284265472
new file mode 100644
index 0000000..3894f36
--- /dev/null
+++ b/src/tests/data/test_fuzz_dump/clusterfuzz-testcase-minimized-fuzz_dump-5757362284265472
Binary files differ
diff --git a/src/tests/data/test_fuzz_dump/outofmemory-5570076898623488 b/src/tests/data/test_fuzz_dump/outofmemory-5570076898623488
new file mode 100644
index 0000000..c3e9f29
--- /dev/null
+++ b/src/tests/data/test_fuzz_dump/outofmemory-5570076898623488
Binary files differ
diff --git a/src/tests/data/test_fuzz_dump/timeout-6462239459115008 b/src/tests/data/test_fuzz_dump/timeout-6462239459115008
new file mode 100644
index 0000000..82f7b18
--- /dev/null
+++ b/src/tests/data/test_fuzz_dump/timeout-6462239459115008
Binary files differ
diff --git a/src/tests/data/test_fuzz_dump/timeout-7e498daecad7ee646371a466d4a317c59fe7db89 b/src/tests/data/test_fuzz_dump/timeout-7e498daecad7ee646371a466d4a317c59fe7db89
new file mode 100644
index 0000000..bca84a6
--- /dev/null
+++ b/src/tests/data/test_fuzz_dump/timeout-7e498daecad7ee646371a466d4a317c59fe7db89
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyimport/crash_25f06f13b48d58a5faf6c36fae7fcbd958359199 b/src/tests/data/test_fuzz_keyimport/crash_25f06f13b48d58a5faf6c36fae7fcbd958359199
new file mode 100644
index 0000000..5f674c4
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyimport/crash_25f06f13b48d58a5faf6c36fae7fcbd958359199
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyimport/crash_37e8ed57ee47c1991b387fa0506f361f9cd9c663 b/src/tests/data/test_fuzz_keyimport/crash_37e8ed57ee47c1991b387fa0506f361f9cd9c663
new file mode 100644
index 0000000..98045a1
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyimport/crash_37e8ed57ee47c1991b387fa0506f361f9cd9c663
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyimport/crash_e932261875271ccf497715de56adf7caf30ca8a7 b/src/tests/data/test_fuzz_keyimport/crash_e932261875271ccf497715de56adf7caf30ca8a7
new file mode 100644
index 0000000..f959cb7
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyimport/crash_e932261875271ccf497715de56adf7caf30ca8a7
@@ -0,0 +1,3 @@
+¬b message.txt_»4This is test message to be signed, and/or encrypted, cleartext signed and detached signed.
+It will use keys from keyrings/1.
+End of message. \ No newline at end of file
diff --git a/src/tests/data/test_fuzz_keyimport/leak_11307b70cc609c93fc3a49d37f3a31166df50f44 b/src/tests/data/test_fuzz_keyimport/leak_11307b70cc609c93fc3a49d37f3a31166df50f44
new file mode 100644
index 0000000..951ce63
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyimport/leak_11307b70cc609c93fc3a49d37f3a31166df50f44
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyimport/leak_371b211d7e9cf9857befcf06c7da74835e249ee7 b/src/tests/data/test_fuzz_keyimport/leak_371b211d7e9cf9857befcf06c7da74835e249ee7
new file mode 100644
index 0000000..6118247
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyimport/leak_371b211d7e9cf9857befcf06c7da74835e249ee7
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyimport/timeout-9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051 b/src/tests/data/test_fuzz_keyimport/timeout-9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051
new file mode 100644
index 0000000..e42c641
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyimport/timeout-9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyimport/timeout_9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051 b/src/tests/data/test_fuzz_keyimport/timeout_9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051
new file mode 100644
index 0000000..bc8fffd
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyimport/timeout_9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring/crash-7ff10f10a95b78461d6f3578f5f99e870c792b9f b/src/tests/data/test_fuzz_keyring/crash-7ff10f10a95b78461d6f3578f5f99e870c792b9f
new file mode 100644
index 0000000..5c57ca5
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring/crash-7ff10f10a95b78461d6f3578f5f99e870c792b9f
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring/crash-8619144979e56d07ab4890bf564b90271ae9b1c9 b/src/tests/data/test_fuzz_keyring/crash-8619144979e56d07ab4890bf564b90271ae9b1c9
new file mode 100644
index 0000000..dada7b9
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring/crash-8619144979e56d07ab4890bf564b90271ae9b1c9
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring/leak-542d4e51506e3e9d34c9b243e608a964dabfdb21 b/src/tests/data/test_fuzz_keyring/leak-542d4e51506e3e9d34c9b243e608a964dabfdb21
new file mode 100755
index 0000000..a1b4f08
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring/leak-542d4e51506e3e9d34c9b243e608a964dabfdb21
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring/leak-5ee77f7ae99d7815d069afe037c42f4887193215 b/src/tests/data/test_fuzz_keyring/leak-5ee77f7ae99d7815d069afe037c42f4887193215
new file mode 100644
index 0000000..3b993ba
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring/leak-5ee77f7ae99d7815d069afe037c42f4887193215
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring/timeout-6140201111519232 b/src/tests/data/test_fuzz_keyring/timeout-6140201111519232
new file mode 100644
index 0000000..386ee60
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring/timeout-6140201111519232
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring_g10/crash_4ec166859e821aee27350dcde3e9c06b07a677f7 b/src/tests/data/test_fuzz_keyring_g10/crash_4ec166859e821aee27350dcde3e9c06b07a677f7
new file mode 100644
index 0000000..7741079
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring_g10/crash_4ec166859e821aee27350dcde3e9c06b07a677f7
@@ -0,0 +1 @@
+((1:r1:(1:r1:r)
diff --git a/src/tests/data/test_fuzz_keyring_g10/crash_5528625325932544 b/src/tests/data/test_fuzz_keyring_g10/crash_5528625325932544
new file mode 100644
index 0000000..d527e79
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring_g10/crash_5528625325932544
@@ -0,0 +1 @@
+(((ec{eye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweyeye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1ewe 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweyeweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1e 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweyeweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1ewe 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1ewe 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweyeweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1e 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweyeweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1ewe 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweyeweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1e 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweyeweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1ewe 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1e 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye ye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1ewe 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1ewe1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeweyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye0eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye 1eweyeye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeyeweyeye1eweye1eweyeye1eweye weyeye1eweweyeye1eweye1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1eweye1eweye1eweyeye1eweye weyeye1eweye 1eweyeye1eweye weyeye1eweye weyeye1eweye 1eweyeye1ewe 1eweyeye1ewe 1eweyeye1eweye1eweyeye1eweye we \ No newline at end of file
diff --git a/src/tests/data/test_fuzz_keyring_g10/crash_c9cabce6f8d7b36fde0306c86ce81c4f554cbd2a b/src/tests/data/test_fuzz_keyring_g10/crash_c9cabce6f8d7b36fde0306c86ce81c4f554cbd2a
new file mode 100644
index 0000000..89370a3
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring_g10/crash_c9cabce6f8d7b36fde0306c86ce81c4f554cbd2a
@@ -0,0 +1 @@
+(((2:h()
diff --git a/src/tests/data/test_fuzz_keyring_kbx/crash-5526a2e13255018c857ce493c28ce7108b8b2987 b/src/tests/data/test_fuzz_keyring_kbx/crash-5526a2e13255018c857ce493c28ce7108b8b2987
new file mode 100644
index 0000000..bee428a
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring_kbx/crash-5526a2e13255018c857ce493c28ce7108b8b2987
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring_kbx/crash-b894a2f79f7d38a16ae0ee8d74972336aa3f5798 b/src/tests/data/test_fuzz_keyring_kbx/crash-b894a2f79f7d38a16ae0ee8d74972336aa3f5798
new file mode 100644
index 0000000..95885af
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring_kbx/crash-b894a2f79f7d38a16ae0ee8d74972336aa3f5798
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring_kbx/leak-52c65c00b53997178f4cd9defa0343573ea8dda6 b/src/tests/data/test_fuzz_keyring_kbx/leak-52c65c00b53997178f4cd9defa0343573ea8dda6
new file mode 100644
index 0000000..822e398
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring_kbx/leak-52c65c00b53997178f4cd9defa0343573ea8dda6
Binary files differ
diff --git a/src/tests/data/test_fuzz_keyring_kbx/leak-b02cd1c6b70c10a8a673a34ba3770b39468b7ddf b/src/tests/data/test_fuzz_keyring_kbx/leak-b02cd1c6b70c10a8a673a34ba3770b39468b7ddf
new file mode 100644
index 0000000..ced77e4
--- /dev/null
+++ b/src/tests/data/test_fuzz_keyring_kbx/leak-b02cd1c6b70c10a8a673a34ba3770b39468b7ddf
Binary files differ
diff --git a/src/tests/data/test_fuzz_sigimport/timeout-821848a7b6b667fc41e5ff130415b3efd22ed118 b/src/tests/data/test_fuzz_sigimport/timeout-821848a7b6b667fc41e5ff130415b3efd22ed118
new file mode 100644
index 0000000..65f5e7c
--- /dev/null
+++ b/src/tests/data/test_fuzz_sigimport/timeout-821848a7b6b667fc41e5ff130415b3efd22ed118
Binary files differ
diff --git a/src/tests/data/test_fuzz_verify/timeout-25b8c9d824c8eb492c827689795748298a2b0a46 b/src/tests/data/test_fuzz_verify/timeout-25b8c9d824c8eb492c827689795748298a2b0a46
new file mode 100644
index 0000000..f82b83b
--- /dev/null
+++ b/src/tests/data/test_fuzz_verify/timeout-25b8c9d824c8eb492c827689795748298a2b0a46
Binary files differ
diff --git a/src/tests/data/test_fuzz_verify/timeout-5229070269153280 b/src/tests/data/test_fuzz_verify/timeout-5229070269153280
new file mode 100644
index 0000000..caf9b44
--- /dev/null
+++ b/src/tests/data/test_fuzz_verify/timeout-5229070269153280
Binary files differ
diff --git a/src/tests/data/test_fuzz_verify/timeout-6613852539453440 b/src/tests/data/test_fuzz_verify/timeout-6613852539453440
new file mode 100644
index 0000000..ccd44a0
--- /dev/null
+++ b/src/tests/data/test_fuzz_verify/timeout-6613852539453440
Binary files differ
diff --git a/src/tests/data/test_fuzz_verify/timeout-c2aff538c73b447bca689005e9762840b5a022d0 b/src/tests/data/test_fuzz_verify/timeout-c2aff538c73b447bca689005e9762840b5a022d0
new file mode 100644
index 0000000..46f0f46
--- /dev/null
+++ b/src/tests/data/test_fuzz_verify/timeout-c2aff538c73b447bca689005e9762840b5a022d0
Binary files differ
diff --git a/src/tests/data/test_fuzz_verify_detached/clusterfuzz-testcase-minimized-fuzz_verify_detached-5092660526972928 b/src/tests/data/test_fuzz_verify_detached/clusterfuzz-testcase-minimized-fuzz_verify_detached-5092660526972928
new file mode 100644
index 0000000..0a1a017
--- /dev/null
+++ b/src/tests/data/test_fuzz_verify_detached/clusterfuzz-testcase-minimized-fuzz_verify_detached-5092660526972928
Binary files differ
diff --git a/src/tests/data/test_fuzz_verify_detached/outofmemory-23094cb781b2cf6d1749ebac8bd0576e51440498-z b/src/tests/data/test_fuzz_verify_detached/outofmemory-23094cb781b2cf6d1749ebac8bd0576e51440498-z
new file mode 100644
index 0000000..e21d4e3
--- /dev/null
+++ b/src/tests/data/test_fuzz_verify_detached/outofmemory-23094cb781b2cf6d1749ebac8bd0576e51440498-z
Binary files differ
diff --git a/src/tests/data/test_fuzz_verify_detached/outofmemory-dea88a4aa4ab5fec1291446db702ee893d5559cf b/src/tests/data/test_fuzz_verify_detached/outofmemory-dea88a4aa4ab5fec1291446db702ee893d5559cf
new file mode 100644
index 0000000..0c598df
--- /dev/null
+++ b/src/tests/data/test_fuzz_verify_detached/outofmemory-dea88a4aa4ab5fec1291446db702ee893d5559cf
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-2-keys-same-grip.pgp b/src/tests/data/test_key_edge_cases/alice-2-keys-same-grip.pgp
new file mode 100644
index 0000000..6729aed
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-2-keys-same-grip.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-2-subs-same-grip.pgp b/src/tests/data/test_key_edge_cases/alice-2-subs-same-grip.pgp
new file mode 100644
index 0000000..1d312c8
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-2-subs-same-grip.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-3-uids-primary-boris.pgp b/src/tests/data/test_key_edge_cases/alice-3-uids-primary-boris.pgp
new file mode 100644
index 0000000..b8bfcbe
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-3-uids-primary-boris.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-3-uids-primary-expiring.pgp b/src/tests/data/test_key_edge_cases/alice-3-uids-primary-expiring.pgp
new file mode 100644
index 0000000..887f4ad
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-3-uids-primary-expiring.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-3-uids.pgp b/src/tests/data/test_key_edge_cases/alice-3-uids.pgp
new file mode 100644
index 0000000..7aed289
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-3-uids.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-rev-no-reason.pgp b/src/tests/data/test_key_edge_cases/alice-rev-no-reason.pgp
new file mode 100644
index 0000000..c166aa8
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-rev-no-reason.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-s2k-101-1-subs.pgp b/src/tests/data/test_key_edge_cases/alice-s2k-101-1-subs.pgp
new file mode 100644
index 0000000..f01b8f2
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-s2k-101-1-subs.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp b/src/tests/data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp
new file mode 100644
index 0000000..93e7b09
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-s2k-101-2-card.pgp b/src/tests/data/test_key_edge_cases/alice-s2k-101-2-card.pgp
new file mode 100644
index 0000000..ce751ef
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-s2k-101-2-card.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-s2k-101-3.pgp b/src/tests/data/test_key_edge_cases/alice-s2k-101-3.pgp
new file mode 100644
index 0000000..6510447
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-s2k-101-3.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-s2k-101-no-sign-sub.pgp b/src/tests/data/test_key_edge_cases/alice-s2k-101-no-sign-sub.pgp
new file mode 100644
index 0000000..b6872a0
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-s2k-101-no-sign-sub.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-s2k-101-unknown.pgp b/src/tests/data/test_key_edge_cases/alice-s2k-101-unknown.pgp
new file mode 100644
index 0000000..8bca1b0
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-s2k-101-unknown.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-sig-misc-values.pgp b/src/tests/data/test_key_edge_cases/alice-sig-misc-values.pgp
new file mode 100644
index 0000000..3be5047
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-sig-misc-values.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-sub-rev-no-reason.pgp b/src/tests/data/test_key_edge_cases/alice-sub-rev-no-reason.pgp
new file mode 100644
index 0000000..92743b9
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-sub-rev-no-reason.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-sub-sig-fp.pgp b/src/tests/data/test_key_edge_cases/alice-sub-sig-fp.pgp
new file mode 100644
index 0000000..739daed
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-sub-sig-fp.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-sub-sig-keyid.pgp b/src/tests/data/test_key_edge_cases/alice-sub-sig-keyid.pgp
new file mode 100644
index 0000000..09f4e82
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-sub-sig-keyid.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-uid-binding.pgp b/src/tests/data/test_key_edge_cases/alice-uid-binding.pgp
new file mode 100644
index 0000000..e083ff4
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-uid-binding.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/alice-wrong-mpi-bit-count.pgp b/src/tests/data/test_key_edge_cases/alice-wrong-mpi-bit-count.pgp
new file mode 100644
index 0000000..399fcb2
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/alice-wrong-mpi-bit-count.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec-prot.asc b/src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec-prot.asc
new file mode 100644
index 0000000..1dddea2
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec-prot.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xYYEYUh6kxYJKwYBBAHaRw8BAQdAko/IzElBsrhU/2QuryqTfQIzWq/EeTwfe3Wjf7h8Go/+CQMI
+HjyNcbEWswD9/h842EF8C4xGQz84EQzsW0YHOa1NkDqzVbY0WKctc2oxaHAM+7gN6yoIH9jl9oJH
+1Ceec1sUdExyrwh5fxdjucSFz6jkX80XZWRkc2EtMjU1MTktbm9uLXR3ZWFrZWTCiwQTFggAMxYh
+BN3g7lOcAX0r0/YEpTF2/BSGqiUoBQJhSHqTAhsDBQsJCAcCBhUICQoLAgUWAgMBAAAKCRAxdvwU
+hqolKOZjAQDu0eL2RSWpt+UWBZ2D336eayv1KayhUYXi6qd2ROjspQD/dSy58x+P9zZVDWa+vWyR
+6dAyahooGLQsmgbIFMRtCwrHiwRhSHqTEgorBgEEAZdVAQUBAQdAanGHiDbqQ2cjJ69cxior6tHg
+bMEEpnQKs3Yv0pKb+HsDAQgH/gkDCDYHyCxU90o5/HC1kV5DtPawPtr38yk4D8r+oVUw7+jIcg2o
+PCZbEsb2xBNEHDRc0aUX7htMuVonyg699WGl92WgCU6rgT70EdeKQjKik/bCeAQYFggAIBYhBN3g
+7lOcAX0r0/YEpTF2/BSGqiUoBQJhSHqTAhsMAAoJEDF2/BSGqiUoDPUBAIzClKCukREdWGtAxvH1
+JjUqYFVazFNc8fGoFMaWrRGbAP99B7YSNM/XQ3jrqmYRfdp+au69PAGWVtT4zI9CV67WDw==
+=Y4FB
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec.asc b/src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec.asc
new file mode 100644
index 0000000..ad4823d
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-25519-non-tweaked-sec.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xVgEYUh6kxYJKwYBBAHaRw8BAQdAko/IzElBsrhU/2QuryqTfQIzWq/EeTwfe3Wjf7h8Go8AAP9V
+pLY7sMkGlT9OporbTWHC4bxavj60oTVDLuB2CSINKA+pzRdlZGRzYS0yNTUxOS1ub24tdHdlYWtl
+ZMKLBBMWCAAzFiEE3eDuU5wBfSvT9gSlMXb8FIaqJSgFAmFIepMCGwMFCwkIBwIGFQgJCgsCBRYC
+AwEAAAoJEDF2/BSGqiUo5mMBAO7R4vZFJam35RYFnYPffp5rK/UprKFRheLqp3ZE6OylAP91LLnz
+H4/3NlUNZr69bJHp0DJqGigYtCyaBsgUxG0LCsddBGFIepMSCisGAQQBl1UBBQEBB0BqcYeINupD
+ZyMnr1zGKivq0eBswQSmdAqzdi/Skpv4ewMBCAcAAQCXobu+cYjidL6kUffBZu+jzS/GrF3sWw1k
+OrvZ5Jr6rBPZwngEGBYIACAWIQTd4O5TnAF9K9P2BKUxdvwUhqolKAUCYUh6kwIbDAAKCRAxdvwU
+hqolKAz1AQCMwpSgrpERHVhrQMbx9SY1KmBVWsxTXPHxqBTGlq0RmwD/fQe2EjTP10N466pmEX3a
+fmruvTwBllbU+MyPQleu1g8=
+=o9U3
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-25519-non-tweaked.asc b/src/tests/data/test_key_edge_cases/key-25519-non-tweaked.asc
new file mode 100644
index 0000000..511d00a
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-25519-non-tweaked.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xjMEYUh6kxYJKwYBBAHaRw8BAQdAko/IzElBsrhU/2QuryqTfQIzWq/EeTwfe3Wjf7h8Go/NF2Vk
+ZHNhLTI1NTE5LW5vbi10d2Vha2VkwosEExYIADMWIQTd4O5TnAF9K9P2BKUxdvwUhqolKAUCYUh6
+kwIbAwULCQgHAgYVCAkKCwIFFgIDAQAACgkQMXb8FIaqJSjmYwEA7tHi9kUlqbflFgWdg99+nmsr
+9SmsoVGF4uqndkTo7KUA/3UsufMfj/c2VQ1mvr1skenQMmoaKBi0LJoGyBTEbQsKzjgEYUh6kxIK
+KwYBBAGXVQEFAQEHQGpxh4g26kNnIyevXMYqK+rR4GzBBKZ0CrN2L9KSm/h7AwEIB8J4BBgWCAAg
+FiEE3eDuU5wBfSvT9gSlMXb8FIaqJSgFAmFIepMCGwwACgkQMXb8FIaqJSgM9QEAjMKUoK6RER1Y
+a0DG8fUmNSpgVVrMU1zx8agUxpatEZsA/30HthI0z9dDeOuqZhF92n5q7r08AZZW1PjMj0JXrtYP
+=wnec
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-25519-tweaked-sec.asc b/src/tests/data/test_key_edge_cases/key-25519-tweaked-sec.asc
new file mode 100644
index 0000000..c2ea018
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-25519-tweaked-sec.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xVgEYUh6kxYJKwYBBAHaRw8BAQdAko/IzElBsrhU/2QuryqTfQIzWq/EeTwfe3Wjf7h8Go8AAP9V
+pLY7sMkGlT9OporbTWHC4bxavj60oTVDLuB2CSINKA+pzRdlZGRzYS0yNTUxOS1ub24tdHdlYWtl
+ZMKLBBMWCAAzFiEE3eDuU5wBfSvT9gSlMXb8FIaqJSgFAmFIepMCGwMFCwkIBwIGFQgJCgsCBRYC
+AwEAAAoJEDF2/BSGqiUo5mMBAO7R4vZFJam35RYFnYPffp5rK/UprKFRheLqp3ZE6OylAP91LLnz
+H4/3NlUNZr69bJHp0DJqGigYtCyaBsgUxG0LCsddBGFIepMSCisGAQQBl1UBBQEBB0BqcYeINupD
+ZyMnr1zGKivq0eBswQSmdAqzdi/Skpv4ewMBCAcAAP9Xobu+cYjidL6kUffBZu+jzS/GrF3sWw1k
+OrvZ5Jr6qBSTwngEGBYIACAWIQTd4O5TnAF9K9P2BKUxdvwUhqolKAUCYUh6kwIbDAAKCRAxdvwU
+hqolKAz1AQCMwpSgrpERHVhrQMbx9SY1KmBVWsxTXPHxqBTGlq0RmwD/fQe2EjTP10N466pmEX3a
+fmruvTwBllbU+MyPQleu1g8=
+=uGwR
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-25519-tweaked-wrong-crc.asc b/src/tests/data/test_key_edge_cases/key-25519-tweaked-wrong-crc.asc
new file mode 100644
index 0000000..29bb4d9
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-25519-tweaked-wrong-crc.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xVgEYUh6kxYJKwYBBAHaRw8BAQdAko/IzElBsrhU/2QuryqTfQIzWq/EeTwfe3Wjf7h8Go8AAP9V
+pLY7sMkGlT9OporbTWHC4bxavj60oTVDLuB2CSINKA+pzRdlZGRzYS0yNTUxOS1ub24tdHdlYWtl
+ZMKLBBMWCAAzFiEE3eDuU5wBfSvT9gSlMXb8FIaqJSgFAmFIepMCGwMFCwkIBwIGFQgJCgsCBRYC
+AwEAAAoJEDF2/BSGqiUo5mMBAO7R4vZFJam35RYFnYPffp5rK/UprKFRheLqp3ZE6OylAP91LLnz
+H4/3NlUNZr69bJHp0DJqGigYtCyaBsgUxG0LCsddBGFIepMSCisGAQQBl1UBBQEBB0BqcYeINupD
+ZyMnr1zGKivq0eBswQSmdAqzdi/Skpv4ewMBCAcAAP5Xobu+cYjidL6kUffBZu+jzS/GrF3sWw1k
+OrvZ5Jr6qBPZwngEGBYIACAWIQTd4O5TnAF9K9P2BKUxdvwUhqolKAUCYUh6kwIbDAAKCRAxdvwU
+hqolKAz1AQCMwpSgrpERHVhrQMbx9SY1KmBVWsxTXPHxqBTGlq0RmwD/fQe2EjTP10N466pmEX3a
+fmruvTwBllbU+MyPQleu1g8=
+=Ys8t
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-binding-hash-alg.asc b/src/tests/data/test_key_edge_cases/key-binding-hash-alg.asc
new file mode 100644
index 0000000..bfb007a
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-binding-hash-alg.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xk8EXyLNuRMFK4EEAAoCAwQsMUQw2qWcpy7qVxRGe9uj1l3OY3JCHxRbeEqyWZURqX4FSlLmT6OE
+FrZm1sLzyLmgsUg+JKqwfnQC746zBlJ4zQtlY2RzYV9lY2RzYcKLBBMTCAAzFiEEWBgjYO/P/GYn
+Sxwn+Bowql3L0B4FAl8izbkCGwMFCwkIBwIGFQgJCgsCBRYCAwEAAAoJEPgaMKpdy9AeyFMA/2hG
+JUCM2Wvo4UlD3aIZ4uC6gCrfw+fSOTHGYfzfXjXEAP9L9A4Ta4unjDz4HxAtMqcIjpJWUSckgRdT
+u8MgoyEoe85SBF8izbkTCCqGSM49AwEHAgMEINvmlOrfx5zNrhwBtMOeQkc/vmhJKeQ9Mi2DXpES
+X+K/a5zMUZTpXwN9WIS/FJysIy8iFLGlxWaHY4c3XoP3XMLAGAQYEwgAIBYhBFgYI2Dvz/xmJ0sc
+J/gaMKpdy9AeBQJfIs25AhsDAGpfIAQZEwkABgUCXyLNuQAKCRDdcWUWpySXEVmOAQDVTiGo91Un
+EIMemRslEO1ka9ysSQTwcK+LFHHYNb0+PwD+IVBZ3wqYf3q5GCfCLT0tNyJfHK+mbehuZYH8YDZp
+2XMJEPgaMKpdy9Ae9TkA/3lYN4nl2QFyThuXaUXxry/0kV2vmhJVjcWWnpWX03+7AP965TSB1f+S
++l+TSKAYnz5mFrzus97S4v44oLxq/chJqQ==
+=r6q/
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-create-expiry-32bit.asc b/src/tests/data/test_key_edge_cases/key-create-expiry-32bit.asc
new file mode 100644
index 0000000..92bfc25
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-create-expiry-32bit.asc
@@ -0,0 +1,12 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xo0EYIlOsQEEAMGUTPiyg95XQjTIc7+m4zkGOwo41J+IcTZcQrANum6Gj47HPvkzxu4b9DDz96Z/
+p2mpKgnUivNxcS73HEMEAta/8q1qlmeYYHSuzCkENweBJd6bkRwP8E5AyUMRWhoYl8QbX6vj6jul
+USEm1V0wrM4e1Fpvg3xsQ8Y1YsLW6N4vABEBAAHNN1JTQSAoRW5jcnlwdCBvciBTaWduKSAxMDI0
+LWJpdCBrZXkgPG5pY2tvbGF5QGxvY2FsaG9zdD7CwA8EEwEIADkWIQRqkd4uhU/ORoUurrFg6snd
+8NmsnwUCYIlOsQUJn3axTgIbAwULCQgHAgYVCAkKCwIFFgIDAQAACgkQYOrJ3fDZrJ8e8gP7BApp
+lPY5ayOHFyr873uP6adcqnAc5yGNiIYr7Pe8PyO/JIBWceDGnSMJyFmURvmlKAyqkzb29xGt4ahh
+dYgFQUxJWTOWIMhMeX10MmlvIwavArMlyj2jQI9kvAZXtTXX0T0WZWH44W0y0VUlpd4SmqMz/Vr2
+TA3IBfb3TgTNNXM=
+=TgXI
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-critical-notations-sec.pgp b/src/tests/data/test_key_edge_cases/key-critical-notations-sec.pgp
new file mode 100644
index 0000000..828e807
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-critical-notations-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-critical-notations.pgp b/src/tests/data/test_key_edge_cases/key-critical-notations.pgp
new file mode 100644
index 0000000..a7a278d
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-critical-notations.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-eddsa-small-x-pub.asc b/src/tests/data/test_key_edge_cases/key-eddsa-small-x-pub.asc
new file mode 100644
index 0000000..e7c4915
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-eddsa-small-x-pub.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xjMEYVWOgxYJKwYBBAHaRw8BAQdAzJddic9PzthiDPOayDZvvVFl8Af6uqMkv04Hz8X8wMXNDWVk
+ZHNhX3NtYWxsX3jCiwQTFggAMxYhBCT7Uh0s1zK+6oyVgHvFW5vc424YBQJhVY6DAhsDBQsJCAcC
+BhUICQoLAgUWAgMBAAAKCRB7xVub3ONuGH/eAQCHhZTc7ySEITCaIDf31aZlQTdNnAuJFDVg9fzO
+zvoBkAD/dY5XG4PRpEjLSh1/VW0WGLgAcApD2g97VxTZdvFaWwXOOARhVY6DEgorBgEEAZdVAQUB
+AQdAOZW14cWUkEPNLMZzW1sUGoyAqLPKZ336iGxcFEdkJGEDAQgHwngEGBYIACAWIQQk+1IdLNcy
+vuqMlYB7xVub3ONuGAUCYVWOgwIbDAAKCRB7xVub3ONuGJ8rAQCyeCuKXRNVyu0J49xALC7nEWRS
+5osENDRRxLyein0iOAEAlzK6LHkWgDSE1p6z+5G6CiuUZO8Y0rsirPhQiEGqAQE=
+=oEKi
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-eddsa-small-x-sec.asc b/src/tests/data/test_key_edge_cases/key-eddsa-small-x-sec.asc
new file mode 100644
index 0000000..07fc842
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-eddsa-small-x-sec.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xYUEYVWOgxYJKwYBBAHaRw8BAQdAzJddic9PzthiDPOayDZvvVFl8Af6uqMkv04Hz8X8wMX+CQMI
+Yncs4llakFX/JaevHHflKYZ8qMxss6QihgBZUoRTfGULv4B9e/vGuSgRHw1lP3KwPYWPxjfq63d9
+gRtQq6JQuYS4KRmLAb/lftwJR3IezQ1lZGRzYV9zbWFsbF94wosEExYIADMWIQQk+1IdLNcyvuqM
+lYB7xVub3ONuGAUCYVWOgwIbAwULCQgHAgYVCAkKCwIFFgIDAQAACgkQe8Vbm9zjbhh/3gEAh4WU
+3O8khCEwmiA399WmZUE3TZwLiRQ1YPX8zs76AZAA/3WOVxuD0aRIy0odf1VtFhi4AHAKQ9oPe1cU
+2XbxWlsFx4sEYVWOgxIKKwYBBAGXVQEFAQEHQDmVteHFlJBDzSzGc1tbFBqMgKizymd9+ohsXBRH
+ZCRhAwEIB/4JAwjMjGN1bB1esf/tvNBzXzuctSz1WHPkMxwAYT1xe3hsxc9rncR2SpURFXWsQLeg
+7lHW/WvPSVo3bXWIQ27shrpbkhGQ+5E2OaXSmgCByEaawngEGBYIACAWIQQk+1IdLNcyvuqMlYB7
+xVub3ONuGAUCYVWOgwIbDAAKCRB7xVub3ONuGJ8rAQCyeCuKXRNVyu0J49xALC7nEWRS5osENDRR
+xLyein0iOAEAlzK6LHkWgDSE1p6z+5G6CiuUZO8Y0rsirPhQiEGqAQE=
+=65Pa
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-eg-4096-pub.pgp b/src/tests/data/test_key_edge_cases/key-eg-4096-pub.pgp
new file mode 100644
index 0000000..c7fd4ca
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-eg-4096-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-eg-4096-sec.pgp b/src/tests/data/test_key_edge_cases/key-eg-4096-sec.pgp
new file mode 100644
index 0000000..cf31e20
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-eg-4096-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-pub.pgp b/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-pub.pgp
new file mode 100644
index 0000000..eec55fb
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec-enc.pgp b/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec-enc.pgp
new file mode 100644
index 0000000..690405e
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec-enc.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec.pgp b/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec.pgp
new file mode 100644
index 0000000..b002930
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-eg-small-subgroup-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-empty-packets.pgp b/src/tests/data/test_key_edge_cases/key-empty-packets.pgp
new file mode 100644
index 0000000..4f4e84e
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-empty-packets.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-empty-packets.txt b/src/tests/data/test_key_edge_cases/key-empty-packets.txt
new file mode 100644
index 0000000..ee688b8
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-empty-packets.txt
@@ -0,0 +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/src/tests/data/test_key_edge_cases/key-empty-uid-raw.txt b/src/tests/data/test_key_edge_cases/key-empty-uid-raw.txt
new file mode 100644
index 0000000..0d213cf
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-empty-uid-raw.txt
@@ -0,0 +1,91 @@
+:off 0: packet header 0x9833 (tag 6, len 51)
+:off 2: packet contents (51 bytes)
+ 00000 | 04 5e c6 91 74 16 09 2b 06 01 04 01 da 47 0f 01 | .^..t..+.....G..
+ 00016 | 01 07 40 6b 11 04 c2 03 24 19 05 23 f9 b0 35 2b | ..@k....$..#..5+
+ 00032 | 1d 0a 94 82 d7 96 30 77 3b 78 7e 5a 53 61 f3 c6 | ......0w;x~ZSa..
+ 00048 | 84 56 e1 | .V.
+
+Public key packet
+ version: 4
+ creation time: 1590071668 (??? ??? ?? ??:??:?? ????)
+ public key algorithm: 22 (EdDSA)
+ public key material:
+ ecc p: 263 bits
+ ecc curve: Ed25519
+ keyid: 0xc972affd358bf887
+:off 53: packet header 0xb400 (tag 13, len 0)
+:off 55: packet contents (first 146 bytes)
+ 00000 | 88 90 04 13 16 08 00 38 16 21 04 75 3d 5b 94 7e | .......8.!.u=[.~
+ 00016 | 9a 2b 2e 01 14 7c 1f c9 72 af fd 35 8b f8 87 05 | .+...|..r..5....
+ 00032 | 02 5e c6 91 74 02 1b 03 05 0b 09 08 07 02 06 15 | .^..t...........
+ 00048 | 0a 09 08 0b 02 04 16 02 03 01 02 1e 01 02 17 80 | ................
+ 00064 | 00 0a 09 10 c9 72 af fd 35 8b f8 87 de c3 00 fa | .....r..5.......
+ 00080 | 02 b2 e8 10 20 19 c4 f2 60 b0 d1 6b 5e 50 83 21 | .... ...`..k^P.!
+ 00096 | b8 3a a4 cd c0 39 18 fa 66 23 29 e0 2b 09 90 fe | .:...9..f#).+...
+ 00112 | 01 00 cd 1f a9 5e 59 be ba c9 35 ce 45 76 2b 3a | .....^Y...5.Ev+:
+ 00128 | 4e 8c b8 7a 9f 06 77 15 dd e3 c2 d9 da e7 4e 5c | N..z..w.......N\
+ 00144 | b2 0f | ..
+
+UserID packet
+ id:
+:off 55: packet header 0x8890 (tag 2, len 144)
+:off 57: packet contents (144 bytes)
+ 00000 | 04 13 16 08 00 38 16 21 04 75 3d 5b 94 7e 9a 2b | .....8.!.u=[.~.+
+ 00016 | 2e 01 14 7c 1f c9 72 af fd 35 8b f8 87 05 02 5e | ...|..r..5.....^
+ 00032 | c6 91 74 02 1b 03 05 0b 09 08 07 02 06 15 0a 09 | ..t.............
+ 00048 | 08 0b 02 04 16 02 03 01 02 1e 01 02 17 80 00 0a | ................
+ 00064 | 09 10 c9 72 af fd 35 8b f8 87 de c3 00 fa 02 b2 | ...r..5.........
+ 00080 | e8 10 20 19 c4 f2 60 b0 d1 6b 5e 50 83 21 b8 3a | .. ...`..k^P.!.:
+ 00096 | a4 cd c0 39 18 fa 66 23 29 e0 2b 09 90 fe 01 00 | ...9..f#).+.....
+ 00112 | cd 1f a9 5e 59 be ba c9 35 ce 45 76 2b 3a 4e 8c | ...^Y...5.Ev+:N.
+ 00128 | b8 7a 9f 06 77 15 dd e3 c2 d9 da e7 4e 5c b2 0f | .z..w.......N\..
+
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 22 (EdDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 33, len 21
+ :subpacket contents:
+ 00000 | 04 75 3d 5b 94 7e 9a 2b 2e 01 14 7c 1f c9 72 af | .u=[.~.+...|..r.
+ 00016 | fd 35 8b f8 87 | .5...
+ issuer fingerprint: 0x753d5b947e9a2b2e01147c1fc972affd358bf887 (20 bytes)
+ :type 2, len 4
+ :subpacket contents:
+ 00000 | 5e c6 91 74 | ^..t
+ signature creation time: 1590071668 (??? ??? ?? ??:??:?? ????)
+ :type 27, len 1
+ :subpacket contents:
+ 00000 | 03 | .
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ :subpacket contents:
+ 00000 | 09 08 07 02 | ....
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 21, len 5
+ :subpacket contents:
+ 00000 | 0a 09 08 0b 02 | .....
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ :subpacket contents:
+ 00000 | 02 03 01 | ...
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ :subpacket contents:
+ 00000 | 01 | .
+ features: 0x01 ( mdc )
+ :type 23, len 1
+ :subpacket contents:
+ 00000 | 80 | .
+ key server preferences
+ no-modify: 1
+ unhashed subpackets:
+ :type 16, len 8
+ :subpacket contents:
+ 00000 | c9 72 af fd 35 8b f8 87 | .r..5...
+ issuer key ID: 0xc972affd358bf887
+ lbits: 0xdec3
+ signature material:
+ ecc r: 250 bits
+ ecc s: 256 bits
diff --git a/src/tests/data/test_key_edge_cases/key-empty-uid.json b/src/tests/data/test_key_edge_cases/key-empty-uid.json
new file mode 100644
index 0000000..c78bc9f
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-empty-uid.json
@@ -0,0 +1,170 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9833",
+ "length":51,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1590071668,
+ "algorithm":22,
+ "algorithm.str":"EdDSA",
+ "material":{
+ "p.bits":263,
+ "curve":"Ed25519"
+ },
+ "keyid":"c972affd358bf887"
+ },
+ {
+ "header":{
+ "offset":53,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b400",
+ "length":0,
+ "partial":false,
+ "indeterminate":false
+ },
+ "userid":""
+ },
+ {
+ "header":{
+ "offset":55,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8890",
+ "length":144,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":19,
+ "type.str":"Positive User ID certification",
+ "algorithm":22,
+ "algorithm.str":"EdDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"753d5b947e9a2b2e01147c1fc972affd358bf887"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1590071668
+ },
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":3,
+ "flags.str":[
+ "certify",
+ "sign"
+ ]
+ },
+ {
+ "type":11,
+ "type.str":"preferred symmetric algorithms",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 9,
+ 8,
+ 7,
+ 2
+ ],
+ "algorithms.str":[
+ "AES-256",
+ "AES-192",
+ "AES-128",
+ "TripleDES"
+ ]
+ },
+ {
+ "type":21,
+ "type.str":"preferred hash algorithms",
+ "length":5,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 10,
+ 9,
+ 8,
+ 11,
+ 2
+ ],
+ "algorithms.str":[
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA224",
+ "SHA1"
+ ]
+ },
+ {
+ "type":22,
+ "type.str":"preferred compression algorithms",
+ "length":3,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 3,
+ 1
+ ],
+ "algorithms.str":[
+ "ZLib",
+ "BZip2",
+ "ZIP"
+ ]
+ },
+ {
+ "type":30,
+ "type.str":"features",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "mdc":true,
+ "aead":false,
+ "v5 keys":false
+ },
+ {
+ "type":23,
+ "type.str":"key server preferences",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "no-modify":true
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"c972affd358bf887"
+ }
+ ],
+ "lbits":"dec3",
+ "material":{
+ "r.bits":250,
+ "s.bits":256
+ }
+ }
+]
diff --git a/src/tests/data/test_key_edge_cases/key-empty-uid.pgp b/src/tests/data/test_key_edge_cases/key-empty-uid.pgp
new file mode 100644
index 0000000..ad9876d
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-empty-uid.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-empty-uid.txt b/src/tests/data/test_key_edge_cases/key-empty-uid.txt
new file mode 100644
index 0000000..dcb94e9
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-empty-uid.txt
@@ -0,0 +1,43 @@
+:off 0: packet header 0x9833 (tag 6, len 51)
+Public key packet
+ version: 4
+ creation time: 1590071668 (??? ??? ?? ??:??:?? ????)
+ public key algorithm: 22 (EdDSA)
+ public key material:
+ ecc p: 263 bits
+ ecc curve: Ed25519
+ keyid: 0xc972affd358bf887
+:off 53: packet header 0xb400 (tag 13, len 0)
+UserID packet
+ id:
+:off 55: packet header 0x8890 (tag 2, len 144)
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 22 (EdDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 33, len 21
+ issuer fingerprint: 0x753d5b947e9a2b2e01147c1fc972affd358bf887 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1590071668 (??? ??? ?? ??:??:?? ????)
+ :type 27, len 1
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 21, len 5
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ features: 0x01 ( mdc )
+ :type 23, len 1
+ key server preferences
+ no-modify: 1
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0xc972affd358bf887
+ lbits: 0xdec3
+ signature material:
+ ecc r: 250 bits
+ ecc s: 256 bits
diff --git a/src/tests/data/test_key_edge_cases/key-expired-cert-direct.pgp b/src/tests/data/test_key_edge_cases/key-expired-cert-direct.pgp
new file mode 100644
index 0000000..e86c1cc
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-expired-cert-direct.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-malf-sig.json b/src/tests/data/test_key_edge_cases/key-malf-sig.json
new file mode 100644
index 0000000..a873f17
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-malf-sig.json
@@ -0,0 +1,45 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9833",
+ "length":51,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1590071668,
+ "algorithm":22,
+ "algorithm.str":"EdDSA",
+ "material":{
+ "p.bits":263,
+ "curve":"Ed25519"
+ },
+ "keyid":"c972affd358bf887"
+ },
+ {
+ "header":{
+ "offset":53,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b400",
+ "length":0,
+ "partial":false,
+ "indeterminate":false
+ },
+ "userid":""
+ },
+ {
+ "header":{
+ "offset":55,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8890",
+ "length":144,
+ "partial":false,
+ "indeterminate":false
+ }
+ }
+]
diff --git a/src/tests/data/test_key_edge_cases/key-malf-sig.pgp b/src/tests/data/test_key_edge_cases/key-malf-sig.pgp
new file mode 100644
index 0000000..e9d6a53
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-malf-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-malf-sig.txt b/src/tests/data/test_key_edge_cases/key-malf-sig.txt
new file mode 100644
index 0000000..862d64b
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-malf-sig.txt
@@ -0,0 +1,15 @@
+:off 0: packet header 0x9833 (tag 6, len 51)
+Public key packet
+ version: 4
+ creation time: 1590071668 (??? ??? ?? ??:??:?? ????)
+ public key algorithm: 22 (EdDSA)
+ public key material:
+ ecc p: 263 bits
+ ecc curve: Ed25519
+ keyid: 0xc972affd358bf887
+:off 53: packet header 0xb400 (tag 13, len 0)
+UserID packet
+ id:
+:off 55: packet header 0x8890 (tag 2, len 144)
+Signature packet
+ failed to parse
diff --git a/src/tests/data/test_key_edge_cases/key-primary-uid-conflict-pub.pgp b/src/tests/data/test_key_edge_cases/key-primary-uid-conflict-pub.pgp
new file mode 100644
index 0000000..482d12f
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-primary-uid-conflict-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-primary-uid-conflict-sec.pgp b/src/tests/data/test_key_edge_cases/key-primary-uid-conflict-sec.pgp
new file mode 100644
index 0000000..3ba4fe6
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-primary-uid-conflict-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-rsa-2001-pub.asc b/src/tests/data/test_key_edge_cases/key-rsa-2001-pub.asc
new file mode 100644
index 0000000..6418ec2
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-rsa-2001-pub.asc
@@ -0,0 +1,26 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xsBNBDuaygABCADt/ym+Cc2JVpjUoIkwS+eqWt/pKh73Vl3jRbPEQgOAyY0l2pVlBOovkC74RW3c
+5HN9QvZUC94njbW52vQVUOqRDRNDDT4dl4djpAxl5hUP65zgYC3lTISzePs1zjq4Z0H69AfH7qyb
+/49cuqHVVQAQC2ez/Zn31A0SrSZBQlCJND2vDK4BmK07pSv4d3nPqmqaSvoCDDzgS1sAvcsSMFwW
+EgO3suJP3qale0CEE7xHEgoKAuHsf3j8mfoD7c4U36lfPNbQLlzc9k/bS/gkCOChoq1T6pqyZtWp
+oSHiWWiVI8MdgwIyfDXjzX+gPc7jxzKNdNHu1D+K5BEQVjQXU9W7ABEBAAHNDHJzYS1rZXktMjAw
+McLAhwQTAQgAMRYhBDtD89a/PyOnIzH9TWyXZJPEYz+1BQI7msoAAhsDBAsJCAcFFQgJCgsFFgID
+AQAACgkQbJdkk8RjP7UURwf8D5nyfLOIRHoTgUJjnIVbluJR8FFum3/qt405o/NrLIXXz+F1MAF8
+nQBhkgDOMnWk5Keek2C8VGSR6FIyiai0umDvTx4OccIqVyZGiSwxEIF3Ts05RlF0j1UywmI/CTBT
+GSg/Dsk0ujnHwJQmzgQvtbzbyehQ/wCqn48hSBEocU3d6KaVuH7TWm0ZQ8YYxE6SGwDdngBUmqU+
+uWxapoXKltb0g/xsFOmlrYCFKhFkrjsgHco8XBXEgy9E/uFDAPZbC1FVuHnFaJllEqLdUtioXs3P
+cUI0OHGwZHXnZuRBbeeJmvYKtGNN7u6qhCwPquPN+95uBGUC9k81npFQHUt5ss7ATQQ7msoAAQgA
+5YL/OTcsuQHcTwa1kq2N+NcehTbDvZQy1he7mgJaNPo9bNWAHtqm1Ox5Dq6HjHY6eVYtTbVudeOx
+dxrS/BWCbkaaSPX0yGUpM7OsUQKl2/zrgDEmEXYwEtu0bj/fFA3IsETQdy4UEr8Uj4rzizqBj8au
+BgU5D1R+Ec6cK1caNmJgSUEJEThxEekJk+NNH8QaXE2Todho7nvcBTr8Tpyue/w2Wdso0Wcrvefz
+gWqrwe35iZYOemkLt0OqVOJuG3XHHhO0wZiDKwT7zqGUgPISp0jDZxgLnKMbRuj3gqQcPfL/LkN8
+7dLWlqsKQHBygSaM7FF1qKSd4XmSWV4g4q1NMQARAQABwsB2BBgBCAAgFiEEO0Pz1r8/I6cjMf1N
+bJdkk8RjP7UFAjuaygACGwwACgkQbJdkk8RjP7WHhAf/Ur/fsfSSQKft6FeCaPaWznfbgnwl0DXS
+b/gUzZAMTAEwfLS/4LxywiJ5qbVhRf5N7CwP0fbyg5VZFYa86t15iWheqOAUFZuZ0NEsYpqyVkGb
+W50rxuHU/Hps05rqH43aUIJJyzpxndniBMxBelB8QobXnjmUK97iw0e9uoMiPj+aqSzyW/ecHdwD
+LyJyFs3jB1kRSUCcZEUtZVa3ij2NSnY+LOykXiRf+2GymuoPM6lJG5KCLFD+SQEBpFk1TTiaQggW
+z3nPu1xqUDOx2MjApLDF7T/rLCSx1u4Wa45Vzx3tTfm8vO0E2YiPFpAHZ+BMXlKgstp97YhCYJN5
+zLgslg==
+=Wxb0
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-rsa-2001-sec.asc b/src/tests/data/test_key_edge_cases/key-rsa-2001-sec.asc
new file mode 100644
index 0000000..175e77f
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-rsa-2001-sec.asc
@@ -0,0 +1,50 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=gTGj
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-sec.asc b/src/tests/data/test_key_edge_cases/key-sec.asc
new file mode 100644
index 0000000..404bc29
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-sec.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lIYEXsaRdBYJKwYBBAHaRw8BAQdAaxEEwgMkGQUj+bA1Kx0KlILXljB3O3h+WlNh
+88aEVuH+BwMCXuyNBH0+daDjGdIZx6FJFjJnaeSH2dOy6Z6dZXFjnASj/FgG8wuT
+OISz9ttnpnEW5KoAoTwz03wNEXVngwF8+5jFQaBFajFYhyTN6wilkLQFTm9VSUSI
+kAQTFggAOBYhBHU9W5R+misuARR8H8lyr/01i/iHBQJexpF0AhsDBQsJCAcCBhUK
+CQgLAgQWAgMBAh4BAheAAAoJEMlyr/01i/iH3sMA+gKy6BAgGcTyYLDRa15QgyG4
+OqTNwDkY+mYjKeArCZD+AQDNH6leWb66yTXORXYrOk6MuHqfBncV3ePC2drnTlyy
+Dw==
+=R2SZ
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_edge_cases/key-sub-0-expiry.pgp b/src/tests/data/test_key_edge_cases/key-sub-0-expiry.pgp
new file mode 100644
index 0000000..95006a3
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-sub-0-expiry.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-sub-crit-note-pub.pgp b/src/tests/data/test_key_edge_cases/key-sub-crit-note-pub.pgp
new file mode 100644
index 0000000..ed04a77
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-sub-crit-note-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-sub-crit-note-sec.pgp b/src/tests/data/test_key_edge_cases/key-sub-crit-note-sec.pgp
new file mode 100644
index 0000000..957613e
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-sub-crit-note-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-subpacket-101-110.json b/src/tests/data/test_key_edge_cases/key-subpacket-101-110.json
new file mode 100644
index 0000000..f558513
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-subpacket-101-110.json
@@ -0,0 +1,250 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9833",
+ "length":51,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1590071668,
+ "algorithm":22,
+ "algorithm.str":"EdDSA",
+ "material":{
+ "p.bits":263,
+ "curve":"Ed25519"
+ },
+ "keyid":"c972affd358bf887"
+ },
+ {
+ "header":{
+ "offset":53,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b405",
+ "length":5,
+ "partial":false,
+ "indeterminate":false
+ },
+ "userid":"NoUID"
+ },
+ {
+ "header":{
+ "offset":60,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"88b8",
+ "length":184,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":19,
+ "type.str":"Positive User ID certification",
+ "algorithm":22,
+ "algorithm.str":"EdDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"753d5b947e9a2b2e01147c1fc972affd358bf887"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1590071668
+ },
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":3,
+ "flags.str":[
+ "certify",
+ "sign"
+ ]
+ },
+ {
+ "type":11,
+ "type.str":"preferred symmetric algorithms",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 9,
+ 8,
+ 7,
+ 2
+ ],
+ "algorithms.str":[
+ "AES-256",
+ "AES-192",
+ "AES-128",
+ "TripleDES"
+ ]
+ },
+ {
+ "type":21,
+ "type.str":"preferred hash algorithms",
+ "length":5,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 10,
+ 9,
+ 8,
+ 11,
+ 2
+ ],
+ "algorithms.str":[
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA224",
+ "SHA1"
+ ]
+ },
+ {
+ "type":22,
+ "type.str":"preferred compression algorithms",
+ "length":3,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 3,
+ 1
+ ],
+ "algorithms.str":[
+ "ZLib",
+ "BZip2",
+ "ZIP"
+ ]
+ },
+ {
+ "type":30,
+ "type.str":"features",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "mdc":true,
+ "aead":false,
+ "v5 keys":false
+ },
+ {
+ "type":23,
+ "type.str":"key server preferences",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "no-modify":true
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"c972affd358bf887"
+ },
+ {
+ "type":101,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0101"
+ },
+ {
+ "type":102,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0102"
+ },
+ {
+ "type":103,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0103"
+ },
+ {
+ "type":104,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0104"
+ },
+ {
+ "type":105,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0105"
+ },
+ {
+ "type":106,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0106"
+ },
+ {
+ "type":107,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0107"
+ },
+ {
+ "type":108,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0108"
+ },
+ {
+ "type":109,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0109"
+ },
+ {
+ "type":110,
+ "type.str":"Unknown",
+ "length":2,
+ "hashed":false,
+ "critical":false,
+ "raw":"0110"
+ }
+ ],
+ "lbits":"dec3",
+ "material":{
+ "r.bits":250,
+ "s.bits":256
+ }
+ }
+]
diff --git a/src/tests/data/test_key_edge_cases/key-subpacket-101-110.pgp b/src/tests/data/test_key_edge_cases/key-subpacket-101-110.pgp
new file mode 100644
index 0000000..4f23250
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-subpacket-101-110.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/key-subpacket-101-110.txt b/src/tests/data/test_key_edge_cases/key-subpacket-101-110.txt
new file mode 100644
index 0000000..2ff8aac
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-subpacket-101-110.txt
@@ -0,0 +1,63 @@
+:off 0: packet header 0x9833 (tag 6, len 51)
+Public key packet
+ version: 4
+ creation time: 1590071668 (??? ??? ?? ??:??:?? ????)
+ public key algorithm: 22 (EdDSA)
+ public key material:
+ ecc p: 263 bits
+ ecc curve: Ed25519
+ keyid: 0xc972affd358bf887
+:off 53: packet header 0xb405 (tag 13, len 5)
+UserID packet
+ id: NoUID
+:off 60: packet header 0x88b8 (tag 2, len 184)
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 22 (EdDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 33, len 21
+ issuer fingerprint: 0x753d5b947e9a2b2e01147c1fc972affd358bf887 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1590071668 (??? ??? ?? ??:??:?? ????)
+ :type 27, len 1
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 21, len 5
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ features: 0x01 ( mdc )
+ :type 23, len 1
+ key server preferences
+ no-modify: 1
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0xc972affd358bf887
+ :type 101, len 2
+ 00000 | 01 01 | ..
+ :type 102, len 2
+ 00000 | 01 02 | ..
+ :type 103, len 2
+ 00000 | 01 03 | ..
+ :type 104, len 2
+ 00000 | 01 04 | ..
+ :type 105, len 2
+ 00000 | 01 05 | ..
+ :type 106, len 2
+ 00000 | 01 06 | ..
+ :type 107, len 2
+ 00000 | 01 07 | ..
+ :type 108, len 2
+ 00000 | 01 08 | ..
+ :type 109, len 2
+ 00000 | 01 09 | ..
+ :type 110, len 2
+ 00000 | 01 10 | ..
+ lbits: 0xdec3
+ signature material:
+ ecc r: 250 bits
+ ecc s: 256 bits
diff --git a/src/tests/data/test_key_edge_cases/key-unhashed-subpkts.pgp b/src/tests/data/test_key_edge_cases/key-unhashed-subpkts.pgp
new file mode 100644
index 0000000..ff4ad28
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/key-unhashed-subpkts.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/pubring-malf-cert.pgp b/src/tests/data/test_key_edge_cases/pubring-malf-cert.pgp
new file mode 100644
index 0000000..04acb2f
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/pubring-malf-cert.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0-bind.pgp b/src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0-bind.pgp
new file mode 100644
index 0000000..21eb20f
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0-bind.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0.pgp b/src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0.pgp
new file mode 100644
index 0000000..b5f1ff9
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/pubring-malf-key0-sub0.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/pubring-malf-key0.pgp b/src/tests/data/test_key_edge_cases/pubring-malf-key0.pgp
new file mode 100644
index 0000000..8d11977
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/pubring-malf-key0.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/secring-malf-key0.pgp b/src/tests/data/test_key_edge_cases/secring-malf-key0.pgp
new file mode 100644
index 0000000..cfd1c0c
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/secring-malf-key0.pgp
Binary files differ
diff --git a/src/tests/data/test_key_edge_cases/secring-malf-key1.pgp b/src/tests/data/test_key_edge_cases/secring-malf-key1.pgp
new file mode 100644
index 0000000..4a6bf8d
--- /dev/null
+++ b/src/tests/data/test_key_edge_cases/secring-malf-key1.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/CMakeLists.txt b/src/tests/data/test_key_validity/CMakeLists.txt
new file mode 100644
index 0000000..553173c
--- /dev/null
+++ b/src/tests/data/test_key_validity/CMakeLists.txt
@@ -0,0 +1,25 @@
+# Copyright (c) 2020 Ribose Inc.
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
+# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS
+# BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+# POSSIBILITY OF SUCH DAMAGE.
+
+add_subdirectory(case5)
diff --git a/src/tests/data/test_key_validity/alice-cert.pgp b/src/tests/data/test_key_validity/alice-cert.pgp
new file mode 100644
index 0000000..998f246
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-cert.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-expired-claus-cert.asc b/src/tests/data/test_key_validity/alice-expired-claus-cert.asc
new file mode 100644
index 0000000..0240748
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-expired-claus-cert.asc
@@ -0,0 +1,12 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEXgS/LxYJKwYBBAHaRw8BAQdAJ/BnDcmcOCED/rW3y1zPHSX6lABI7G19R6mP
+hgfIgj+0EUFsaWNlIDxhbGljZUBybnA+iJAEExYIADgWIQRz7cyRGa/I4tu9zeUE
+UUCWaf/ePAUCXgS/LwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAEUUCW
+af/ePCSdAP9OWq8uOk5B5LUtPvFnxqGkrZlAHt+tgR271QSggRV3MAEAvtL/ru5o
+ss9jx26EqYj2GUgHGtsYqsz8j1y97S5lMQqIewQQEwgAIxYhBK3nrEqPSa2yNPp9
+APei/UoD3soZBQJf1LOoBYMAAVGAAAoJEPei/UoD3soZiyEA/0fH6Yhr+/dlHY7G
+B74bdzDz2ILpzzy3oJWyO1vnVXQ/APwIaIzQQFJegqUJvWIwXJnkvZnbPujEgCT5
+rHi9CFqIgA==
+=Wl6/
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/alice-pub.asc b/src/tests/data/test_key_validity/alice-pub.asc
new file mode 100644
index 0000000..9f37298
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-pub.asc
@@ -0,0 +1,9 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEXgS/LxYJKwYBBAHaRw8BAQdAJ/BnDcmcOCED/rW3y1zPHSX6lABI7G19R6mP
+hgfIgj+0EUFsaWNlIDxhbGljZUBybnA+iJAEExYIADgWIQRz7cyRGa/I4tu9zeUE
+UUCWaf/ePAUCXgS/LwIbAwULCQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRAEUUCW
+af/ePCSdAP9OWq8uOk5B5LUtPvFnxqGkrZlAHt+tgR271QSggRV3MAEAvtL/ru5o
+ss9jx26EqYj2GUgHGtsYqsz8j1y97S5lMQo=
+=H16D
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/alice-rev.pgp b/src/tests/data/test_key_validity/alice-rev.pgp
new file mode 100644
index 0000000..283bc61
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-rev.pgp
@@ -0,0 +1,8 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: This is a revocation certificate
+
+iHgEIBYIACAWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCXhh87wIdAQAKCRAEUUCW
+af/ePGfNAP95upN+yGiVOmXKRakzQyaj1Anv/PWTEA+95vZgWhgHVwD/UMhzdqs0
+TvlnxEVh21Ni1tjTIucTbMNX4sGIehebrgw=
+=//kr
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/alice-revoker-sig.asc b/src/tests/data/test_key_validity/alice-revoker-sig.asc
new file mode 100644
index 0000000..27cb9b6
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-revoker-sig.asc
@@ -0,0 +1,8 @@
+-----BEGIN PGP SIGNATURE-----
+Version: rnp 0.9.0+git20200116.640.e602d4f
+
+iJAEHxYIADgWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCXhiX/hcMgBPRQ4EZc7heJrE0t6ALKwn3
+1+puDgIHAAAKCRAEUUCWaf/ePEXhAP96sFjJDa5KKzVANFOxWMMZfctszuYAfgTXiYAeb9u39AEA
+mkTmfIbXZyihRvL+8QI+/+RbzfPbLSvCxHzYPFQDhAw=
+=kROR
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_key_validity/alice-revoker-sig.pgp b/src/tests/data/test_key_validity/alice-revoker-sig.pgp
new file mode 100644
index 0000000..f97423b
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-revoker-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-revoker.pgp b/src/tests/data/test_key_validity/alice-revoker.pgp
new file mode 100644
index 0000000..cae63eb
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-revoker.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-sec.asc b/src/tests/data/test_key_validity/alice-sec.asc
new file mode 100644
index 0000000..fcf6e7d
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sec.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lIYEXgS/LxYJKwYBBAHaRw8BAQdAJ/BnDcmcOCED/rW3y1zPHSX6lABI7G19R6mP
+hgfIgj/+BwMChdoHsOumkbDkVpYuDIt3rxOge9ceSDfswEZVXfw/91Vqzbkis8DT
+iVIsodBlVvaZCvnEyn/aGwRrteXjoUwrZwtVqcjAAGMp3YKPnt5/cbQRQWxpY2Ug
+PGFsaWNlQHJucD6IkAQTFggAOBYhBHPtzJEZr8ji273N5QRRQJZp/948BQJeBL8v
+AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEARRQJZp/948JJ0A/05ary46
+TkHktS0+8WfGoaStmUAe362BHbvVBKCBFXcwAQC+0v+u7miyz2PHboSpiPYZSAca
+2xiqzPyPXL3tLmUxCg==
+=iU19
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/alice-sign-sub-exp-pub.asc b/src/tests/data/test_key_validity/alice-sign-sub-exp-pub.asc
new file mode 100644
index 0000000..9397850
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sign-sub-exp-pub.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xjMEXgS/LxYJKwYBBAHaRw8BAQdAJ/BnDcmcOCED/rW3y1zPHSX6lABI7G19R6mPhgfIgj/NEUFs
+aWNlIDxhbGljZUBybnA+wpYEExYIAD4WIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCXv3VBAIbAwUL
+CQgHAgYVCgkICwIEFgIDAQIeAQIXgAUJAPkV1wAKCRAEUUCWaf/ePP4FAQCNXznY8KJThLGqXU34
+2JyGobV2qEb/XN0yf50sLQ02KgEAzpkPMaVArBKi3y3jBvNGnGhuBtW4eqbDoZ5XRi3p0QTOTwRe
+mYNBEwUrgQQACgIDBFPE8ItgY9glqERWNtvK+Nn1nizHiqOcbhFuW6zK4FfsQcnbodf/CfRjKbe3
+XnGpi58+s7xjZdrpYp9JdzZaTmHCwB4EGBYIACYWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCXv3V
+BQIbAgUJAGRRxwBqCRAEUUCWaf/ePF8gBBkTCAAGBQJe/dUGAAoJECLzohfA5DnL088A/2o3wWzo
+oEzmQXLOdH/WeqeC2s7GxYwYM8mPpWZwZHd/AQDMXPj4HX0p8Qvwk+1Gj7utGLjDb1t8816mO8an
+B2MTIb2CAQCTLUSN21aCiEdcCstPza6EGff8QfmmTUDkztcv9MXODwEAloWl4Es42VubkI9G+yOC
+mHD+4wEM7QAx5MtV/5d1lwU=
+=AXHJ
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/alice-sign-sub-exp-sec.asc b/src/tests/data/test_key_validity/alice-sign-sub-exp-sec.asc
new file mode 100644
index 0000000..9cb88f8
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sign-sub-exp-sec.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xYYEXgS/LxYJKwYBBAHaRw8BAQdAJ/BnDcmcOCED/rW3y1zPHSX6lABI7G19R6mPhgfIgj/+BwMC
+Tty9b5Ilu0Ljpjxw8rMT7GLc1MvcD+MxmlIjudT5ZQKX6jSH0VhxvX3cvgsazWZ2Rhu4S1QkNaHa
+3PUstbe2HT4n6Igr/pXWkKH4moHJzs0RQWxpY2UgPGFsaWNlQHJucD7ClgQTFggAPhYhBHPtzJEZ
+r8ji273N5QRRQJZp/948BQJe/dUEAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheABQkA+RXXAAoJ
+EARRQJZp/948/gUBAI1fOdjwolOEsapdTfjYnIahtXaoRv9c3TJ/nSwtDTYqAQDOmQ8xpUCsEqLf
+LeMG80acaG4G1bh6psOhnldGLenRBMeiBF6Zg0ETBSuBBAAKAgMEU8Twi2Bj2CWoRFY228r42fWe
+LMeKo5xuEW5brMrgV+xByduh1/8J9GMpt7decamLnz6zvGNl2ulin0l3NlpOYf4HAwKlLd08tunp
+b+M8yaWryRt1ixLVhitlej6Ytg/H7gjqpHUSOECgKqJQRrRiDzjqFShgenbGVKvSghp0ZokveXao
+rYpSu9GNEUdYnARGszpEwsAeBBgWCAAmFiEEc+3MkRmvyOLbvc3lBFFAlmn/3jwFAl791QUCGwIF
+CQBkUccAagkQBFFAlmn/3jxfIAQZEwgABgUCXv3VBgAKCRAi86IXwOQ5y9PPAP9qN8Fs6KBM5kFy
+znR/1nqngtrOxsWMGDPJj6VmcGR3fwEAzFz4+B19KfEL8JPtRo+7rRi4w29bfPNepjvGpwdjEyG9
+ggEAky1EjdtWgohHXArLT82uhBn3/EH5pk1A5M7XL/TFzg8BAJaFpeBLONlbm5CPRvsjgphw/uMB
+DO0AMeTLVf+XdZcF
+=O2AD
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/alice-sign-sub-pub.pgp b/src/tests/data/test_key_validity/alice-sign-sub-pub.pgp
new file mode 100644
index 0000000..e6c7827
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sign-sub-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-sign-sub-sec.pgp b/src/tests/data/test_key_validity/alice-sign-sub-sec.pgp
new file mode 100644
index 0000000..a0adab1
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sign-sub-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-sigs-malf.pgp b/src/tests/data/test_key_validity/alice-sigs-malf.pgp
new file mode 100644
index 0000000..d6e0339
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sigs-malf.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-sigs.asc b/src/tests/data/test_key_validity/alice-sigs.asc
new file mode 100644
index 0000000..334519b
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sigs.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP SIGNATURE-----
+Version: rnp 0.9.0+git20200116.640.e602d4f
+
+iHgEIBYIACAWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCXhh87wIdAQAKCRAEUUCWaf/ePGfNAP95
+upN+yGiVOmXKRakzQyaj1Anv/PWTEA+95vZgWhgHVwD/UMhzdqs0TvlnxEVh21Ni1tjTIucTbMNX
+4sGIehebrgw=
+=//kr
+-----END PGP SIGNATURE-----
+-----BEGIN PGP SIGNATURE-----
+Version: rnp 0.9.0+git20200116.640.e602d4f
+
+iJAEHxYIADgWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCXhiX/hcMgBPRQ4EZc7heJrE0t6ALKwn3
+1+puDgIHAAAKCRAEUUCWaf/ePEXhAP96sFjJDa5KKzVANFOxWMMZfctszuYAfgTXiYAeb9u39AEA
+mkTmfIbXZyihRvL+8QI+/+RbzfPbLSvCxHzYPFQDhAw=
+=kROR
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_key_validity/alice-sigs.pgp b/src/tests/data/test_key_validity/alice-sigs.pgp
new file mode 100644
index 0000000..ddda00b
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sigs.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-sub-pub.pgp b/src/tests/data/test_key_validity/alice-sub-pub.pgp
new file mode 100644
index 0000000..e62dd28
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sub-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/alice-sub-sec.pgp b/src/tests/data/test_key_validity/alice-sub-sec.pgp
new file mode 100644
index 0000000..109ea74
--- /dev/null
+++ b/src/tests/data/test_key_validity/alice-sub-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/basil-pub.asc b/src/tests/data/test_key_validity/basil-pub.asc
new file mode 100644
index 0000000..e552056
--- /dev/null
+++ b/src/tests/data/test_key_validity/basil-pub.asc
@@ -0,0 +1,10 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mFIEXgTAUBMIKoZIzj0DAQcCAwQGEAe84kNby6xYoyQGYRhcCmaDqhIMie+Ne1yQ
+Pw9rIlX30geA4COxsX63fEfe96tiO5rz4CB6/JDk2EiWckeetBFCYXNpbCA8YmFz
+aWxAcm5wPoiQBBMTCAA4FiEE0UOBGXO4XiaxNLegCysJ99fqbg4FAl4EwFACGwMF
+CwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQCysJ99fqbg6DhwD+NTrtmafXQrL5
+jUib/zflc63nONVb5sGzZkYxETEQJx0A/1dP48zqju0qcfEO4FYDqIYRZPOvWoq1
+l5BhDsxz6OOB
+=BfO6
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/basil-sec.asc b/src/tests/data/test_key_validity/basil-sec.asc
new file mode 100644
index 0000000..5051701
--- /dev/null
+++ b/src/tests/data/test_key_validity/basil-sec.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lKUEXgTAUBMIKoZIzj0DAQcCAwQGEAe84kNby6xYoyQGYRhcCmaDqhIMie+Ne1yQ
+Pw9rIlX30geA4COxsX63fEfe96tiO5rz4CB6/JDk2EiWckee/gcDAqtPKpVvPTRt
+5POnlu3HMr4AXfkf4Pc+KgGq2yZJqJBLySOEZsxWBRXkDgaceJn3m5hu95bGTTsu
+1mBOR8V0e9iRpNdNuZoulJdkvSxpSBa0EUJhc2lsIDxiYXNpbEBybnA+iJAEExMI
+ADgWIQTRQ4EZc7heJrE0t6ALKwn31+puDgUCXgTAUAIbAwULCQgHAgYVCgkICwIE
+FgIDAQIeAQIXgAAKCRALKwn31+puDoOHAP41Ou2Zp9dCsvmNSJv/N+Vzrec41Vvm
+wbNmRjERMRAnHQD/V0/jzOqO7Spx8Q7gVgOohhFk869airWXkGEOzHPo44E=
+=Ymbv
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/case1/pubring.gpg b/src/tests/data/test_key_validity/case1/pubring.gpg
new file mode 100644
index 0000000..f03864d
--- /dev/null
+++ b/src/tests/data/test_key_validity/case1/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case10/pubring.gpg b/src/tests/data/test_key_validity/case10/pubring.gpg
new file mode 100644
index 0000000..aa04e1e
--- /dev/null
+++ b/src/tests/data/test_key_validity/case10/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case11/pubring.gpg b/src/tests/data/test_key_validity/case11/pubring.gpg
new file mode 100644
index 0000000..ed9e689
--- /dev/null
+++ b/src/tests/data/test_key_validity/case11/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case12/pubring.gpg b/src/tests/data/test_key_validity/case12/pubring.gpg
new file mode 100644
index 0000000..d8fae2b
--- /dev/null
+++ b/src/tests/data/test_key_validity/case12/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case13/pubring.gpg b/src/tests/data/test_key_validity/case13/pubring.gpg
new file mode 100644
index 0000000..b63390c
--- /dev/null
+++ b/src/tests/data/test_key_validity/case13/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case14/pubring.gpg b/src/tests/data/test_key_validity/case14/pubring.gpg
new file mode 100644
index 0000000..e91d70c
--- /dev/null
+++ b/src/tests/data/test_key_validity/case14/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case15/pubring.gpg b/src/tests/data/test_key_validity/case15/pubring.gpg
new file mode 100644
index 0000000..960bb8d
--- /dev/null
+++ b/src/tests/data/test_key_validity/case15/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case2/pubring.gpg b/src/tests/data/test_key_validity/case2/pubring.gpg
new file mode 100644
index 0000000..9d28bfe
--- /dev/null
+++ b/src/tests/data/test_key_validity/case2/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case3/pubring.gpg b/src/tests/data/test_key_validity/case3/pubring.gpg
new file mode 100644
index 0000000..0505f1d
--- /dev/null
+++ b/src/tests/data/test_key_validity/case3/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case4/pubring.gpg b/src/tests/data/test_key_validity/case4/pubring.gpg
new file mode 100644
index 0000000..5cef0b3
--- /dev/null
+++ b/src/tests/data/test_key_validity/case4/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case5/CMakeLists.txt b/src/tests/data/test_key_validity/case5/CMakeLists.txt
new file mode 100644
index 0000000..ea91155
--- /dev/null
+++ b/src/tests/data/test_key_validity/case5/CMakeLists.txt
@@ -0,0 +1,38 @@
+# Copyright (c) 202 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.
+
+add_executable(tkv_case5_generate generate.cpp)
+
+set_target_properties(tkv_case5_generate PROPERTIES OUTPUT_NAME "generate")
+
+target_include_directories(tkv_case5_generate
+ PRIVATE
+ "${PROJECT_SOURCE_DIR}/src"
+ "${PROJECT_SOURCE_DIR}/src/lib"
+)
+
+target_link_libraries(tkv_case5_generate
+ PRIVATE
+ librnp
+)
diff --git a/src/tests/data/test_key_validity/case5/generate.cpp b/src/tests/data/test_key_validity/case5/generate.cpp
new file mode 100644
index 0000000..23735f4
--- /dev/null
+++ b/src/tests/data/test_key_validity/case5/generate.cpp
@@ -0,0 +1,160 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <iostream>
+#include <cstdint>
+#include "librepgp/stream-key.h"
+#include "librepgp/stream-packet.h"
+#include "fingerprint.h"
+#include "pgp-key.h"
+#include "crypto/signatures.h"
+
+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);
+ src_close(&src);
+ return res;
+}
+
+bool calculate_primary_binding(const pgp_key_pkt_t &key,
+ const pgp_key_pkt_t &subkey,
+ pgp_hash_alg_t halg,
+ pgp_signature_t & sig,
+ rnp::Hash & hash,
+ rnp::RNG & rng);
+
+int
+main(int argc, char **argv)
+{
+ if (argc < 3) {
+ std::cout << "Generate test file with subkey, signed by the other key.\n Usage: "
+ "./generate ../alice-sub-sec.asc ../basil-sec.asc\n";
+ return 1;
+ }
+
+ pgp_transferable_key_t tpkey = {};
+ pgp_transferable_key_t tskey = {};
+
+ if (!load_transferable_key(&tpkey, argv[1])) {
+ std::cout << "Failed to load first key.\n";
+ return 1;
+ }
+
+ if (!load_transferable_key(&tskey, argv[2])) {
+ std::cout << "Failed to load second key.\n";
+ return 1;
+ }
+
+ pgp_transferable_subkey_t *subkey =
+ (pgp_transferable_subkey_t *) list_front(tpkey.subkeys);
+ pgp_signature_t *binding = (pgp_signature_t *) list_front(subkey->signatures);
+
+ if (decrypt_secret_key(&tskey.key, "password")) {
+ RNP_LOG("Failed to decrypt secret key");
+ return 1;
+ }
+ if (decrypt_secret_key(&subkey->subkey, "password")) {
+ RNP_LOG("Failed to decrypt secret subkey");
+ return 1;
+ }
+
+ /* now let's rebuild binding using the other key */
+ uint8_t keyid[PGP_KEY_ID_SIZE];
+ pgp_fingerprint_t keyfp;
+
+ free(binding->hashed_data);
+ binding->hashed_data = NULL;
+ binding->hashed_len = 0;
+
+ pgp_keyid(keyid, sizeof(keyid), tskey.key);
+ pgp_fingerprint(&keyfp, tskey.key);
+
+ binding->halg = pgp_hash_adjust_alg_to_key(binding->halg, &tskey.key);
+ binding->palg = tskey.key.alg;
+ binding->set_keyfp(keyfp);
+
+ /* This requires transition to rnp::Hash once will be used */
+ rnp::Hash hash;
+ rnp::Hash hashcp;
+
+ binding->fill_hashed_data();
+ if (!signature_hash_binding(binding, &tpkey.key, &subkey->subkey, &hash) ||
+ !pgp_hash_copy(&hashcp, &hash)) {
+ RNP_LOG("failed to hash signature");
+ return 1;
+ }
+
+ rnp::RNG rng(rnp::RNG::Type::System);
+ if (signature_calculate(binding, &tskey.key.material, &hash, &rng)) {
+ RNP_LOG("failed to calculate signature");
+ return 1;
+ }
+
+ pgp_key_flags_t realkf = (pgp_key_flags_t) binding.key_flags();
+ if (!realkf) {
+ realkf = pgp_pk_alg_capabilities(subkey->subkey.alg);
+ }
+ if (realkf & PGP_KF_SIGN) {
+ pgp_signature_t embsig = {};
+ bool embres;
+
+ if (!calculate_primary_binding(
+ &tpkey.key, &subkey->subkey, binding->halg, &embsig, &hashcp, &rng)) {
+ RNP_LOG("failed to calculate primary key binding signature");
+ return 1;
+ }
+ embres = signature_set_embedded_sig(binding, &embsig);
+ free_signature(&embsig);
+ if (!embres) {
+ RNP_LOG("failed to add primary key binding signature");
+ return 1;
+ }
+ }
+
+ try {
+ binding->set_keyid(keyid);
+ } catch (const std::exception &e) {
+ RNP_LOG("failed to set issuer key id: %s", e.what());
+ return 1;
+ }
+
+ if (!transferable_key_to_public(&tpkey)) {
+ RNP_LOG("Failed to extract public key part.");
+ return 1;
+ }
+
+ pgp_dest_t dst = {};
+ init_stdout_dest(&dst);
+ write_transferable_key(tpkey, dst, true);
+ dst_close(&dst, false);
+
+ transferable_key_destroy(&tpkey);
+ transferable_key_destroy(&tskey);
+
+ return 0;
+} \ No newline at end of file
diff --git a/src/tests/data/test_key_validity/case5/pubring.gpg b/src/tests/data/test_key_validity/case5/pubring.gpg
new file mode 100644
index 0000000..fd35e11
--- /dev/null
+++ b/src/tests/data/test_key_validity/case5/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case6/pubring.gpg b/src/tests/data/test_key_validity/case6/pubring.gpg
new file mode 100644
index 0000000..a5d412b
--- /dev/null
+++ b/src/tests/data/test_key_validity/case6/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case7/pubring.gpg b/src/tests/data/test_key_validity/case7/pubring.gpg
new file mode 100644
index 0000000..f6b346d
--- /dev/null
+++ b/src/tests/data/test_key_validity/case7/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case8/message.txt b/src/tests/data/test_key_validity/case8/message.txt
new file mode 100644
index 0000000..0382ea8
--- /dev/null
+++ b/src/tests/data/test_key_validity/case8/message.txt
@@ -0,0 +1,3 @@
+Hello, world!
+
+
diff --git a/src/tests/data/test_key_validity/case8/message.txt.asc b/src/tests/data/test_key_validity/case8/message.txt.asc
new file mode 100644
index 0000000..fc304e8
--- /dev/null
+++ b/src/tests/data/test_key_validity/case8/message.txt.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+Hello, world!
+
+
+
+-----BEGIN PGP SIGNATURE-----
+Version: rnp 0.9.0+git20200420.739.148391d
+
+wnsEARYIACMWIQRz7cyRGa/I4tu9zeUEUUCWaf/ePAUCXqBD+QUDAAAAAAAKCRAEUUCWaf/ePAA4
+AQCab1d6mRcCdEmsIuipCsNHdpEI5Pxcz4DtnOx6GQLL3AD+JlsN4VDfzZiOXY5cDCGmkBgcxMYA
+ERpgLB46Y2iCKgbCewQBEwgAIxYhBM/lsBS+5D2dJPPvOCLzohfA5DnLBQJeoEP5BQMAAAAAAAoJ
+ECLzohfA5DnLSzAA/A/oRyERJXtKiFiZ6hq4esWMcM7eShnhW2cFaT1Og/NAAQDcU5vOxpu3LNdH
+wgpXa7eh2M1O04RHfFrsgO1Pvw8UJg==
+=BeZD
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_key_validity/case8/primary.pgp b/src/tests/data/test_key_validity/case8/primary.pgp
new file mode 100644
index 0000000..5570060
--- /dev/null
+++ b/src/tests/data/test_key_validity/case8/primary.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/case8/pubring.gpg b/src/tests/data/test_key_validity/case8/pubring.gpg
new file mode 100644
index 0000000..cde37a4
--- /dev/null
+++ b/src/tests/data/test_key_validity/case8/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/case8/subkey-no-sig.pgp b/src/tests/data/test_key_validity/case8/subkey-no-sig.pgp
new file mode 100644
index 0000000..ac79d77
--- /dev/null
+++ b/src/tests/data/test_key_validity/case8/subkey-no-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/case8/subkey.pgp b/src/tests/data/test_key_validity/case8/subkey.pgp
new file mode 100644
index 0000000..64a06f4
--- /dev/null
+++ b/src/tests/data/test_key_validity/case8/subkey.pgp
Binary files differ
diff --git a/src/tests/data/test_key_validity/case9/pubring.gpg b/src/tests/data/test_key_validity/case9/pubring.gpg
new file mode 100644
index 0000000..9b6e63d
--- /dev/null
+++ b/src/tests/data/test_key_validity/case9/pubring.gpg
Binary files differ
diff --git a/src/tests/data/test_key_validity/cases.txt b/src/tests/data/test_key_validity/cases.txt
new file mode 100644
index 0000000..fae0a9f
--- /dev/null
+++ b/src/tests/data/test_key_validity/cases.txt
@@ -0,0 +1,77 @@
+This folder contains keys with misc edge cases related to key signature validation.
+Main keys are Alice, Basil and Claus.
+
+Case1:
+Keys: Alice [pub]
+Alice is signed by Basil, but without the Basil's key.
+Result: Alice [valid]
+
+Case2:
+Keys: Alice [pub], Basil [pub]
+Alice is signed by Basil, Basil is signed by Alice, but Alice's self-signature is corrupted.
+Result: Alice [invalid], Basil [valid]
+
+Case3:
+Keys: Alice [pub], Basil [pub]
+Alice is signed by Basil, but doesn't have self-signature
+Result: Alice [invalid]
+
+Case4:
+Keys Alice [pub, sub]
+Alice subkey has invalid binding signature
+Result: Alice [valid], Alice sub [invalid]
+
+Case5:
+Keys Alice [pub, sub], Basil [pub]
+Alice subkey has valid binding signature, but from the key Basil
+Result: Alice [valid], Alice sub [invalid]
+
+Case6:
+Keys Alice [pub, sub]
+Key Alice has revocation signature by Alice, and subkey doesn't
+Result: Alice [invalid], Alice sub [invalid]
+
+Case7:
+Keys Alice [pub, sub]
+Alice subkey has revocation signature by Alice
+Result: Alice [valid], Alice sub [invalid]
+
+Case8:
+Keys Alice [pub, sub]
+Userid is stripped from the key, but it still has valid subkey binding
+Result: Alice [valid], Alice sub[valid]
+
+Case9:
+Keys Alice [pub, sub]
+Alice key has two self-signatures, one which expires key and second without key expiration.
+Result: Alice [valid], Alice sub[valid]
+
+Case10:
+Keys Alice [pub, sub]
+Alice key has expiring direct-key signature and non-expiring self-certification.
+Result: Alice [invalid], Alice sub[invalid]
+
+Case11:
+Keys Alice [pub, sub]
+Alice key has expiring direct-key signature, non-expiring self-certification and expiring
+primary userid certification. Result: Alice [invalid], Alice sub[invalid]
+
+Case12:
+Keys Alice [pub, sub]
+Alice key has non-expiring direct-key signature, non-expiring self-certification and expiring
+primary userid certification. Result: Alice [invalid], Alice sub[invalid]
+
+Case13:
+Keys Alice [pub, sub]
+Alice key has expiring direct-key signature, non-expiring self-certification and non-expiring
+primary userid certification. Result: Alice [invalid], Alice sub[invalid]
+
+Case14:
+Keys Alice [pub, sub]
+Alice key has expiring direct-key signature, non-expiring self-certification and non-expiring
+primary userid certification (with 0 key expiration subpacket). Result: Alice [invalid], Alice sub[invalid]
+
+Case15:
+Keys [pub, sub]
+Signing subkey has expired primary-key signature embedded into the subkey binding.
+Result: primary [valid], sub[invalid]
diff --git a/src/tests/data/test_key_validity/claus-pub.asc b/src/tests/data/test_key_validity/claus-pub.asc
new file mode 100644
index 0000000..2e147c6
--- /dev/null
+++ b/src/tests/data/test_key_validity/claus-pub.asc
@@ -0,0 +1,10 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mE8EXgTAxhMFK4EEAAoCAwR3wkOvTiag9w8Goe03997l0yBrJ2vc6qghSlP7iMtA
+SnhhRXSv6jUpnWBjw6O+eA/0oqD9NQfwr3xb/ayP5mFNtBFDbGF1cyA8Y2xhdXNA
+cm5wPoiQBBMTCAA4FiEEreesSo9JrbI0+n0A96L9SgPeyhkFAl4EwMYCGwMFCwkI
+BwIGFQoJCAsCBBYCAwECHgECF4AACgkQ96L9SgPeyhnudAEA/pagJBknFEPbI5Xt
+2tRfcoyzaArPC2YPXwXxLjDlDEoA/217MbgglQMbN2sazFyhoEMhilg80TLIbRc1
+jrxz06Fv
+=CPnm
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/claus-sec.asc b/src/tests/data/test_key_validity/claus-sec.asc
new file mode 100644
index 0000000..c4677e6
--- /dev/null
+++ b/src/tests/data/test_key_validity/claus-sec.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lKIEXgTAxhMFK4EEAAoCAwR3wkOvTiag9w8Goe03997l0yBrJ2vc6qghSlP7iMtA
+SnhhRXSv6jUpnWBjw6O+eA/0oqD9NQfwr3xb/ayP5mFN/gcDAkcNOGGfhODC5JpB
+I5WoxzQBnS1FdI+fgPgE38q6dkbbDun3Vl8APQFGna5FxgguvxA8r+zeVllkGOuo
+V5Y4nckIpJjEJxIA0Yen6M7fdiG0EUNsYXVzIDxjbGF1c0BybnA+iJAEExMIADgW
+IQSt56xKj0mtsjT6fQD3ov1KA97KGQUCXgTAxgIbAwULCQgHAgYVCgkICwIEFgID
+AQIeAQIXgAAKCRD3ov1KA97KGe50AQD+lqAkGScUQ9sjle3a1F9yjLNoCs8LZg9f
+BfEuMOUMSgD/bXsxuCCVAxs3axrMXKGgQyGKWDzRMshtFzWOvHPToW8=
+=oZiR
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/encrypting-primary.pgp b/src/tests/data/test_key_validity/encrypting-primary.pgp
new file mode 100644
index 0000000..d69d6b1
--- /dev/null
+++ b/src/tests/data/test_key_validity/encrypting-primary.pgp
@@ -0,0 +1,41 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+1B1z1Lv6KkBX3Z2n/iDgZGPLxhs9YFA=
+=TiK1
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/rsa_key_small_sig-pub.asc b/src/tests/data/test_key_validity/rsa_key_small_sig-pub.asc
new file mode 100644
index 0000000..357cd5f
--- /dev/null
+++ b/src/tests/data/test_key_validity/rsa_key_small_sig-pub.asc
@@ -0,0 +1,26 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xsBNBGDN004BCAC36d974nRKfeDqUjRL2DcTyKpc/MJGoGsBsAKPUayra23a0PZliH4jvfDLXU0w
+fsUmf9jEMi47j9/mIR3+A+wcanR6/VFahaA2OikakLfjQCJsIW649QJxyBWlDgl4ZZwVwomgRk58
+dmbODA7XTdjUP5YOs+Jweqe4fjUyggkjbJ7YtEOOw8DgUallMvb/XwjimCiM72mYrW2yS2uLdeet
+nrpI81nLZ7I8KXamN+KOUqRQBDYJ4TfPW0yUm39onAPvInb8ufg0soakVNkdu+zw7VOrkcmf5lLz
+1Cy7CWlkQXHeiJXCRBJgCDDO2OdXX9CQBTWEPJUJijALHAz6WSVdABEBAAHNDXJzYV9zbWFsbF9z
+aWfCwIgEEwEIADMWIQQWeYvrPYfh3qbRJf3tI7AQWUfygwUCYM3TTgIbAwULCQgHAgYVCAkKCwIF
+FgIDAQAACgkQ7SOwEFlH8oNs+gf493MI+AkpioPOua8d7WGUD5dDMhJiRMpPfGuZT9AGirLDMN07
+Cbyxr0k+4Nj7+J3P6rhApGobHLihYO9dtRbkFSjYRtO77zu76SfNZeaUsq5AMh2w7y4GUL6fJreY
+mhJD0RBsplh/lGCga+Nm2N3zzkGI0P9qwZx+ICManutwi4i91nCEE6pfOcaEhbe05/AinxFqG07k
+g/JKwFq979QOsAXqsRBa+EbHDfoZIcBr98vX1GW60B2Fnz/8DvXtzhLD8a84SkP2/eMmtV5dqQ3n
+NS09/hVg+nl6YYS/3wP0XbKGyp3wvbi6Ol/6NS2H7NwaayJp57+HSU6oKq2MmvJrzsBNBGDN004B
+CACqc6kbG+bcrF1QADvb9UuveoLLvtpM+o3TTNvUKeADikNkCQ4eoFPOiFZW+IT5gx26Fpg7wd51
+CJp4G4FRHdyqJKz6WvImG9zuhWW2WIvQ5W613C799S/pYY0tEw2cW+eZwrv7FhuXuru/Bb2pt4X/
+977eFHDBJ3q1i0e/LHFVIPdSIvSF+DyveOboWMw9C2htDIXj+tAhvbCTtXVgDuvdqq3UX5xi09F6
+dLSs3M1YmqOp8xDMUiNCpJ51rARqYFl6eiPUsGwk7Z0On72bxC61bkuwxjR1LLtjhkhSVlTztteh
+5jwhRPbdugU7JVB6APwrboFnyykZ5dABL1UaU4wTABEBAAHCwHYEGAEIACAWIQQWeYvrPYfh3qbR
+Jf3tI7AQWUfygwUCYM3TTgIbDAAKCRDtI7AQWUfyg+xcCACHageLUXJhoq3u888JBOCd2mDZ4Zwq
+CxFVHy3pDPWvMJbQzdv2SzQBf9egT6oD2eIHoIIn9Fyf7ooe2czSvARZT+tkoFKOg/GAD5mJoT3H
+10E2Q75begS63YVKtahN2BeEm6AhqrOT8mOJvO1GJVMAY69uni8K64Pmb1DNERMdTK4S48jc3Qi3
+CzeaKC/DfqnoqQalfYsJEIJUk/SXekZM01yQjpNTgQpXFbHQoilXhILU1mWzQsHobfTTdysGBAp8
+RsyBysCJTrDc9SyCj2oPXKrgQODfEY4HCuM/T4a4lANjn2HQ7F/jAcGuL6F4is6qCWYcRUaj1Mpa
+WoOBvSDo
+=SEyg
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_key_validity/rsa_key_small_sig-sec.asc b/src/tests/data/test_key_validity/rsa_key_small_sig-sec.asc
new file mode 100644
index 0000000..01c6761
--- /dev/null
+++ b/src/tests/data/test_key_validity/rsa_key_small_sig-sec.asc
@@ -0,0 +1,48 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=0M+o
+-----END PGP PRIVATE KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_large_MPIs/message.enc.rsa16384.pgp b/src/tests/data/test_large_MPIs/message.enc.rsa16384.pgp
new file mode 100644
index 0000000..0d84942
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/message.enc.rsa16384.pgp
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/message.enc.rsa16385.pgp b/src/tests/data/test_large_MPIs/message.enc.rsa16385.pgp
new file mode 100644
index 0000000..753ee51
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/message.enc.rsa16385.pgp
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/rsa-priv-16384bits.pgp b/src/tests/data/test_large_MPIs/rsa-priv-16384bits.pgp
new file mode 100644
index 0000000..cf9453b
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/rsa-priv-16384bits.pgp
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/rsa-priv-16385bits.pgp b/src/tests/data/test_large_MPIs/rsa-priv-16385bits.pgp
new file mode 100644
index 0000000..5c1a0d5
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/rsa-priv-16385bits.pgp
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/rsa-pub-16384bits.pgp b/src/tests/data/test_large_MPIs/rsa-pub-16384bits.pgp
new file mode 100644
index 0000000..0774687
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/rsa-pub-16384bits.pgp
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/rsa-pub-16385bits.pgp b/src/tests/data/test_large_MPIs/rsa-pub-16385bits.pgp
new file mode 100644
index 0000000..f3e90ec
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/rsa-pub-16385bits.pgp
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp b/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp
new file mode 100644
index 0000000..0db3c63
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.16385sig.sig b/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.16385sig.sig
new file mode 100644
index 0000000..cddf2fc
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.16385sig.sig
Binary files differ
diff --git a/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.sig b/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.sig
new file mode 100644
index 0000000..e0c96a3
--- /dev/null
+++ b/src/tests/data/test_large_MPIs/rsa-pub-65535bits.pgp.sig
Binary files differ
diff --git a/src/tests/data/test_large_packet/4g.bzip2.gpg b/src/tests/data/test_large_packet/4g.bzip2.gpg
new file mode 100644
index 0000000..81b0ef6
--- /dev/null
+++ b/src/tests/data/test_large_packet/4g.bzip2.gpg
Binary files differ
diff --git a/src/tests/data/test_list_packets/ecc-p256-pub.asc b/src/tests/data/test_list_packets/ecc-p256-pub.asc
new file mode 100644
index 0000000..fd1509e
--- /dev/null
+++ b/src/tests/data/test_list_packets/ecc-p256-pub.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V
+dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3tAhlY2MtcDI1NoiU
+BBMTCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwIXgBYhBLVP3ru2c0I6
+XQqlRCNnTyGyRBUnBQJcVa/nAAoJECNnTyGyRBUn1ycA+wVg9sEfHDBaGtLqlUSB
+WdGKURrHN7CJe2UTz1/7oQCBAQDDi4RQyLHs+TfOrBNSbLEswCu1oEh8VmHt/SN7
++mqNLbhWBFrDgjUSCCqGSM49AwEHAgMELDOArLIG85ABQu1IwgQMpiIuUwj+N7ib
+gGenTRck5dkBpX48eK3lbjovXn4YkBneA7z14iez3+Sdg6UFAMFV2QMBCAeIeAQY
+EwgAIAIbDBYhBLVP3ru2c0I6XQqlRCNnTyGyRBUnBQJcVa/vAAoJECNnTyGyRBUn
+ZKoBAJ64gv3w27nFBERvIsRqufvR6xcimqS7Gif+WehBU+P5AQC5bqoISh0oSQid
+adI84f60RuOaozpjvR3B1bPZiR6u7w==
+=H2xn
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_list_packets/list_all.txt b/src/tests/data/test_list_packets/list_all.txt
new file mode 100644
index 0000000..c9bfe26
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_all.txt
@@ -0,0 +1,152 @@
+:armored input
+:off 0: packet header 0x9852 (tag 6, len 82)
+:off 2: packet contents (82 bytes)
+ 00000 | 04 5a c3 82 35 13 08 2a 86 48 ce 3d 03 01 07 02 | .Z..5..*.H.=....
+ 00016 | 03 04 12 e4 6e a6 9f 97 61 6a 66 7a eb bf 12 5c | ....n...ajfz...\
+ 00032 | 4d 5a cd ab ea 7f c0 48 b5 63 bd 42 ff 15 75 2d | MZ.....H.c.B..u-
+ 00048 | 6f 10 4a 03 d7 8b ab af 93 90 29 32 ee 1e 10 d1 | o.J.......)2....
+ 00064 | 8e f4 90 75 ac 51 b6 47 f4 b0 8e 75 4f bd b7 d2 | ...u.Q.G...uO...
+ 00080 | c4 f7 | ..
+
+Public key packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 19 (ECDSA)
+ public key material:
+ ecc p: 515 bits, 0412e46ea69f97616a667aebbf125c4d5acdabea7fc048b563bd42ff15752d6f104a03d78babaf93902932ee1e10d18ef49075ac51b647f4b08e754fbdb7d2c4f7
+ ecc curve: NIST P-256
+ keyid: 0x23674f21b2441527
+ fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527
+ grip: 0xfc81aece90bce6e54d0d637d266109783ac8dac0
+:off 84: packet header 0xb408 (tag 13, len 8)
+:off 86: packet contents (8 bytes)
+ 00000 | 65 63 63 2d 70 32 35 36 | ecc-p256
+
+UserID packet
+ id: ecc-p256
+:off 94: packet header 0x8894 (tag 2, len 148)
+:off 96: packet contents (148 bytes)
+ 00000 | 04 13 13 08 00 3c 02 1b 03 05 0b 09 08 07 02 03 | .....<..........
+ 00016 | 22 02 01 06 15 0a 09 08 0b 02 04 16 02 03 01 02 | "...............
+ 00032 | 1e 03 02 17 80 16 21 04 b5 4f de bb b6 73 42 3a | ......!..O...sB:
+ 00048 | 5d 0a a5 44 23 67 4f 21 b2 44 15 27 05 02 5c 55 | ]..D#gO!.D.'..\U
+ 00064 | af e7 00 0a 09 10 23 67 4f 21 b2 44 15 27 d7 27 | ......#gO!.D.'.'
+ 00080 | 00 fb 05 60 f6 c1 1f 1c 30 5a 1a d2 ea 95 44 81 | ...`....0Z....D.
+ 00096 | 59 d1 8a 51 1a c7 37 b0 89 7b 65 13 cf 5f fb a1 | Y..Q..7..{e.._..
+ 00112 | 00 81 01 00 c3 8b 84 50 c8 b1 ec f9 37 ce ac 13 | .......P....7...
+ 00128 | 52 6c b1 2c c0 2b b5 a0 48 7c 56 61 ed fd 23 7b | Rl.,.+..H|Va..#{
+ 00144 | fa 6a 8d 2d | .j.-
+
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ :subpacket contents:
+ 00000 | 03 | .
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ :subpacket contents:
+ 00000 | 09 08 07 02 | ....
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 34, len 2
+ :subpacket contents:
+ 00000 | 02 01 | ..
+ preferred aead algorithms: OCB, EAX (2, 1)
+ :type 21, len 5
+ :subpacket contents:
+ 00000 | 0a 09 08 0b 02 | .....
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ :subpacket contents:
+ 00000 | 02 03 01 | ...
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ :subpacket contents:
+ 00000 | 03 | .
+ features: 0x03 ( mdc aead )
+ :type 23, len 1
+ :subpacket contents:
+ 00000 | 80 | .
+ key server preferences
+ no-modify: 1
+ :type 33, len 21
+ :subpacket contents:
+ 00000 | 04 b5 4f de bb b6 73 42 3a 5d 0a a5 44 23 67 4f | ..O...sB:]..D#gO
+ 00016 | 21 b2 44 15 27 | !.D.'
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ :subpacket contents:
+ 00000 | 5c 55 af e7 | \U..
+ signature creation time: 1549119463 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ :subpacket contents:
+ 00000 | 23 67 4f 21 b2 44 15 27 | #gO!.D.'
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0xd727
+ signature material:
+ ecc r: 251 bits, 0560f6c11f1c305a1ad2ea95448159d18a511ac737b0897b6513cf5ffba10081
+ ecc s: 256 bits, c38b8450c8b1ecf937ceac13526cb12cc02bb5a0487c5661edfd237bfa6a8d2d
+:off 244: packet header 0xb856 (tag 14, len 86)
+:off 246: packet contents (86 bytes)
+ 00000 | 04 5a c3 82 35 12 08 2a 86 48 ce 3d 03 01 07 02 | .Z..5..*.H.=....
+ 00016 | 03 04 2c 33 80 ac b2 06 f3 90 01 42 ed 48 c2 04 | ..,3.......B.H..
+ 00032 | 0c a6 22 2e 53 08 fe 37 b8 9b 80 67 a7 4d 17 24 | ..".S..7...g.M.$
+ 00048 | e5 d9 01 a5 7e 3c 78 ad e5 6e 3a 2f 5e 7e 18 90 | ....~<x..n:/^~..
+ 00064 | 19 de 03 bc f5 e2 27 b3 df e4 9d 83 a5 05 00 c1 | ......'.........
+ 00080 | 55 d9 03 01 08 07 | U.....
+
+Public subkey packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 18 (ECDH)
+ public key material:
+ ecdh p: 515 bits, 042c3380acb206f3900142ed48c2040ca6222e5308fe37b89b8067a74d1724e5d901a57e3c78ade56e3a2f5e7e189019de03bcf5e227b3dfe49d83a50500c155d9
+ ecdh curve: NIST P-256
+ ecdh hash algorithm: 8 (SHA256)
+ ecdh key wrap algorithm: 7
+ keyid: 0x37e285e9e9851491
+ fingerprint: 0x40e608afbc8d62cdcc08904f37e285e9e9851491
+ grip: 0xa56dc8db8355747a809037459b4258b8a743eab5
+:off 332: packet header 0x8878 (tag 2, len 120)
+:off 334: packet contents (120 bytes)
+ 00000 | 04 18 13 08 00 20 02 1b 0c 16 21 04 b5 4f de bb | ..... ....!..O..
+ 00016 | b6 73 42 3a 5d 0a a5 44 23 67 4f 21 b2 44 15 27 | .sB:]..D#gO!.D.'
+ 00032 | 05 02 5c 55 af ef 00 0a 09 10 23 67 4f 21 b2 44 | ..\U......#gO!.D
+ 00048 | 15 27 64 aa 01 00 9e b8 82 fd f0 db b9 c5 04 44 | .'d............D
+ 00064 | 6f 22 c4 6a b9 fb d1 eb 17 22 9a a4 bb 1a 27 fe | o".j....."....'.
+ 00080 | 59 e8 41 53 e3 f9 01 00 b9 6e aa 08 4a 1d 28 49 | Y.AS.....n..J.(I
+ 00096 | 08 9d 69 d2 3c e1 fe b4 46 e3 9a a3 3a 63 bd 1d | ..i.<...F...:c..
+ 00112 | c1 d5 b3 d9 89 1e ae ef | ........
+
+Signature packet
+ version: 4
+ type: 24 (Subkey Binding Signature)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ :subpacket contents:
+ 00000 | 0c | .
+ key flags: 0x0c ( encrypt_comm encrypt_storage )
+ :type 33, len 21
+ :subpacket contents:
+ 00000 | 04 b5 4f de bb b6 73 42 3a 5d 0a a5 44 23 67 4f | ..O...sB:]..D#gO
+ 00016 | 21 b2 44 15 27 | !.D.'
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ :subpacket contents:
+ 00000 | 5c 55 af ef | \U..
+ signature creation time: 1549119471 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ :subpacket contents:
+ 00000 | 23 67 4f 21 b2 44 15 27 | #gO!.D.'
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0x64aa
+ signature material:
+ ecc r: 256 bits, 9eb882fdf0dbb9c504446f22c46ab9fbd1eb17229aa4bb1a27fe59e84153e3f9
+ ecc s: 256 bits, b96eaa084a1d2849089d69d23ce1feb446e39aa33a63bd1dc1d5b3d9891eaeef
diff --git a/src/tests/data/test_list_packets/list_grips.txt b/src/tests/data/test_list_packets/list_grips.txt
new file mode 100644
index 0000000..3cdf995
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_grips.txt
@@ -0,0 +1,81 @@
+:armored input
+:off 0: packet header 0x9852 (tag 6, len 82)
+Public key packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 19 (ECDSA)
+ public key material:
+ ecc p: 515 bits
+ ecc curve: NIST P-256
+ keyid: 0x23674f21b2441527
+ fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527
+ grip: 0xfc81aece90bce6e54d0d637d266109783ac8dac0
+:off 84: packet header 0xb408 (tag 13, len 8)
+UserID packet
+ id: ecc-p256
+:off 94: packet header 0x8894 (tag 2, len 148)
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 34, len 2
+ preferred aead algorithms: OCB, EAX (2, 1)
+ :type 21, len 5
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ features: 0x03 ( mdc aead )
+ :type 23, len 1
+ key server preferences
+ no-modify: 1
+ :type 33, len 21
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1549119463 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0xd727
+ signature material:
+ ecc r: 251 bits
+ ecc s: 256 bits
+:off 244: packet header 0xb856 (tag 14, len 86)
+Public subkey packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 18 (ECDH)
+ public key material:
+ ecdh p: 515 bits
+ ecdh curve: NIST P-256
+ ecdh hash algorithm: 8 (SHA256)
+ ecdh key wrap algorithm: 7
+ keyid: 0x37e285e9e9851491
+ fingerprint: 0x40e608afbc8d62cdcc08904f37e285e9e9851491
+ grip: 0xa56dc8db8355747a809037459b4258b8a743eab5
+:off 332: packet header 0x8878 (tag 2, len 120)
+Signature packet
+ version: 4
+ type: 24 (Subkey Binding Signature)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ key flags: 0x0c ( encrypt_comm encrypt_storage )
+ :type 33, len 21
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1549119471 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0x64aa
+ signature material:
+ ecc r: 256 bits
+ ecc s: 256 bits
diff --git a/src/tests/data/test_list_packets/list_json.txt b/src/tests/data/test_list_packets/list_json.txt
new file mode 100644
index 0000000..f03ae61
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_json.txt
@@ -0,0 +1,270 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9852",
+ "length":82,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "material":{
+ "p.bits":515,
+ "curve":"NIST P-256"
+ },
+ "keyid":"23674f21b2441527"
+ },
+ {
+ "header":{
+ "offset":84,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b408",
+ "length":8,
+ "partial":false,
+ "indeterminate":false
+ },
+ "userid":"ecc-p256"
+ },
+ {
+ "header":{
+ "offset":94,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8894",
+ "length":148,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":19,
+ "type.str":"Positive User ID certification",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":3,
+ "flags.str":[
+ "certify",
+ "sign"
+ ]
+ },
+ {
+ "type":11,
+ "type.str":"preferred symmetric algorithms",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 9,
+ 8,
+ 7,
+ 2
+ ],
+ "algorithms.str":[
+ "AES-256",
+ "AES-192",
+ "AES-128",
+ "TripleDES"
+ ]
+ },
+ {
+ "type":34,
+ "type.str":"preferred AEAD algorithms",
+ "length":2,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 1
+ ],
+ "algorithms.str":[
+ "OCB",
+ "EAX"
+ ]
+ },
+ {
+ "type":21,
+ "type.str":"preferred hash algorithms",
+ "length":5,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 10,
+ 9,
+ 8,
+ 11,
+ 2
+ ],
+ "algorithms.str":[
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA224",
+ "SHA1"
+ ]
+ },
+ {
+ "type":22,
+ "type.str":"preferred compression algorithms",
+ "length":3,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 3,
+ 1
+ ],
+ "algorithms.str":[
+ "ZLib",
+ "BZip2",
+ "ZIP"
+ ]
+ },
+ {
+ "type":30,
+ "type.str":"features",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "mdc":true,
+ "aead":true,
+ "v5 keys":false
+ },
+ {
+ "type":23,
+ "type.str":"key server preferences",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "no-modify":true
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1549119463
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"d727",
+ "material":{
+ "r.bits":251,
+ "s.bits":256
+ }
+ },
+ {
+ "header":{
+ "offset":244,
+ "tag":14,
+ "tag.str":"Public Subkey",
+ "raw":"b856",
+ "length":86,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":18,
+ "algorithm.str":"ECDH",
+ "material":{
+ "p.bits":515,
+ "curve":"NIST P-256",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "key wrap algorithm":7,
+ "key wrap algorithm.str":"AES-128"
+ },
+ "keyid":"37e285e9e9851491"
+ },
+ {
+ "header":{
+ "offset":332,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8878",
+ "length":120,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":24,
+ "type.str":"Subkey Binding Signature",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":12,
+ "flags.str":[
+ "encrypt_comm",
+ "encrypt_storage"
+ ]
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1549119471
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"64aa",
+ "material":{
+ "r.bits":256,
+ "s.bits":256
+ }
+ }
+]
diff --git a/src/tests/data/test_list_packets/list_json_all.txt b/src/tests/data/test_list_packets/list_json_all.txt
new file mode 100644
index 0000000..f5c0dc4
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_json_all.txt
@@ -0,0 +1,299 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9852",
+ "length":82,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"045ac3823513082a8648ce3d03010702030412e46ea69f97616a667aebbf125c4d5acdabea7fc048b563bd42ff15752d6f104a03d78babaf93902932ee1e10d18ef49075ac51b647f4b08e754fbdb7d2c4f7",
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "material":{
+ "p.bits":515,
+ "p.raw":"0412e46ea69f97616a667aebbf125c4d5acdabea7fc048b563bd42ff15752d6f104a03d78babaf93902932ee1e10d18ef49075ac51b647f4b08e754fbdb7d2c4f7",
+ "curve":"NIST P-256"
+ },
+ "keyid":"23674f21b2441527",
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527",
+ "grip":"fc81aece90bce6e54d0d637d266109783ac8dac0"
+ },
+ {
+ "header":{
+ "offset":84,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b408",
+ "length":8,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"6563632d70323536",
+ "userid":"ecc-p256"
+ },
+ {
+ "header":{
+ "offset":94,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8894",
+ "length":148,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"04131308003c021b03050b090807020322020106150a09080b020416020301021e03021780162104b54fdebbb673423a5d0aa54423674f21b244152705025c55afe7000a091023674f21b2441527d72700fb0560f6c11f1c305a1ad2ea95448159d18a511ac737b0897b6513cf5ffba100810100c38b8450c8b1ecf937ceac13526cb12cc02bb5a0487c5661edfd237bfa6a8d2d",
+ "version":4,
+ "type":19,
+ "type.str":"Positive User ID certification",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"03",
+ "flags":3,
+ "flags.str":[
+ "certify",
+ "sign"
+ ]
+ },
+ {
+ "type":11,
+ "type.str":"preferred symmetric algorithms",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "raw":"09080702",
+ "algorithms":[
+ 9,
+ 8,
+ 7,
+ 2
+ ],
+ "algorithms.str":[
+ "AES-256",
+ "AES-192",
+ "AES-128",
+ "TripleDES"
+ ]
+ },
+ {
+ "type":34,
+ "type.str":"preferred AEAD algorithms",
+ "length":2,
+ "hashed":true,
+ "critical":false,
+ "raw":"0201",
+ "algorithms":[
+ 2,
+ 1
+ ],
+ "algorithms.str":[
+ "OCB",
+ "EAX"
+ ]
+ },
+ {
+ "type":21,
+ "type.str":"preferred hash algorithms",
+ "length":5,
+ "hashed":true,
+ "critical":false,
+ "raw":"0a09080b02",
+ "algorithms":[
+ 10,
+ 9,
+ 8,
+ 11,
+ 2
+ ],
+ "algorithms.str":[
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA224",
+ "SHA1"
+ ]
+ },
+ {
+ "type":22,
+ "type.str":"preferred compression algorithms",
+ "length":3,
+ "hashed":true,
+ "critical":false,
+ "raw":"020301",
+ "algorithms":[
+ 2,
+ 3,
+ 1
+ ],
+ "algorithms.str":[
+ "ZLib",
+ "BZip2",
+ "ZIP"
+ ]
+ },
+ {
+ "type":30,
+ "type.str":"features",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"03",
+ "mdc":true,
+ "aead":true,
+ "v5 keys":false
+ },
+ {
+ "type":23,
+ "type.str":"key server preferences",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"80",
+ "no-modify":true
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "raw":"04b54fdebbb673423a5d0aa54423674f21b2441527",
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "raw":"5c55afe7",
+ "creation time":1549119463
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "raw":"23674f21b2441527",
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"d727",
+ "material":{
+ "r.bits":251,
+ "r.raw":"0560f6c11f1c305a1ad2ea95448159d18a511ac737b0897b6513cf5ffba10081",
+ "s.bits":256,
+ "s.raw":"c38b8450c8b1ecf937ceac13526cb12cc02bb5a0487c5661edfd237bfa6a8d2d"
+ }
+ },
+ {
+ "header":{
+ "offset":244,
+ "tag":14,
+ "tag.str":"Public Subkey",
+ "raw":"b856",
+ "length":86,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"045ac3823512082a8648ce3d0301070203042c3380acb206f3900142ed48c2040ca6222e5308fe37b89b8067a74d1724e5d901a57e3c78ade56e3a2f5e7e189019de03bcf5e227b3dfe49d83a50500c155d903010807",
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":18,
+ "algorithm.str":"ECDH",
+ "material":{
+ "p.bits":515,
+ "p.raw":"042c3380acb206f3900142ed48c2040ca6222e5308fe37b89b8067a74d1724e5d901a57e3c78ade56e3a2f5e7e189019de03bcf5e227b3dfe49d83a50500c155d9",
+ "curve":"NIST P-256",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "key wrap algorithm":7,
+ "key wrap algorithm.str":"AES-128"
+ },
+ "keyid":"37e285e9e9851491",
+ "fingerprint":"40e608afbc8d62cdcc08904f37e285e9e9851491",
+ "grip":"a56dc8db8355747a809037459b4258b8a743eab5"
+ },
+ {
+ "header":{
+ "offset":332,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8878",
+ "length":120,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"041813080020021b0c162104b54fdebbb673423a5d0aa54423674f21b244152705025c55afef000a091023674f21b244152764aa01009eb882fdf0dbb9c504446f22c46ab9fbd1eb17229aa4bb1a27fe59e84153e3f90100b96eaa084a1d2849089d69d23ce1feb446e39aa33a63bd1dc1d5b3d9891eaeef",
+ "version":4,
+ "type":24,
+ "type.str":"Subkey Binding Signature",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"0c",
+ "flags":12,
+ "flags.str":[
+ "encrypt_comm",
+ "encrypt_storage"
+ ]
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "raw":"04b54fdebbb673423a5d0aa54423674f21b2441527",
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "raw":"5c55afef",
+ "creation time":1549119471
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "raw":"23674f21b2441527",
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"64aa",
+ "material":{
+ "r.bits":256,
+ "r.raw":"9eb882fdf0dbb9c504446f22c46ab9fbd1eb17229aa4bb1a27fe59e84153e3f9",
+ "s.bits":256,
+ "s.raw":"b96eaa084a1d2849089d69d23ce1feb446e39aa33a63bd1dc1d5b3d9891eaeef"
+ }
+ }
+]
diff --git a/src/tests/data/test_list_packets/list_json_grips.txt b/src/tests/data/test_list_packets/list_json_grips.txt
new file mode 100644
index 0000000..63e6b42
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_json_grips.txt
@@ -0,0 +1,274 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9852",
+ "length":82,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "material":{
+ "p.bits":515,
+ "curve":"NIST P-256"
+ },
+ "keyid":"23674f21b2441527",
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527",
+ "grip":"fc81aece90bce6e54d0d637d266109783ac8dac0"
+ },
+ {
+ "header":{
+ "offset":84,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b408",
+ "length":8,
+ "partial":false,
+ "indeterminate":false
+ },
+ "userid":"ecc-p256"
+ },
+ {
+ "header":{
+ "offset":94,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8894",
+ "length":148,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":19,
+ "type.str":"Positive User ID certification",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":3,
+ "flags.str":[
+ "certify",
+ "sign"
+ ]
+ },
+ {
+ "type":11,
+ "type.str":"preferred symmetric algorithms",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 9,
+ 8,
+ 7,
+ 2
+ ],
+ "algorithms.str":[
+ "AES-256",
+ "AES-192",
+ "AES-128",
+ "TripleDES"
+ ]
+ },
+ {
+ "type":34,
+ "type.str":"preferred AEAD algorithms",
+ "length":2,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 1
+ ],
+ "algorithms.str":[
+ "OCB",
+ "EAX"
+ ]
+ },
+ {
+ "type":21,
+ "type.str":"preferred hash algorithms",
+ "length":5,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 10,
+ 9,
+ 8,
+ 11,
+ 2
+ ],
+ "algorithms.str":[
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA224",
+ "SHA1"
+ ]
+ },
+ {
+ "type":22,
+ "type.str":"preferred compression algorithms",
+ "length":3,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 3,
+ 1
+ ],
+ "algorithms.str":[
+ "ZLib",
+ "BZip2",
+ "ZIP"
+ ]
+ },
+ {
+ "type":30,
+ "type.str":"features",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "mdc":true,
+ "aead":true,
+ "v5 keys":false
+ },
+ {
+ "type":23,
+ "type.str":"key server preferences",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "no-modify":true
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1549119463
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"d727",
+ "material":{
+ "r.bits":251,
+ "s.bits":256
+ }
+ },
+ {
+ "header":{
+ "offset":244,
+ "tag":14,
+ "tag.str":"Public Subkey",
+ "raw":"b856",
+ "length":86,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":18,
+ "algorithm.str":"ECDH",
+ "material":{
+ "p.bits":515,
+ "curve":"NIST P-256",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "key wrap algorithm":7,
+ "key wrap algorithm.str":"AES-128"
+ },
+ "keyid":"37e285e9e9851491",
+ "fingerprint":"40e608afbc8d62cdcc08904f37e285e9e9851491",
+ "grip":"a56dc8db8355747a809037459b4258b8a743eab5"
+ },
+ {
+ "header":{
+ "offset":332,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8878",
+ "length":120,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":24,
+ "type.str":"Subkey Binding Signature",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":12,
+ "flags.str":[
+ "encrypt_comm",
+ "encrypt_storage"
+ ]
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1549119471
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"64aa",
+ "material":{
+ "r.bits":256,
+ "s.bits":256
+ }
+ }
+]
diff --git a/src/tests/data/test_list_packets/list_json_mpi.txt b/src/tests/data/test_list_packets/list_json_mpi.txt
new file mode 100644
index 0000000..e204f2c
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_json_mpi.txt
@@ -0,0 +1,276 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9852",
+ "length":82,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "material":{
+ "p.bits":515,
+ "p.raw":"0412e46ea69f97616a667aebbf125c4d5acdabea7fc048b563bd42ff15752d6f104a03d78babaf93902932ee1e10d18ef49075ac51b647f4b08e754fbdb7d2c4f7",
+ "curve":"NIST P-256"
+ },
+ "keyid":"23674f21b2441527"
+ },
+ {
+ "header":{
+ "offset":84,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b408",
+ "length":8,
+ "partial":false,
+ "indeterminate":false
+ },
+ "userid":"ecc-p256"
+ },
+ {
+ "header":{
+ "offset":94,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8894",
+ "length":148,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":19,
+ "type.str":"Positive User ID certification",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":3,
+ "flags.str":[
+ "certify",
+ "sign"
+ ]
+ },
+ {
+ "type":11,
+ "type.str":"preferred symmetric algorithms",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 9,
+ 8,
+ 7,
+ 2
+ ],
+ "algorithms.str":[
+ "AES-256",
+ "AES-192",
+ "AES-128",
+ "TripleDES"
+ ]
+ },
+ {
+ "type":34,
+ "type.str":"preferred AEAD algorithms",
+ "length":2,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 1
+ ],
+ "algorithms.str":[
+ "OCB",
+ "EAX"
+ ]
+ },
+ {
+ "type":21,
+ "type.str":"preferred hash algorithms",
+ "length":5,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 10,
+ 9,
+ 8,
+ 11,
+ 2
+ ],
+ "algorithms.str":[
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA224",
+ "SHA1"
+ ]
+ },
+ {
+ "type":22,
+ "type.str":"preferred compression algorithms",
+ "length":3,
+ "hashed":true,
+ "critical":false,
+ "algorithms":[
+ 2,
+ 3,
+ 1
+ ],
+ "algorithms.str":[
+ "ZLib",
+ "BZip2",
+ "ZIP"
+ ]
+ },
+ {
+ "type":30,
+ "type.str":"features",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "mdc":true,
+ "aead":true,
+ "v5 keys":false
+ },
+ {
+ "type":23,
+ "type.str":"key server preferences",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "no-modify":true
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1549119463
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"d727",
+ "material":{
+ "r.bits":251,
+ "r.raw":"0560f6c11f1c305a1ad2ea95448159d18a511ac737b0897b6513cf5ffba10081",
+ "s.bits":256,
+ "s.raw":"c38b8450c8b1ecf937ceac13526cb12cc02bb5a0487c5661edfd237bfa6a8d2d"
+ }
+ },
+ {
+ "header":{
+ "offset":244,
+ "tag":14,
+ "tag.str":"Public Subkey",
+ "raw":"b856",
+ "length":86,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":18,
+ "algorithm.str":"ECDH",
+ "material":{
+ "p.bits":515,
+ "p.raw":"042c3380acb206f3900142ed48c2040ca6222e5308fe37b89b8067a74d1724e5d901a57e3c78ade56e3a2f5e7e189019de03bcf5e227b3dfe49d83a50500c155d9",
+ "curve":"NIST P-256",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "key wrap algorithm":7,
+ "key wrap algorithm.str":"AES-128"
+ },
+ "keyid":"37e285e9e9851491"
+ },
+ {
+ "header":{
+ "offset":332,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8878",
+ "length":120,
+ "partial":false,
+ "indeterminate":false
+ },
+ "version":4,
+ "type":24,
+ "type.str":"Subkey Binding Signature",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "flags":12,
+ "flags.str":[
+ "encrypt_comm",
+ "encrypt_storage"
+ ]
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "creation time":1549119471
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"64aa",
+ "material":{
+ "r.bits":256,
+ "r.raw":"9eb882fdf0dbb9c504446f22c46ab9fbd1eb17229aa4bb1a27fe59e84153e3f9",
+ "s.bits":256,
+ "s.raw":"b96eaa084a1d2849089d69d23ce1feb446e39aa33a63bd1dc1d5b3d9891eaeef"
+ }
+ }
+]
diff --git a/src/tests/data/test_list_packets/list_json_raw.txt b/src/tests/data/test_list_packets/list_json_raw.txt
new file mode 100644
index 0000000..c70138a
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_json_raw.txt
@@ -0,0 +1,289 @@
+[
+ {
+ "header":{
+ "offset":0,
+ "tag":6,
+ "tag.str":"Public Key",
+ "raw":"9852",
+ "length":82,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"045ac3823513082a8648ce3d03010702030412e46ea69f97616a667aebbf125c4d5acdabea7fc048b563bd42ff15752d6f104a03d78babaf93902932ee1e10d18ef49075ac51b647f4b08e754fbdb7d2c4f7",
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "material":{
+ "p.bits":515,
+ "curve":"NIST P-256"
+ },
+ "keyid":"23674f21b2441527"
+ },
+ {
+ "header":{
+ "offset":84,
+ "tag":13,
+ "tag.str":"User ID",
+ "raw":"b408",
+ "length":8,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"6563632d70323536",
+ "userid":"ecc-p256"
+ },
+ {
+ "header":{
+ "offset":94,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8894",
+ "length":148,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"04131308003c021b03050b090807020322020106150a09080b020416020301021e03021780162104b54fdebbb673423a5d0aa54423674f21b244152705025c55afe7000a091023674f21b2441527d72700fb0560f6c11f1c305a1ad2ea95448159d18a511ac737b0897b6513cf5ffba100810100c38b8450c8b1ecf937ceac13526cb12cc02bb5a0487c5661edfd237bfa6a8d2d",
+ "version":4,
+ "type":19,
+ "type.str":"Positive User ID certification",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"03",
+ "flags":3,
+ "flags.str":[
+ "certify",
+ "sign"
+ ]
+ },
+ {
+ "type":11,
+ "type.str":"preferred symmetric algorithms",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "raw":"09080702",
+ "algorithms":[
+ 9,
+ 8,
+ 7,
+ 2
+ ],
+ "algorithms.str":[
+ "AES-256",
+ "AES-192",
+ "AES-128",
+ "TripleDES"
+ ]
+ },
+ {
+ "type":34,
+ "type.str":"preferred AEAD algorithms",
+ "length":2,
+ "hashed":true,
+ "critical":false,
+ "raw":"0201",
+ "algorithms":[
+ 2,
+ 1
+ ],
+ "algorithms.str":[
+ "OCB",
+ "EAX"
+ ]
+ },
+ {
+ "type":21,
+ "type.str":"preferred hash algorithms",
+ "length":5,
+ "hashed":true,
+ "critical":false,
+ "raw":"0a09080b02",
+ "algorithms":[
+ 10,
+ 9,
+ 8,
+ 11,
+ 2
+ ],
+ "algorithms.str":[
+ "SHA512",
+ "SHA384",
+ "SHA256",
+ "SHA224",
+ "SHA1"
+ ]
+ },
+ {
+ "type":22,
+ "type.str":"preferred compression algorithms",
+ "length":3,
+ "hashed":true,
+ "critical":false,
+ "raw":"020301",
+ "algorithms":[
+ 2,
+ 3,
+ 1
+ ],
+ "algorithms.str":[
+ "ZLib",
+ "BZip2",
+ "ZIP"
+ ]
+ },
+ {
+ "type":30,
+ "type.str":"features",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"03",
+ "mdc":true,
+ "aead":true,
+ "v5 keys":false
+ },
+ {
+ "type":23,
+ "type.str":"key server preferences",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"80",
+ "no-modify":true
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "raw":"04b54fdebbb673423a5d0aa54423674f21b2441527",
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "raw":"5c55afe7",
+ "creation time":1549119463
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "raw":"23674f21b2441527",
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"d727",
+ "material":{
+ "r.bits":251,
+ "s.bits":256
+ }
+ },
+ {
+ "header":{
+ "offset":244,
+ "tag":14,
+ "tag.str":"Public Subkey",
+ "raw":"b856",
+ "length":86,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"045ac3823512082a8648ce3d0301070203042c3380acb206f3900142ed48c2040ca6222e5308fe37b89b8067a74d1724e5d901a57e3c78ade56e3a2f5e7e189019de03bcf5e227b3dfe49d83a50500c155d903010807",
+ "version":4,
+ "creation time":1522762293,
+ "algorithm":18,
+ "algorithm.str":"ECDH",
+ "material":{
+ "p.bits":515,
+ "curve":"NIST P-256",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "key wrap algorithm":7,
+ "key wrap algorithm.str":"AES-128"
+ },
+ "keyid":"37e285e9e9851491"
+ },
+ {
+ "header":{
+ "offset":332,
+ "tag":2,
+ "tag.str":"Signature",
+ "raw":"8878",
+ "length":120,
+ "partial":false,
+ "indeterminate":false
+ },
+ "raw":"041813080020021b0c162104b54fdebbb673423a5d0aa54423674f21b244152705025c55afef000a091023674f21b244152764aa01009eb882fdf0dbb9c504446f22c46ab9fbd1eb17229aa4bb1a27fe59e84153e3f90100b96eaa084a1d2849089d69d23ce1feb446e39aa33a63bd1dc1d5b3d9891eaeef",
+ "version":4,
+ "type":24,
+ "type.str":"Subkey Binding Signature",
+ "algorithm":19,
+ "algorithm.str":"ECDSA",
+ "hash algorithm":8,
+ "hash algorithm.str":"SHA256",
+ "subpackets":[
+ {
+ "type":27,
+ "type.str":"key flags",
+ "length":1,
+ "hashed":true,
+ "critical":false,
+ "raw":"0c",
+ "flags":12,
+ "flags.str":[
+ "encrypt_comm",
+ "encrypt_storage"
+ ]
+ },
+ {
+ "type":33,
+ "type.str":"issuer fingerprint",
+ "length":21,
+ "hashed":true,
+ "critical":false,
+ "raw":"04b54fdebbb673423a5d0aa54423674f21b2441527",
+ "fingerprint":"b54fdebbb673423a5d0aa54423674f21b2441527"
+ },
+ {
+ "type":2,
+ "type.str":"signature creation time",
+ "length":4,
+ "hashed":true,
+ "critical":false,
+ "raw":"5c55afef",
+ "creation time":1549119471
+ },
+ {
+ "type":16,
+ "type.str":"issuer key ID",
+ "length":8,
+ "hashed":false,
+ "critical":false,
+ "raw":"23674f21b2441527",
+ "issuer keyid":"23674f21b2441527"
+ }
+ ],
+ "lbits":"64aa",
+ "material":{
+ "r.bits":256,
+ "s.bits":256
+ }
+ }
+]
diff --git a/src/tests/data/test_list_packets/list_mpi.txt b/src/tests/data/test_list_packets/list_mpi.txt
new file mode 100644
index 0000000..0e119e5
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_mpi.txt
@@ -0,0 +1,77 @@
+:armored input
+:off 0: packet header 0x9852 (tag 6, len 82)
+Public key packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 19 (ECDSA)
+ public key material:
+ ecc p: 515 bits, 0412e46ea69f97616a667aebbf125c4d5acdabea7fc048b563bd42ff15752d6f104a03d78babaf93902932ee1e10d18ef49075ac51b647f4b08e754fbdb7d2c4f7
+ ecc curve: NIST P-256
+ keyid: 0x23674f21b2441527
+:off 84: packet header 0xb408 (tag 13, len 8)
+UserID packet
+ id: ecc-p256
+:off 94: packet header 0x8894 (tag 2, len 148)
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 34, len 2
+ preferred aead algorithms: OCB, EAX (2, 1)
+ :type 21, len 5
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ features: 0x03 ( mdc aead )
+ :type 23, len 1
+ key server preferences
+ no-modify: 1
+ :type 33, len 21
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1549119463 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0xd727
+ signature material:
+ ecc r: 251 bits, 0560f6c11f1c305a1ad2ea95448159d18a511ac737b0897b6513cf5ffba10081
+ ecc s: 256 bits, c38b8450c8b1ecf937ceac13526cb12cc02bb5a0487c5661edfd237bfa6a8d2d
+:off 244: packet header 0xb856 (tag 14, len 86)
+Public subkey packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 18 (ECDH)
+ public key material:
+ ecdh p: 515 bits, 042c3380acb206f3900142ed48c2040ca6222e5308fe37b89b8067a74d1724e5d901a57e3c78ade56e3a2f5e7e189019de03bcf5e227b3dfe49d83a50500c155d9
+ ecdh curve: NIST P-256
+ ecdh hash algorithm: 8 (SHA256)
+ ecdh key wrap algorithm: 7
+ keyid: 0x37e285e9e9851491
+:off 332: packet header 0x8878 (tag 2, len 120)
+Signature packet
+ version: 4
+ type: 24 (Subkey Binding Signature)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ key flags: 0x0c ( encrypt_comm encrypt_storage )
+ :type 33, len 21
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1549119471 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0x64aa
+ signature material:
+ ecc r: 256 bits, 9eb882fdf0dbb9c504446f22c46ab9fbd1eb17229aa4bb1a27fe59e84153e3f9
+ ecc s: 256 bits, b96eaa084a1d2849089d69d23ce1feb446e39aa33a63bd1dc1d5b3d9891eaeef
diff --git a/src/tests/data/test_list_packets/list_raw.txt b/src/tests/data/test_list_packets/list_raw.txt
new file mode 100644
index 0000000..7c6cd12
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_raw.txt
@@ -0,0 +1,148 @@
+:armored input
+:off 0: packet header 0x9852 (tag 6, len 82)
+:off 2: packet contents (82 bytes)
+ 00000 | 04 5a c3 82 35 13 08 2a 86 48 ce 3d 03 01 07 02 | .Z..5..*.H.=....
+ 00016 | 03 04 12 e4 6e a6 9f 97 61 6a 66 7a eb bf 12 5c | ....n...ajfz...\
+ 00032 | 4d 5a cd ab ea 7f c0 48 b5 63 bd 42 ff 15 75 2d | MZ.....H.c.B..u-
+ 00048 | 6f 10 4a 03 d7 8b ab af 93 90 29 32 ee 1e 10 d1 | o.J.......)2....
+ 00064 | 8e f4 90 75 ac 51 b6 47 f4 b0 8e 75 4f bd b7 d2 | ...u.Q.G...uO...
+ 00080 | c4 f7 | ..
+
+Public key packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 19 (ECDSA)
+ public key material:
+ ecc p: 515 bits
+ ecc curve: NIST P-256
+ keyid: 0x23674f21b2441527
+:off 84: packet header 0xb408 (tag 13, len 8)
+:off 86: packet contents (8 bytes)
+ 00000 | 65 63 63 2d 70 32 35 36 | ecc-p256
+
+UserID packet
+ id: ecc-p256
+:off 94: packet header 0x8894 (tag 2, len 148)
+:off 96: packet contents (148 bytes)
+ 00000 | 04 13 13 08 00 3c 02 1b 03 05 0b 09 08 07 02 03 | .....<..........
+ 00016 | 22 02 01 06 15 0a 09 08 0b 02 04 16 02 03 01 02 | "...............
+ 00032 | 1e 03 02 17 80 16 21 04 b5 4f de bb b6 73 42 3a | ......!..O...sB:
+ 00048 | 5d 0a a5 44 23 67 4f 21 b2 44 15 27 05 02 5c 55 | ]..D#gO!.D.'..\U
+ 00064 | af e7 00 0a 09 10 23 67 4f 21 b2 44 15 27 d7 27 | ......#gO!.D.'.'
+ 00080 | 00 fb 05 60 f6 c1 1f 1c 30 5a 1a d2 ea 95 44 81 | ...`....0Z....D.
+ 00096 | 59 d1 8a 51 1a c7 37 b0 89 7b 65 13 cf 5f fb a1 | Y..Q..7..{e.._..
+ 00112 | 00 81 01 00 c3 8b 84 50 c8 b1 ec f9 37 ce ac 13 | .......P....7...
+ 00128 | 52 6c b1 2c c0 2b b5 a0 48 7c 56 61 ed fd 23 7b | Rl.,.+..H|Va..#{
+ 00144 | fa 6a 8d 2d | .j.-
+
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ :subpacket contents:
+ 00000 | 03 | .
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ :subpacket contents:
+ 00000 | 09 08 07 02 | ....
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 34, len 2
+ :subpacket contents:
+ 00000 | 02 01 | ..
+ preferred aead algorithms: OCB, EAX (2, 1)
+ :type 21, len 5
+ :subpacket contents:
+ 00000 | 0a 09 08 0b 02 | .....
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ :subpacket contents:
+ 00000 | 02 03 01 | ...
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ :subpacket contents:
+ 00000 | 03 | .
+ features: 0x03 ( mdc aead )
+ :type 23, len 1
+ :subpacket contents:
+ 00000 | 80 | .
+ key server preferences
+ no-modify: 1
+ :type 33, len 21
+ :subpacket contents:
+ 00000 | 04 b5 4f de bb b6 73 42 3a 5d 0a a5 44 23 67 4f | ..O...sB:]..D#gO
+ 00016 | 21 b2 44 15 27 | !.D.'
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ :subpacket contents:
+ 00000 | 5c 55 af e7 | \U..
+ signature creation time: 1549119463 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ :subpacket contents:
+ 00000 | 23 67 4f 21 b2 44 15 27 | #gO!.D.'
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0xd727
+ signature material:
+ ecc r: 251 bits
+ ecc s: 256 bits
+:off 244: packet header 0xb856 (tag 14, len 86)
+:off 246: packet contents (86 bytes)
+ 00000 | 04 5a c3 82 35 12 08 2a 86 48 ce 3d 03 01 07 02 | .Z..5..*.H.=....
+ 00016 | 03 04 2c 33 80 ac b2 06 f3 90 01 42 ed 48 c2 04 | ..,3.......B.H..
+ 00032 | 0c a6 22 2e 53 08 fe 37 b8 9b 80 67 a7 4d 17 24 | ..".S..7...g.M.$
+ 00048 | e5 d9 01 a5 7e 3c 78 ad e5 6e 3a 2f 5e 7e 18 90 | ....~<x..n:/^~..
+ 00064 | 19 de 03 bc f5 e2 27 b3 df e4 9d 83 a5 05 00 c1 | ......'.........
+ 00080 | 55 d9 03 01 08 07 | U.....
+
+Public subkey packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 18 (ECDH)
+ public key material:
+ ecdh p: 515 bits
+ ecdh curve: NIST P-256
+ ecdh hash algorithm: 8 (SHA256)
+ ecdh key wrap algorithm: 7
+ keyid: 0x37e285e9e9851491
+:off 332: packet header 0x8878 (tag 2, len 120)
+:off 334: packet contents (120 bytes)
+ 00000 | 04 18 13 08 00 20 02 1b 0c 16 21 04 b5 4f de bb | ..... ....!..O..
+ 00016 | b6 73 42 3a 5d 0a a5 44 23 67 4f 21 b2 44 15 27 | .sB:]..D#gO!.D.'
+ 00032 | 05 02 5c 55 af ef 00 0a 09 10 23 67 4f 21 b2 44 | ..\U......#gO!.D
+ 00048 | 15 27 64 aa 01 00 9e b8 82 fd f0 db b9 c5 04 44 | .'d............D
+ 00064 | 6f 22 c4 6a b9 fb d1 eb 17 22 9a a4 bb 1a 27 fe | o".j....."....'.
+ 00080 | 59 e8 41 53 e3 f9 01 00 b9 6e aa 08 4a 1d 28 49 | Y.AS.....n..J.(I
+ 00096 | 08 9d 69 d2 3c e1 fe b4 46 e3 9a a3 3a 63 bd 1d | ..i.<...F...:c..
+ 00112 | c1 d5 b3 d9 89 1e ae ef | ........
+
+Signature packet
+ version: 4
+ type: 24 (Subkey Binding Signature)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ :subpacket contents:
+ 00000 | 0c | .
+ key flags: 0x0c ( encrypt_comm encrypt_storage )
+ :type 33, len 21
+ :subpacket contents:
+ 00000 | 04 b5 4f de bb b6 73 42 3a 5d 0a a5 44 23 67 4f | ..O...sB:]..D#gO
+ 00016 | 21 b2 44 15 27 | !.D.'
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ :subpacket contents:
+ 00000 | 5c 55 af ef | \U..
+ signature creation time: 1549119471 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ :subpacket contents:
+ 00000 | 23 67 4f 21 b2 44 15 27 | #gO!.D.'
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0x64aa
+ signature material:
+ ecc r: 256 bits
+ ecc s: 256 bits
diff --git a/src/tests/data/test_list_packets/list_standard.txt b/src/tests/data/test_list_packets/list_standard.txt
new file mode 100644
index 0000000..b3c5001
--- /dev/null
+++ b/src/tests/data/test_list_packets/list_standard.txt
@@ -0,0 +1,77 @@
+:armored input
+:off 0: packet header 0x9852 (tag 6, len 82)
+Public key packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 19 (ECDSA)
+ public key material:
+ ecc p: 515 bits
+ ecc curve: NIST P-256
+ keyid: 0x23674f21b2441527
+:off 84: packet header 0xb408 (tag 13, len 8)
+UserID packet
+ id: ecc-p256
+:off 94: packet header 0x8894 (tag 2, len 148)
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 34, len 2
+ preferred aead algorithms: OCB, EAX (2, 1)
+ :type 21, len 5
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ features: 0x03 ( mdc aead )
+ :type 23, len 1
+ key server preferences
+ no-modify: 1
+ :type 33, len 21
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1549119463 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0xd727
+ signature material:
+ ecc r: 251 bits
+ ecc s: 256 bits
+:off 244: packet header 0xb856 (tag 14, len 86)
+Public subkey packet
+ version: 4
+ creation time: 1522762293 (??? ??? ?? ??:??:?? 2018)
+ public key algorithm: 18 (ECDH)
+ public key material:
+ ecdh p: 515 bits
+ ecdh curve: NIST P-256
+ ecdh hash algorithm: 8 (SHA256)
+ ecdh key wrap algorithm: 7
+ keyid: 0x37e285e9e9851491
+:off 332: packet header 0x8878 (tag 2, len 120)
+Signature packet
+ version: 4
+ type: 24 (Subkey Binding Signature)
+ public key algorithm: 19 (ECDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 27, len 1
+ key flags: 0x0c ( encrypt_comm encrypt_storage )
+ :type 33, len 21
+ issuer fingerprint: 0xb54fdebbb673423a5d0aa54423674f21b2441527 (20 bytes)
+ :type 2, len 4
+ signature creation time: 1549119471 (??? ??? ?? ??:??:?? 2019)
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x23674f21b2441527
+ lbits: 0x64aa
+ signature material:
+ ecc r: 256 bits
+ ecc s: 256 bits
diff --git a/src/tests/data/test_messages/data.enc.small-rsa b/src/tests/data/test_messages/data.enc.small-rsa
new file mode 100644
index 0000000..37b45df
--- /dev/null
+++ b/src/tests/data/test_messages/data.enc.small-rsa
@@ -0,0 +1,10 @@
+-----BEGIN PGP MESSAGE-----
+
+wcBLAzLESw+ElNMdAQf3SMPGDe5lDEHwnmoA4yZiSlECdXbfZ4rkMcWbxlcsmNLHR4LcMdWi9+fP
+pxTHZmaAVRCpMHpZ0TNt3d5TyaHD0/qitJLHAq847jE5HeS+naguPRKYdjCsXUpC5dd5+5Inj3YB
+PRMk7cP/0vgB66X0E8ebVB92Yp+Azs9/I1+hdFoAMGs9wfli0OkvIxySsjeASXj4W4v2WVl8eOOD
+LePAta6QPtvBT/9+ImMgBtV4A+zPuvofIMD25SeqPCTaWCjJs6oO/lUIfcByzoEQ2fGJvmkvQ0lU
+ZI1DfSmn2cuRQqr3CjY/9MfYtOR85rZUqBR9i8F19vtZU4J+QzYAkuOS0jYBM6DWkZ36znNchLYd
+gCR9Pl8XdI786rkQefmJh+2zjPkdcq/jx/df5T7kyJKW3uJm3Z/hs+Q=
+=I1qv
+-----END PGP MESSAGE-----
diff --git a/src/tests/data/test_messages/eddsa-zero-r.txt.sig b/src/tests/data/test_messages/eddsa-zero-r.txt.sig
new file mode 100644
index 0000000..9d6a77d
--- /dev/null
+++ b/src/tests/data/test_messages/eddsa-zero-r.txt.sig
Binary files differ
diff --git a/src/tests/data/test_messages/eddsa-zero-s.txt.sig b/src/tests/data/test_messages/eddsa-zero-s.txt.sig
new file mode 100644
index 0000000..71643e6
--- /dev/null
+++ b/src/tests/data/test_messages/eddsa-zero-s.txt.sig
Binary files differ
diff --git a/src/tests/data/test_messages/expired_signing_key-pub.asc b/src/tests/data/test_messages/expired_signing_key-pub.asc
new file mode 100644
index 0000000..aa4bb98
--- /dev/null
+++ b/src/tests/data/test_messages/expired_signing_key-pub.asc
@@ -0,0 +1,12 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xjMEYMN7SRYJKwYBBAHaRw8BAQdAetcGdMn/MmB7zoADCUGSZgc+DPxSFYFhRyg7eEE7PmfNE2V4
+cGlyZWRfc2lnbmluZ19rZXnCkQQTFggAORYhBHOUCytaUnScCXP5cDD8DXdpFbpEBQJgw3tJAhsD
+BQsJCAcCBhUICQoLAgUWAgMBAAUJAAACWAAKCRAw/A13aRW6RF/1AP9thoItoUQrO4aqDkc1sSWk
+LvggAKIevfeGxShwsy1hwwEAuSSAgIkERZMF71RquIcTfc2L9Iso5+RWitIPQtRNeADOOARgw3tJ
+EgorBgEEAZdVAQUBAQdA2zVnzDcD1PdHdk+DgcN68k8TXlM1PMPh02Sacdw5SVgDAQgHwogEGBYI
+ACYWIQRzlAsrWlJ0nAlz+XAw/A13aRW6RAUCYMN7SQIbDAUJAAACWAAUCRAw/A13aRW6RAkQMPwN
+d2kVukTsagD/cOpEKU5cLspBuIrPBdu8TPmVFUt0Tzf40pemubU6ZcoA/3304jooeYRI7ptW5yc9
+uA2+jfVqvl6QQHy8dRomRpgA
+=Jay7
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_messages/expired_signing_key-sec.asc b/src/tests/data/test_messages/expired_signing_key-sec.asc
new file mode 100644
index 0000000..b42114b
--- /dev/null
+++ b/src/tests/data/test_messages/expired_signing_key-sec.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xVgEYMN7SRYJKwYBBAHaRw8BAQdAetcGdMn/MmB7zoADCUGSZgc+DPxSFYFhRyg7eEE7PmcAAP4q
+NRaTRYF8nFvVfVItlGzpmYRpDabKnGt3DokcmGIQlw7OzRNleHBpcmVkX3NpZ25pbmdfa2V5wpEE
+ExYIADkWIQRzlAsrWlJ0nAlz+XAw/A13aRW6RAUCYMN7SQIbAwULCQgHAgYVCAkKCwIFFgIDAQAF
+CQAAAlgACgkQMPwNd2kVukRf9QD/bYaCLaFEKzuGqg5HNbElpC74IACiHr33hsUocLMtYcMBALkk
+gICJBEWTBe9UariHE33Ni/SLKOfkVorSD0LUTXgAx10EYMN7SRIKKwYBBAGXVQEFAQEHQNs1Z8w3
+A9T3R3ZPg4HDevJPE15TNTzD4dNkmnHcOUlYAwEIBwAA/0VAh1OhdOolrZxcimOrXkqGKx0SOS2a
+s0TxduWLngjKD4XCiAQYFggAJhYhBHOUCytaUnScCXP5cDD8DXdpFbpEBQJgw3tJAhsMBQkAAAJY
+ABQJEDD8DXdpFbpECRAw/A13aRW6ROxqAP9w6kQpTlwuykG4is8F27xM+ZUVS3RPN/jSl6a5tTpl
+ygD/ffTiOih5hEjum1bnJz24Db6N9Wq+XpBAfLx1GiZGmAA=
+=M1na
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_messages/expired_signing_sub-pub.asc b/src/tests/data/test_messages/expired_signing_sub-pub.asc
new file mode 100644
index 0000000..73e162f
--- /dev/null
+++ b/src/tests/data/test_messages/expired_signing_sub-pub.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+xjMEYMs/6xYJKwYBBAHaRw8BAQdAAECuFoeguDCcB5qGqAe66VI3ZNhVFgG55bfcGFnGQWrNE2V4
+cGlyZWRfc2lnbmluZ19zdWLCiwQTFggAMxYhBHZ5odVM8ncO7JQrEjYQf+4XGK5JBQJgyz/rAhsD
+BQsJCAcCBhUICQoLAgUWAgMBAAAKCRA2EH/uFxiuScpnAP9Ta0P+Pl1R/qBMM/gCaSnyaxBPJtBk
+OHzJcYK9KoJFXQEAzLF4YxUEoZNUlyMAnCWo2QBKeGt5HlBPONjL4oSV1g7OOARgyz/rEgorBgEE
+AZdVAQUBAQdApuvIqKp4Y2euY0wQI1Q2nKaB3VzpGmCo1z4pDD2+kEYDAQgHwngEGBYIACAWIQR2
+eaHVTPJ3DuyUKxI2EH/uFxiuSQUCYMs/6wIbDAAKCRA2EH/uFxiuSdLyAP9/YJFILtfbjgHiK3pN
+te8hSyL+DhDoBqah/U/09Ocd+QD+I1k8WzVwNUPbMrEAY/cYp4UZo86xUbF4598fG3LLjAnOMwRg
+yz/rFgkrBgEEAdpHDwEBB0DRNz0aTPozcpFN9z5VsJBXnzobWDZtNDgYiY156UnHmcLAHgQYFggA
+JhYhBHZ5odVM8ncO7JQrEjYQf+4XGK5JBQJgyz/rBQkAAAJYAhsCAGpfIAQZFggABgUCYMs/6wAK
+CRDZOkf9kxkf0ZM2AP9NOvgOixIJ+fkC/KFeGe/e93bo7mRVtQVVXU8zUfaHtQEA7cqSPdn1+wuV
+0piXcfnbn6pl7Ie733OqJeLVvGV/4wQJEDYQf+4XGK5JGi8A/jp2A636m9L6PWfY1fw+UghcJMfu
+vkbQlXyPHQ91JahjAQDoFB5Wjw4FiW1q9v1xZR3GAXO7hzfIQ8EQm877N1FxAw==
+=TgqB
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_messages/expired_signing_sub-sec.asc b/src/tests/data/test_messages/expired_signing_sub-sec.asc
new file mode 100644
index 0000000..b2c7426
--- /dev/null
+++ b/src/tests/data/test_messages/expired_signing_sub-sec.asc
@@ -0,0 +1,18 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+xVgEYMs/6xYJKwYBBAHaRw8BAQdAAECuFoeguDCcB5qGqAe66VI3ZNhVFgG55bfcGFnGQWoAAP9L
+qKEsA/3tfzCAwp8TlaBleuRBeWYWQdmZv06AuJmqRRD9zRNleHBpcmVkX3NpZ25pbmdfc3ViwosE
+ExYIADMWIQR2eaHVTPJ3DuyUKxI2EH/uFxiuSQUCYMs/6wIbAwULCQgHAgYVCAkKCwIFFgIDAQAA
+CgkQNhB/7hcYrknKZwD/U2tD/j5dUf6gTDP4Amkp8msQTybQZDh8yXGCvSqCRV0BAMyxeGMVBKGT
+VJcjAJwlqNkASnhreR5QTzjYy+KEldYOx10EYMs/6xIKKwYBBAGXVQEFAQEHQKbryKiqeGNnrmNM
+ECNUNpymgd1c6RpgqNc+KQw9vpBGAwEIBwABAOtfweyQTnm6yipf+OborMtiAEwNi2Mh5IP4B16M
+ZxWiENHCeAQYFggAIBYhBHZ5odVM8ncO7JQrEjYQf+4XGK5JBQJgyz/rAhsMAAoJEDYQf+4XGK5J
+0vIA/39gkUgu19uOAeIrek217yFLIv4OEOgGpqH9T/T05x35AP4jWTxbNXA1Q9sysQBj9xinhRmj
+zrFRsXjn3x8bcsuMCcdYBGDLP+sWCSsGAQQB2kcPAQEHQNE3PRpM+jNykU33PlWwkFefOhtYNm00
+OBiJjXnpSceZAAEAhqKJA7TT25vCpzjc2jd8dogpzjDPPCIZvgZ4iPFeQcEQPMLAHgQYFggAJhYh
+BHZ5odVM8ncO7JQrEjYQf+4XGK5JBQJgyz/rBQkAAAJYAhsCAGpfIAQZFggABgUCYMs/6wAKCRDZ
+Okf9kxkf0ZM2AP9NOvgOixIJ+fkC/KFeGe/e93bo7mRVtQVVXU8zUfaHtQEA7cqSPdn1+wuV0piX
+cfnbn6pl7Ie733OqJeLVvGV/4wQJEDYQf+4XGK5JGi8A/jp2A636m9L6PWfY1fw+UghcJMfuvkbQ
+lXyPHQ91JahjAQDoFB5Wjw4FiW1q9v1xZR3GAXO7hzfIQ8EQm877N1FxAw==
+=ArH/
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_messages/future.pgp b/src/tests/data/test_messages/future.pgp
new file mode 100644
index 0000000..efd6088
--- /dev/null
+++ b/src/tests/data/test_messages/future.pgp
Binary files differ
diff --git a/src/tests/data/test_messages/message-32k-crlf.txt b/src/tests/data/test_messages/message-32k-crlf.txt
new file mode 100644
index 0000000..d5ff21a
--- /dev/null
+++ b/src/tests/data/test_messages/message-32k-crlf.txt
@@ -0,0 +1,738 @@
+This is 32k message with interleaved LF/CRLF line endings, to test the correct work of lastcr variable during signed_src_update() in text mode.
+Some padding, generated by random text generator, will follow:
+
+Bonus timeo ad si ex eadem mirum potui. Aeternum vim hoc res ens ignorata lectores putandum.
+Re quaesita totamque ea refutent secundum in. Nia dum pla credo illis cogor solem illam.
+Illi esto fato nudi idem mo tius re. Utramque credatur spectant si mo cavendum compages eo.
+In ii inter in putem fingi de. Attendam tractatu id vigiliam et ha et aliaeque.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum facilius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quaerere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
+
+Suam ex in quos actu unde foco se aspi to.
+Ita fas deest tenus nec saepe situs caput.
+Profundum cui nam uti quantitas sit cognoscam tribuebam.
+Rea afferri nos ipsarum tur visione hoc animali frigida.
+Opportune de summopere de immittant extitisse gi quadratam.
+Indubitati fundamenta realitatis cogitantur ab is propugnent perciperem in et.
+Errores mox has erroris petitis creando.
+Vi pauciora potestis me experior existens ab.
+Pedem ei cupio adsit vi solam ut certe an calor.
+Simulque ac singulas emergere insomnia ii facilius circulum ha de.
+
+Automata in ut pluribus agnoscam si.
+Vestro videor ignoro mendax duo sae pla tactum.
+Etc sex mox verarum existat vel indutum.
+Conspicuum re spectentur et et scripturas to praecipuas.
+Aliquid maximum ut et fuerint to sperare.
+Ut fundamenta et id perspicuas attendendo.
+
+At ad momentis secundum cogitare illasque ad is periculi.
+Ac dispari organis is eo liquida si vigilia ignotas aetatem.
+Delusisse spontaneo immittere explorant ea medicinam et examinavi.
+At articulo ac veritate cognosci habendum ii tangitur physicae.
+Deus plus fuse vi ii et an nova. Ullum an clara se tamen coeco.
+At funditus ut exemplum existens profecta potuisse imaginer.
+Ecce ob nisi gi ipso actu hinc soni et.
+Ac si praesenti et id distingui extitisse.
+Venturum figmenta indiciis id in chimerae se arbitror at.
+
+Existentia quascunque ne collabitur an is ii.
+Vidi loco quam fal etc qua.
+Memoria usitata sit usitate incumbo uno his conabor liberet.
+Dubitem dei defixus ibi junctae est est homines respexi alterum.
+Ex at debent tritam essent vestra.
+Effectibus imaginaria continetur agi jam verumtamen agnoscerem.
+Volens postea dicunt sap hac nomine.
+Hinc et quem fore ab post scio ne fuse im.
+Eo mo ut meditabor occasione societati gi.
+An persuadet videlicet ii ingeniosi.
+
+Negat prava agi fit rebus tanti utens.
+Ut ei ex separatum meditabor ne praeterea tribuebam somniemus.
+Vereorque immittant admovetur vos tam etc dubitandi infinitum ima cunctatus.
+Tertia ibidem deponi is somnia si id.
+Duo rei continetur persuadere potentiale inchoandum sit uno attendamus praecipuis.
+Agi quantitas vos extendere videlicet ita persuasus delaberer lus usurpabam.
+Perfacile tractatur ad ad delibarem admovetur explorant occurrere.
+
+Si quare ha eodem ex porro.
+Possumne hos sum aeternum faci
+lius perpauca vos.
+Et de partem co operis scirem secius gustum pulses.
+Assentiar complexus consistit at partiales id denegante.
+Fortassis im si dubitandi importare requirunt studebunt attributa.
+Chimerae sum importat sex usu ego recensui inanimes attendam noluisse.
+Solvendae inveniant jactantur ad imaginari at du se.
+Mearum ea mo negavi ut tamque ut.
+
+Vul student quaeque brachia nos divelli.
+Ha ex alterius locusque insidias formalis ii mutuatur judiciis.
+Is revocare posuisse to possumne diversas arbitror obturabo.
+Eos voce inge ente ibi ideo enim.
+Volo vera nemo tam nolo more alia tum.
+Hic confirmari manifestum meo formaliter.
+Cui plane uti factu sumne aut multi timeo fas.
+
+Adverten quae
+rere recenseo ope hac.
+Interitum an recurrunt in aliquibus continent.
+Scio erat cui ausi ullo qua fiat.
+Eos fidei reges nec essem lor fidam hoc.
+Ima praeclare dubitarem sui objectiva age est.
+Si nempe de is re saepe alias.
+
+Sopitum eam per corpora allatae relabor.
+Respondebo sequentium at religionis majestatis in imaginarer.
+Hae vetus intra sciri age inter cum.
+Certus et possim altera nescio at inanes in multae.
+Ex luce isti boni si si ulla.
+Eo soni et duce ausi an ut.
+Si agendis optarem im divelli vestiri mo ea.
+Facultatem distribuam cohibendam sufficeret lor hic pauperrimi.
+Detractis conflatos aut fruebatur sic quadratam consuetae immittant.
diff --git a/src/tests/data/test_messages/message-32k-crlf.txt.gpg b/src/tests/data/test_messages/message-32k-crlf.txt.gpg
new file mode 100644
index 0000000..e693980
--- /dev/null
+++ b/src/tests/data/test_messages/message-32k-crlf.txt.gpg
Binary files differ
diff --git a/src/tests/data/test_messages/message-32k-crlf.txt.sig b/src/tests/data/test_messages/message-32k-crlf.txt.sig
new file mode 100644
index 0000000..cd82a9d
--- /dev/null
+++ b/src/tests/data/test_messages/message-32k-crlf.txt.sig
Binary files differ
diff --git a/src/tests/data/test_messages/message-trailing-cr.txt b/src/tests/data/test_messages/message-trailing-cr.txt
new file mode 100644
index 0000000..72c9d3d
--- /dev/null
+++ b/src/tests/data/test_messages/message-trailing-cr.txt
@@ -0,0 +1,3 @@
+Text line with 3 trailing CR characters
+ Another line with CR at the beginning, should not be stripped.
+Last line with trailing CR and no LF. \ No newline at end of file
diff --git a/src/tests/data/test_messages/message-trailing-cr.txt.sig-text b/src/tests/data/test_messages/message-trailing-cr.txt.sig-text
new file mode 100644
index 0000000..8c49611
--- /dev/null
+++ b/src/tests/data/test_messages/message-trailing-cr.txt.sig-text
Binary files differ
diff --git a/src/tests/data/test_messages/message.4k-long-lines b/src/tests/data/test_messages/message.4k-long-lines
new file mode 100644
index 0000000..1d11013
--- /dev/null
+++ b/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/src/tests/data/test_messages/message.4k-long-lines.asc b/src/tests/data/test_messages/message.4k-long-lines.asc
new file mode 100644
index 0000000..1c4b45d
--- /dev/null
+++ b/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/src/tests/data/test_messages/message.aead-last-zero-chunk.enc b/src/tests/data/test_messages/message.aead-last-zero-chunk.enc
new file mode 100644
index 0000000..16b84ea
--- /dev/null
+++ b/src/tests/data/test_messages/message.aead-last-zero-chunk.enc
Binary files differ
diff --git a/src/tests/data/test_messages/message.aead-last-zero-chunk.enc-ocb b/src/tests/data/test_messages/message.aead-last-zero-chunk.enc-ocb
new file mode 100644
index 0000000..a1d6a69
--- /dev/null
+++ b/src/tests/data/test_messages/message.aead-last-zero-chunk.enc-ocb
Binary files differ
diff --git a/src/tests/data/test_messages/message.aead-last-zero-chunk.txt b/src/tests/data/test_messages/message.aead-last-zero-chunk.txt
new file mode 100644
index 0000000..379194a
--- /dev/null
+++ b/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/src/tests/data/test_messages/message.compr-encr.31-rounds b/src/tests/data/test_messages/message.compr-encr.31-rounds
new file mode 100644
index 0000000..8736960
--- /dev/null
+++ b/src/tests/data/test_messages/message.compr-encr.31-rounds
@@ -0,0 +1,45 @@
+-----BEGIN PGP MESSAGE-----
+
+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
+=kt6o
+-----END PGP MESSAGE-----
diff --git a/src/tests/data/test_messages/message.compr-encr.32-rounds b/src/tests/data/test_messages/message.compr-encr.32-rounds
new file mode 100644
index 0000000..f7ece09
--- /dev/null
+++ b/src/tests/data/test_messages/message.compr-encr.32-rounds
@@ -0,0 +1,51 @@
+-----BEGIN PGP MESSAGE-----
+
+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
+9UJ26CiJ
+=LABm
+-----END PGP MESSAGE-----
diff --git a/src/tests/data/test_messages/message.compr.128-rounds b/src/tests/data/test_messages/message.compr.128-rounds
new file mode 100644
index 0000000..8c6fc72
--- /dev/null
+++ b/src/tests/data/test_messages/message.compr.128-rounds
@@ -0,0 +1,320 @@
+-----BEGIN PGP MESSAGE-----
+
+yO0BACZA2b/I7QNCWmg5MUFZJlNZ1ZzQsAAamH//////////////////////////////////////
+///////gK8+73ze7tPu889c+9vre7N13Tu+xe9993fdvV3bfK7Z3e3t9lbnTve7fde0fHt7e73d8
+31u2++3l3d8d7r33t6bvvr7X3ffetXucffcdTb6vtd768voze9vu7tt93bru++u33HT5r7fN77Wv
+nvte+vdr52Y+T2+3d2+uas3ve+33z3vXfe1932+u533Tu93vvvu7W7r2r5u2mbIVPYNEyZGJphNp
+MTJ6BGnppJ4CelPEwAAEyYEwAJgTRpqeNEwGhMCZMjCYmTRkxMTaCGJkZMJmEaZMmQAAaTaYVRCp
++DIGiE8TCYptMp4mCnhDCep6J6mATBPQCYI0wk20Jkyangp4JkzQCMp4U8ENNkNDIaANMmTRgRpo
+jJiMKeAnqYSeAiiVP0wp41MJk2mgU/QTaE0yPSYmCep6aEyZMp5GNTE2iNJjJlNgpmp5MMjTRiMp
+jIwRpiNNTTEaaeplPTIxPKYGk9Qxkk9Mp4JiaaamGENFEKn4psBpNMAaJpiYEYJgBE2aJgAmAYCG
+Q0NDRkNTBNE9oJhGAU/UaPSYCJ6ZNGBGaCaegEybU9JkYATamEbUwAqIVP9JkZTybUwm0BGmmKnt
+pkyAwExGmiY0YU2qn5MpPaMhGwNEyaNU9iaMFPU3o0m0Gp6CeppqemmJgRmpmiNoCGaaEyGmTIzR
+NNJ+kxTwJpRCp/iaMVPMpjExNT0xNDIBNqZTYmDU1PNAU09MaCYFP0TU9J4TTTTBMAJphNNPIKbR
+keiYACY1MmjCaMCTaaaaZPQk08CaDam0TDSmplNXdh3IAS7TZoHkZy/uk3Xr/nR0ZQ8gj4qaHGFW
+cUmlmYpjXY6LAi4oH471wRVPllhntuM0SIU73j2u07owbUe9YCAcjkq+rIgDd6PuMPFEyatLiPvo
+FnjzVN6CsE88CcW8mFOmdbIEZWeOb1asTJ+XQZgMY480fsYR/E7Q3Xd0wbSMMjRgjkeDVVMDZ2fR
+jmmy6HV50yGJuk3Eb7oL7QtDykYnH5FmMYL7mKu57Et05ye5jHF517WPlP2SesdTMzWOCxRP+wdl
+THJRYupFLCZVSeG+to/wTmQ+mzBoIdGCZRt7GO7sL3NzWdb+yMk9EUGmDD/DIopx8aL6mTx5LguO
+uvX83YxkAaDQMJm0+Qt4A2VckYYXxjnMQyDgcoQs68gwu5b9SQgwSJiLP6m1fUysllyWuGyKKIfb
+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
+9lZNuD/4u5IpwoSGrOaFgA==
+=VHv5
+-----END PGP MESSAGE-----
diff --git a/src/tests/data/test_messages/message.enc-password b/src/tests/data/test_messages/message.enc-password
new file mode 100644
index 0000000..36e5a29
--- /dev/null
+++ b/src/tests/data/test_messages/message.enc-password
Binary files differ
diff --git a/src/tests/data/test_messages/message.sig.asc.malf b/src/tests/data/test_messages/message.sig.asc.malf
new file mode 100644
index 0000000..6153ef9
--- /dev/null
+++ b/src/tests/data/test_messages/message.sig.asc.malf
@@ -0,0 +1,9 @@
+-----BEGIN PGP SIGNATURE-----
+Version: rnp 0.9.0+git20191027.478.8db9a76
+
+wrkEAQEIACMWIQTpWjy/WDqoCizMU6p7xnCbFcI6SgUCXbK6xwUDAAAAAAAKCRB7xnCbFcI6SjvL
+BP9OOzkgc3JwM2X4uykxu4kvmRWuhKY4hZegu7Nt/eBQm24aNZbHt6z1ZOfMBJGzDzJjAtFdDZ5O
+9LqAvWEf1kqLT2u5v2TB5LHA2GWFNK3WxTapceeWOo+3Q2Ssky0tUBxazHBE14WOdM+MPQevTwtw
+C2Q+p06E1lE+SiIa+KP1Og==
+=qsNW
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_messages/message.text-sig-crcr b/src/tests/data/test_messages/message.text-sig-crcr
new file mode 100644
index 0000000..0462a2c
--- /dev/null
+++ b/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/src/tests/data/test_messages/message.text-sig-crcr.sig b/src/tests/data/test_messages/message.text-sig-crcr.sig
new file mode 100644
index 0000000..e7118ae
--- /dev/null
+++ b/src/tests/data/test_messages/message.text-sig-crcr.sig
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt b/src/tests/data/test_messages/message.txt
new file mode 100644
index 0000000..4ac8083
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt
@@ -0,0 +1,3 @@
+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. \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.txt.2sigs b/src/tests/data/test_messages/message.txt.2sigs
new file mode 100644
index 0000000..201a3fb
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.2sigs
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.2sigs-2 b/src/tests/data/test_messages/message.txt.2sigs-2
new file mode 100644
index 0000000..fa1a2df
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.2sigs-2
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.asc b/src/tests/data/test_messages/message.txt.asc
new file mode 100644
index 0000000..5c5254f
--- /dev/null
+++ b/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/src/tests/data/test_messages/message.txt.clear-2-sigs b/src/tests/data/test_messages/message.txt.clear-2-sigs
new file mode 100644
index 0000000..39e9cb2
--- /dev/null
+++ b/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/src/tests/data/test_messages/message.txt.clear-2-sigs-2 b/src/tests/data/test_messages/message.txt.clear-2-sigs-2
new file mode 100644
index 0000000..0b2a7ae
--- /dev/null
+++ b/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/src/tests/data/test_messages/message.txt.cleartext-malf b/src/tests/data/test_messages/message.txt.cleartext-malf
new file mode 100644
index 0000000..8f23c30
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.cleartext-malf
@@ -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-----
+Version: rnp 0.9.0+git20191027.478.8db9a76
+
+wrkEAQEIACMWIQTpWjy/WDqoCizMU6p7xnCbFcI6SgUCXbK6xwUDAAAAAAAKCRB7xnCbFcI6SjvL
+BP9OOzkgc3JwM2X4uykxu4kvmRWuhKY4hZegu7Nt/eBQm24aNZbHt6z1ZOfMBJGzDzJjAtFdDZ5O
+9LqAvWEf1kqLT2u5v2TB5LHA2GWFNK3WxTapceeWOo+3Q2Ssky0tUBxazHBE14WOdM+MPQevTwtw
+C2Q+p06E1lE+SiIa+KP1Og==
+=qsNW
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_messages/message.txt.cleartext-nosig b/src/tests/data/test_messages/message.txt.cleartext-nosig
new file mode 100644
index 0000000..92d58a6
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.cleartext-nosig
@@ -0,0 +1,8 @@
+-----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-----
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_messages/message.txt.cleartext-signed b/src/tests/data/test_messages/message.txt.cleartext-signed
new file mode 100644
index 0000000..0eaba67
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.cleartext-signed
@@ -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-----
+Version: rnp 0.9.0+git20191024.471.5201802
+
+wrkEAQEIACMWIQTpWjy/WDqoCizMU6p7xnCbFcI6SgUCXbK6xwUDAAAAAAAKCRB7xnCbFcI6SjvL
+A/9OOzkgc3JwM2X4uykxu4kvmRWuhKY4hZegu7Nt/eBQm24aNZbHt6z1ZOfMBJGzDzJjAtFdDZ5O
+9LqAvWEf1kqLT2u5v2TB5LHA2GWFNK3WxTapceeWOo+3Q2Ssky0tUBxazHBE14WOdM+MPQevTwtw
+C2Q+p06E1lE+SiIa+KP1Og==
+=AFtl
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_messages/message.txt.cleartext-signed-nonewline b/src/tests/data/test_messages/message.txt.cleartext-signed-nonewline
new file mode 100644
index 0000000..7020fcc
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.cleartext-signed-nonewline
@@ -0,0 +1,14 @@
+-----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
+
+wrkEAQEIACMWIQTpWjy/WDqoCizMU6p7xnCbFcI6SgUCXbK6xwUDAAAAAAAKCRB7xnCbFcI6SjvL
+A/9OOzkgc3JwM2X4uykxu4kvmRWuhKY4hZegu7Nt/eBQm24aNZbHt6z1ZOfMBJGzDzJjAtFdDZ5O
+9LqAvWEf1kqLT2u5v2TB5LHA2GWFNK3WxTapceeWOo+3Q2Ssky0tUBxazHBE14WOdM+MPQevTwtw
+C2Q+p06E1lE+SiIa+KP1Og==
+=AFtl
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_messages/message.txt.crlf b/src/tests/data/test_messages/message.txt.crlf
new file mode 100644
index 0000000..bef4fdd
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.crlf
@@ -0,0 +1,3 @@
+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. \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.txt.empty.sig b/src/tests/data/test_messages/message.txt.empty.sig
new file mode 100644
index 0000000..920b2b7
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.empty.sig
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-3key-2p b/src/tests/data/test_messages/message.txt.enc-3key-2p
new file mode 100644
index 0000000..f3d3b1b
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-3key-2p
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-aead-eax b/src/tests/data/test_messages/message.txt.enc-aead-eax
new file mode 100644
index 0000000..9935fa9
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-aead-eax
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-aead-eax-malf b/src/tests/data/test_messages/message.txt.enc-aead-eax-malf
new file mode 100644
index 0000000..132d15f
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-aead-eax-malf
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-aead-ocb b/src/tests/data/test_messages/message.txt.enc-aead-ocb
new file mode 100644
index 0000000..66f319e
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-aead-ocb
@@ -0,0 +1 @@
+ŒE ¼ÊàT#\íïY¸{Ǥç¸×!tBgÃŶ°’7%GùZ ,äö»"R9˜\>']’Ÿ©È BVÈØ?Aª‘]ÙÔÀ Á‚qb²8jœdÇsuÃé²~fËìü‡» ÿ†yŸî ˜ÒA¦ÅÇ wÞ)Jè¦ZΛ:]¾ -aÌôÀûcq“ ªªsÝ „³1ÎQ3k–w:yÿá˜HHÿàš]°4°[©ÑU¶Kþ[r×›Ä>ç*ÓUˆ}‚£>ÚIûKöÌãî^¼8,¶Vá Ü}à—I /b-/Ê€^MÍ/3ÒJà9…„æAÿ5¥Ø-$–«2è*“eÓéË9öræ±áÑá½@ñ-;u \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.txt.enc-aead-ocb-aes b/src/tests/data/test_messages/message.txt.enc-aead-ocb-aes
new file mode 100644
index 0000000..d30e185
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-aead-ocb-aes
@@ -0,0 +1,2 @@
+ÃE½ßµ×mèhÿ§i:Z”–*"ÖŸ­k3ìV1öBÐJŽq5âk GªM·ýd¸Ä›~,·ÿ^,nq>MücÔ² òÖå7Å”Y¶v{ñS{בä.M‡=,ƒn%2G¤l-éØ´Èäh (Ãoº¶J]×F¤¼²UAÀ~ôN_šÂ JZ‰IÍB·}bsº3îÌv•».ÅR¦âmd~óÍ!ÄxLÕa_mð
+zž_âΩJ·,9Gý˜šÄ.PZæÇÄ.8äŒ~½±=0Üø¨‚é‡þ.ã íVŸƒ~å®R: ª¸èŽÔe)Î&à: \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.txt.enc-aead-ocb-malf b/src/tests/data/test_messages/message.txt.enc-aead-ocb-malf
new file mode 100644
index 0000000..0d3ee0c
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-aead-ocb-malf
@@ -0,0 +1 @@
+ŒE ¼ÊàT#\íïY¸{Ǥç¸×!tBgÃŶ°’7%GùZ ,äö»"R9˜\>']’Ÿ©È BVÈØ?Aª‘]ÙÔÀ Á‚qb²8jœdÇsuÃé²~fËìü‡» ÿ†yŸî ˜ÒA¦ÅÇ wÞ)Jè¦ZΛ:]¾ -aÌôÀûcq“ ªªsÝ „³1ÎQ3k–w:yÿá˜HHÿàš]°4°[©ÑU¶Kþ[r×›Ä>ç*ÓUˆ}‚£>ÚIûKöÌãî^¼8,¶Vá Ü}à—I /b-/Ê€^MÍ/3ÒJà9…„æAÿ5¥Ø-$–«2è*“eÓéË9öræ±áÒá½@ñ-;u \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.txt.enc-eg-bad b/src/tests/data/test_messages/message.txt.enc-eg-bad
new file mode 100644
index 0000000..999765c
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-eg-bad
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-eg-bad2 b/src/tests/data/test_messages/message.txt.enc-eg-bad2
new file mode 100644
index 0000000..ec8ffc6
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-eg-bad2
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-hidden-1 b/src/tests/data/test_messages/message.txt.enc-hidden-1
new file mode 100644
index 0000000..c5a0329
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-hidden-1
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-hidden-2 b/src/tests/data/test_messages/message.txt.enc-hidden-2
new file mode 100644
index 0000000..e5e1c53
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-hidden-2
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-malf-1 b/src/tests/data/test_messages/message.txt.enc-malf-1
new file mode 100644
index 0000000..0d2b9bb
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-malf-1
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-malf-2 b/src/tests/data/test_messages/message.txt.enc-malf-2
new file mode 100644
index 0000000..2e9a6a2
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-malf-2
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-malf-3 b/src/tests/data/test_messages/message.txt.enc-malf-3
new file mode 100644
index 0000000..7ab1711
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-malf-3
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-malf-4 b/src/tests/data/test_messages/message.txt.enc-malf-4
new file mode 100644
index 0000000..020812a
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-malf-4
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-malf-5 b/src/tests/data/test_messages/message.txt.enc-malf-5
new file mode 100644
index 0000000..9f56341
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-malf-5
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-mdc b/src/tests/data/test_messages/message.txt.enc-mdc
new file mode 100644
index 0000000..c9461ea
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-mdc
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-no-mdc b/src/tests/data/test_messages/message.txt.enc-no-mdc
new file mode 100644
index 0000000..2eeeb17
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-no-mdc
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-sign-25519 b/src/tests/data/test_messages/message.txt.enc-sign-25519
new file mode 100644
index 0000000..84bc275
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-sign-25519
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.enc-wrong-alg b/src/tests/data/test_messages/message.txt.enc-wrong-alg
new file mode 100644
index 0000000..bb05e6c
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.enc-wrong-alg
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.encrypted b/src/tests/data/test_messages/message.txt.encrypted
new file mode 100644
index 0000000..93df3d3
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.encrypted
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.literal b/src/tests/data/test_messages/message.txt.literal
new file mode 100644
index 0000000..f959cb7
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.literal
@@ -0,0 +1,3 @@
+¬b message.txt_»4This is test message to be signed, and/or encrypted, cleartext signed and detached signed.
+It will use keys from keyrings/1.
+End of message. \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.txt.marker b/src/tests/data/test_messages/message.txt.marker
new file mode 100644
index 0000000..0fdc631
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.marker
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.marker.asc b/src/tests/data/test_messages/message.txt.marker.asc
new file mode 100644
index 0000000..47ca17a
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.marker.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP MESSAGE-----
+
+qANQR1DBjAMe1j7lb63DTQED/iTsYcGh58LQLzmAIePkOmjo3ygptHPPheOIEMaxfqTImy0S1/I3
+/YV5eYeNhpnorwqPII3sF/JhVE0NL5WvcrTCbZNnoQZZrfa/l7bTrkkaxwoL6Wj+14YOCSz1hk7+
+8ECZi0oIKZJmLycmZn0lmp/xNScivXKqgrC0bwYP9XC+0sC1Ad1CxI2hLFNP0KMgv49nX2syJh3G
+WlyKnO+dwV8PtDa5a5jVjrq2wCk6AQIh6xHDgvQctD9cD98gHSwtcTpxlnFX7MpHo8BP+EuH/1dX
+ABkRLjleP1idMFOyTi1ZIn42yk8ttZENgxmhDgR8Pc2VYbnPWEeYrf+oGhuZvp3Jz6XDy4yAIRVg
+cpXDzaJvcBFTpdF5ZiXzg8fnFR+kgFGfSrS7u8dXdX1PMn6r7GUkkpADZCXNYrjxhThNcnoIGgPb
+7S7f8Jh3DKg3jamgp8vaIVLcXqSSMtY67uJA8qU8ATNn7gg+E4MWGobSdZvLqtibPUt6QAtYfoYD
+OjzcOHbVfHv3G8WKkAmRn8wS2tcvqHFuB2fJokrWFEUM51T9eZGagxuVUzChmSMOt94GlYigoxgx
+kUutw6HoIqQpbDQKd8H3JwgWOQxEkRz6C9T8uevIrR0ZUeil2pOXsEMYeVkUEp7P4scPyrbuZB90
+62ZLmraR/0BUnA==
+=ciVs
+-----END PGP MESSAGE-----
diff --git a/src/tests/data/test_messages/message.txt.marker.malf b/src/tests/data/test_messages/message.txt.marker.malf
new file mode 100644
index 0000000..cfb2de0
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.marker.malf
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.pkesk-skesk-v10 b/src/tests/data/test_messages/message.txt.pkesk-skesk-v10
new file mode 100644
index 0000000..7703d89
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.pkesk-skesk-v10
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.pkesk-skesk-v10-only b/src/tests/data/test_messages/message.txt.pkesk-skesk-v10-only
new file mode 100644
index 0000000..d12b0c8
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.pkesk-skesk-v10-only
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.sig b/src/tests/data/test_messages/message.txt.sig
new file mode 100644
index 0000000..3b5a814
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.sig
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.sig-text b/src/tests/data/test_messages/message.txt.sig-text
new file mode 100644
index 0000000..15fb330
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.sig-text
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.sig.malf b/src/tests/data/test_messages/message.txt.sig.malf
new file mode 100644
index 0000000..b1c6d6c
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.sig.malf
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.sig.wrong-mpi-bitlen b/src/tests/data/test_messages/message.txt.sig.wrong-mpi-bitlen
new file mode 100644
index 0000000..e19da04
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.sig.wrong-mpi-bitlen
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.sign-small-eddsa-x b/src/tests/data/test_messages/message.txt.sign-small-eddsa-x
new file mode 100644
index 0000000..364de84
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.sign-small-eddsa-x
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed b/src/tests/data/test_messages/message.txt.signed
new file mode 100644
index 0000000..44fa5dd
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-2-2-onepass b/src/tests/data/test_messages/message.txt.signed-2-2-onepass
new file mode 100644
index 0000000..56d45aa
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-2-2-onepass
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-2-2-onepass-v10 b/src/tests/data/test_messages/message.txt.signed-2-2-onepass-v10
new file mode 100644
index 0000000..7c25dab
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-2-2-onepass-v10
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-2-2-sig-v10 b/src/tests/data/test_messages/message.txt.signed-2-2-sig-v10
new file mode 100644
index 0000000..dbddcc4
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-2-2-sig-v10
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-2-2-sig-v10-2 b/src/tests/data/test_messages/message.txt.signed-2-2-sig-v10-2
new file mode 100644
index 0000000..fcd3758
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-2-2-sig-v10-2
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-2-onepass b/src/tests/data/test_messages/message.txt.signed-2-onepass
new file mode 100644
index 0000000..49000b7
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-2-onepass
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-class19 b/src/tests/data/test_messages/message.txt.signed-class19
new file mode 100644
index 0000000..f679a19
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-class19
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-encrypted b/src/tests/data/test_messages/message.txt.signed-encrypted
new file mode 100644
index 0000000..aba797d
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-encrypted
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-expired-key b/src/tests/data/test_messages/message.txt.signed-expired-key
new file mode 100644
index 0000000..5909222
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-expired-key
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-expired-sub b/src/tests/data/test_messages/message.txt.signed-expired-sub
new file mode 100644
index 0000000..d1103fd
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-expired-sub
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-md5-after b/src/tests/data/test_messages/message.txt.signed-md5-after
new file mode 100644
index 0000000..348c9aa
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-md5-after
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-md5-before b/src/tests/data/test_messages/message.txt.signed-md5-before
new file mode 100644
index 0000000..ac9160b
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-md5-before
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-no-z b/src/tests/data/test_messages/message.txt.signed-no-z
new file mode 100644
index 0000000..b9e0fe6
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-no-z
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-no-z-malf b/src/tests/data/test_messages/message.txt.signed-no-z-malf
new file mode 100644
index 0000000..91c00ad
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-no-z-malf
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-sha1-after b/src/tests/data/test_messages/message.txt.signed-sha1-after
new file mode 100644
index 0000000..17099ad
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-sha1-after
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-sha1-before b/src/tests/data/test_messages/message.txt.signed-sha1-before
new file mode 100644
index 0000000..b005446
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-sha1-before
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-sym-none-z b/src/tests/data/test_messages/message.txt.signed-sym-none-z
new file mode 100644
index 0000000..9497705
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-sym-none-z
@@ -0,0 +1,2 @@
+à  KÉ1F0gàúÒÀÖ|ºׄòTãRB]ˆGÝzí ·ó(“ïhªÅ€¥³—û¼#UÕsÞŒg{°ÄÚŽÇ*Cnm_q‰^#k ³t–ó9P2}‹¸Ùó`ùºXKRZZ+hD‰#æ^ *ÙmU‰´³%/ñ2yáyÖÈäÁªÉVÐë<¾>@ YTÕi ¤S¥î4;Ä£¸Šà@«>ÒØšÌ4 <˜Qï¨AÊx~è´æ¦jæä‹þ&"‰¤ÅÜp-j‚AnùŽ_°¸hœwœ»%Äƺ™[,úK_úŒÆ ^,%¼Üœ÷­{5ܟ׮
+¦K¤Þ?µþ”BÆÆMŒCn&Ì~À!x&ß•­~õ‘„÷,÷Ò"|× Ò¨™Oàg~•µu>?&k­ÖœÌõªy¢ã&>øÀl,.„`çäRü$=V$^†„Sí?\Q $’p\†ÍYÞ÷˜øNŽà‹fñÿât;ËÇésóßsØT† «~6rè•6±ûÙß|ö]ï);1*éÀÅp"þíZ—r!Ëarêô \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.txt.signed-unknown-onepass-hash b/src/tests/data/test_messages/message.txt.signed-unknown-onepass-hash
new file mode 100644
index 0000000..10288c5
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-unknown-onepass-hash
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-wrong-onepass b/src/tests/data/test_messages/message.txt.signed-wrong-onepass
new file mode 100644
index 0000000..7fa651c
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-wrong-onepass
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed-wrong-onepass-hash b/src/tests/data/test_messages/message.txt.signed-wrong-onepass-hash
new file mode 100644
index 0000000..fc827cd
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed-wrong-onepass-hash
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed.crit-notation b/src/tests/data/test_messages/message.txt.signed.crit-notation
new file mode 100644
index 0000000..702473c
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed.crit-notation
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed.invsig b/src/tests/data/test_messages/message.txt.signed.invsig
new file mode 100644
index 0000000..9e54c84
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed.invsig
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed.malfsig b/src/tests/data/test_messages/message.txt.signed.malfsig
new file mode 100644
index 0000000..2d0988a
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed.malfsig
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed.md5 b/src/tests/data/test_messages/message.txt.signed.md5
new file mode 100644
index 0000000..8e921cf
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed.md5
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed.nosig b/src/tests/data/test_messages/message.txt.signed.nosig
new file mode 100644
index 0000000..5bc0e39
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed.nosig
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed.sha1 b/src/tests/data/test_messages/message.txt.signed.sha1
new file mode 100644
index 0000000..fc5df6c
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed.sha1
Binary files differ
diff --git a/src/tests/data/test_messages/message.txt.signed.unknown b/src/tests/data/test_messages/message.txt.signed.unknown
new file mode 100644
index 0000000..45ab185
--- /dev/null
+++ b/src/tests/data/test_messages/message.txt.signed.unknown
Binary files differ
diff --git a/src/tests/data/test_messages/message.wrong-armor.asc b/src/tests/data/test_messages/message.wrong-armor.asc
new file mode 100644
index 0000000..b9f669d
--- /dev/null
+++ b/src/tests/data/test_messages/message.wrong-armor.asc
@@ -0,0 +1,3 @@
+-----BEGIN PGP WEIRD MESSAGE---
+Something wrong with this message.
+-----END PGP WEIRD MESSAGE \ No newline at end of file
diff --git a/src/tests/data/test_messages/message.zlib-quine b/src/tests/data/test_messages/message.zlib-quine
new file mode 100644
index 0000000..dcbaabe
--- /dev/null
+++ b/src/tests/data/test_messages/message.zlib-quine
Binary files differ
diff --git a/src/tests/data/test_messages/message_mdc_8k_1.pgp b/src/tests/data/test_messages/message_mdc_8k_1.pgp
new file mode 100644
index 0000000..659561e
--- /dev/null
+++ b/src/tests/data/test_messages/message_mdc_8k_1.pgp
Binary files differ
diff --git a/src/tests/data/test_messages/message_mdc_8k_2.pgp b/src/tests/data/test_messages/message_mdc_8k_2.pgp
new file mode 100644
index 0000000..3e5e19b
--- /dev/null
+++ b/src/tests/data/test_messages/message_mdc_8k_2.pgp
Binary files differ
diff --git a/src/tests/data/test_messages/message_mdc_8k_cut1.pgp b/src/tests/data/test_messages/message_mdc_8k_cut1.pgp
new file mode 100644
index 0000000..d45c72d
--- /dev/null
+++ b/src/tests/data/test_messages/message_mdc_8k_cut1.pgp
Binary files differ
diff --git a/src/tests/data/test_messages/message_mdc_8k_cut22.pgp b/src/tests/data/test_messages/message_mdc_8k_cut22.pgp
new file mode 100644
index 0000000..7d5d5a8
--- /dev/null
+++ b/src/tests/data/test_messages/message_mdc_8k_cut22.pgp
Binary files differ
diff --git a/src/tests/data/test_messages/shattered-1.pdf b/src/tests/data/test_messages/shattered-1.pdf
new file mode 100644
index 0000000..ba9aaa1
--- /dev/null
+++ b/src/tests/data/test_messages/shattered-1.pdf
Binary files differ
diff --git a/src/tests/data/test_messages/shattered-1.pdf.sig b/src/tests/data/test_messages/shattered-1.pdf.sig
new file mode 100644
index 0000000..9768e96
--- /dev/null
+++ b/src/tests/data/test_messages/shattered-1.pdf.sig
Binary files differ
diff --git a/src/tests/data/test_messages/shattered-2.pdf b/src/tests/data/test_messages/shattered-2.pdf
new file mode 100644
index 0000000..b621eec
--- /dev/null
+++ b/src/tests/data/test_messages/shattered-2.pdf
Binary files differ
diff --git a/src/tests/data/test_messages/shattered-2.pdf.gpg b/src/tests/data/test_messages/shattered-2.pdf.gpg
new file mode 100644
index 0000000..d7d3611
--- /dev/null
+++ b/src/tests/data/test_messages/shattered-2.pdf.gpg
Binary files differ
diff --git a/src/tests/data/test_partial_length/message.txt.partial-1g b/src/tests/data/test_partial_length/message.txt.partial-1g
new file mode 100644
index 0000000..e73821e
--- /dev/null
+++ b/src/tests/data/test_partial_length/message.txt.partial-1g
Binary files differ
diff --git a/src/tests/data/test_partial_length/message.txt.partial-256 b/src/tests/data/test_partial_length/message.txt.partial-256
new file mode 100644
index 0000000..643034d
--- /dev/null
+++ b/src/tests/data/test_partial_length/message.txt.partial-256
Binary files differ
diff --git a/src/tests/data/test_partial_length/message.txt.partial-signed b/src/tests/data/test_partial_length/message.txt.partial-signed
new file mode 100644
index 0000000..8f2b270
--- /dev/null
+++ b/src/tests/data/test_partial_length/message.txt.partial-signed
Binary files differ
diff --git a/src/tests/data/test_partial_length/message.txt.partial-zero-last b/src/tests/data/test_partial_length/message.txt.partial-zero-last
new file mode 100644
index 0000000..d3341c4
--- /dev/null
+++ b/src/tests/data/test_partial_length/message.txt.partial-zero-last
Binary files differ
diff --git a/src/tests/data/test_partial_length/pubring.gpg.partial b/src/tests/data/test_partial_length/pubring.gpg.partial
new file mode 100644
index 0000000..3e56c03
--- /dev/null
+++ b/src/tests/data/test_partial_length/pubring.gpg.partial
Binary files differ
diff --git a/src/tests/data/test_repgp/encrypted_key.gpg b/src/tests/data/test_repgp/encrypted_key.gpg
new file mode 100644
index 0000000..1c20eec
--- /dev/null
+++ b/src/tests/data/test_repgp/encrypted_key.gpg
Binary files differ
diff --git a/src/tests/data/test_repgp/encrypted_text.gpg b/src/tests/data/test_repgp/encrypted_text.gpg
new file mode 100644
index 0000000..afe4943
--- /dev/null
+++ b/src/tests/data/test_repgp/encrypted_text.gpg
Binary files differ
diff --git a/src/tests/data/test_repgp/signed.gpg b/src/tests/data/test_repgp/signed.gpg
new file mode 100644
index 0000000..d51a8d0
--- /dev/null
+++ b/src/tests/data/test_repgp/signed.gpg
Binary files differ
diff --git a/src/tests/data/test_single_export_subkeys/list_key_export_single.txt b/src/tests/data/test_single_export_subkeys/list_key_export_single.txt
new file mode 100644
index 0000000..22c8d9e
--- /dev/null
+++ b/src/tests/data/test_single_export_subkeys/list_key_export_single.txt
@@ -0,0 +1,75 @@
+:armored input
+:off 0: packet header 0xc633 (tag 6, len 51)
+Public key packet
+ version: 4
+ creation time: 1577369391 (??? ??? ?? ??:??:?? 2019)
+ public key algorithm: 22 (EdDSA)
+ public key material:
+ ecc p: 263 bits
+ ecc curve: Ed25519
+ keyid: 0x0451409669ffde3c
+:off 53: packet header 0xcd11 (tag 13, len 17)
+UserID packet
+ id: Alice <alice@rnp>
+:off 72: packet header 0xc290 (tag 2, len 144)
+Signature packet
+ version: 4
+ type: 19 (Positive User ID certification)
+ public key algorithm: 22 (EdDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 33, len 21
+ issuer fingerprint: 0x73edcc9119afc8e2dbbdcde50451409669ffde3c (20 bytes)
+ :type 2, len 4
+ signature creation time: 1577369391 (??? ??? ?? ??:??:?? 2019)
+ :type 27, len 1
+ key flags: 0x03 ( certify sign )
+ :type 11, len 4
+ preferred symmetric algorithms: AES-256, AES-192, AES-128, TripleDES (9, 8, 7, 2)
+ :type 21, len 5
+ preferred hash algorithms: SHA512, SHA384, SHA256, SHA224, SHA1 (10, 9, 8, 11, 2)
+ :type 22, len 3
+ preferred compression algorithms: ZLib, BZip2, ZIP (2, 3, 1)
+ :type 30, len 1
+ features: 0x01 ( mdc )
+ :type 23, len 1
+ key server preferences
+ no-modify: 1
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x0451409669ffde3c
+ lbits: 0x249d
+ signature material:
+ ecc r: 255 bits
+ ecc s: 256 bits
+:off 218: packet header 0xce57 (tag 14, len 87)
+Public subkey packet
+ version: 4
+ creation time: 1577455297 (??? ??? ?? ??:??:?? 2019)
+ public key algorithm: 18 (ECDH)
+ public key material:
+ ecdh p: 515 bits
+ ecdh curve: brainpoolP256r1
+ ecdh hash algorithm: 8 (SHA256)
+ ecdh key wrap algorithm: 7
+ keyid: 0xdd23ceb7febeff17
+:off 307: packet header 0xc278 (tag 2, len 120)
+Signature packet
+ version: 4
+ type: 24 (Subkey Binding Signature)
+ public key algorithm: 22 (EdDSA)
+ hash algorithm: 8 (SHA256)
+ hashed subpackets:
+ :type 33, len 21
+ issuer fingerprint: 0x73edcc9119afc8e2dbbdcde50451409669ffde3c (20 bytes)
+ :type 2, len 4
+ signature creation time: 1577455297 (??? ??? ?? ??:??:?? 2019)
+ :type 27, len 1
+ key flags: 0x0c ( encrypt_comm encrypt_storage )
+ unhashed subpackets:
+ :type 16, len 8
+ issuer key ID: 0x0451409669ffde3c
+ lbits: 0xa1a0
+ signature material:
+ ecc r: 256 bits
+ ecc s: 254 bits
diff --git a/src/tests/data/test_stream_armor/1024_peek_buf.asc b/src/tests/data/test_stream_armor/1024_peek_buf.asc
new file mode 100644
index 0000000..760117c
--- /dev/null
+++ b/src/tests/data/test_stream_armor/1024_peek_buf.asc
@@ -0,0 +1,52 @@
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_armor/64k_whitespace_before_armored_message.asc b/src/tests/data/test_stream_armor/64k_whitespace_before_armored_message.asc
new file mode 100644
index 0000000..a429b16
--- /dev/null
+++ b/src/tests/data/test_stream_armor/64k_whitespace_before_armored_message.asc
@@ -0,0 +1,52 @@
+
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_armor/b64_trailer_extra_data.b64 b/src/tests/data/test_stream_armor/b64_trailer_extra_data.b64
new file mode 100644
index 0000000..3ad8d92
--- /dev/null
+++ b/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/src/tests/data/test_stream_armor/blank_line_with_whitespace.asc b/src/tests/data/test_stream_armor/blank_line_with_whitespace.asc
new file mode 100644
index 0000000..0f43d43
--- /dev/null
+++ b/src/tests/data/test_stream_armor/blank_line_with_whitespace.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_armor/duplicate_header_line.asc b/src/tests/data/test_stream_armor/duplicate_header_line.asc
new file mode 100644
index 0000000..23e185c
--- /dev/null
+++ b/src/tests/data/test_stream_armor/duplicate_header_line.asc
@@ -0,0 +1,54 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: Alice's OpenPGP Transferable Secret Key
+Comment: https://www.ietf.org/id/draft-bre-openpgp-samples-01.html
+
+mQINBFi33EIBEADz1RxlUUPmcE4zLuKmmTlXSsVOnAaRwtOFeA04//LQbJrZNN7S
+7Os+IeeJ/sdctXKeb18p68JeXkk9FwVo6CpAuxbhskA79jiyNLcz094Owv1/Exsm
+kYJSBdWlKl74o9GqBz90lwYaYvMMAe92n8qMEs63s6NKn/AiDe/rFBge+DSXNssc
++XmM2ZQAdid9djvLIq1EsKiUoKaoR3USQTWTA8QoA3Q/Apro+sMCuf21drjtCrxA
+OCjjR/G7G+5C96li0tFddO2mpG9mbdmiJOOyJteq6BBvdKJc/zeKH0JCM9hsPwdN
+kpCIwtvHgaW/7MiclXqoQ4eFFGX4LN3zN0pKtfHGNRPRdMPSQJ+rSbLWs8DcXS7r
+otPcCZa/ui0D/3rC5VTgkg2p7nvhc0P/N66hsQFqzLnIdTV5qtPEUZpHYjStIp9u
+q4Ah/AAluzxVtkc8WgctZ/3PaI7RdicyrC9IlAWPytanrufEvCU22YgpXjAPMzYG
+NR7XNTbwiKkgR/EhYq9hY+TZ2Qe6AakXoYnYI7W1+f95EbILhZ3lcJMuk2MrRSG8
+cVzTEZNHo00NV8wEA71/kJ+MxnYV8wJ2NxUV/6e/bNCMOyaNWdxpw57Az+ccc3wl
+4Ety6UtnQKfeUcvl0AOznFHjfzKGVNxw+Rf4mi8+64WiP0OawvJ56UiiEQARAQAB
+tCZSaWJvc2UgUGFja2FnaW5nIDxwYWNrYWdlc0ByaWJvc2UuY29tPokCPwQTAQgA
+KQUCWLfcQgIbAwUJAeEzgAcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAAAoJEFcZ
+BjotiZj6miEQAKBvMXhpr5HVAEWV7v8qhU7cH5pdQIOtR2TiYCC4o9o59mV2vdkq
+x+lrsXpcPMRQn0l4WIvLGt6phofqUb6U6Jr+Xa+npdJsZ0uhikNSK2fW0TtidhY3
+WVfU5lwhGBocwM6gVA0uJOTNBUa/JB8jXqsTOerS4Rgt68MjpSAJsISpN3X+5jJB
+xMK0y73RYiL4gyjzkFd8fGlZRLbNE72gae+nhvUq7AdA5m5+QjQCqwm2/USwD0Qa
+qH5Qs75ZhFkBVWbxpom+UMS6+knhjo93IG8Ro999H4qTmZGsczWZNodLHsAD/s9U
+Wo3cIcP6mB8rMvQrNKD4YFKOewyPISC3g/VlcQ1AP/XgVNzcDdapeWk164heVej6
+fQJG00/Z2S7I/7ihDIGpNeU0SifDWPvPVJ2k030TF1+wLeD5hHK7fas/z3vGLYky
+1ToUmBJjZkI6TOFPI8iOHtENvfOkLIXzpom/Z8wjk+jTVT1NAEF+lNq+aLqKguTA
+WV2mRjOVulXFoDFKjS73BcBXSrD8b/CHreoVuaSx1UWk1cfMmUU2RAP1ptIKZO/J
+7oI8KjMbs4VR49Gj2DpGQx6cVvDPy6haJpF4Is48Sl/139dB/v+s6SMQew1YhPcY
+RqSzD6Tk14M+mSYek0mGRYCfEAjpaqRxpOwRqM+dp7bmsnR6OE+XOEAZuQINBFi3
+3EIBEAC0jlnnDbk+tVvoUEBZT5GVTyufthCJlrYpNWCKv4pPtgyAzPjTWsVOok6R
+JI6wqBw+rsZrK+Oxq0nlAFMbt4d3gHeGpyU7rf5xZDQbxjZNVv3hgLqZkpj+oJIz
+HJSGDB8ywxuhFFg//gQx0/buRPzn7JZQwCzDgRq1HwuWGxjrrNKPRKzfPU+0lPCh
+n7SRfQDfPpiihHdI+RcX65uHAehgQEhwKteGYUOgy1KJYXtA+LM+boVHvvMfolQZ
+CF4ump3SZ0rFoam7gQ1c7UcoXFgQpMZtXirgfU8Xnt5sLRcjgmiM64JnELirFec1
+Rzwjlgk3CSNnMPYeJE2ja74X7ggfnYM3z0qVsGJghapQZunoqgFK0++po6pwzavp
+A3mcjQZTcHYzXYUidCrXXLG93UgRzVu80ybv5BBZtbz5GB0yi6yf2VA4w2Q0dMms
+48mLMRk/JjRjUUHnZ2kEmUKwN5tN7aotuaZg8rADDktXEZdjvchYMsfzpKjV8eg8
+Lu+C2TXI1bq6zF22JTQbeL0SQ3yUOVes53ZyKreTLlTIU3ERsvQR7HF97WYI/1el
+7572FkHleq0Nlg8mdNndK+9JHVAGY5o5lHg4oXwEFrNgfb6e00Dn4aQi9QsvY9Qv
+6SpBD5xBe11PozFg5IaG0n5ZoeylABhxWom4PGqxCKVoiY95mwARAQABiQIlBBgB
+CAAPBQJYt9xCAhsMBQkB4TOAAAoJEFcZBjotiZj6giYQAI7/2tELIxzk6Q1JyhRz
+TJ9FR0pqZgZcQjnW3+MARD+ZYQhMknIrxhZPxFL2iA7pfTMHlbCuBYMJsea1Rg29
+AHK4FlW3jqD+7ITErIQZFxC6j5NxmKTFowd6sQ3lCr8DwnO7fcuOTl+rEpkPJwzG
+Rz9eumD2Xc2iWkbU4HemG/V0PhxpJPy8hgBz0FRuKtUSCcFt+r5obyMGstR/0PDe
+MG8diP8H/oEEOa10Vsj0UfEgfMDyP2jGp/ltrnMSabbvuv+puJuG0fpE+mXsQ1OK
+8f8fh1/76SIJezN9wFyHoIiZuieLrZu8CYb2Xni4DqyqVIGy49DfpLPQbfQVaW/3
+FbdjXYzadfJngehIhRoFIyMdN9khiooozdVk4dYTGZeC5q/qf3uJ9c0dyah9dhwt
+npJzrgu5jEeneHd34gA9tBFaXKY0c6k0dXHhbXAko+8/qCZxq1HWl3xaFSqSk2KG
+xvBjBm06DYb6Dg1Y5tq0FEVb9O0Nv9SHPAnvlvlL09cVWYtktkjXIcb1FjoOAl6q
+MKfjR36eF5wlPdGZ/MNdiYuFQLf/U1jEOLRlTVuNmtT6rKD2FqUC5xscyb86bic3
+CXZZLZcsvG0SeiIUKVVasK08jRkXAcT/delsNnO92EsY8KVee+EXuBJweFmi2Tla
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/ecc-25519-pub-bad-crc.asc b/src/tests/data/test_stream_armor/ecc-25519-pub-bad-crc.asc
new file mode 100644
index 0000000..ebcf99e
--- /dev/null
+++ b/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/src/tests/data/test_stream_armor/empty_header_line.asc b/src/tests/data/test_stream_armor/empty_header_line.asc
new file mode 100644
index 0000000..daf03c6
--- /dev/null
+++ b/src/tests/data/test_stream_armor/empty_header_line.asc
@@ -0,0 +1,53 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Hash:
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/extra_line_before_trailer.asc b/src/tests/data/test_stream_armor/extra_line_before_trailer.asc
new file mode 100644
index 0000000..27c63be
--- /dev/null
+++ b/src/tests/data/test_stream_armor/extra_line_before_trailer.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/long_b64_trailer.b64 b/src/tests/data/test_stream_armor/long_b64_trailer.b64
new file mode 100644
index 0000000..2118103
--- /dev/null
+++ b/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/src/tests/data/test_stream_armor/long_header_line.asc b/src/tests/data/test_stream_armor/long_header_line.asc
new file mode 100644
index 0000000..e545ed0
--- /dev/null
+++ b/src/tests/data/test_stream_armor/long_header_line.asc
@@ -0,0 +1,53 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: very long comment, more than 64 symbols. Some padding: zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zz zzz zzzz zzz zzz zzz zzz zz zzz zzzzz zz
+
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/long_header_line_1024.asc b/src/tests/data/test_stream_armor/long_header_line_1024.asc
new file mode 100644
index 0000000..69673eb
--- /dev/null
+++ b/src/tests/data/test_stream_armor/long_header_line_1024.asc
@@ -0,0 +1,53 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: this line which is longer then 1024 characters. Some padding: zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz
+
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/long_header_line_64k.asc b/src/tests/data/test_stream_armor/long_header_line_64k.asc
new file mode 100644
index 0000000..f17796a
--- /dev/null
+++ b/src/tests/data/test_stream_armor/long_header_line_64k.asc
@@ -0,0 +1,53 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Comment: very long comment, more than 64 symbols. Some padding: zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zz zzz zzzz zzz zzz zzz zzz zz zzz zzzzz zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz
+
+
+mQINBFi33EIBEADz1RxlUUPmcE4zLuKmmTlXSsVOnAaRwtOFeA04//LQbJrZNN7S
+7Os+IeeJ/sdctXKeb18p68JeXkk9FwVo6CpAuxbhskA79jiyNLcz094Owv1/Exsm
+kYJSBdWlKl74o9GqBz90lwYaYvMMAe92n8qMEs63s6NKn/AiDe/rFBge+DSXNssc
++XmM2ZQAdid9djvLIq1EsKiUoKaoR3USQTWTA8QoA3Q/Apro+sMCuf21drjtCrxA
+OCjjR/G7G+5C96li0tFddO2mpG9mbdmiJOOyJteq6BBvdKJc/zeKH0JCM9hsPwdN
+kpCIwtvHgaW/7MiclXqoQ4eFFGX4LN3zN0pKtfHGNRPRdMPSQJ+rSbLWs8DcXS7r
+otPcCZa/ui0D/3rC5VTgkg2p7nvhc0P/N66hsQFqzLnIdTV5qtPEUZpHYjStIp9u
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_armor/long_header_nameline_64k.asc b/src/tests/data/test_stream_armor/long_header_nameline_64k.asc
new file mode 100644
index 0000000..235e3e7
--- /dev/null
+++ b/src/tests/data/test_stream_armor/long_header_nameline_64k.asc
@@ -0,0 +1,53 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzComment: very long comment, more than 64 symbols. Some padding: zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zzz zz zzz zzzz zzz zzz zzz zzz zz zzz zzzzz zz
+
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_armor/message_64k_oneline.asc b/src/tests/data/test_stream_armor/message_64k_oneline.asc
new file mode 100644
index 0000000..69e31df
--- /dev/null
+++ b/src/tests/data/test_stream_armor/message_64k_oneline.asc
@@ -0,0 +1,5 @@
+-----BEGIN PGP MESSAGE-----
+
+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
+=C1b3
+-----END PGP MESSAGE-----
diff --git a/src/tests/data/test_stream_armor/too_short_header.asc b/src/tests/data/test_stream_armor/too_short_header.asc
new file mode 100644
index 0000000..0d4db20
--- /dev/null
+++ b/src/tests/data/test_stream_armor/too_short_header.asc
@@ -0,0 +1,4 @@
+----------
+-----BEGIN PGP
+
+=azaS
diff --git a/src/tests/data/test_stream_armor/wrong_b64_trailer.asc b/src/tests/data/test_stream_armor/wrong_b64_trailer.asc
new file mode 100644
index 0000000..b5c2922
--- /dev/null
+++ b/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/src/tests/data/test_stream_armor/wrong_chars_base64_1.asc b/src/tests/data/test_stream_armor/wrong_chars_base64_1.asc
new file mode 100644
index 0000000..e8fca60
--- /dev/null
+++ b/src/tests/data/test_stream_armor/wrong_chars_base64_1.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFi3ÿ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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/wrong_chars_base64_2.asc b/src/tests/data/test_stream_armor/wrong_chars_base64_2.asc
new file mode 100644
index 0000000..7992dc0
--- /dev/null
+++ b/src/tests/data/test_stream_armor/wrong_chars_base64_2.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFi33Þ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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/wrong_chars_base64_3.asc b/src/tests/data/test_stream_armor/wrong_chars_base64_3.asc
new file mode 100644
index 0000000..9c1b142
--- /dev/null
+++ b/src/tests/data/test_stream_armor/wrong_chars_base64_3.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFi33EÝ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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/wrong_chars_base64_4.asc b/src/tests/data/test_stream_armor/wrong_chars_base64_4.asc
new file mode 100644
index 0000000..5d42129
--- /dev/null
+++ b/src/tests/data/test_stream_armor/wrong_chars_base64_4.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFi33EIÝ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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/wrong_chars_crc.asc b/src/tests/data/test_stream_armor/wrong_chars_crc.asc
new file mode 100644
index 0000000..d53f328
--- /dev/null
+++ b/src/tests/data/test_stream_armor/wrong_chars_crc.asc
@@ -0,0 +1,52 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=a‚aI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/wrong_chars_header.asc b/src/tests/data/test_stream_armor/wrong_chars_header.asc
new file mode 100644
index 0000000..c2e9ec9
--- /dev/null
+++ b/src/tests/data/test_stream_armor/wrong_chars_header.asc
@@ -0,0 +1,52 @@
+ ‚ -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQINBFi33EIBEADz1RxlUUPmcE4zLuKmmTlXSsVOnAaRwtOFeA04//LQbJrZNN7S
+7Os+IeeJ/sdctXKeb18p68JeXkk9FwVo6CpAuxbhskA79jiyNLcz094Owv1/Exsm
+kYJSBdWlKl74o9GqBz90lwYaYvMMAe92n8qMEs63s6NKn/AiDe/rFBge+DSXNssc
++XmM2ZQAdid9djvLIq1EsKiUoKaoR3USQTWTA8QoA3Q/Apro+sMCuf21drjtCrxA
+OCjjR/G7G+5C96li0tFddO2mpG9mbdmiJOOyJteq6BBvdKJc/zeKH0JCM9hsPwdN
+kpCIwtvHgaW/7MiclXqoQ4eFFGX4LN3zN0pKtfHGNRPRdMPSQJ+rSbLWs8DcXS7r
+otPcCZa/ui0D/3rC5VTgkg2p7nvhc0P/N66hsQFqzLnIdTV5qtPEUZpHYjStIp9u
+q4Ah/AAluzxVtkc8WgctZ/3PaI7RdicyrC9IlAWPytanrufEvCU22YgpXjAPMzYG
+NR7XNTbwiKkgR/EhYq9hY+TZ2Qe6AakXoYnYI7W1+f95EbILhZ3lcJMuk2MrRSG8
+cVzTEZNHo00NV8wEA71/kJ+MxnYV8wJ2NxUV/6e/bNCMOyaNWdxpw57Az+ccc3wl
+4Ety6UtnQKfeUcvl0AOznFHjfzKGVNxw+Rf4mi8+64WiP0OawvJ56UiiEQARAQAB
+tCZSaWJvc2UgUGFja2FnaW5nIDxwYWNrYWdlc0ByaWJvc2UuY29tPokCPwQTAQgA
+KQUCWLfcQgIbAwUJAeEzgAcLCQgHAwIBBhUIAgkKCwQWAgMBAh4BAheAAAoJEFcZ
+BjotiZj6miEQAKBvMXhpr5HVAEWV7v8qhU7cH5pdQIOtR2TiYCC4o9o59mV2vdkq
+x+lrsXpcPMRQn0l4WIvLGt6phofqUb6U6Jr+Xa+npdJsZ0uhikNSK2fW0TtidhY3
+WVfU5lwhGBocwM6gVA0uJOTNBUa/JB8jXqsTOerS4Rgt68MjpSAJsISpN3X+5jJB
+xMK0y73RYiL4gyjzkFd8fGlZRLbNE72gae+nhvUq7AdA5m5+QjQCqwm2/USwD0Qa
+qH5Qs75ZhFkBVWbxpom+UMS6+knhjo93IG8Ro999H4qTmZGsczWZNodLHsAD/s9U
+Wo3cIcP6mB8rMvQrNKD4YFKOewyPISC3g/VlcQ1AP/XgVNzcDdapeWk164heVej6
+fQJG00/Z2S7I/7ihDIGpNeU0SifDWPvPVJ2k030TF1+wLeD5hHK7fas/z3vGLYky
+1ToUmBJjZkI6TOFPI8iOHtENvfOkLIXzpom/Z8wjk+jTVT1NAEF+lNq+aLqKguTA
+WV2mRjOVulXFoDFKjS73BcBXSrD8b/CHreoVuaSx1UWk1cfMmUU2RAP1ptIKZO/J
+7oI8KjMbs4VR49Gj2DpGQx6cVvDPy6haJpF4Is48Sl/139dB/v+s6SMQew1YhPcY
+RqSzD6Tk14M+mSYek0mGRYCfEAjpaqRxpOwRqM+dp7bmsnR6OE+XOEAZuQINBFi3
+3EIBEAC0jlnnDbk+tVvoUEBZT5GVTyufthCJlrYpNWCKv4pPtgyAzPjTWsVOok6R
+JI6wqBw+rsZrK+Oxq0nlAFMbt4d3gHeGpyU7rf5xZDQbxjZNVv3hgLqZkpj+oJIz
+HJSGDB8ywxuhFFg//gQx0/buRPzn7JZQwCzDgRq1HwuWGxjrrNKPRKzfPU+0lPCh
+n7SRfQDfPpiihHdI+RcX65uHAehgQEhwKteGYUOgy1KJYXtA+LM+boVHvvMfolQZ
+CF4ump3SZ0rFoam7gQ1c7UcoXFgQpMZtXirgfU8Xnt5sLRcjgmiM64JnELirFec1
+Rzwjlgk3CSNnMPYeJE2ja74X7ggfnYM3z0qVsGJghapQZunoqgFK0++po6pwzavp
+A3mcjQZTcHYzXYUidCrXXLG93UgRzVu80ybv5BBZtbz5GB0yi6yf2VA4w2Q0dMms
+48mLMRk/JjRjUUHnZ2kEmUKwN5tN7aotuaZg8rADDktXEZdjvchYMsfzpKjV8eg8
+Lu+C2TXI1bq6zF22JTQbeL0SQ3yUOVes53ZyKreTLlTIU3ERsvQR7HF97WYI/1el
+7572FkHleq0Nlg8mdNndK+9JHVAGY5o5lHg4oXwEFrNgfb6e00Dn4aQi9QsvY9Qv
+6SpBD5xBe11PozFg5IaG0n5ZoeylABhxWom4PGqxCKVoiY95mwARAQABiQIlBBgB
+CAAPBQJYt9xCAhsMBQkB4TOAAAoJEFcZBjotiZj6giYQAI7/2tELIxzk6Q1JyhRz
+TJ9FR0pqZgZcQjnW3+MARD+ZYQhMknIrxhZPxFL2iA7pfTMHlbCuBYMJsea1Rg29
+AHK4FlW3jqD+7ITErIQZFxC6j5NxmKTFowd6sQ3lCr8DwnO7fcuOTl+rEpkPJwzG
+Rz9eumD2Xc2iWkbU4HemG/V0PhxpJPy8hgBz0FRuKtUSCcFt+r5obyMGstR/0PDe
+MG8diP8H/oEEOa10Vsj0UfEgfMDyP2jGp/ltrnMSabbvuv+puJuG0fpE+mXsQ1OK
+8f8fh1/76SIJezN9wFyHoIiZuieLrZu8CYb2Xni4DqyqVIGy49DfpLPQbfQVaW/3
+FbdjXYzadfJngehIhRoFIyMdN9khiooozdVk4dYTGZeC5q/qf3uJ9c0dyah9dhwt
+npJzrgu5jEeneHd34gA9tBFaXKY0c6k0dXHhbXAko+8/qCZxq1HWl3xaFSqSk2KG
+xvBjBm06DYb6Dg1Y5tq0FEVb9O0Nv9SHPAnvlvlL09cVWYtktkjXIcb1FjoOAl6q
+MKfjR36eF5wlPdGZ/MNdiYuFQLf/U1jEOLRlTVuNmtT6rKD2FqUC5xscyb86bic3
+CXZZLZcsvG0SeiIUKVVasK08jRkXAcT/delsNnO92EsY8KVee+EXuBJweFmi2Tla
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_armor/wrong_header_line.asc b/src/tests/data/test_stream_armor/wrong_header_line.asc
new file mode 100644
index 0000000..f7c27b0
--- /dev/null
+++ b/src/tests/data/test_stream_armor/wrong_header_line.asc
@@ -0,0 +1,54 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Hash:
+Some malformed header put here.
+
+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
+MmxJsq4yrVw0fTL7NREZ5AV9
+=aQaI
+
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/dsa-eg-pub.asc b/src/tests/data/test_stream_key_load/dsa-eg-pub.asc
new file mode 100644
index 0000000..db06732
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/dsa-eg-pub.asc
@@ -0,0 +1,44 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+v5BhpoOOm+jM
+=PnGk
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/dsa-eg-sec.asc b/src/tests/data/test_stream_key_load/dsa-eg-sec.asc
new file mode 100644
index 0000000..39b63cd
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/dsa-eg-sec.asc
@@ -0,0 +1,48 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+6Mw=
+=RpuN
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-25519-photo-pub.asc b/src/tests/data/test_stream_key_load/ecc-25519-photo-pub.asc
new file mode 100644
index 0000000..1b75bae
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-25519-photo-pub.asc
@@ -0,0 +1,44 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo
+ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID
+AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco
+lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU
+9RrUJSYZnMla/pQdgOxd7/PjRCpbCtHFMsUwARAAAQEAAAAAAAAAAAAAAAD/2P/g
+ABBKRklGAAEBAABIAEgAAP/hAExFeGlmAABNTQAqAAAACAABh2kABAAAAAEAAAAa
+AAAAAAADoAEAAwAAAAEAAQAAoAIABAAAAAEAAAAqoAMABAAAAAEAAAAqAAAAAP/t
+ADhQaG90b3Nob3AgMy4wADhCSU0EBAAAAAAAADhCSU0EJQAAAAAAENQdjNmPALIE
+6YAJmOz4Qn7/wAARCAAqACoDASIAAhEBAxEB/8QAHwAAAQUBAQEBAQEAAAAAAAAA
+AAECAwQFBgcICQoL/8QAtRAAAgEDAwIEAwUFBAQAAAF9AQIDAAQRBRIhMUEGE1Fh
+ByJxFDKBkaEII0KxwRVS0fAkM2JyggkKFhcYGRolJicoKSo0NTY3ODk6Q0RFRkdI
+SUpTVFVWV1hZWmNkZWZnaGlqc3R1dnd4eXqDhIWGh4iJipKTlJWWl5iZmqKjpKWm
+p6ipqrKztLW2t7i5usLDxMXGx8jJytLT1NXW19jZ2uHi4+Tl5ufo6erx8vP09fb3
++Pn6/8QAHwEAAwEBAQEBAQEBAQAAAAAAAAECAwQFBgcICQoL/8QAtREAAgECBAQD
+BAcFBAQAAQJ3AAECAxEEBSExBhJBUQdhcRMiMoEIFEKRobHBCSMzUvAVYnLRChYk
+NOEl8RcYGRomJygpKjU2Nzg5OkNERUZHSElKU1RVVldYWVpjZGVmZ2hpanN0dXZ3
+eHl6goOEhYaHiImKkpOUlZaXmJmaoqOkpaanqKmqsrO0tba3uLm6wsPExcbHyMnK
+0tPU1dbX2Nna4uPk5ebn6Onq8vP09fb3+Pn6/9sAQwACAgICAgIDAgIDBQMDAwUG
+BQUFBQYIBgYGBgYICggICAgICAoKCgoKCgoKDAwMDAwMDg4ODg4PDw8PDw8PDw8P
+/9sAQwECAgIEBAQHBAQHEAsJCxAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQ
+EBAQEBAQEBAQEBAQEBAQEBAQEBAQ/90ABAAD/9oADAMBAAIRAxEAPwD9dfiD8QX8
+DvYoliLz7YJDzJs27Nv+y2c5rzr/AIX9N/0BF/8AAg//ABFeu+LPAui+Mmtn1Zpl
+NoHCeUwX7+M5yp9K5D/hR/g3/nrd/wDf1f8A4ivVw08KoL2i1+f+Z9hleIyiNCKx
+MG59d+/r2OR/4X9N/wBARf8AwIP/AMRR/wAL+m/6Ai/+BB/+Irrv+FH+Df8Anrd/
+9/V/+Io/4Uf4N/563f8A39X/AOIrf2mC/lf4/wCZ6H1rIf8An2/x/wAyj4V+MUvi
+TxBZ6G2lLbi7Zl8wTFtuFLdNoz09a9wrzLQvhR4Y8P6tb6zYyXJntSWQPIpXJBXk
+BR2PrXptedjJUnJexWh8xndTByqp4KNo213317tn/9D9TPjL4m1/w9LpK6JfSWYn
+WYvsx820pjOQemTXS/CXWtV13wvJe6xcvdzi5kQO+M7QqkDgD1riPjpYX17Noxs7
+aW42LPu8tGfGSmM7QcVt/CG8s9G8KyWmsTpYTm6kby7hhE+0quDtfBwcda9qdOLw
+cWlr+O7Pua2HpvJqcoxXPftru/me2bWPasrW5pbbRdQuIGKSxW8rqw6hlQkH8DXx
+v8QdWuZPGWsPY3rtAZvkMcpKEbR93acflX1Bc65o03hCS3TULeSd7AoEEyF2cw4x
+jOSSeMdc1z1cA6ahK97nm4zh6eHjRq35ue2lttnr954X8PfHPi7VfGGl6fqOqzXF
+tM7B0bbhgEY84A7ivrCvjT4Z6Rq1v440ia4sZ4o0dtzPE6qP3bdSRgV9l1rnMYqo
+uVdDp41pUo4mCpJJcvS3d9j/0f3BBI6HFfN3xX8E+KPEPildQ0jT3uoPs8abwyD5
+lLZHzEHvX0hRXThcTKlLnienlOaTwdX21NJu1tT4l/4Vd49/6A8n/fcf/wAVV/SP
+hn44t9WsbibSHSOKeJ2bfHwquCT970r7Lor0HndRq1l+J9HPjvFSTi4R19f8xzMS
+TzkU2iivGPiT/9mIkAQTFggAOBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcyYDc
+AhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJEMx4YniYGwcoaxoBAKw5VDAr
+Md1N1RijTkliotkPUvOdfUY4xk6ZrVz1ZMqaAPsGqb0QzTo323cxK98lSav1iIf4
+XK3ZSij0xkDs9ShSBA==
+=cwQb
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-25519-pub-2.b64 b/src/tests/data/test_stream_key_load/ecc-25519-pub-2.b64
new file mode 100644
index 0000000..74dbad4
--- /dev/null
+++ b/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/src/tests/data/test_stream_key_load/ecc-25519-pub-3.b64 b/src/tests/data/test_stream_key_load/ecc-25519-pub-3.b64
new file mode 100644
index 0000000..40b4a27
--- /dev/null
+++ b/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/src/tests/data/test_stream_key_load/ecc-25519-pub-4.b64 b/src/tests/data/test_stream_key_load/ecc-25519-pub-4.b64
new file mode 100644
index 0000000..fcf6abb
--- /dev/null
+++ b/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/src/tests/data/test_stream_key_load/ecc-25519-pub.asc b/src/tests/data/test_stream_key_load/ecc-25519-pub.asc
new file mode 100644
index 0000000..636a5a9
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-25519-pub.asc
@@ -0,0 +1,9 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo
+ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID
+AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco
+lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU
+9RrUJSYZnMla/pQdgOxd7/PjRCpbCg==
+=miZp
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-25519-pub.b64 b/src/tests/data/test_stream_key_load/ecc-25519-pub.b64
new file mode 100644
index 0000000..2e14643
--- /dev/null
+++ b/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/src/tests/data/test_stream_key_load/ecc-25519-sec.asc b/src/tests/data/test_stream_key_load/ecc-25519-sec.asc
new file mode 100644
index 0000000..3120c15
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-25519-sec.asc
@@ -0,0 +1,11 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lIYEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo
+ugCpCfj+BwMCkuClwXrc7H3i9J2+l5bS6+TGJVRP2/yrh9tCcsgmUf0Z1T7uwS7A
+BadlAPIokvZ3aLmU5ahSJY7SpK/EV3vEG76FMCxxXOJTDIKfsHoS87QJZWNjLTI1
+NTE5iJQEExYIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEIfxo
+J0quO13jmkJ3zHhieJgbBygFAlxVr80ACgkQzHhieJgbByiUUAD+My3dFRRvnG3r
+clbocVytirRGsMBxgyxcBjveJmk+wRwBAOYpsfbUuTCgKVT1GtQlJhmcyVr+lB2A
+7F3v8+NEKlsK
+=q44s
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-bp256-pub.asc b/src/tests/data/test_stream_key_load/ecc-bp256-pub.asc
new file mode 100644
index 0000000..8427cfc
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-bp256-pub.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mFMEWsODvRMJKyQDAwIIAQEHAgMEQLOxiiHZ/V6v3kvrhbnRtTp+oOPVpuvDKOiy
+gJOCZ7EWMVAwTr4syaSh8W8hdRgZ85Evv/1PYNFovYb6vzgVr7QJZWNjLWJwMjU2
+iJQEExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEBjPF9yoZ
+j1HmUOSr0Mij2vngY0oFAlxVsKIACgkQ0Mij2vngY0pARAD/RozGDidH/0aFlxeU
+VWNJKjPiax6vdHqur5dqBS/RhhIA/1sPUnyAIvAXXID1uhK6oIBRKi7WJ5rI7vSy
+rBR5MlNJuFcEWsODvRIJKyQDAwIIAQEHAgMEE9Vd8dIjHJkmRs/8MLz4Krfwz5BK
+hunq1T0xnp65OEZJd00VxA+VUXdEUHfaDehtSv7izCpq4lbXGCkEGFN7QwMBCAeI
+eAQYEwgAIAIbDBYhBAYzxfcqGY9R5lDkq9DIo9r54GNKBQJcVbCpAAoJENDIo9r5
+4GNK0MYA/2p5cq5smjSvKD/EGkosQwfcqkeygsQuEpDDLeEdsv8vAP9j+RHKX2tl
+W08zbayxGB0E+aCHuKCF8iLPeI4eroi/fw==
+=vsa4
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-bp256-sec.asc b/src/tests/data/test_stream_key_load/ecc-bp256-sec.asc
new file mode 100644
index 0000000..1983791
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-bp256-sec.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lKYEWsODvRMJKyQDAwIIAQEHAgMEQLOxiiHZ/V6v3kvrhbnRtTp+oOPVpuvDKOiy
+gJOCZ7EWMVAwTr4syaSh8W8hdRgZ85Evv/1PYNFovYb6vzgVr/4HAwJ9gzsrQYtV
+WuIMMz3DmNZ9c4WcCWTmmlbvuH5/rO8X6/Ixqflbu509RZO7zankApYTksoz4FUh
+UHmzvboqU+cXss2bBjHlQ2rLpy0GQp+YtAllY2MtYnAyNTaIlAQTEwgAPAIbAwUL
+CQgHAgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQQGM8X3KhmPUeZQ5KvQyKPa+eBj
+SgUCXFWwogAKCRDQyKPa+eBjSkBEAP9GjMYOJ0f/RoWXF5RVY0kqM+JrHq90eq6v
+l2oFL9GGEgD/Ww9SfIAi8BdcgPW6ErqggFEqLtYnmsju9LKsFHkyU0mcqgRaw4O9
+EgkrJAMDAggBAQcCAwQT1V3x0iMcmSZGz/wwvPgqt/DPkEqG6erVPTGenrk4Rkl3
+TRXED5VRd0RQd9oN6G1K/uLMKmriVtcYKQQYU3tDAwEIB/4HAwIcRK/TJLpefeKI
+48u5WkRLQs6VHYH5OY+aUpN8JGTWmlDgIHmtSov/sg2pvkt7U95N5wVWqbgr1WAI
+DGFZz6y391KPrHbwsegMUwNR/EHXiHgEGBMIACACGwwWIQQGM8X3KhmPUeZQ5KvQ
+yKPa+eBjSgUCXFWwqQAKCRDQyKPa+eBjStDGAP9qeXKubJo0ryg/xBpKLEMH3KpH
+soLELhKQwy3hHbL/LwD/Y/kRyl9rZVtPM22ssRgdBPmgh7ighfIiz3iOHq6Iv38=
+=mWGf
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-bp384-pub.asc b/src/tests/data/test_stream_key_load/ecc-bp384-pub.asc
new file mode 100644
index 0000000..bdb20fe
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-bp384-pub.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mHMEWsOEUBMJKyQDAwIIAQELAwMEivcvlPJsPmivhJcrfHx+ORxyum57GtRhWM49
+Yr8fJ48gyFqj9cLYOBdhEVvcfceyBPXmyt0TozWtjkGzgbF4LIvN1EB0DW0Rlsdn
+p72/hf0gnXvWZdD8euArX4RaAYuQtAllY2MtYnAzODSItAQTEwkAPAIbAwULCQgH
+AgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQRbiiVMgjztmN7NEO1s8tzoVZmtogUC
+XFWwugAKCRBs8tzoVZmtoj1yAX9P1UV7FYpGUIP13aPP0d5Bx8HdQDAoexdXz3WW
+WPL/7OhSjPde23Q8TfgWyO21M2wBf1oWjOsDSjO5mDLCr7ypAFF6IJAgx76tSUe9
+Qmy7sL94OWDQ4+1Dccnc9GGiHLtRI7h3BFrDhFASCSskAwMCCAEBCwMDBETUkqGr
+7p8kX2dm38jzzxXRh1+OL7nmY168Zeo9yfwDbnyx8BoihP9ZgPWjGXmefT78GSfw
+ZDaYgC2NFQOcI/b8agh3PcjrXgZaFCZbUR9v2DnLUpCF8ZbxDJwEqweNTAMBCQmI
+mAQYEwkAIAIbDBYhBFuKJUyCPO2Y3s0Q7Wzy3OhVma2iBQJcVbDCAAoJEGzy3OhV
+ma2ig1IBfifduIiwdAlD45MOolSpHMX0AT7KoJHpt9ZFvWnjQkq9ZGEA/RA9vx7Z
+sLb7IsG1GgF/Sn+gtf/JIteXaZMnOhEOZ2oFUufij6o8gII8/9s8mkIjkrIICy//
+0n3q82ndFg0c
+=fcpz
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-bp384-sec.asc b/src/tests/data/test_stream_key_load/ecc-bp384-sec.asc
new file mode 100644
index 0000000..fdc1aec
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-bp384-sec.asc
@@ -0,0 +1,21 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lNYEWsOEUBMJKyQDAwIIAQELAwMEivcvlPJsPmivhJcrfHx+ORxyum57GtRhWM49
+Yr8fJ48gyFqj9cLYOBdhEVvcfceyBPXmyt0TozWtjkGzgbF4LIvN1EB0DW0Rlsdn
+p72/hf0gnXvWZdD8euArX4RaAYuQ/gcDArBnnTzqjPlr4tU1qRuOdWtN9jK1Bi5d
+TIwywaBqFqCzV/nkFVN3/HiYeksnOHfCGU+ChYpXQxZPqG1pY6D/GDQTtd7ai8wD
+2S9pwpJadql9M7Rw5EZj97CuIWn7j2TutAllY2MtYnAzODSItAQTEwkAPAIbAwUL
+CQgHAgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQRbiiVMgjztmN7NEO1s8tzoVZmt
+ogUCXFWwugAKCRBs8tzoVZmtoj1yAX9P1UV7FYpGUIP13aPP0d5Bx8HdQDAoexdX
+z3WWWPL/7OhSjPde23Q8TfgWyO21M2wBf1oWjOsDSjO5mDLCr7ypAFF6IJAgx76t
+SUe9Qmy7sL94OWDQ4+1Dccnc9GGiHLtRI5zaBFrDhFASCSskAwMCCAEBCwMDBETU
+kqGr7p8kX2dm38jzzxXRh1+OL7nmY168Zeo9yfwDbnyx8BoihP9ZgPWjGXmefT78
+GSfwZDaYgC2NFQOcI/b8agh3PcjrXgZaFCZbUR9v2DnLUpCF8ZbxDJwEqweNTAMB
+CQn+BwMCfai+1IRYtH3idMi3rAwo8f1PENTdrQOg9xwkgxdBvcP1RLlLoyCOM1wT
+r9ggbRBDNKIzZ8aG5ymGtQKxMh0nnMBYufWjskOrvXS4LhqUfLbdqXeI2EvcBCqq
+5VKn0rWImAQYEwkAIAIbDBYhBFuKJUyCPO2Y3s0Q7Wzy3OhVma2iBQJcVbDCAAoJ
+EGzy3OhVma2ig1IBfifduIiwdAlD45MOolSpHMX0AT7KoJHpt9ZFvWnjQkq9ZGEA
+/RA9vx7ZsLb7IsG1GgF/Sn+gtf/JIteXaZMnOhEOZ2oFUufij6o8gII8/9s8mkIj
+krIICy//0n3q82ndFg0c
+=Yh3t
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-bp512-pub.asc b/src/tests/data/test_stream_key_load/ecc-bp512-pub.asc
new file mode 100644
index 0000000..5b4bca2
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-bp512-pub.asc
@@ -0,0 +1,19 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mJMEWsOEhxMJKyQDAwIIAQENBAMEA28ylDnn3LR7t6EQow5DszCpmUA+yup03LsT
+9o0Tw4/asi8nAz+1tlRY5HD49j53PziOlsGzKYa/jxGWjhVESgqLrJp/Eo65zK9v
+yDhX+iCkSYQ15WGKr3QaRUmBOUbX9PqE6dY+DDGQ1kewI93QIGCB1gn+OSmyKPm3
+YaVIbo60CWVjYy1icDUxMojUBBMTCgA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID
+AQIeAwIXgBYhBExZq5JyqmofYLhb0KpcWNFPe49IBQJcVbDYAAoJEKpcWNFPe49I
+F8UB/i8DwypbNusdbAqTbu1Twpn/QFMaVKHn8Ysgzqpimv+6hRq7uzekCvEOPOjl
+Oke5yLp8bpTTMPRKyfjNatQhdn8B/2+54qtXJuQd9oTSz7f2eFYvA8ZsMQgApYNl
+ksvKSw6dhSNX/DXK7JYIlgZXx7UGTkP4h3FQSiyCUJhVpClVVGa4lwRaw4SHEgkr
+JAMDAggBAQ0EAwRCtEqQkEgzQDxNGCj0duj0aGvnH+DHKlP4V6p9LJVIL0TuF1uZ
+BzP04efRvZT2vzCTcvvdE/G6G/txEZsR/989OchbkVWOPM/oCVktkaA02rBMcefh
+k9wKD+O9E3oHEN+tBt3yhmsv0MIR9IfPwi1GCsu55p4WUI//+ysB2T0YaQMBCgmI
+uAQYEwoAIAIbDBYhBExZq5JyqmofYLhb0KpcWNFPe49IBQJcVbDgAAoJEKpcWNFP
+e49IZQUB/R4U4aWBMimZSL8kyaK+/Y8NcInIGqRzrm5zPnTSHrgQeH55SVKahzsq
+j57D1Ec1HnUd4ctISVocOxpUfnJq5NAB/1fzbh+1RN2ZyNW6tAJlA/Irkwzzbil9
+6fAIvRolwwaGsUZNMEiCF3rTcFaenJg9UhQvX6BoqXCdwawqTZCRN6E=
+=h+On
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-bp512-sec.asc b/src/tests/data/test_stream_key_load/ecc-bp512-sec.asc
new file mode 100644
index 0000000..ea511ae
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-bp512-sec.asc
@@ -0,0 +1,24 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQEGBFrDhIcTCSskAwMCCAEBDQQDBANvMpQ559y0e7ehEKMOQ7MwqZlAPsrqdNy7
+E/aNE8OP2rIvJwM/tbZUWORw+PY+dz84jpbBsymGv48Rlo4VREoKi6yafxKOucyv
+b8g4V/ogpEmENeVhiq90GkVJgTlG1/T6hOnWPgwxkNZHsCPd0CBggdYJ/jkpsij5
+t2GlSG6O/gcDAsk6xAw3VyJ24kqhm+0ZAIkdmTuIN6PlFEyQHOWdS7GTuSqnr6it
+isLFRJdTn92z6VSQERaVwCnULd+RvAYvyJxCYe7TS2XABCfTqTW8ZsQajIDrIII7
+9VMYztRNcm0BmDeX/w9MGfs1DKGLHIRLjrQJZWNjLWJwNTEyiNQEExMKADwCGwMF
+CwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEETFmrknKqah9guFvQqlxY0U97
+j0gFAlxVsNgACgkQqlxY0U97j0gXxQH+LwPDKls26x1sCpNu7VPCmf9AUxpUoefx
+iyDOqmKa/7qFGru7N6QK8Q486OU6R7nIunxulNMw9ErJ+M1q1CF2fwH/b7niq1cm
+5B32hNLPt/Z4Vi8DxmwxCAClg2WSy8pLDp2FI1f8NcrslgiWBlfHtQZOQ/iHcVBK
+LIJQmFWkKVVUZp0BCgRaw4SHEgkrJAMDAggBAQ0EAwRCtEqQkEgzQDxNGCj0duj0
+aGvnH+DHKlP4V6p9LJVIL0TuF1uZBzP04efRvZT2vzCTcvvdE/G6G/txEZsR/989
+OchbkVWOPM/oCVktkaA02rBMcefhk9wKD+O9E3oHEN+tBt3yhmsv0MIR9IfPwi1G
+Csu55p4WUI//+ysB2T0YaQMBCgn+BwMCx8Y2ssPiWyjice4CGX+iVz5Oq3tvJGq0
+bJ2ws3AHqwWl4xsysPMLslHjViP3WC/bPrwbPAWCRBBhn6lYCwI8xROJ7ybgWoKT
+CDNznvR010YvZH7tm1sEXEfJwPVQI5JTCxKGNQRoyou3Y9y87FoJiLgEGBMKACAC
+GwwWIQRMWauScqpqH2C4W9CqXFjRT3uPSAUCXFWw4AAKCRCqXFjRT3uPSGUFAf0e
+FOGlgTIpmUi/JMmivv2PDXCJyBqkc65ucz500h64EHh+eUlSmoc7Ko+ew9RHNR51
+HeHLSElaHDsaVH5yauTQAf9X824ftUTdmcjVurQCZQPyK5MM824pfenwCL0aJcMG
+hrFGTTBIghd603BWnpyYPVIUL1+gaKlwncGsKk2QkTeh
+=Ip/f
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p256-pub.asc b/src/tests/data/test_stream_key_load/ecc-p256-pub.asc
new file mode 100644
index 0000000..fd1509e
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256-pub.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V
+dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3tAhlY2MtcDI1NoiU
+BBMTCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwIXgBYhBLVP3ru2c0I6
+XQqlRCNnTyGyRBUnBQJcVa/nAAoJECNnTyGyRBUn1ycA+wVg9sEfHDBaGtLqlUSB
+WdGKURrHN7CJe2UTz1/7oQCBAQDDi4RQyLHs+TfOrBNSbLEswCu1oEh8VmHt/SN7
++mqNLbhWBFrDgjUSCCqGSM49AwEHAgMELDOArLIG85ABQu1IwgQMpiIuUwj+N7ib
+gGenTRck5dkBpX48eK3lbjovXn4YkBneA7z14iez3+Sdg6UFAMFV2QMBCAeIeAQY
+EwgAIAIbDBYhBLVP3ru2c0I6XQqlRCNnTyGyRBUnBQJcVa/vAAoJECNnTyGyRBUn
+ZKoBAJ64gv3w27nFBERvIsRqufvR6xcimqS7Gif+WehBU+P5AQC5bqoISh0oSQid
+adI84f60RuOaozpjvR3B1bPZiR6u7w==
+=H2xn
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p256-revoked-key.asc b/src/tests/data/test_stream_key_load/ecc-p256-revoked-key.asc
new file mode 100644
index 0000000..c1405f3
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256-revoked-key.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V
+dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3iIwEIBMIADQWIQS1
+T967tnNCOl0KpUQjZ08hskQVJwUCXMrhPhYdAVN1cGVyc2VkZWQga2V5IHRlc3Qu
+AAoJECNnTyGyRBUna9QA/0ZGSvjlxXAwYI0Q8ag+Z2/3AoIbyjVBOuqeAgAgkXdm
+AQDuCOaypq3Y6hGjjKWPuDMtMj6SpgiqxXNRwXGwhzcUsLQIZWNjLXAyNTaIlAQT
+EwgAPAIbAwULCQgHAgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQS1T967tnNCOl0K
+pUQjZ08hskQVJwUCXFWv5wAKCRAjZ08hskQVJ9cnAPsFYPbBHxwwWhrS6pVEgVnR
+ilEaxzewiXtlE89f+6EAgQEAw4uEUMix7Pk3zqwTUmyxLMArtaBIfFZh7f0je/pq
+jS24VgRaw4I1EggqhkjOPQMBBwIDBCwzgKyyBvOQAULtSMIEDKYiLlMI/je4m4Bn
+p00XJOXZAaV+PHit5W46L15+GJAZ3gO89eIns9/knYOlBQDBVdkDAQgHiHgEGBMI
+ACACGwwWIQS1T967tnNCOl0KpUQjZ08hskQVJwUCXFWv7wAKCRAjZ08hskQVJ2Sq
+AQCeuIL98Nu5xQREbyLEarn70esXIpqkuxon/lnoQVPj+QEAuW6qCEodKEkInWnS
+POH+tEbjmqM6Y70dwdWz2Ykeru8=
+=Ijdn
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p256-revoked-sub.asc b/src/tests/data/test_stream_key_load/ecc-p256-revoked-sub.asc
new file mode 100644
index 0000000..4ea1600
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256-revoked-sub.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V
+dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3tAhlY2MtcDI1NoiU
+BBMTCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwIXgBYhBLVP3ru2c0I6
+XQqlRCNnTyGyRBUnBQJcVa/nAAoJECNnTyGyRBUn1ycA+wVg9sEfHDBaGtLqlUSB
+WdGKURrHN7CJe2UTz1/7oQCBAQDDi4RQyLHs+TfOrBNSbLEswCu1oEh8VmHt/SN7
++mqNLbhWBFrDgjUSCCqGSM49AwEHAgMELDOArLIG85ABQu1IwgQMpiIuUwj+N7ib
+gGenTRck5dkBpX48eK3lbjovXn4YkBneA7z14iez3+Sdg6UFAMFV2QMBCAeIjwQo
+EwgANxYhBLVP3ru2c0I6XQqlRCNnTyGyRBUnBQJcyEzdGR0CU3Via2V5IHJldm9j
+YXRpb24gdGVzdC4ACgkQI2dPIbJEFSdhZQEA6adrSCHXWuZybURP7CUxElb+n/lw
+GOWwSxepM0AgGkQBAKsUWDRUt7DXbdt9Dek4kGLUriCrH09M++Bks+bKlHrKiHgE
+GBMIACACGwwWIQS1T967tnNCOl0KpUQjZ08hskQVJwUCXFWv7wAKCRAjZ08hskQV
+J2SqAQCeuIL98Nu5xQREbyLEarn70esXIpqkuxon/lnoQVPj+QEAuW6qCEodKEkI
+nWnSPOH+tEbjmqM6Y70dwdWz2Ykeru8=
+=K8i6
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p256-revoked-uid.asc b/src/tests/data/test_stream_key_load/ecc-p256-revoked-uid.asc
new file mode 100644
index 0000000..d0cbef0
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256-revoked-uid.asc
@@ -0,0 +1,20 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mFIEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V
+dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3tAhlY2MtcDI1NoiU
+BBMTCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgIDAQIeAwIXgBYhBLVP3ru2c0I6
+XQqlRCNnTyGyRBUnBQJcVa/nAAoJECNnTyGyRBUn1ycA+wVg9sEfHDBaGtLqlUSB
+WdGKURrHN7CJe2UTz1/7oQCBAQDDi4RQyLHs+TfOrBNSbLEswCu1oEh8VmHt/SN7
++mqNLbQQZWNjLXAyNTYtcmV2b2tlZIiMBDATCAA0FiEEtU/eu7ZzQjpdCqVEI2dP
+IbJEFScFAlzISscWHSBVSUQgcmV2b2NhdGlvbiB0ZXN0LgAKCRAjZ08hskQVJ7KR
+AP4rGz9F7791X7YVcGlAi/EYf6yoDIeudzlEoX8xHKQ1mQEA2ITqr6t3FhmiPEDM
+R7THVTn6JGEAKrA8vDBlQDO3TwuIkAQTEwgAOBYhBLVP3ru2c0I6XQqlRCNnTyGy
+RBUnBQJcyEqhAhsDBQsJCAcCBhUKCQgLAgQWAgMBAh4BAheAAAoJECNnTyGyRBUn
+PjMA/0+GUwMQAZI79U+TkMiU2bu/YR7a7loGc4xvMVjqG43oAP9VmmgoQF0YxDv1
+8poUA76g3dNjPkUh9d9sloZJ+kWDeLhWBFrDgjUSCCqGSM49AwEHAgMELDOArLIG
+85ABQu1IwgQMpiIuUwj+N7ibgGenTRck5dkBpX48eK3lbjovXn4YkBneA7z14iez
+3+Sdg6UFAMFV2QMBCAeIeAQYEwgAIAIbDBYhBLVP3ru2c0I6XQqlRCNnTyGyRBUn
+BQJcVa/vAAoJECNnTyGyRBUnZKoBAJ64gv3w27nFBERvIsRqufvR6xcimqS7Gif+
+WehBU+P5AQC5bqoISh0oSQidadI84f60RuOaozpjvR3B1bPZiR6u7w==
+=5aIp
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p256-sec.asc b/src/tests/data/test_stream_key_load/ecc-p256-sec.asc
new file mode 100644
index 0000000..bedf1a9
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256-sec.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lKUEWsOCNRMIKoZIzj0DAQcCAwQS5G6mn5dhamZ6678SXE1azavqf8BItWO9Qv8V
+dS1vEEoD14urr5OQKTLuHhDRjvSQdaxRtkf0sI51T7230sT3/gcDAhltumvWSy17
+4tTtpAm1zorNqKF8LcpvpLCDPzm2ssrbrwwJ+vz9nK3EjHoc8wnrQyPBm2f/vBiD
+MZ0XmuckPNrf7gAijevCRIY5bONEjDG0CGVjYy1wMjU2iJQEExMIADwCGwMFCwkI
+BwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEtU/eu7ZzQjpdCqVEI2dPIbJEFScF
+AlxVr+cACgkQI2dPIbJEFSfXJwD7BWD2wR8cMFoa0uqVRIFZ0YpRGsc3sIl7ZRPP
+X/uhAIEBAMOLhFDIsez5N86sE1JssSzAK7WgSHxWYe39I3v6ao0tnKkEWsOCNRII
+KoZIzj0DAQcCAwQsM4CssgbzkAFC7UjCBAymIi5TCP43uJuAZ6dNFyTl2QGlfjx4
+reVuOi9efhiQGd4DvPXiJ7Pf5J2DpQUAwVXZAwEIB/4HAwJL9Ru8AoywP+Jhn+z4
+H4OoZbWVtJrRf+cbZlvT+8q2Wm18Zf7SnSSG8xluWP0M5LP/a4xOipN0PHbXiNMa
+u6+OexwczmWBMCrUdVu+fjDLiHgEGBMIACACGwwWIQS1T967tnNCOl0KpUQjZ08h
+skQVJwUCXFWv7wAKCRAjZ08hskQVJ2SqAQCeuIL98Nu5xQREbyLEarn70esXIpqk
+uxon/lnoQVPj+QEAuW6qCEodKEkInWnSPOH+tEbjmqM6Y70dwdWz2Ykeru8=
+=+i8S
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p256k1-pub-2.b64 b/src/tests/data/test_stream_key_load/ecc-p256k1-pub-2.b64
new file mode 100644
index 0000000..00790a8
--- /dev/null
+++ b/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/src/tests/data/test_stream_key_load/ecc-p256k1-pub.asc b/src/tests/data/test_stream_key_load/ecc-p256k1-pub.asc
new file mode 100644
index 0000000..837f8a8
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256k1-pub.asc
@@ -0,0 +1,14 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mE8EWsOE8xMFK4EEAAoCAwQVHqFZWqedXUIkNFs82PsQB3bsCDhrL/73xZca3+vo
+kB4T7jHcACThuMZYuUqUo9NzNTJioluOvZG+UdYXPdfdtAplY2MtcDI1NmsxiJQE
+ExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEgfdytX1Ov+cA
+CmYjPqW7b5aSwaAFAlxVsQ0ACgkQPqW7b5aSwaD2tQD/R4d15NBuSJ6IB1brH0E9
+nEWkqo892PaAY5akdCO/i9EBAMsjE5NPxBnCs03c+VHFU200k27ixdrWpUa+HZEI
+A5wSuFMEWsOE8xIFK4EEAAoCAwSUWwe7CaaOYRANiKet2evLiOumefIHuvRpyOSK
+hyRdclIWpBUCAWEnmalkEL/8cEM5fjtILtCOKXqCOBsPv45HAwEIB4h4BBgTCAAg
+AhsMFiEEgfdytX1Ov+cACmYjPqW7b5aSwaAFAlxVsRUACgkQPqW7b5aSwaCETgD/
+YXzCMYMbPGAU2oTitjAno8hDWmgTeaFWeCmqf6l9mP8BAKvpewWeFGZfWGAQcWPi
+E+jv7vadvEt1yMA8rmT041F5
+=mDCI
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p256k1-pub.b64 b/src/tests/data/test_stream_key_load/ecc-p256k1-pub.b64
new file mode 100644
index 0000000..8007e29
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256k1-pub.b64
@@ -0,0 +1 @@
+mE8EWsOE8xMFK4EEAAoCAwQVHqFZWqedXUIkNFs82PsQB3bsCDhrL/73xZca3+vokB4T7jHcACThuMZYuUqUo9NzNTJioluOvZG+UdYXPdfdtAplY2MtcDI1NmsxiJQEExMIADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEgfdytX1Ov+cACmYjPqW7b5aSwaAFAlxVsQ0ACgkQPqW7b5aSwaD2tQD/R4d15NBuSJ6IB1brH0E9nEWkqo892PaAY5akdCO/i9EBAMsjE5NPxBnCs03c+VHFU200k27ixdrWpUa+HZEIA5wSuFMEWsOE8xIFK4EEAAoCAwSUWwe7CaaOYRANiKet2evLiOumefIHuvRpyOSKhyRdclIWpBUCAWEnmalkEL/8cEM5fjtILtCOKXqCOBsPv45HAwEIB4h4BBgTCAAgAhsMFiEEgfdytX1Ov+cACmYjPqW7b5aSwaAFAlxVsRUACgkQPqW7b5aSwaCETgD/YXzCMYMbPGAU2oTitjAno8hDWmgTeaFWeCmqf6l9mP8BAKvpewWeFGZfWGAQcWPiE+jv7vadvEt1yMA8rmT041F5
diff --git a/src/tests/data/test_stream_key_load/ecc-p256k1-pub.pgp b/src/tests/data/test_stream_key_load/ecc-p256k1-pub.pgp
new file mode 100644
index 0000000..3309e0d
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256k1-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/ecc-p256k1-sec.asc b/src/tests/data/test_stream_key_load/ecc-p256k1-sec.asc
new file mode 100644
index 0000000..51f8b75
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p256k1-sec.asc
@@ -0,0 +1,17 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lKIEWsOE8xMFK4EEAAoCAwQVHqFZWqedXUIkNFs82PsQB3bsCDhrL/73xZca3+vo
+kB4T7jHcACThuMZYuUqUo9NzNTJioluOvZG+UdYXPdfd/gcDAmcowcf/1Fgc4jeG
+5372WA6986ggFCFRR6HTTMxm3cRMXy85TUMgiTvZlId3+Ao90NGiMI19UORZsfRj
+9XvKVsD5ib2R3PNYFXNN/gBsIHm0CmVjYy1wMjU2azGIlAQTEwgAPAIbAwULCQgH
+AgMiAgEGFQoJCAsCBBYCAwECHgMCF4AWIQSB93K1fU6/5wAKZiM+pbtvlpLBoAUC
+XFWxDQAKCRA+pbtvlpLBoPa1AP9Hh3Xk0G5InogHVusfQT2cRaSqjz3Y9oBjlqR0
+I7+L0QEAyyMTk0/EGcKzTdz5UcVTbTSTbuLF2talRr4dkQgDnBKcpgRaw4TzEgUr
+gQQACgIDBJRbB7sJpo5hEA2Ip63Z68uI66Z58ge69GnI5IqHJF1yUhakFQIBYSeZ
+qWQQv/xwQzl+O0gu0I4peoI4Gw+/jkcDAQgH/gcDApoF5BqduTaC4lchC3s9mFKD
+Q3mIgWfyHSC6/JPX8SM5o+qJ4lJK2Kq8DE9MxT1zXhsZoaNEHh6wnJTSDK3+U3YP
+5u4Sl73ryE0MqjqVj12IeAQYEwgAIAIbDBYhBIH3crV9Tr/nAApmIz6lu2+WksGg
+BQJcVbEVAAoJED6lu2+WksGghE4A/2F8wjGDGzxgFNqE4rYwJ6PIQ1poE3mhVngp
+qn+pfZj/AQCr6XsFnhRmX1hgEHFj4hPo7+72nbxLdcjAPK5k9ONReQ==
+=9r0z
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p384-pub.asc b/src/tests/data/test_stream_key_load/ecc-p384-pub.asc
new file mode 100644
index 0000000..b2b5995
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p384-pub.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mG8EWsOCnBMFK4EEACIDAwTRTCLBHlq6SUTiXZfWR0vbUh/0VAlrePaqHVIE4LEK
+0UBhPCIQOGuGL4JIufc8UzBWhiMLJ0z7KRjBWufsMZR2+rqj+unOK2lLu7sc9or8
+X6B74hhP3Ili24PgGFAeAG+0CGVjYy1wMzg0iLQEExMJADwCGwMFCwkIBwIDIgIB
+BhUKCQgLAgQWAgMBAh4DAheAFiEEqyXLoELdkkw6zD7TJCo6peqF9EoFAlxVsBEA
+CgkQJCo6peqF9EooJQF7BPZelriXwZ/kJzaamImHBddkLFc7d2WbuSfDxEZQ+Mfw
+BAP3+QYUaFtfeqApjY69AX4w6LhTUgI2kl4O0Vc7ZOlqZBlwAc8CMV08TTfOEio2
+b51SItvhLdDrFRJ2K4jiO+a4cwRaw4KcEgUrgQQAIgMDBORWqhYflSrYzF04SK8q
+8Om+DYTvwRtUlr3Aoq44+gm5yBcmJmgT3TKrp/bx5Jg/zwzIASFn0agbxkqKpQqH
+sHeelWsSBROQzy98HXdCp3nVmghI2aDk8zdD6AV4m7c2ewMBCQmImAQYEwkAIAIb
+DBYhBKsly6BC3ZJMOsw+0yQqOqXqhfRKBQJcVbAZAAoJECQqOqXqhfRKgAIBf3Wk
+TsqUA1JXkPGetA9sjHglIICN+DZY5k+PwTJUxaW2zrkiPJ3BYEnKbmmBLzA7BgGA
+4RYatyl2WOUYh/poRLgu7JpE4oRqdmNA+QOpCILMId1AeXfj4W01RKFWaKeH+3Yy
+=2H/0
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p384-sec.asc b/src/tests/data/test_stream_key_load/ecc-p384-sec.asc
new file mode 100644
index 0000000..c9f0136
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p384-sec.asc
@@ -0,0 +1,21 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lNIEWsOCnBMFK4EEACIDAwTRTCLBHlq6SUTiXZfWR0vbUh/0VAlrePaqHVIE4LEK
+0UBhPCIQOGuGL4JIufc8UzBWhiMLJ0z7KRjBWufsMZR2+rqj+unOK2lLu7sc9or8
+X6B74hhP3Ili24PgGFAeAG/+BwMCfUxHUAMEmtTiCKgq9GVc4dyjX5sQ3Wnugg3L
+Fz/wT3HRqkDkTVWRWQwWKo2HhkiHRbuLy+XNASgYrmovSPoMhMtB2QBQeDXmDtpH
+ZEQoBWqY80cgzhSoBali/NETsw20CGVjYy1wMzg0iLQEExMJADwCGwMFCwkIBwID
+IgIBBhUKCQgLAgQWAgMBAh4DAheAFiEEqyXLoELdkkw6zD7TJCo6peqF9EoFAlxV
+sBEACgkQJCo6peqF9EooJQF7BPZelriXwZ/kJzaamImHBddkLFc7d2WbuSfDxEZQ
++MfwBAP3+QYUaFtfeqApjY69AX4w6LhTUgI2kl4O0Vc7ZOlqZBlwAc8CMV08TTfO
+Eio2b51SItvhLdDrFRJ2K4jiO+ac1gRaw4KcEgUrgQQAIgMDBORWqhYflSrYzF04
+SK8q8Om+DYTvwRtUlr3Aoq44+gm5yBcmJmgT3TKrp/bx5Jg/zwzIASFn0agbxkqK
+pQqHsHeelWsSBROQzy98HXdCp3nVmghI2aDk8zdD6AV4m7c2ewMBCQn+BwMCB/CW
+b4nsCnTiAJU55Rbh23Zu/keMcWKc4HfxKX9YIrsb1OgEk+dvxuzOZ5npwvdDoVJc
+1LnU902c0a1hDbObQTKM8dJG9BqdjFGNfcxRXz5x6E27Xt/mfwizvNHBQBKImAQY
+EwkAIAIbDBYhBKsly6BC3ZJMOsw+0yQqOqXqhfRKBQJcVbAZAAoJECQqOqXqhfRK
+gAIBf3WkTsqUA1JXkPGetA9sjHglIICN+DZY5k+PwTJUxaW2zrkiPJ3BYEnKbmmB
+LzA7BgGA4RYatyl2WOUYh/poRLgu7JpE4oRqdmNA+QOpCILMId1AeXfj4W01RKFW
+aKeH+3Yy
+=6jzN
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p521-pub.asc b/src/tests/data/test_stream_key_load/ecc-p521-pub.asc
new file mode 100644
index 0000000..db18f81
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p521-pub.asc
@@ -0,0 +1,19 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mJMEWsODGxMFK4EEACMEIwQA8OZCJ8Iv4Qr2oRr0kqez0nPSW/vNxYBZRpCJ9ab8
+kVaRhW7+5vEsecm5XugZSePbAmERmk3oEiSgu35r6aovowcAGrOBfBm9fyIVqaoX
+veTS3yRHH6NEf044+pC+uBaaFukkrDmVTecdRvYr3Yrdc5ifyOve053znlpQ6a4n
+9bh4GGy0CGVjYy1wNTIxiNYEExMKADwCGwMFCwkIBwIDIgIBBhUKCQgLAgQWAgMB
+Ah4DAheAFiEET7Of9vpIV6S9fvW0IJLKgyQmO2oFAlxVsCkACgkQIJLKgyQmO2oK
+DwIGO72zo6otVkbHfeI9hWx/8FAOXh4MT4YtDicF/sj8QbHzdbEBHcLCByLYAnph
+8VVoCxpPcBLmNSHbNyreoksjEE0CB10P5kPrd/bYkdNx/HTu+1i8f7a448+Nr2rF
+PwdI9tOsghkT41qobZavjjnBlT/xv5DqXldJkEfaeiJxPHOKvMhWuJcEWsODGxIF
+K4EEACMEIwQBAY7ZCAjks1MWWxibg/EVaz5t6iEKJTwu8mGGKWdPZAQRKKNtNpf0
+pZAMV3I8ue/WQMsYKRYv5AGq1PnjV19DmLsA0aGw4MDM260coctkcn/2MAJQMC9+
+3Z+BJS3hqzwDuZ+LS13r0RLpgnt3ks+3ucG4II38ZZ1lTwKoIc+w/OuhsOIDAQoJ
+iLsEGBMKACACGwwWIQRPs5/2+khXpL1+9bQgksqDJCY7agUCXFWwLgAKCRAgksqD
+JCY7ahqbAgkBiXYtiBlp5dmSYnbc4JoIYWcxTBQ+/dGHyU6ZEfC5VQz2mrdJetK1
+bIID0rFSsd24/8IzAqM3L+nY9h9bULWroroCBjTohh0j2EbW+hFOrRqL01osnlY+
+1/G8e44blB5JqsPI9FqOZOUj6IzsUuV1N9gJbm1RHu/hSpm52d6rX4nOTbqt
+=3jnl
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-p521-sec.asc b/src/tests/data/test_stream_key_load/ecc-p521-sec.asc
new file mode 100644
index 0000000..db39589
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-p521-sec.asc
@@ -0,0 +1,24 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lQEHBFrDgxsTBSuBBAAjBCMEAPDmQifCL+EK9qEa9JKns9Jz0lv7zcWAWUaQifWm
+/JFWkYVu/ubxLHnJuV7oGUnj2wJhEZpN6BIkoLt+a+mqL6MHABqzgXwZvX8iFamq
+F73k0t8kRx+jRH9OOPqQvrgWmhbpJKw5lU3nHUb2K92K3XOYn8jr3tOd855aUOmu
+J/W4eBhs/gcDAoPLFnD4UjBV4vhbqK8X0Acg1Gvjodifspk8jhXTX7UvyWsjM4jY
+bySolQmYYDMfoH32M/durDLkIlpXASwFdO6CcSUQNLykcefEkANZBFqhuPyteG8L
+ym0hZucheCmSplOrN/kbbt3xaCakgLA+bMi0CGVjYy1wNTIxiNYEExMKADwCGwMF
+CwkIBwIDIgIBBhUKCQgLAgQWAgMBAh4DAheAFiEET7Of9vpIV6S9fvW0IJLKgyQm
+O2oFAlxVsCkACgkQIJLKgyQmO2oKDwIGO72zo6otVkbHfeI9hWx/8FAOXh4MT4Yt
+DicF/sj8QbHzdbEBHcLCByLYAnph8VVoCxpPcBLmNSHbNyreoksjEE0CB10P5kPr
+d/bYkdNx/HTu+1i8f7a448+Nr2rFPwdI9tOsghkT41qobZavjjnBlT/xv5DqXldJ
+kEfaeiJxPHOKvMhWnQELBFrDgxsSBSuBBAAjBCMEAQGO2QgI5LNTFlsYm4PxFWs+
+beohCiU8LvJhhilnT2QEESijbTaX9KWQDFdyPLnv1kDLGCkWL+QBqtT541dfQ5i7
+ANGhsODAzNutHKHLZHJ/9jACUDAvft2fgSUt4as8A7mfi0td69ES6YJ7d5LPt7nB
+uCCN/GWdZU8CqCHPsPzrobDiAwEKCf4HAwIuWcge4wilMuIYCuL3vpjk4I75bSCk
+UjBWck9faAM8JcRnUOpuSAN+4yVNdj/cwIUFefWtAph0LDNUYNaA8ide3xCsgy2b
+diVW5+n6kfQ1Ft//xiE9LaOi7HttB27JTPK9EqYZNEsnyehKOY8/64c4iLsEGBMK
+ACACGwwWIQRPs5/2+khXpL1+9bQgksqDJCY7agUCXFWwLgAKCRAgksqDJCY7ahqb
+AgkBiXYtiBlp5dmSYnbc4JoIYWcxTBQ+/dGHyU6ZEfC5VQz2mrdJetK1bIID0rFS
+sd24/8IzAqM3L+nY9h9bULWroroCBjTohh0j2EbW+hFOrRqL01osnlY+1/G8e44b
+lB5JqsPI9FqOZOUj6IzsUuV1N9gJbm1RHu/hSpm52d6rX4nOTbqt
+=Fv6R
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-x25519-pub.asc b/src/tests/data/test_stream_key_load/ecc-x25519-pub.asc
new file mode 100644
index 0000000..d531d7a
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-x25519-pub.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEW8SVGhYJKwYBBAHaRw8BAQdA9SMZ2uw0YugMFcl5TpEZeBRAGniEk9a42XNs
+7QA4Tky0DGVkZHNhLXgyNTUxOYiQBBMWCAA4FiEETJc4pvK+Thp5bJt7lBgioPwb
+MKUFAlvElRoCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQlBgioPwbMKUi
+1wEAgMq3X7o17OJBPfY3He/exDR6LhWwAAXrVQR/WdRiHkEBALd1Mj0BlZZLoKTr
+uJ4MD5CYZLicXTRwOv6e52F/DHwJuDgEW8SVGhIKKwYBBAGXVQEFAQEHQA0Lh2mG
+lB1O4xDYgztm/aX7+8AdHEGaMsCF1RQ6wVUeAwEIB4h4BBgWCAAgFiEETJc4pvK+
+Thp5bJt7lBgioPwbMKUFAlvElRoCGwwACgkQlBgioPwbMKXmlQD+KxVg2dGL8lRW
+rQajwzmuwMrJX1lvJylg5Ozk6SGrBeABANZrdt8bmArEqeRVxFO2F4P7btyIpf1w
+5aNpqqtvkRcB
+=EYfV
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/ecc-x25519-sec.asc b/src/tests/data/test_stream_key_load/ecc-x25519-sec.asc
new file mode 100644
index 0000000..563c283
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/ecc-x25519-sec.asc
@@ -0,0 +1,16 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+lIYEW8SVGhYJKwYBBAHaRw8BAQdA9SMZ2uw0YugMFcl5TpEZeBRAGniEk9a42XNs
+7QA4Tkz+BwMCLAlPURvxQR/iIVF7BZrlBBNy9xK1xEXRMlUbBJHafjAMQpLi2nY9
+E7p1+0Clb22shQOFNgOiGimmB83MCo3XHzoRt8xQGZlKpMDceNEECrQMZWRkc2Et
+eDI1NTE5iJAEExYIADgWIQRMlzim8r5OGnlsm3uUGCKg/BswpQUCW8SVGgIbAwUL
+CQgHAgYVCgkICwIEFgIDAQIeAQIXgAAKCRCUGCKg/BswpSLXAQCAyrdfujXs4kE9
+9jcd797ENHouFbAABetVBH9Z1GIeQQEAt3UyPQGVlkugpOu4ngwPkJhkuJxdNHA6
+/p7nYX8MfAmciwRbxJUaEgorBgEEAZdVAQUBAQdADQuHaYaUHU7jENiDO2b9pfv7
+wB0cQZoywIXVFDrBVR4DAQgH/gcDAt5EcLA0DdpZ4sf8eYvJX7czRLAi+yyIDSrL
+q5Gosim8ZH1KHSa/JMRsQrxqQTEn5adD4oJpuXKDwd0SQxfr1o4CIvNJmJNh5DET
+b/rSLKyIeAQYFggAIBYhBEyXOKbyvk4aeWybe5QYIqD8GzClBQJbxJUaAhsMAAoJ
+EJQYIqD8GzCl5pUA/isVYNnRi/JUVq0Go8M5rsDKyV9ZbycpYOTs5OkhqwXgAQDW
+a3bfG5gKxKnkVcRTtheD+27ciKX9cOWjaaqrb5EXAQ==
+=QKtG
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/eddsa-00-pub.pgp b/src/tests/data/test_stream_key_load/eddsa-00-pub.pgp
new file mode 100644
index 0000000..7679d15
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/eddsa-00-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/eddsa-00-sec.pgp b/src/tests/data/test_stream_key_load/eddsa-00-sec.pgp
new file mode 100644
index 0000000..6bb0f45
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/eddsa-00-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/2F25DB025DEBF3EA2715350209B985829B04F50A.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/2F25DB025DEBF3EA2715350209B985829B04F50A.key
new file mode 100644
index 0000000..987b189
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/2F25DB025DEBF3EA2715350209B985829B04F50A.key
@@ -0,0 +1,2 @@
+(21:protected-private-key(3:ecc(5:curve15:brainpoolP384r1)(1:q97:Š÷/”òl>h¯„—+||~9rºn{ÔaXÎ=b¿' ÈZ£õÂØ8a[Ü}DzõæÊÝ£5­ŽA³±x,‹ÍÔ@t m–Çg§½¿…ý {ÖeÐüzà+_„Z‹)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:áÓ• Ùó·ø8:20212736)16:ÔhG‰(Š³”ö]˜Ã¹/)112:9|Ï¢m
+îb¦M¬­1ž‹…ÆÃFº:ÃJ~gó%"ñ þˆ@Þe.Òz ÕšTÇŠm˜<Æd 6qu:LrËXYšFÈ %ÚÕ¯¦À†õÅ~á… oÜûÀ˜oÄ‘wÓRHŽW9ôãPÛL@Äq)(12:protected-at15:20181013T100247))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/3E36CDC06F95B604429321B3E3D6B2A2A5CDD562.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/3E36CDC06F95B604429321B3E3D6B2A2A5CDD562.key
new file mode 100644
index 0000000..4cc67f2
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/3E36CDC06F95B604429321B3E3D6B2A2A5CDD562.key
@@ -0,0 +1,32 @@
+Created: 20211224T104452
+Key: (protected-private-key (rsa (n #00B91023E58EBA3BBA97843FCA51F12616
+ DEACA6749842E45BF4B23F784A0FA6B2DE5EEA86FA9EA103778A1473E638152BA2BDD2
+ 42763F6444A0E76CA8F8A80169FE5882DECBBB149D1F4B7DA9751F6F55A59E9490C599
+ DB56A6F8443C09E29E751A90FC472078034A4294B60C1BBEB219C6E99DC40FDDBBD37A
+ 456C97C378276B004AFBB6F3D47A63628A111618DDFD84361B9066F71EA2F530D8DF6D
+ 4108365D285D71B049C28ECF9C52B030A89740A9C34DC993FFB5D7D1F5494B4881AA94
+ 54FB08CA3DEED003F7EB8A77D38CBF449A6D3836DCBD0057DB2AD87BEBAE280581194D
+ 1632E5DB118D0C686CF6A862FC805014506815C4DC8CA44F094D02FBA4E1#)(e
+ #010001#)(protected openpgp-s2k3-ocb-aes ((sha1 #C7C580AFA00CF157#
+ "43018240")#F3243CEA72C0BC4CB27E2B52#)#0BBBC70F39F7EDA3F9679012AC6637
+ E8FD2FA5FB4606F138FF71A6678AFCA2B753AD25D31E9FF2BB037D83E19F8EFF31528C
+ C2C4DCACD69E07723F6E01BEB758E6FAFFD21B4FEE3644D47AE5A33B6C3E893F7440DC
+ 6C838ABE94BE85D419C41AFFB66F0E64FE2BBFCE5626D89683E306D9B2460ED6FAF7B9
+ 41536003714C8AB1E935FE4791C50A88DF76B91F4C9AE2354E34EA15AF521DD56D16B5
+ 938EFEA5393A718C74340411B4619716D898ECB3FFD731CAFAE7CDE495A87BC3B0DAA3
+ 64400A5D85645AAAA283AF19B6EA2B433A480D6DAE039B97827D95FD812A4952CE4B2C
+ 1D90E0FBF016E61B9018DD314F91893FB8D1318D5234459E1D399A28901706A29226BB
+ D5F63975D9800A61F413BE23C880D3767DCEA8D830EFC6E99130208F16EDF14E80DAF4
+ CE1066C6F7814A142B5986C6F4536E81C9B239CCDBB981B70802435FA71343AD5655C3
+ 3461B76431CEA0C38A926407FB55F0A15BEE0913418FB6BA9672683CBD17CD1FF2644F
+ AFBDB109FA327750C3FBDB3AEA7EE52237355EF3F6748F0EAECFF726537EB5ECB6AB3D
+ 831959C6FA6C3B37CCCC91A0CA584FD05C9C4D1832F563A9C78CC8ED319F99E256CE91
+ 96C217148ED363B9B0B706A9335D3B20E3D2AD923AFBAE42262A36ABE75AB5BE46EF60
+ DB08E9A5BABEA4554D5DE190F8C0948AD345239AD93476414331F5AF71287A79EC6E65
+ D74165CBFB7D3CD7F3FAC37F021016B35377DCC70B54E70E72F044C65E8EF6B8E31E2C
+ 37241D062B7809BD68D7831CB2C29D792E0FB560F2B10C6AC701859FCED31024BE68CA
+ 8DB00F4FB880184AEC7033E5456A8BD2895A8E7D4D28EA5E586C77CD749CCE71207CEB
+ A3F9173D27CCE8F97243DD1C6207408825956D4CFB9B18E3CCF3A38B0800D2DB41EAC5
+ 0F4DC182FFD8F53FD2E5BD07A7B6ED47DC7821F72D9A189ED8C60E1C5C80DF89370ED7
+ 724A86D642480C162E3EA87DBBC898E86D8625#)(protected-at
+ "20230116T185755")))
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/48FFED40D018747363BDEFFDD404D1F4870F8064.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/48FFED40D018747363BDEFFDD404D1F4870F8064.key
new file mode 100644
index 0000000..a7c86bb
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/48FFED40D018747363BDEFFDD404D1F4870F8064.key
@@ -0,0 +1 @@
+(21:protected-private-key(3:ecc(5:curve9:secp256k1)(1:q65:”[» ¦Ža ˆ§­Ùëˈë¦yòºôiÈ䊇$]rR¤a'™©d¿üpC9~;H.ÐŽ)z‚8¿ŽG)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:‡úœ¨º¸8:21371904)16:,†Á'´Üpq7Àß=weÙå)96:öP»Öô% ºEÄðC€10Jñæý’%o–ÇÝ”¿Ö¤ÈMHÄ/åÄ™µYq𡵑7}šΑΊgŸŸ[²¤é@#¤ˆ´W®™TJ©>y„¾Z),zÓˆW¢)(12:protected-at15:20181025T180550))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/498B89C485489BA16B40755C0EBA580166393074.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/498B89C485489BA16B40755C0EBA580166393074.key
new file mode 100644
index 0000000..67cbc17
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/498B89C485489BA16B40755C0EBA580166393074.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/552286BEB2999F0A9E26A50385B90D9724001187.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/552286BEB2999F0A9E26A50385B90D9724001187.key
new file mode 100644
index 0000000..7f612e2
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/552286BEB2999F0A9E26A50385B90D9724001187.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/5A484F56AB4B8B6583B6365034999F6543FAE1AE.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/5A484F56AB4B8B6583B6365034999F6543FAE1AE.key
new file mode 100644
index 0000000..634480e
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/5A484F56AB4B8B6583B6365034999F6543FAE1AE.key
@@ -0,0 +1,2 @@
+(21:protected-private-key(3:ecc(5:curve15:brainpoolP512r1)(1:q129:o2”9çÜ´{·¡£C³0©™@>ÊêtÜ»öÃÚ²/'?µ¶TXäpøö>w?8Ž–Á³)†¿–ŽDJ
+‹¬šŽ¹Ì¯oÈ8Wú ¤I„5åaŠ¯tEI9F×ôú„éÖ> 1ÖG°#ÝÐ `Ö þ9)²(ù·a¥HnŽ)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:ø/°±”§ Ë8:20212736)16:rJ”þ¯ÁIÎ3ŸÜîOg)128:ÛmÁ±¼½Ã1l­K§œ[3Fx´I|ܘµºø*laþ™~º²DâÇÖ ÔVP¡7Õÿ¹`Uó÷áŒËïŒ þq†Îæ‹vÍñÓ=¦D6~ÌàuEö˜ëË4]ðØú"¼y¼¢ŒØ–½™›‚Íèæm…«©UÓOŽt +}*g)ƒ)(12:protected-at15:20181013T100249))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/636C983EDB558527BA82780B52CB5DAE011BE46B.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/636C983EDB558527BA82780B52CB5DAE011BE46B.key
new file mode 100644
index 0000000..cf42361
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/636C983EDB558527BA82780B52CB5DAE011BE46B.key
@@ -0,0 +1,2 @@
+(21:protected-private-key(3:ecc(5:curve10:Curve25519)(5:flags9:djb-tweak)(1:q33:@ ‡i†”Nã؃;fý¥ûûÀAš2À…Õ:ÁU)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:PŒÝ2¥3h8:19621888)16:“Ö-Sfçú_õß½¶þ)96: ¯‹eñÔ 8·©þÊ4ü³ÓÖglR
+›:_j4ùLÈÁÓ…®‚"×ï›xh‘P+îÌ3Üþæ1M&~e û#H^&ƒÅE¹¹òAgsCëÙI;hþ~˜)(12:protected-at15:20181015T132542))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF.key
new file mode 100644
index 0000000..605e229
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF.key
@@ -0,0 +1,3 @@
+(21:protected-private-key(3:ecc(5:curve10:NIST P-384)(1:q97:äVª•*ØÌ]8H¯*ðé¾ „ïÁT–½À¢®8ú ¹È&&hÝ2«§öñä˜?Ï È!gѨÆJŠ¥
+‡°wž•kÏ/|wB§yÕšHÙ äó7Cèx›·6{)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:¶uYU,‰yš8:20091904)16:ùÝx›gâæ¶7'±Î)112:¤U!W|’]J"ëi’›­©ɱ+›¼6-¨ª3w«ücÎ 3ЭÚÁ=rÎ_„G_GßÍßJM»r'ræ‚Ðò©SèÌa£juND
+v~Ü‚@e«è™EÃFtÂó•Ï_RÛR˜~ùžÖxj)(12:protected-at15:20181005T160531))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/9133E4A7E8FC8515518DF444C3F2F247EEBBADEC.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/9133E4A7E8FC8515518DF444C3F2F247EEBBADEC.key
new file mode 100644
index 0000000..e1ae7f9
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/9133E4A7E8FC8515518DF444C3F2F247EEBBADEC.key
@@ -0,0 +1,3 @@
+(21:protected-private-key(3:ecc(5:curve15:brainpoolP512r1)(1:q129:B´JH3@<M(ôvèôhkçàÇ*SøWª},•H/Dî[™3ôáçѽ”ö¿0“rûÝñºûq›ÿß=9È[‘UŽ<Ïè Y-‘ 4Ú°Lqçá“Ü
+ã½zß­Ýò†k/ÐÂô‡ÏÂ-F
+˹æžPÿû+Ù=i)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:M ¥l äÃ8:20212736)16:/øtÊ/ÃË)u2[‰i{)128:L±L½ç8›š &¬ù-£ù‡)&YOñ¨ÅaiÔèRE»îšõžž~0 5¶]îÀŸt”jê¸n6¹dˆ¶óAšÌÚM†UQRS<Ø/K+Él4‡=Ì }òšý_ëI`I 3Ÿ¡w3÷Æ°¨l‘—'‚9HtÌú1³„Ýù)(12:protected-at15:20181013T100249))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/940D97D75C306D737A59A98EAFF1272832CEDC0B.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/940D97D75C306D737A59A98EAFF1272832CEDC0B.key
new file mode 100644
index 0000000..3dfd055
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/940D97D75C306D737A59A98EAFF1272832CEDC0B.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7.key
new file mode 100644
index 0000000..af836d5
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A1338230AED1C9C125663518470B49056C9D1733.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A1338230AED1C9C125663518470B49056C9D1733.key
new file mode 100644
index 0000000..8b6977e
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A1338230AED1C9C125663518470B49056C9D1733.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.key
new file mode 100644
index 0000000..04e5007
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A56DC8DB8355747A809037459B4258B8A743EAB5.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C.key
new file mode 100644
index 0000000..c143668
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE.key
new file mode 100644
index 0000000..2d7722a
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8.key
new file mode 100644
index 0000000..bae7b50
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8.key
@@ -0,0 +1,2 @@
+(21:protected-private-key(3:ecc(5:curve15:brainpoolP384r1)(1:q97:DÔ’¡«îŸ$_gfßÈóÏч_Ž/¹æc^¼eê=Éün|±ð"„ÿY€õ£yž}>ü'ðd6˜€-œ#öüjw=Èë^Z&[QoØ9ËR…ñ–ñ œ«L)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:GŒmë.FÏ8:20212736)16:Lj/¯üøk%¯~wÓ
+™å)112:mÿþ¼lCØ]Ó;mYc–yhêÇ:NÙuØ°8>1Jê’'“!}ð„DݨRŒÚªLWœ„‡”ñåëBh þ(nô=¿ð{#_èi»wßéú-ç‡Éí}w_ùdEš#Õ¶Áz¡é‹U¼.±+·‚H)(12:protected-at15:20181013T100247))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/C1678B7DE5F144C93B89468D5F9764ACE182ED36.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/C1678B7DE5F144C93B89468D5F9764ACE182ED36.key
new file mode 100644
index 0000000..bd34376
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/C1678B7DE5F144C93B89468D5F9764ACE182ED36.key
@@ -0,0 +1,3 @@
+(21:protected-private-key(3:ecc(5:curve15:brainpoolP256r1)(1:q65:Õ]ñÒ#™&FÏü0¼ø*·ðÏJ†éêÕ=1žž¹8FIwMÄ•QwDPwÚ èmJþâÌ*jâV×)S{C)(9:protected25:openpgp-s2k3-sha1-aes-cbc((4:sha18:^Øž'ÉŽX8:20212736)16:IªooÛuËlNÓaø)96:ì®?QÝ
+üùŒ€¥Å?”,N¸ñ•Ðûd“Jeà·Ó¤?퀶’YnÞÞ.èÇ×ÕêÀÇ/ï|÷æèÃOï-,ªB:³añ£
+g?ItO¶²ß#ÔBB…}ʤ#x)uS)(12:protected-at15:20181013T100244))) \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/CED7034A8EB5F4CE90DF99147EC33D86FCD3296C.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/CED7034A8EB5F4CE90DF99147EC33D86FCD3296C.key
new file mode 100644
index 0000000..929582f
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/CED7034A8EB5F4CE90DF99147EC33D86FCD3296C.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D148210FAF36468055B83D0F5A6DEB83FBC8E864.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D148210FAF36468055B83D0F5A6DEB83FBC8E864.key
new file mode 100644
index 0000000..b7fb498
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D148210FAF36468055B83D0F5A6DEB83FBC8E864.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D91B789603EC9138AA20342A2B6DC86C81B70F5D.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D91B789603EC9138AA20342A2B6DC86C81B70F5D.key
new file mode 100644
index 0000000..e331a25
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/D91B789603EC9138AA20342A2B6DC86C81B70F5D.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.key
new file mode 100644
index 0000000..96bea9b
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FC81AECE90BCE6E54D0D637D266109783AC8DAC0.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FCEB1E2A5E3402B8E76E7B89A4EB12CF52B50C25.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FCEB1E2A5E3402B8E76E7B89A4EB12CF52B50C25.key
new file mode 100644
index 0000000..66a75ed
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FCEB1E2A5E3402B8E76E7B89A4EB12CF52B50C25.key
@@ -0,0 +1,31 @@
+Created: 20211224T104452
+Key: (protected-private-key (rsa (n #00B8C92A0D942366DCDD228E1B9F2CFC7E
+ A9EB0162DEEE5BC8A127029ADDF50556C75A3E3769FFCB414F923D4088EB6474ACE791
+ E4BA0D2FFC2A4A7AE22C936ED722996C7F403FF34410DEC4043FC36D01E1D0535DD456
+ 0D57A5F1EEA31F9077DD5A27454495177EE67F990BA9F1F2306F01472C7172C3F25980
+ 39A3B186996E442D98CBD6A1C32E75225A5948089804A09B67A87176BD337FA1A38E8E
+ 3B50A2D627AD1504BF4040D668FDA943910A365BE343950A7547476700BFE44848DA30
+ F884F98DBD5729C4C6A7526F02369BE9F37DDEA6E73710FFB4290F3CF60CF57DC73E76
+ 88108E9C95127416DCDC8928251D72D50C257A82937A0654272DA66E5645#)(e
+ #010001#)(protected openpgp-s2k3-ocb-aes ((sha1 #5F888265C35D2860#
+ "43018240")"U}­LAΰ`$xZÔ")#D54F10FA742D1434DCC19EA06F45558828FF5E183B
+ 1626BA315AD4B58355AE6B26A86E88246F023C6F6E340FA60DDDDC61800F44C17DCA4E
+ CAA029F6542BDA91A43375DFB3C6888788DD8644771079B30A6518AA3A2BF82B6A4E17
+ 7B32E68AF0A898CEB4F51D95A488018BE538D18E7ECA5CBEEE3DE2436B14CE87B0EC67
+ 0916FEAE68C863C017054C41DB14A67DCD9AE1395632E7D77FDA7546608489E3EDF088
+ D37315570AF044F84283340B99916B91329143B2678C85B648BA1825A9548C7590B2CE
+ 57116D18A60BE4F50E31E25A82320E05A04C57D59A98C1DB7CB023FD4DBA57337D6381
+ 69EB377C7882E86FB364E845414A405C8184E8046A72B74A7B138F4A2AE16CBF44AFA0
+ 3D816BE17554A15E2DCCB1B3EDCA9A0ACDE9F52AD8910D6F64967063A82A15389027CA
+ E63F46C19C1AEFCAB0E50D97D80173E3B03174C27DFBFEF379A4E59257DADEF5114C05
+ 88C57A3977E47B6D27BEBFE7DDDAC9912ECDC3748ECD2BFB32AD1A7F9063B3D591A485
+ 9ECC9EF19BBAC7B1B7744D34778ECB2992BB997FB4BEC619904EEEAE3346D7A78618F7
+ 15C89AF9EE525534E128BD50F71B444DBA613FF41B94675F634FF44A9867B01225E983
+ 2C4FA3CD22CAD2B51B4547461A4586D6D6C5A2477D5F13ABC69A3288BB2329A37C7D9B
+ F20C585F84F3199D742DB7E91EA48321E3385A29BD78ED66DC38175B93DA83E8067C99
+ 4EB313B8568F9410956E1467D530B4C57F20879000535BCC870ACA545C7069C47B0CB5
+ CDBF9E92380CC9D08FAF742806B0E48B983D6E63A43280CC969D3F03CC89853E62EDFF
+ 9E2A6FE777BCE4BD49848668E6D5652C7B7C2B5DEC5082CC9836747D67991170F7B18E
+ FF437C1F4279E3AA8A78201C06938EC5177C1ED7A13496A7029C6A9C81FC68B5D89C0A
+ DACF5CF6EDD0E59D6B00A8F42B588830A2BA32A2245C9238BBF4F9D85AD96776E7A28C
+ 768F7F7E65F64917FFBA341163#)(protected-at "20230116T185756")))
diff --git a/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA.key b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA.key
new file mode 100644
index 0000000..5276dbe
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/private-keys-v1.d/FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA.key
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g10/pubring.kbx b/src/tests/data/test_stream_key_load/g10/pubring.kbx
new file mode 100644
index 0000000..716feb2
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g10/pubring.kbx
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/2D5DAB4841F4DBB74DEC7050A4B07458234ACB82.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/2D5DAB4841F4DBB74DEC7050A4B07458234ACB82.key
new file mode 100644
index 0000000..2971f91
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/2D5DAB4841F4DBB74DEC7050A4B07458234ACB82.key
@@ -0,0 +1,7 @@
+Created: 20221231T084420
+Key: (protected-private-key (ecc (curve Ed25519)(flags eddsa)(q
+ #403C555FDC6E190A0EAA37AFB1E8944645D52218E3BB8108E16F08A026F292851B#)
+ (protected openpgp-s2k3-ocb-aes ((sha1 #61DEC0F5FEE3B711#
+ "43295744")#A3AAD9A1A05A4B0D6353B384#)#8E11CE57E099975C68C1327AC14877
+ 2B78DD0D8B531A34B39629E74DD96CF1202CE8DBB24B25F6EF8DC468271919B7B749CE
+ 6AEB99F3555F93CC98C2#)(protected-at "20221231T084428")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5012C691581B550965573790E1156BBE903ABAA0.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5012C691581B550965573790E1156BBE903ABAA0.key
new file mode 100644
index 0000000..b387379
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5012C691581B550965573790E1156BBE903ABAA0.key
@@ -0,0 +1,45 @@
+Created: 20221231T084202
+Key: (protected-private-key (rsa (n #00C8DBF44379E1EDEBCFB4FA0DAF729358
+ D66964B05F7DADFEA86125DEBD8A4AA1C6D55E458BCCB1025F5D37A30A1C8B6AA826FB
+ 7779C55CCBFC3E13E627EEFB34847487AE3FB43579B12D8537B1432EE7FBCD8B628458
+ 413087D3A7DE6694345F260245C7031AF4CFAED0971A6643D519F2942757D9AC74603E
+ B0CDD7637F507424739973E6C8B72D20A973DBAF4AC09EA82A56346DA53AB8355FA5E3
+ AD673E4036718A5D997D46C283FFE2C7E7DCAB0267E419ADE2611DC6267D5609D67437
+ 9C134B1376BC078F7A94C9C6012F6BD11C6E75B512A8034DF96953F5C6FA1B1611C0A9
+ 054386ACE70B2107E975D72E0C5C6E87BAF7A8E3FE87DA66670130FCB5013C025B0DDD
+ CFD16164A43A03D89F843A9327073CFAD30154F7C6A2500DA4509EC0FBA9C0C01D1877
+ 2AB395095B31865452F27B7FA616354FF593EDAE64DA213FC4AC8052513B41196E1711
+ 668E322AE61D9ED7EB26C06F84C97F195CD44CE81DC3FDE72E1B7B48A31A2E256EE1C8
+ C8C884332C4FF8F5C7BC4160AFC1FE8BCBC7#)(e #010001#)(protected
+ openpgp-s2k3-ocb-aes ((sha1 #583A40087D62C09D#
+ "43295744")#F28D3EBDCA1D23E7474D9DD7#)#2F075FB117AD5F068EC4B6DE4578FD
+ 4E8C7B61F57E3B97C5D44144DD2FABC87C63A00EF45967E3592B849D69054CAA1E9358
+ A1748A9AA9AFA49D50766B52BB32EB98DA2E06B31F1CED540C17F3ED498A59F28CF219
+ 3C6A6FC65EE2086EE160A3CD31CFE7FC69F52500BB902146D12A2B7E51A70E52E80763
+ 3934E256D148C38D09394D08618D74671728351D4E6EE258CCD45CF6EAC964D5448A15
+ C4B61AF381322559F1771602A27615AC09D4A202F1FD68C51840237B7FF3AD3E978941
+ 732E5A12A9E6147F2B278A7E0C9443357249A7748E63B4EDD76B4A6111C0D7E7ED8CC5
+ BDEFC3067717617FCD0635AC7D8CD9FBB2C7F94CA090711EF6E085317AA84423F61DE3
+ BF3DDB9920978F3AC1E4AF44D9092336041DFEE445AAA7A0CB232FF54FF45DA8B0CB2B
+ 371AC5F9F229665797318A73A5CF16FBC056648FAA5791A56FFD8111D15989DB0E145E
+ 8A721148734F3CF45F4CDC888241062BF2639E4023C7ED4BC4CFD74D674A6E13DC6F09
+ 98E06E493DEDE88210F7C967023397C0FEE95C7B324225E172D5AC89743F90389C270B
+ D3A06B90374B769097FF0B55B5ABA4240E44236BBA5454D9AFD57786AA343EDD865F45
+ 4B8402D6B67577550250E0FD372C842E263AB52070E65AD9DD49946FD23D3C8D5D91BC
+ DA4EEFB6160E046D962448C71047C9A092193D28372519918A7CE96953949429B913D8
+ CA56016AABEA5FE9C1806F061681894430B125CC10A469579282D3FD19812032346D8F
+ FABB8AE4072A0DE32A3B8A508037D2CD9E9D2CB1072F80E6B956C717CC3ADA23D51067
+ 239DA7AA983F521C5017AC4F06A9F5974A0634EAFBAC92F18D7A9DFF912CFDE65500BC
+ 110367D3BB8B0CB096DB1ED0941E08B6E213E1910E51CCEE16EB88DEF53C0D01B42215
+ 3C398DE654CFAF4D50DED5F447FEA38D17B81025C65E0B3CBCD81AA26E2B08CBED7CC1
+ CAF25C6664CD8D237AA725ACD5BC39746EC59FB5ABEA4787E7621FB3320E7D3F5E9015
+ F8FE97AD49A4F01C6851F66934A1C1C7A360069101022386AAA189C0E4B8CC6D977128
+ 2A87CCF5EB2C0689DEC15F8F045DD8C71B25ACCF1584D75A18458E61EEC7BAEEAC09E4
+ FEBB3D7F7BA89230CB3116A494AC0AA22FA48A28937740B55B6F2584D892A751B41C1E
+ 370D9566E8BA99AABF3BFB9D538AAC6DA5F6DE6CC12AAC1A8C090C9EC2DAF89A48AE50
+ 4D8420F1C64DFD14BEDFE044EE524EA8FD4836BA4C3CF1F52BD5485856B19D23614DBC
+ 1D146A6B37B495E7230FE6F649E6D8190EEF5543656E086DE7A8874B2B2DB9422BEB48
+ 12CA7D243AFD6596D551C397A7F1AD4A0730BAABDA8A9E9FFDF4CA3F78113C1F6BF797
+ B73359C57D6E78E12A32A9EB077990583FEE85A084450091CACAD73774E57C8850918A
+ B14F5AF77A58CB3F4D41969FDAC7760D8383312ACEEAD2#)(protected-at
+ "20221231T084214")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B0DF3754AA0877E228FBFFDBDE337744EA244D4.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B0DF3754AA0877E228FBFFDBDE337744EA244D4.key
new file mode 100644
index 0000000..13435c1
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B0DF3754AA0877E228FBFFDBDE337744EA244D4.key
@@ -0,0 +1,8 @@
+Created: 20221231T090524
+Key: (protected-private-key (ecc (curve brainpoolP256r1)(q
+ #047DBA66E23B2FD9DD42608D4D6891D4C9D4F29CEC97285871F791FDCD36FECE894F
+ B163013166E2D2FDEC126C77DCCBD6D8D4D715C74AED5BF710211A7DA9C627#)(prote
+ cted openpgp-s2k3-ocb-aes ((sha1 #9B8AFFFA0C5E26D8#
+ "42743808")#08C47421A803CFD9410A77EC#)#1EE08BF6A813615F874B4ACAA24604
+ 853AC62096FBBB3D6BEFB4A701DB66A38EE1A68858AF89C7FF25BA871AFEE1FE0D8FF1
+ D456621A2A43DE63EC43#)(protected-at "20221231T090532")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B5E0586D3F942C5DBBF1FD21CCD46C364EFC4C4.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B5E0586D3F942C5DBBF1FD21CCD46C364EFC4C4.key
new file mode 100644
index 0000000..9a0bc0a
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/5B5E0586D3F942C5DBBF1FD21CCD46C364EFC4C4.key
@@ -0,0 +1,24 @@
+Created: 20221217T141407
+Key: (protected-private-key (elg
+(p #00F80B3F7119F92F9CC8FE36CB56FA6FF4
+ F5C475A03F837EBCC21726E5AD9D321FD5854AA4EF2574BC74BEA3E582F516EE0045BD
+ 1615C5D54976E55012AB1ACEF91BEFC8B4864985A2545D2375B1B004B35E0FCCFF081C
+ 1A72E9C77F79DA6BE44E50E7A8B89C887FD34CAF64705643119ADF095BAA4404366F8E
+ 11F41A02733626F6890E8A716313B3931D4AEB38E762566F6EF6BD84CBE8233FB1B111
+ 588D915EBD911143C37FF6F0EDA4F623874027C0CAEA025C26EB06C1A7270B9F7BA6FE
+ A1AAF629D894A98336139EEAC668A42888A18EE4D1353886EA3801440B59EF4C11B6F0
+ 8F23F1F86F9D6476427A295BA31F3BD5971D1306E94FEAE2177D88EBC3F3#)
+ (g #05#)
+ (y #00CC4FB30B45DA8A77328BBFEE8ADFB7A90007C5638B1F5E10BCF293FF5B
+ DDC915C6303F3AF73BBDC85485822C9EC13D3040A785D22579804BAC40B3530447E600
+ DF7BA6426836F741E55F164D4C4B92F7E243D74F82694171DE88927E9BC1FE44B441CB
+ EA1ACE589A6DD1CF28D80B3D65DD49F6D810F411FC9264A15151DBD02989A8F8DA82C0
+ 4352C550981DFF36B83F1E349648BCB4F51080808026506FB7626EFCE5101F3BD9604A
+ 71805EA762DF808AF3BFC390D109A7B61858F30516AF87687607D6F2D7E90772FDCAD0
+ B540279F62B10F164112EA492427051001913F28555F97559942D74B3EAA9FA5F0C7E0
+ FA5BF5D5586A7000F1DDB67CE24008D2E4#)
+ (protected openpgp-s2k3-ocb-aes
+ ((sha1 #3A911DAB4A6A1C9E# "42473472")#2D16E46DC89AAF535496AED5#)#CEB1
+ 738EB01C4CD60E59F0FC7BF5A761722922CEB1118E976B39B85AB8C5F6594F3394AB77
+ 1FDDE89BD2724346D542A329AE6B82C91F89407AB2D7358D9398AA26E6EB9BE965B6#)
+ (protected-at "20221217T141426")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/7C1187B9FD883651040A6EA6D50C226317A16C5A.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/7C1187B9FD883651040A6EA6D50C226317A16C5A.key
new file mode 100644
index 0000000..91fa935
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/7C1187B9FD883651040A6EA6D50C226317A16C5A.key
@@ -0,0 +1,7 @@
+Created: 20221231T090149
+Key: (protected-private-key (ecc (curve Curve25519)(flags djb-tweak)(q
+ #40B3901CCCE2C664F446B61DEB9DC1FFDC0ABEF2AA3FEEF3ED0799B95B07B38630#)
+ (protected openpgp-s2k3-ocb-aes ((sha1 #8A41101D8C7EA547#
+ "42743808")#03124C4F0BC5CAE14BFF4883#)#CC4BD95D94F061C42076E0470F6B67
+ 967F749FEFA560BA2DE264D8B9D17CF1B2599980ED77E530593CA53595729C3A1B810D
+ A0629C79994AC90227B4#)(protected-at "20221231T090158")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/8C28B6E8F9ABCD9F9F24B0AFA139828BF700E8CE.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/8C28B6E8F9ABCD9F9F24B0AFA139828BF700E8CE.key
new file mode 100644
index 0000000..987e07d
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/8C28B6E8F9ABCD9F9F24B0AFA139828BF700E8CE.key
@@ -0,0 +1,10 @@
+Created: 20221231T090356
+Key: (protected-private-key (ecc (curve "NIST P-384")(q
+ #0460D64C0A94C86CAF26E02B7DC29D50C397A057AA9A8BE66CD7D94F3B4215EAE52B
+ 6FE9D0E62A32EAEF01D407E63115A71FC7B585620A9EC8B3196187FCC780279B1E04AC
+ 05D36687B8C66ECD269C1DAAC14579487DA9E73962822B38A1DC160B#)(protected
+ openpgp-s2k3-ocb-aes ((sha1 #82FA37CEE131F10B#
+ "42743808")#3FCAAA1088F611124545C668#)#45DDCA810BF5361EE4270C53C8A8AF
+ 26ACF724800F571500A7F4BF51E65F7B5AE318D52DCBE005B585531FBE93C4FB6BE0A0
+ DE6A769D93A40E99171BA612022452D5D0446FAA334B88EA72CDCD#)(protected-at
+ "20221231T090404")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/B06D02DFA4405556F467ED9DAB952260C130FE5C.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/B06D02DFA4405556F467ED9DAB952260C130FE5C.key
new file mode 100644
index 0000000..82b1ab5
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/B06D02DFA4405556F467ED9DAB952260C130FE5C.key
@@ -0,0 +1,7 @@
+Created: 20221231T090149
+Key: (protected-private-key (ecc (curve Ed25519)(flags eddsa)(q
+ #401764C950CBFCD300994661BEC1B19713F1F2E9128E9CAA2246C65FDB39EFB71D#)
+ (protected openpgp-s2k3-ocb-aes ((sha1 #10A6073070189604#
+ "42743808")#704B0E0D14711B1886BE2FD7#)#80FF8787F7FCAEA5C1B21CA900E77A
+ BD4C5E8A81838E782A710036ED4959C85708353614D755DEDD2F376B22110B6AA48025
+ 515AA7FD6ADDFB0F31CD#)(protected-at "20221231T090157")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/C998889DD8F40CF9960C1FE939DAD37DC1F3CB03.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/C998889DD8F40CF9960C1FE939DAD37DC1F3CB03.key
new file mode 100644
index 0000000..083799d
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/C998889DD8F40CF9960C1FE939DAD37DC1F3CB03.key
@@ -0,0 +1,8 @@
+Created: 20221231T090524
+Key: (protected-private-key (ecc (curve brainpoolP256r1)(q
+ #04010F74B91244C0FD0635341F85C25D1F4638863187AF9E65DD7B66A3932AA39508
+ 9DF709BB891742A5335B79274B8BD491108003D4A51611FBC11DC650DF3538#)(prote
+ cted openpgp-s2k3-ocb-aes ((sha1 #26E1E5AA0C71BD83#
+ "42743808")#F2064DAA3D2227877A0D5665#)#51C61B3E699B3683FBC21D28C622AA
+ 8BD507EB7711538C6349428F0425746BAA158F1D29D4C513723EACE2C7036AFA2FB3BB
+ 46D27DFC45C5D161E77009#)(protected-at "20221231T090532")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/D8654AE2EF28B8093824651380B8C1F4B5DF0E46.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/D8654AE2EF28B8093824651380B8C1F4B5DF0E46.key
new file mode 100644
index 0000000..4fd1844
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/D8654AE2EF28B8093824651380B8C1F4B5DF0E46.key
@@ -0,0 +1,10 @@
+Created: 20221231T090356
+Key: (protected-private-key (ecc (curve "NIST P-384")(q
+ #043D7BCBB07997125194BC438A472D5D433F7A864251645B49E97306327475AD8DB8
+ 0F1B03998E8C1A3CE8000C511B11FC29CA937F1AD253108478A4366BDCFF3246D0EA64
+ 1CBB9D82AC4B6CFEB3F3BCD57CD9949E4F907FADAF91E4B3FD481DC6#)(protected
+ openpgp-s2k3-ocb-aes ((sha1 #C3F8190E0D1B5466#
+ "42743808")#C48EDE12C0C2F50B42095C24#)#747666946FB7C0091A2D26A4E9E9F2
+ 58989D8B3F782C967C62908DAAB188AD3BDDCF54D43E04EC33382E89FA7F33702EA395
+ 98132B542ACAC68C5F3E34DF02E5679CCDAA138885CB98E3890B6C#)(protected-at
+ "20221231T090404")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/DCCBAD8A71D6281D1462FD8BDCB1A8567C38357C.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/DCCBAD8A71D6281D1462FD8BDCB1A8567C38357C.key
new file mode 100644
index 0000000..db0f541
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/DCCBAD8A71D6281D1462FD8BDCB1A8567C38357C.key
@@ -0,0 +1,29 @@
+Created: 20221217T141407
+Key: (protected-private-key (dsa (p #00CDF0EE8B04D72C8D73E10815F457F152
+ F2D6B8A5E1C39BDC5F7B0A76E84802C68AFA3257CBDAA1742A2DD727D9BB40FF813233
+ E26B5B7671D3C06B299C52214DC4F74E000B8DC53A77857622214CB78B5497FD8A4825
+ 6F9FFD1295AE8BE32FB0FAF5136726AD9E7C0149CFB4ECBEE19C5F9211D3E4D607F3EE
+ C62B408396089D1DE3C0421C323319A87E15070C7E930B07DDDCC97DDE573CC9EDDEDF
+ 4E4D08E9CEB408528E63C17425977D4ABBF58788BB16AEE9FB402FB39997FF344E3B4A
+ B9A8894A55E9384E1771CF75DB83FDCDEB9800CBB059F0E6D76D34DA83EED2842ADAA0
+ B1164A034EF14100A3E892A3E29D305CB2E1B1D3F774E8C72DA3A4B950BB#)(q
+ #00BA2A3D7656B9FCCC1CA1626F8637A0A4DE6FE81F0DF28875D81783708C980BF3#)
+ (g #40C9B05E707BA59A6908628D63156F8B496072180409910D4BFED0310C8DDC9DBB
+ 1EA197A11022F7E6FEEBC2AE184971208FB03178AF066D6A7E65E4816B3E5206B13117
+ F29574435BDBB2B7D5D4BB064BEEE3D04E0201437F49D1CE156414B254173C6101B638
+ 3C527D81F6C76344547D64A39F37496FE755E21237BF0C9CE7DD5C53E49C64F370F6D4
+ 5CB8DE63B0C02FDB74C15BB54C4488B3E4741656E3598A9A915E1EDBB8255742E1B124
+ 86221F3B9A132A733A5DF5748E555ED895B2A616AF767E0BF9A97BE6F737063E1516D2
+ E2B064EF4E8CBB6A182E4FDE0DCD1027951604AE32FD5157CE3117BAB4CBFE3AC97B6A
+ 94E964753251A671B9A62D3262#)(y #7D1D57498F1DD040FF297FD92FD2C8E66571A6
+ 063DED6F33C5E52B04F4C622AEDC3176E76D4B4F6B73D7F6402AC21D4C72A7F42933E3
+ 5BD7858D951694328152109F5DA6D7A912420757EA5B892E8371993C70FA355BFFDAE3
+ B03A36690B63D2E210CD833871591C90A872A605200CC8BB6BC71CF84B7D2CC8C03EBF
+ 921A9B1CB7FD76D3B02C13FBA5CF8CEAE319EA3795A74015970C77D3B2B5DAE314E268
+ 6C643B2DB0C57DFF0C4DB9A373B9C868E8BEF5AB359384BDCF9DE87883E4C327916A3B
+ 9D91F0F54A1F42648A4BF6041C55AAC605AF93CB997BB2A1D67E7190C0EAD36690C064
+ F36AA21A0744B91A6F66102A0A23D459DD9FE3091D861A92A4D32A#)(protected
+ openpgp-s2k3-ocb-aes ((sha1 #FEAB7E055151B9DB#
+ "42473472")#741880408A37429675269294#)#7BECAE09D88411CB4904F24475D108
+ 5950C1B90AA8D89381881E172FFEB12E74CFD8FAA03BF7DDE08847838A0BE855708AC7
+ 1667E9F7DA1EEF9DBEC8#)(protected-at "20221217T141424")))
diff --git a/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/FD454CBC445A1D8AC346BED0D4A03C3511B8428F.key b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/FD454CBC445A1D8AC346BED0D4A03C3511B8428F.key
new file mode 100644
index 0000000..be28207
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/private-keys-v1.d/FD454CBC445A1D8AC346BED0D4A03C3511B8428F.key
@@ -0,0 +1,45 @@
+Created: 20221231T084202
+Key: (protected-private-key (rsa (n #00992C9B0607E69CF5209CCF4E38A14F9D
+ FA41C2CA38EB5BCF7A56FE95BCCAEF3822C5CE9CF9141D49A663237A2E5CCE66A0BBD5
+ 8523AB551EECC479ED943758AF50234FBF030FC90BD5304593A20A5495050BD5246DF2
+ 0728881FA983FEFBC246A25B230439508044C720BC84DBF6227429FF61EED2DED2D126
+ 923AE8AAD7357D5CE399331E9A64970CA26A9185731D65E31763D9358348DE32C62CBF
+ EB07A396E0EE61181849CECD43DDB0456DC54AEF7A142F120C34161B662BF89D4C92F6
+ ECFB4979FF03BE30249367E441EDF431EB9446BE6BCC18A98A2225CF492E1E38FBC145
+ 1E56C90610533EB3EADB20364FD364DF700D63A8B59CB83050DB37264F829E583ED742
+ F9240AD6490B9BB3E6A1AD6F399BC9C7BB200398E62CE30FB19C26CBA49C8F6B447CEF
+ B5B5A8E1DEE3E9CB742FBB88914DF70C6A8139610EA72853EA5C6D6CFCBD6729BE573B
+ 8A67E34C7954C8B3EF8B02CEE4FF9AD71AA003D74A5B2CEA2C71EA75F4BCE2D3F4D72A
+ E3DCFB056AC7A08DD88EEAC053031E102D9D#)(e #010001#)(protected
+ openpgp-s2k3-ocb-aes ((sha1 #8E56D08D48835201#
+ "43295744")#7865176EE0CBA0CF26FE822A#)#847F68655801524601707FDC5BC406
+ 13D3C653B70DA279A14E99E7FBC430163AB9CC9853F66723CF57656EA97E90BE5CF059
+ 49630098AB30EA784ACE67ACEF20E5168289B1DEC986F1687101E670770981D9158E66
+ 3966737BE0580ECA60F58F5D94EFB6664332A50448287B67AB4ABE5E892F179D972994
+ BA2C8597BDB3FA8F1EC470D2CFB476FEC284C974C07B0743A8EFC1E37049E0E07AEED8
+ 1F5EBE1941DE021D0C07CCC30BC765739440138146D5433F9432BDA1688E6E9A80A77D
+ 64522B85F0A945A8A9430B6FEA922BD0A18B69EEF97245D33EB8C9EAFA047987229543
+ AA024F690373A6046E2C6FF1B1A941C2B34A8F637460973607D8910233D3D188121351
+ 88E3A5F87845FE8637953CF8008B60DFB301400E3A4F6518BA82868519999CD4CFE11D
+ 5D9FE44BA67788AB1362D815413B01011EB7005953476C92E2A1F6EA3A996031371047
+ 4B661FE7D295D89BF5F86695998CC5EA9481ED173B273D9BA0B69FF1CEA09AF8DEAF71
+ DC2D16CFDFBFB3E800EDA3A5AC086D56913A9F1BC94CAB8587B2C0A3944D11E56F486B
+ E213858F34D76ED0C651D25CA29BCDF81A7F0DA2E6C8F9C56AFE8F3F117D2003792F03
+ 995E9CAB89756E3D51470A12C690516D3F5892C4A60AC4720E64446D213BEA521511FD
+ A6EC2554AED4A77472F3997CDEE95B3DAA1870366F256D6768AD8814F7424EBFAFFD25
+ EF897BF1C60B4B31FBA40D73CAF6502986432AFA73CCA445FA8620DDEA0E0756848954
+ C9CFEE00909C22C6C1B09D615EB8BFFD3B2A37705ED82C25D0C1E4DB6921F644C787B4
+ 63085FBDC24C0E234E5A2B6C7A3DB09FF3149C74F425691DDD07C4703B47247819EF4E
+ 70B8471E2C9FABE42227DB172D731DC8D37E0DB2D16E15FED604B76105EA5012FC4542
+ 69ECAC9391D3E950EC0AF07EC78EC558ACB9EA01A677EE00888A3AE528EBE9E3637FC7
+ E10F277C6DC51CAE128919C0FDAF0B430ABA12BF3AD296717E4671CC3C77D418B6E3FC
+ 91567BB642B1D789EEE709A00BE544403797D8DC75DEE20B895201443FE3AC411FEB26
+ 1AAB1EE836DA660415F125E4BF7D5FD64DF825FE92F22C3A099CC46B49B0E0D6A20FA5
+ A7B7E8B9E221711DEFD33DE61A4222FD7ED2C10EA3D37DBDB13EF19363882CC1D2395C
+ 9C1FA72E6CA92C8D17280DDBEFA193CFF97E055BDA86907A5D9304E4D72E0155D84C5F
+ 12A8060ED834CF11FD181F1507FCAFEC880571FC3D7477A870F4C3249FDF6BAD8BDEEB
+ FB9B888E52613AAAEE03AF4C6963FF36C2F79DAE6B1E974292005FA6014CBF7F82780D
+ 04AE9EA4A47140B5B040BA4A94A0B03C930A57A26114095A3E849A040E39D02E111797
+ 5DE0F47F828580D7593DECB53A2FC5606F1D9505BCAFA597655046A381E7455206AB0C
+ EC5ED6914C6BCD8CD5F7D6DDD71C1C4C06E0EF6C919579#)(protected-at
+ "20221231T084212")))
diff --git a/src/tests/data/test_stream_key_load/g23/pubring.kbx b/src/tests/data/test_stream_key_load/g23/pubring.kbx
new file mode 100644
index 0000000..39a4a89
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/g23/pubring.kbx
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/key0-sub01.pgp b/src/tests/data/test_stream_key_load/key0-sub01.pgp
new file mode 100644
index 0000000..54697b2
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/key0-sub01.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/key0-sub02.pgp b/src/tests/data/test_stream_key_load/key0-sub02.pgp
new file mode 100644
index 0000000..da34c19
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/key0-sub02.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/key0-sub1.pgp b/src/tests/data/test_stream_key_load/key0-sub1.pgp
new file mode 100644
index 0000000..49fa1c5
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/key0-sub1.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_load/rsa-rsa-2-pub.asc b/src/tests/data/test_stream_key_load/rsa-rsa-2-pub.asc
new file mode 100644
index 0000000..01e7d95
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/rsa-rsa-2-pub.asc
@@ -0,0 +1,30 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mQENBGHFpKQBCAC5ECPljro7upeEP8pR8SYW3qymdJhC5Fv0sj94Sg+mst5e6ob6
+nqEDd4oUc+Y4FSuivdJCdj9kRKDnbKj4qAFp/liC3su7FJ0fS32pdR9vVaWelJDF
+mdtWpvhEPAninnUakPxHIHgDSkKUtgwbvrIZxumdxA/du9N6RWyXw3gnawBK+7bz
+1HpjYooRFhjd/YQ2G5Bm9x6i9TDY321BCDZdKF1xsEnCjs+cUrAwqJdAqcNNyZP/
+tdfR9UlLSIGqlFT7CMo97tAD9+uKd9OMv0SabTg23L0AV9sq2HvrrigFgRlNFjLl
+2xGNDGhs9qhi/IBQFFBoFcTcjKRPCU0C+6ThABEBAAG0CXJzYS1yc2EtMokBUgQT
+AQgAPBYhBFqpNirqB94jpyZ2LL2GClLRiZwPBQJhxaSkAhsDBQsJCAcCAyICAQYV
+CgkICwIEFgIDAQIeBwIXgAAKCRC9hgpS0YmcDzkDB/4zz6LACglpAvpFK0P4L4oO
+s4BiwDl7xXqgOfgbc/zRtlx6iOitT78+tf+6/tuA+3HjtF7CQqONpc9xjiQjK0BL
+R0N2YR23OBWCzb0hhxmueV6TtDNZkM8aZQdeAjEaMVKiDY4nVF+I0HVyl6AON+2c
+BrU0abio8kBP8XXoSFdPu9QefdNojpNs6+/enItFFD0n6Jhn6zXlo0c0T3dCBL+H
+ckiJYTV/WGYzYXfowVn/GCddOysndnCZ5vXLO5xKgSf8X7D+wI/jG+tqU5qa7Usw
+fytM1B0zrP784Ke2S/V+PgofiTzPI8G5KDXofqiZoIDV3eRpiG3Ry/JOwbF/Yg8H
+uQENBGHFpKQBCAC4ySoNlCNm3N0ijhufLPx+qesBYt7uW8ihJwKa3fUFVsdaPjdp
+/8tBT5I9QIjrZHSs55Hkug0v/CpKeuIsk27XIplsf0A/80QQ3sQEP8NtAeHQU13U
+Vg1XpfHuox+Qd91aJ0VElRd+5n+ZC6nx8jBvAUcscXLD8lmAOaOxhpluRC2Yy9ah
+wy51IlpZSAiYBKCbZ6hxdr0zf6Gjjo47UKLWJ60VBL9AQNZo/alDkQo2W+NDlQp1
+R0dnAL/kSEjaMPiE+Y29VynExqdSbwI2m+nzfd6m5zcQ/7QpDzz2DPV9xz52iBCO
+nJUSdBbc3IkoJR1y1QwleoKTegZUJy2mblZFABEBAAGJATYEGAEIACAWIQRaqTYq
+6gfeI6cmdiy9hgpS0YmcDwUCYcWkpAIbDAAKCRC9hgpS0YmcD0TKCACIoCQInxDa
+P0pQXLwu+uWvivEF2oGUNGpxkqZBySgSJF1wBbPX8CCj64GqJ4WGch/QJKZI2wTZ
+9+MCV1488AoUOYPTSdpvT0tHHFZIUUgo/uxwy3+0+6Yf3vd8c82Y1lKz1AwHebcE
+NSJNgv+KakOfgSVMtk/lt+u4sOaPqc2821HC+wis3+VHyd2ecmSzfdFeR8iUpuui
+2Y4nuu6j2OGwz1m/vQ1LES4/bG1bwqTqgEfHxH6Z2KZGdUa9LXe0df9VeOBPtyXo
+yaTG8azI4z9bxjE+Rex9y14WHipPkvWVPwSfynphiTPLsTx6vlD94FaDOf7tb5Ys
+XJ0kLaL3H0Vh
+=/D6t
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/rsa-rsa-2-sec.asc b/src/tests/data/test_stream_key_load/rsa-rsa-2-sec.asc
new file mode 100644
index 0000000..978c784
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/rsa-rsa-2-sec.asc
@@ -0,0 +1,59 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+lixcnSQtovcfRWE=
+=EEQP
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/rsa-rsa-pub.asc b/src/tests/data/test_stream_key_load/rsa-rsa-pub.asc
new file mode 100644
index 0000000..e74df7a
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/rsa-rsa-pub.asc
@@ -0,0 +1,40 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=aibx
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_load/rsa-rsa-sec.asc b/src/tests/data/test_stream_key_load/rsa-rsa-sec.asc
new file mode 100644
index 0000000..ef58668
--- /dev/null
+++ b/src/tests/data/test_stream_key_load/rsa-rsa-sec.asc
@@ -0,0 +1,83 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+7T27CrSZbhT7
+=AMaM
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_merge/key-both.asc b/src/tests/data/test_stream_key_merge/key-both.asc
new file mode 100644
index 0000000..f83d1db
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-both.asc
@@ -0,0 +1,202 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=8RV7
+-----END PGP PUBLIC KEY BLOCK-----
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=VIm+
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_merge/key-pub-just-key.pgp b/src/tests/data/test_stream_key_merge/key-pub-just-key.pgp
new file mode 100644
index 0000000..d614e05
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-just-key.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-just-subkey-1.pgp b/src/tests/data/test_stream_key_merge/key-pub-just-subkey-1.pgp
new file mode 100644
index 0000000..4701e15
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-just-subkey-1.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-just-subkey-2-no-sigs.pgp b/src/tests/data/test_stream_key_merge/key-pub-just-subkey-2-no-sigs.pgp
new file mode 100644
index 0000000..0a037eb
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-just-subkey-2-no-sigs.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-just-subkey-2.pgp b/src/tests/data/test_stream_key_merge/key-pub-just-subkey-2.pgp
new file mode 100644
index 0000000..2126ff6
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-just-subkey-2.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-no-key-subkey-1.pgp b/src/tests/data/test_stream_key_merge/key-pub-no-key-subkey-1.pgp
new file mode 100644
index 0000000..4701e15
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-no-key-subkey-1.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-subkey-1-no-sigs.pgp b/src/tests/data/test_stream_key_merge/key-pub-subkey-1-no-sigs.pgp
new file mode 100644
index 0000000..3e8c2bc
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-subkey-1-no-sigs.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-subkey-1.pgp b/src/tests/data/test_stream_key_merge/key-pub-subkey-1.pgp
new file mode 100644
index 0000000..6764358
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-subkey-1.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-subkey-2.pgp b/src/tests/data/test_stream_key_merge/key-pub-subkey-2.pgp
new file mode 100644
index 0000000..2052b47
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-subkey-2.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-uid-1-no-sigs.pgp b/src/tests/data/test_stream_key_merge/key-pub-uid-1-no-sigs.pgp
new file mode 100644
index 0000000..9e43588
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-uid-1-no-sigs.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-uid-1.pgp b/src/tests/data/test_stream_key_merge/key-pub-uid-1.pgp
new file mode 100644
index 0000000..bc013c7
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-uid-1.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub-uid-2.pgp b/src/tests/data/test_stream_key_merge/key-pub-uid-2.pgp
new file mode 100644
index 0000000..7da89c7
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub-uid-2.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-pub.asc b/src/tests/data/test_stream_key_merge/key-pub.asc
new file mode 100644
index 0000000..ac05b43
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub.asc
@@ -0,0 +1,79 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+=8RV7
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_merge/key-pub.pgp b/src/tests/data/test_stream_key_merge/key-pub.pgp
new file mode 100644
index 0000000..4d3b468
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-sec-just-subkey-1.pgp b/src/tests/data/test_stream_key_merge/key-sec-just-subkey-1.pgp
new file mode 100644
index 0000000..0c1dcb8
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-sec-just-subkey-1.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-sec-just-subkey-2-no-sigs.pgp b/src/tests/data/test_stream_key_merge/key-sec-just-subkey-2-no-sigs.pgp
new file mode 100644
index 0000000..1bb5a69
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-sec-just-subkey-2-no-sigs.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-sec-no-uid-no-sigs.pgp b/src/tests/data/test_stream_key_merge/key-sec-no-uid-no-sigs.pgp
new file mode 100644
index 0000000..b0de2fc
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-sec-no-uid-no-sigs.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-sec-uid-1-subkey-1.pgp b/src/tests/data/test_stream_key_merge/key-sec-uid-1-subkey-1.pgp
new file mode 100644
index 0000000..8ead3df
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-sec-uid-1-subkey-1.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/key-sec.asc b/src/tests/data/test_stream_key_merge/key-sec.asc
new file mode 100644
index 0000000..5d4b75e
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-sec.asc
@@ -0,0 +1,123 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=eO6C
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_key_merge/key-sec.pgp b/src/tests/data/test_stream_key_merge/key-sec.pgp
new file mode 100644
index 0000000..c7512d8
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/key-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-key-pub.pgp b/src/tests/data/test_stream_key_merge/pkt-key-pub.pgp
new file mode 100644
index 0000000..d614e05
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-key-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-key-sec.pgp b/src/tests/data/test_stream_key_merge/pkt-key-sec.pgp
new file mode 100644
index 0000000..0b8815f
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-key-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-sub0-pub.pgp b/src/tests/data/test_stream_key_merge/pkt-sub0-pub.pgp
new file mode 100644
index 0000000..93e2894
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-sub0-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-sub0-sec.pgp b/src/tests/data/test_stream_key_merge/pkt-sub0-sec.pgp
new file mode 100644
index 0000000..5ff39cd
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-sub0-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-sub0-sig.pgp b/src/tests/data/test_stream_key_merge/pkt-sub0-sig.pgp
new file mode 100644
index 0000000..cfb13ff
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-sub0-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-sub1-pub.pgp b/src/tests/data/test_stream_key_merge/pkt-sub1-pub.pgp
new file mode 100644
index 0000000..0a037eb
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-sub1-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-sub1-sec.pgp b/src/tests/data/test_stream_key_merge/pkt-sub1-sec.pgp
new file mode 100644
index 0000000..7cd5d2b
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-sub1-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-sub1-sig.pgp b/src/tests/data/test_stream_key_merge/pkt-sub1-sig.pgp
new file mode 100644
index 0000000..619c57c
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-sub1-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-uid0-sig.pgp b/src/tests/data/test_stream_key_merge/pkt-uid0-sig.pgp
new file mode 100644
index 0000000..a195e0a
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-uid0-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-uid0.pgp b/src/tests/data/test_stream_key_merge/pkt-uid0.pgp
new file mode 100644
index 0000000..0d2b0c0
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-uid0.pgp
@@ -0,0 +1 @@
+´key-merge-uid-1 \ No newline at end of file
diff --git a/src/tests/data/test_stream_key_merge/pkt-uid1-sig.pgp b/src/tests/data/test_stream_key_merge/pkt-uid1-sig.pgp
new file mode 100644
index 0000000..54e704b
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-uid1-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_key_merge/pkt-uid1.pgp b/src/tests/data/test_stream_key_merge/pkt-uid1.pgp
new file mode 100644
index 0000000..ef52fef
--- /dev/null
+++ b/src/tests/data/test_stream_key_merge/pkt-uid1.pgp
@@ -0,0 +1 @@
+´key-merge-uid-2 \ No newline at end of file
diff --git a/src/tests/data/test_stream_signatures/pub.asc b/src/tests/data/test_stream_signatures/pub.asc
new file mode 100644
index 0000000..9da95e7
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/pub.asc
@@ -0,0 +1,41 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+
+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
+hMHj3/yuwMXx6aMHF1A=
+=zbzp
+-----END PGP PUBLIC KEY BLOCK-----
diff --git a/src/tests/data/test_stream_signatures/revoked-key-sig.gpg b/src/tests/data/test_stream_signatures/revoked-key-sig.gpg
new file mode 100644
index 0000000..d98324d
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/revoked-key-sig.gpg
Binary files differ
diff --git a/src/tests/data/test_stream_signatures/sec.asc b/src/tests/data/test_stream_signatures/sec.asc
new file mode 100644
index 0000000..3b8acf1
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/sec.asc
@@ -0,0 +1,83 @@
+-----BEGIN PGP PRIVATE KEY BLOCK-----
+
+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
+=Nabv
+-----END PGP PRIVATE KEY BLOCK-----
diff --git a/src/tests/data/test_stream_signatures/signature-timestamp.asc b/src/tests/data/test_stream_signatures/signature-timestamp.asc
new file mode 100644
index 0000000..b4b4b4b
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/signature-timestamp.asc
@@ -0,0 +1,12 @@
+-----BEGIN PGP SIGNATURE-----
+Version: LibTMCG 1.3.14
+
+wv8AAAFKBEARCgEU/wAAAAWCW4Qtpv8AAAAChwD/AAAACZAtcnzHaGl3NP8AAAA0
+FIAAAAAAIQAKc2VyaWFsbnVtYmVyQGRvdHMudGVzdGRvbWFpbi50ZXN0VEVTVDAw
+MDAwMf8AAAAtGmh0dHBzOi8vcG9saWN5LnRlc3Rkb21haW4udGVzdC90aW1lc3Rh
+bXBpbmcv/wAAAGqgBAARCgAz/wAAAAUCW4QsOf8AAAAJEC1yfMdoaXc0/wAAABYh
+BKD/RZC7YSLt7248VC1yfMdoaXc0AACjhgCZAWMQ7G7Y8ZgA49Om7rP8M6bzpKUA
+n11pnt+6XH3ytxMjWIPmIypkSH42/wAAABYhBKD/RZC7YSLt7248VC1yfMdoaXc0
+AAAnJwCcC33Agj/STYlb283+HqWQAw/ZIJQAn2lN4+6WHkSc8gm6d2iPfC+3JJGl
+=40KQ
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_stream_signatures/source.txt b/src/tests/data/test_stream_signatures/source.txt
new file mode 100644
index 0000000..70f9e25
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/source.txt
@@ -0,0 +1 @@
+This is detached signed sample file for test_stream_signatures.
diff --git a/src/tests/data/test_stream_signatures/source.txt.asc b/src/tests/data/test_stream_signatures/source.txt.asc
new file mode 100644
index 0000000..0349caa
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/source.txt.asc
@@ -0,0 +1,18 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+This is detached signed sample file for test_stream_signatures.
+-----BEGIN PGP SIGNATURE-----
+
+iQGzBAEBCAAdFiEEemDmcRefm5IPZHiiWHO9c45XU5gFAlrWAugACgkQWHO9c45X
+U5h/ggv9EU6jeRoW+NQg6UPWqqhHAbXCgAvSpzDHihEh3F6aDaaQVOLTz++fuS+x
+mwHAe6O2wI8QlDGOt7Rdb6DHRLL9u1FuPFZoG5mXd9DoAUjnwMpze3LSxUJrUOH7
+i90ZygrA9n7KQBJ5sguWo4QssockxElhjUBGNH5XwNmoviK5GpX04h4Ov437wc3E
+oPSCqbSq5TU3Us2vd/Mk/bIi1mLFefN3bO0vT0JM2DMyKPpzHA1PBx9DSc74PzX6
+llFsn/1VrFN0bxmMhzjX7umYYdTVz35OcpFxdunBS6dFH+KLeCjaKoiOIt7dfTj1
+M+AgW9QDTTgrHYlPpffXs57+jS5OyTgyENaCl7zsCbA+aioilp4OUgTzuimpcH1Z
+aX+DsHRDpbd7iJ2OJYSE4jpSMWCLrVpxGoWDWBvsXN6yAHwo2ZA0L8bPxtMj+LCX
+OMeJ/KoTNmJsSLqlwHo4OS8RMS9lnYfJvcBUUnJVEAmf4YAfgBnazZm8bIfsKXgN
+SkBNR1VW
+=R0gA
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_stream_signatures/source.txt.asc.asc b/src/tests/data/test_stream_signatures/source.txt.asc.asc
new file mode 100644
index 0000000..57919bf
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/source.txt.asc.asc
@@ -0,0 +1,35 @@
+-----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+- -----BEGIN PGP SIGNED MESSAGE-----
+Hash: SHA256
+
+This is detached signed sample file for test_stream_signatures.
+- -----BEGIN PGP SIGNATURE-----
+
+iQGzBAEBCAAdFiEEemDmcRefm5IPZHiiWHO9c45XU5gFAlrWAugACgkQWHO9c45X
+U5h/ggv9EU6jeRoW+NQg6UPWqqhHAbXCgAvSpzDHihEh3F6aDaaQVOLTz++fuS+x
+mwHAe6O2wI8QlDGOt7Rdb6DHRLL9u1FuPFZoG5mXd9DoAUjnwMpze3LSxUJrUOH7
+i90ZygrA9n7KQBJ5sguWo4QssockxElhjUBGNH5XwNmoviK5GpX04h4Ov437wc3E
+oPSCqbSq5TU3Us2vd/Mk/bIi1mLFefN3bO0vT0JM2DMyKPpzHA1PBx9DSc74PzX6
+llFsn/1VrFN0bxmMhzjX7umYYdTVz35OcpFxdunBS6dFH+KLeCjaKoiOIt7dfTj1
+M+AgW9QDTTgrHYlPpffXs57+jS5OyTgyENaCl7zsCbA+aioilp4OUgTzuimpcH1Z
+aX+DsHRDpbd7iJ2OJYSE4jpSMWCLrVpxGoWDWBvsXN6yAHwo2ZA0L8bPxtMj+LCX
+OMeJ/KoTNmJsSLqlwHo4OS8RMS9lnYfJvcBUUnJVEAmf4YAfgBnazZm8bIfsKXgN
+SkBNR1VW
+=R0gA
+- -----END PGP SIGNATURE-----
+-----BEGIN PGP SIGNATURE-----
+
+iQGzBAEBCAAdFiEEemDmcRefm5IPZHiiWHO9c45XU5gFAlrWAwgACgkQWHO9c45X
+U5hwUQwAoKvxFYIK9xyDPCwJXdzpaYeux5mCImWaRLLEEKCtnz6apfrwnUm7GjKo
+/g47wQN0x2HCUDjLJcXssJIIBsCFEuTr8xcD16NXR9HcFTnp3cVKlZW686kcXAJL
+tfUhjMH7r/4YJVAqdrIqjljro0SpifXKIGG2UX4Umy4Hf5mqgtGpRrNHkeaoq46a
+d35uT+bzcWT6FelhNTwCYU4dsWMidKCwSO2V4DDxVjCVa92P27Fwqgth872NnqL8
+3DOgKoNfEVLOUwb1Y6+Xfmu+Nm8j1a/YCt7YeOCvy+tQVzaShclLzDU+6rxKQ+xo
+ZCuoR3kgJgwFpIKf+N67CXxE7HeZtLZvhfWtxizPz6OO6f9Ci8B/2WbHk3BCPF4b
+nEHEDC8T/eXk1KvMuQmrblRbreABar7Bkfml22bWDzbBL/KS90AJgWD1V/E8XA5E
+hMd+8Vyof4J8YkOwDZCjKVrr+JNToEEXDxH0JvwCG1f6Ra/umQIN29A5AdXDf7vv
+e3DJUns5
+=CvVK
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_stream_signatures/source.txt.sig b/src/tests/data/test_stream_signatures/source.txt.sig
new file mode 100644
index 0000000..b3b60d5
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/source.txt.sig
Binary files differ
diff --git a/src/tests/data/test_stream_signatures/source.txt.sig.asc b/src/tests/data/test_stream_signatures/source.txt.sig.asc
new file mode 100644
index 0000000..26f5775
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/source.txt.sig.asc
@@ -0,0 +1,13 @@
+-----BEGIN PGP SIGNATURE-----
+Version: rnp 0.9.0+git20191025.476.bb73afe
+
+iQGzBAABCAAdFiEEemDmcRefm5IPZHiiWHO9c45XU5gFAlq7kZcACgkQWHO9c45XU5iBbgwAh3Jm
+xjIW08qCFQZPtKhfs3IthnhqTqvTMWsnThOxmCHYQFLyWjt6GPiVCZzEzPdN1Cx7+D8dYCx8hBEg
+91igRJ5mSb2eE0w+zsYZOHpqP2gtkUjJ63uwulf0ACTF25vFwBejgGnpU+Wt6hsL7aEbyNkz6PWY
+5wN3qMe/wkXzfhHDt/xEmMK+Ik0/5fuvF4nK61J5CJfhudUvzfB4AU0L76cJ2ihlMaRJ5T6qJule
+BVp9VIa9gLSxDWBG+h1Ie1CwfuCg057JcBs6Z33lrjgeoIhahUpbDHBEzBHMzycynPeFCmmrH71U
+Uz4CkFwLI+G6G6DHTWcO9VQu7nE1rWgCXYwfMyHmQW3R0upruh6jrxgvBM02AibSdou3gRHsavDS
+ph2objLXJuROuvKCHsfYyspzZEczO1H7io36U+yarycM1TRJ4UHYrjyFGaB8MeTO36ZmBZLsZiFz
+24rRgeWZcJ+6amxWAkQQaxgv0SbdvdEtuvJB/v/f4mUVxeeEXGWO
+=xPCX
+-----END PGP SIGNATURE-----
diff --git a/src/tests/data/test_stream_signatures/source.txt.text.sig b/src/tests/data/test_stream_signatures/source.txt.text.sig
new file mode 100644
index 0000000..4825b60
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/source.txt.text.sig
Binary files differ
diff --git a/src/tests/data/test_stream_signatures/source_forged.txt b/src/tests/data/test_stream_signatures/source_forged.txt
new file mode 100644
index 0000000..b73b6b5
--- /dev/null
+++ b/src/tests/data/test_stream_signatures/source_forged.txt
@@ -0,0 +1 @@
+Th1s is detached signed sample file for test_stream_signatures.
diff --git a/src/tests/data/test_stream_verification/verify_encrypted_no_key.pgp b/src/tests/data/test_stream_verification/verify_encrypted_no_key.pgp
new file mode 100644
index 0000000..650f9a9
--- /dev/null
+++ b/src/tests/data/test_stream_verification/verify_encrypted_no_key.pgp
Binary files differ
diff --git a/src/tests/data/test_stream_z/128mb.zip b/src/tests/data/test_stream_z/128mb.zip
new file mode 100644
index 0000000..bd88a60
--- /dev/null
+++ b/src/tests/data/test_stream_z/128mb.zip
Binary files differ
diff --git a/src/tests/data/test_stream_z/128mb.zip.cut b/src/tests/data/test_stream_z/128mb.zip.cut
new file mode 100644
index 0000000..44babae
--- /dev/null
+++ b/src/tests/data/test_stream_z/128mb.zip.cut
Binary files differ
diff --git a/src/tests/data/test_stream_z/128mb.zlib b/src/tests/data/test_stream_z/128mb.zlib
new file mode 100644
index 0000000..71d3872
--- /dev/null
+++ b/src/tests/data/test_stream_z/128mb.zlib
Binary files differ
diff --git a/src/tests/data/test_stream_z/128mb.zlib.cut b/src/tests/data/test_stream_z/128mb.zlib.cut
new file mode 100644
index 0000000..218c049
--- /dev/null
+++ b/src/tests/data/test_stream_z/128mb.zlib.cut
Binary files differ
diff --git a/src/tests/data/test_stream_z/4gb.bzip2 b/src/tests/data/test_stream_z/4gb.bzip2
new file mode 100644
index 0000000..46c0c42
--- /dev/null
+++ b/src/tests/data/test_stream_z/4gb.bzip2
Binary files differ
diff --git a/src/tests/data/test_stream_z/4gb.bzip2.asc b/src/tests/data/test_stream_z/4gb.bzip2.asc
new file mode 100644
index 0000000..b0d1f4a
--- /dev/null
+++ b/src/tests/data/test_stream_z/4gb.bzip2.asc
@@ -0,0 +1,762 @@
+-----BEGIN PGP MESSAGE-----
+Version: rnp 0.9.0+git20191025.476.bb73afe
+
+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
+=voHu
+-----END PGP MESSAGE-----
diff --git a/src/tests/data/test_stream_z/4gb.bzip2.cut b/src/tests/data/test_stream_z/4gb.bzip2.cut
new file mode 100644
index 0000000..441ebf1
--- /dev/null
+++ b/src/tests/data/test_stream_z/4gb.bzip2.cut
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-expired.pgp b/src/tests/data/test_uid_validity/key-expired.pgp
new file mode 100644
index 0000000..144c84d
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-expired.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-sig-expired.pgp b/src/tests/data/test_uid_validity/key-sig-expired.pgp
new file mode 100644
index 0000000..4ce7b91
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-sig-expired.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-sig-revocation.pgp b/src/tests/data/test_uid_validity/key-sig-revocation.pgp
new file mode 100644
index 0000000..67d67c8
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-sig-revocation.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-uid-expired-sig.pgp b/src/tests/data/test_uid_validity/key-uid-expired-sig.pgp
new file mode 100644
index 0000000..e76b6bf
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-uid-expired-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-uid-prim-expired-sig.pgp b/src/tests/data/test_uid_validity/key-uid-prim-expired-sig.pgp
new file mode 100644
index 0000000..917aea6
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-uid-prim-expired-sig.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-uids-pub-no-expire.pgp b/src/tests/data/test_uid_validity/key-uids-pub-no-expire.pgp
new file mode 100644
index 0000000..aa0ffad
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-uids-pub-no-expire.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-uids-pub.pgp b/src/tests/data/test_uid_validity/key-uids-pub.pgp
new file mode 100644
index 0000000..f1c9817
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-uids-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-uids-revoked-valid.pgp b/src/tests/data/test_uid_validity/key-uids-revoked-valid.pgp
new file mode 100644
index 0000000..70b3cee
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-uids-revoked-valid.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-uids-sec.pgp b/src/tests/data/test_uid_validity/key-uids-sec.pgp
new file mode 100644
index 0000000..fee32a2
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-uids-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_uid_validity/key-uids-with-invalid.pgp b/src/tests/data/test_uid_validity/key-uids-with-invalid.pgp
new file mode 100644
index 0000000..2cdf888
--- /dev/null
+++ b/src/tests/data/test_uid_validity/key-uids-with-invalid.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/dsa-eg-pub.pgp b/src/tests/data/test_validate_key_material/dsa-eg-pub.pgp
new file mode 100644
index 0000000..a573094
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/dsa-eg-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/dsa-eg-sec.pgp b/src/tests/data/test_validate_key_material/dsa-eg-sec.pgp
new file mode 100644
index 0000000..7b7add3
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/dsa-eg-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/dsa-pub.pgp b/src/tests/data/test_validate_key_material/dsa-pub.pgp
new file mode 100644
index 0000000..646b7f4
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/dsa-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/dsa-sec.pgp b/src/tests/data/test_validate_key_material/dsa-sec.pgp
new file mode 100644
index 0000000..e60f11d
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/dsa-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/ecdh-p256-sec.pgp b/src/tests/data/test_validate_key_material/ecdh-p256-sec.pgp
new file mode 100644
index 0000000..4a5dee3
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/ecdh-p256-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/ecdsa-p256-sec.pgp b/src/tests/data/test_validate_key_material/ecdsa-p256-sec.pgp
new file mode 100644
index 0000000..f03719c
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/ecdsa-p256-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/ed25519-sec.pgp b/src/tests/data/test_validate_key_material/ed25519-sec.pgp
new file mode 100644
index 0000000..5ffb115
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/ed25519-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/eg-pub.pgp b/src/tests/data/test_validate_key_material/eg-pub.pgp
new file mode 100644
index 0000000..05d9b20
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/eg-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/eg-sec-small-group-enc.pgp b/src/tests/data/test_validate_key_material/eg-sec-small-group-enc.pgp
new file mode 100644
index 0000000..2f1e425
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/eg-sec-small-group-enc.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/eg-sec-small-group.pgp b/src/tests/data/test_validate_key_material/eg-sec-small-group.pgp
new file mode 100644
index 0000000..ede6212
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/eg-sec-small-group.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/eg-sec.pgp b/src/tests/data/test_validate_key_material/eg-sec.pgp
new file mode 100644
index 0000000..5e68955
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/eg-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/readme.txt b/src/tests/data/test_validate_key_material/readme.txt
new file mode 100644
index 0000000..cf14e06
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/readme.txt
@@ -0,0 +1,48 @@
+Some of ElGamal samples were generated by using the following custom key generation snippet:
+
+{
+ std::unique_ptr<Botan::RandomNumberGenerator> rng;
+ rng.reset(new Botan::System_RNG);
+
+ Botan::BigInt p = Botan::random_prime(*rng, keybits, 0, 2, 3, 64);
+ Botan::BigInt p1(p - 1);
+ Botan::BigInt sg = 1;
+ Botan::BigInt g;
+
+ size_t mod = 2;
+ while (mod < 65536) {
+ if (p1 % mod == 0) {
+ if (sg * mod > (1 << 16)) {
+ break;
+ }
+ RNP_LOG("Reduced by %zu", mod);
+ p1 = p1 / mod;
+ sg *= mod;
+ continue;
+ }
+ mod++;
+ }
+ if (Botan::power_mod(3, p - 1, p).cmp_word(1) != 0) {
+ RNP_LOG("3 ^ (p - 1) != 1 (mod p)");
+ goto end;
+ }
+ Botan::BigInt ng = Botan::power_mod(3, p1, p);
+ if (Botan::power_mod(ng, sg, p).cmp_word(1) != 0) {
+ RNP_LOG("ng ^ sg != 1 (mod p)");
+ goto end;
+ }
+ g = ng;
+
+ Botan::BigInt x(*rng, keybits, true);
+ Botan::BigInt y = Botan::power_mod(g, x, p);
+
+ key->p.len = p.bytes();
+ p.binary_encode(key->p.mpi);
+ key->g.len = g.bytes();
+ g.binary_encode(key->g.mpi);
+ key->x.len = x.bytes();
+ x.binary_encode(key->x.mpi);
+ key->y.len = y.bytes();
+ y.binary_encode(key->y.mpi);
+ ret = RNP_SUCCESS;
+}
diff --git a/src/tests/data/test_validate_key_material/rsa-pub.pgp b/src/tests/data/test_validate_key_material/rsa-pub.pgp
new file mode 100644
index 0000000..38e28e5
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/rsa-pub.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/rsa-sec.pgp b/src/tests/data/test_validate_key_material/rsa-sec.pgp
new file mode 100644
index 0000000..b2c2efd
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/rsa-sec.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/rsa-ssb.pgp b/src/tests/data/test_validate_key_material/rsa-ssb.pgp
new file mode 100644
index 0000000..7f838f6
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/rsa-ssb.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/rsa-sub.pgp b/src/tests/data/test_validate_key_material/rsa-sub.pgp
new file mode 100644
index 0000000..d66923e
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/rsa-sub.pgp
Binary files differ
diff --git a/src/tests/data/test_validate_key_material/x25519-sec.pgp b/src/tests/data/test_validate_key_material/x25519-sec.pgp
new file mode 100644
index 0000000..9a47763
--- /dev/null
+++ b/src/tests/data/test_validate_key_material/x25519-sec.pgp
Binary files differ
diff --git a/src/tests/exportkey.cpp b/src/tests/exportkey.cpp
new file mode 100644
index 0000000..ff0db75
--- /dev/null
+++ b/src/tests/exportkey.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
+ * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
+ * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp.h"
+#include <rekey/rnp_key_store.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+TEST_F(rnp_tests, rnpkeys_exportkey_verifyUserId)
+{
+ /* Generate the key and export it */
+ cli_rnp_t rnp = {};
+ int pipefd[2] = {-1, -1};
+
+ /* Initialize the rnp structure. */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, pipefd));
+ /* Generate the key */
+ cli_set_default_rsa_key_desc(rnp.cfg(), "SHA256");
+ assert_true(cli_rnp_generate_key(&rnp, NULL));
+
+ /* Loading keyrings and checking whether they have correct key */
+ assert_true(rnp.load_keyrings(true));
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+
+ std::vector<rnp_key_handle_t> keys;
+ assert_true(
+ cli_rnp_keys_matching_string(&rnp, keys, getenv_logname(), CLI_SEARCH_SUBKEYS_AFTER));
+ assert_int_equal(keys.size(), 2);
+ clear_key_handles(keys);
+
+ /* Try to export the key with specified userid parameter from the env */
+ assert_true(cli_rnp_export_keys(&rnp, getenv_logname()));
+
+ /* try to export the key with specified userid parameter (which is wrong) */
+ assert_false(cli_rnp_export_keys(&rnp, "LOGNAME"));
+
+ if (pipefd[0] != -1) {
+ close(pipefd[0]);
+ }
+ rnp.end(); // Free memory and other allocated resources.
+}
diff --git a/src/tests/ffi-enc.cpp b/src/tests/ffi-enc.cpp
new file mode 100644
index 0000000..55b0d10
--- /dev/null
+++ b/src/tests/ffi-enc.cpp
@@ -0,0 +1,1362 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <fstream>
+#include <vector>
+#include <string>
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include "librepgp/stream-common.h"
+#include "librepgp/stream-packet.h"
+#include "librepgp/stream-sig.h"
+#include <json.h>
+#include <vector>
+#include <string>
+#include "file-utils.h"
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+
+static bool
+getpasscb_once(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ const char **pass = (const char **) app_ctx;
+ if (!*pass) {
+ return false;
+ }
+ size_t pass_len = strlen(*pass);
+ if (pass_len >= buf_len) {
+ return false;
+ }
+ memcpy(buf, *pass, pass_len);
+ *pass = NULL;
+ return true;
+}
+
+static bool
+getpasscb_inc(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ int * num = (int *) app_ctx;
+ std::string pass = "pass" + std::to_string(*num);
+ (*num)++;
+ strncpy(buf, pass.c_str(), buf_len - 1);
+ return true;
+}
+
+#define TBL_MAX_USERIDS 4
+typedef struct key_tbl_t {
+ const uint8_t *key_data;
+ size_t key_data_size;
+ bool secret;
+ const char * keyid;
+ const char * grip;
+ const char * userids[TBL_MAX_USERIDS + 1];
+} key_tbl_t;
+
+static void
+tbl_getkeycb(rnp_ffi_t ffi,
+ void * app_ctx,
+ const char *identifier_type,
+ const char *identifier,
+ bool secret)
+{
+ key_tbl_t *found = NULL;
+ for (key_tbl_t *tbl = (key_tbl_t *) app_ctx; tbl && tbl->key_data && !found; tbl++) {
+ if (tbl->secret != secret) {
+ continue;
+ }
+ if (!strcmp(identifier_type, "keyid") && !strcmp(identifier, tbl->keyid)) {
+ found = tbl;
+ break;
+ } else if (!strcmp(identifier_type, "grip") && !strcmp(identifier, tbl->grip)) {
+ found = tbl;
+ break;
+ } else if (!strcmp(identifier_type, "userid")) {
+ for (size_t i = 0; i < TBL_MAX_USERIDS; i++) {
+ if (!strcmp(identifier, tbl->userids[i])) {
+ found = tbl;
+ break;
+ }
+ }
+ }
+ }
+ if (found) {
+ char *format = NULL;
+ assert_rnp_success(
+ rnp_detect_key_format(found->key_data, found->key_data_size, &format));
+ assert_non_null(format);
+ uint32_t flags = secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS;
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_memory(&input, found->key_data, found->key_data_size, true));
+ assert_non_null(input);
+ assert_rnp_success(rnp_load_keys(ffi, format, input, flags));
+ free(format);
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ }
+}
+
+TEST_F(rnp_tests, test_ffi_encrypt_pass)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char * plaintext = "data1";
+
+ // 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", "data/keyrings/1/secring.gpg"));
+
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input+output w/ bad paths (should fail)
+ input = NULL;
+ assert_rnp_failure(rnp_input_from_path(&input, "noexist"));
+ assert_null(input);
+ assert_rnp_failure(rnp_output_to_path(&output, ""));
+ assert_null(output);
+
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ assert_non_null(output);
+ // create encrypt operation
+ assert_rnp_failure(rnp_op_encrypt_create(NULL, ffi, input, output));
+ assert_rnp_failure(rnp_op_encrypt_create(&op, NULL, input, output));
+ assert_rnp_failure(rnp_op_encrypt_create(&op, ffi, NULL, output));
+ assert_rnp_failure(rnp_op_encrypt_create(&op, ffi, input, NULL));
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // add password (using all defaults)
+ assert_rnp_failure(rnp_op_encrypt_add_password(NULL, "pass1", NULL, 0, NULL));
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, "", NULL, 0, NULL));
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass1", "WRONG", 0, NULL));
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, "WRONG"));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, NULL));
+ // add password
+ if (!sm2_enabled() && !twofish_enabled()) {
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
+ assert_rnp_failure(
+ rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, "TWOFISH"));
+ assert_rnp_success(
+ rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, "BLOWFISH"));
+ } else if (!sm2_enabled() && twofish_enabled()) {
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
+ assert_rnp_success(
+ rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, "TWOFISH"));
+ } else if (sm2_enabled() && !twofish_enabled()) {
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "BLOWFISH"));
+ } else {
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH"));
+ }
+ // set the data encryption cipher
+ assert_rnp_failure(rnp_op_encrypt_set_cipher(NULL, "CAST5"));
+ assert_rnp_failure(rnp_op_encrypt_set_cipher(op, NULL));
+ assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "WRONG"));
+ if (cast5_enabled()) {
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ } else {
+ assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
+ }
+ // execute the operation
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+
+ // make sure the output file was created
+ assert_true(rnp_file_exists("encrypted"));
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ op = NULL;
+
+ /* decrypt */
+
+ // decrypt (no pass provider, should fail)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // decrypt (wrong pass, should fail)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ const char *pass = "wrong1";
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // decrypt (pass1)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass1"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ // compare the decrypted file
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ unlink("decrypted");
+
+ // decrypt (pass2)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass2"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ // compare the decrypted file
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ // final cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_encrypt_pass_provider)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char *plaintext = "Data encrypted with password provided via password provider.";
+
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input + output
+ assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ // create encrypt operation
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // add password with NULL password provider set - should fail
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, NULL, 0, NULL));
+ // add password with password provider set.
+ int pswdnum = 1;
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_inc, &pswdnum));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, NULL, 0, NULL));
+ // add another password with different encryption parameters
+ if (!sm2_enabled() && !twofish_enabled()) {
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SHA256", 12345, "TWOFISH"));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, NULL, 12345, NULL));
+ } else if (!sm2_enabled() && twofish_enabled()) {
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SHA256", 12345, "TWOFISH"));
+ } else if (sm2_enabled() && !twofish_enabled()) {
+ assert_rnp_failure(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, NULL));
+ } else {
+ assert_rnp_success(rnp_op_encrypt_add_password(op, NULL, "SM3", 12345, "TWOFISH"));
+ }
+ // set the data encryption cipher
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAMELLIA256"));
+ // execute the operation
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ // make sure the output file was created
+ assert_true(rnp_file_exists("encrypted"));
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ op = NULL;
+
+ /* decrypt with pass1 */
+ 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 *) "pass1"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ unlink("decrypted");
+
+ /* decrypt with pass2 via provider */
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ pswdnum = 2;
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_inc, &pswdnum));
+ 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(), plaintext);
+ unlink("decrypted");
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_encrypt_set_cipher)
+{
+ /* setup FFI */
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* create input + output */
+ rnp_input_t input = NULL;
+ const char *plaintext = "Data encrypted with password using different CEK/KEK.";
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ /* create encrypt operation */
+ rnp_op_encrypt_t op = NULL;
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ /* use different sym algos */
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES192"));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "password2", NULL, 0, "AES128"));
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
+ /* execute the operation */
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ assert_true(rnp_file_exists("encrypted"));
+ /* cleanup */
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ /* decrypt with password1 */
+ 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 *) "password1"));
+ rnp_op_verify_t verify;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ /* Check protection info */
+ char *mode = NULL;
+ char *cipher = NULL;
+ bool valid = false;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "cfb-mdc");
+ assert_string_equal(cipher, "AES256");
+ assert_true(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ /* Check SESKs */
+ size_t count = 0;
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 2);
+ /* First SESK: AES192 */
+ rnp_symenc_handle_t symenc = NULL;
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ char *aalg = NULL;
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
+ assert_string_equal(aalg, "None");
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES192");
+ rnp_buffer_destroy(aalg);
+ rnp_buffer_destroy(cipher);
+ /* Second SESK: AES128 */
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
+ assert_string_equal(aalg, "None");
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES128");
+ rnp_buffer_destroy(aalg);
+ rnp_buffer_destroy(cipher);
+ unlink("decrypted");
+ unlink("encrypted");
+ rnp_op_verify_destroy(verify);
+
+ /* Now use AEAD */
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) plaintext, strlen(plaintext), false));
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted-aead"));
+ /* create encrypt operation */
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ /* use different sym algos */
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "password1", NULL, 0, "AES256"));
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "password2", NULL, 0, "AES192"));
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES128"));
+ assert_rnp_success(rnp_op_encrypt_set_aead(op, "OCB"));
+ /* execute the operation */
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ assert_true(rnp_file_exists("encrypted-aead"));
+ /* cleanup */
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ /* decrypt with password2 */
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted-aead"));
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password2"));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ /* Check protection info */
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "aead-ocb");
+ assert_string_equal(cipher, "AES128");
+ assert_true(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ /* Check SESKs */
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 2);
+ /* First SESK: AES192 */
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
+ assert_string_equal(aalg, "OCB");
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES256");
+ rnp_buffer_destroy(aalg);
+ rnp_buffer_destroy(cipher);
+ /* Second SESK: AES128 */
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aalg));
+ assert_string_equal(aalg, "OCB");
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES192");
+ rnp_buffer_destroy(aalg);
+ rnp_buffer_destroy(cipher);
+ unlink("decrypted");
+ unlink("encrypted-aead");
+ rnp_op_verify_destroy(verify);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_encrypt_pk)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char * plaintext = "data1";
+
+ // 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", "data/keyrings/1/secring.gpg"));
+
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ assert_non_null(output);
+ // create encrypt operation
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // add recipients
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
+ assert_rnp_failure(rnp_op_encrypt_add_recipient(NULL, key));
+ assert_rnp_failure(rnp_op_encrypt_add_recipient(op, NULL));
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ // set the data encryption cipher
+ if (cast5_enabled()) {
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ } else {
+ assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
+ }
+ // execute the operation
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+
+ // make sure the output file was created
+ assert_true(rnp_file_exists("encrypted"));
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ op = NULL;
+
+ /* decrypt */
+
+ // decrypt (no pass provider, should fail)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // decrypt (wrong pass, should fail)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ const char *pass = "wrong1";
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // decrypt
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ // read in the decrypted file
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ // final cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+bool
+first_key_password_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ if (!key) {
+ throw std::invalid_argument("key");
+ }
+ char *keyid = NULL;
+ rnp_key_get_keyid(key, &keyid);
+ if (strcmp(keyid, "8A05B89FAD5ADED1")) {
+ throw std::invalid_argument("keyid");
+ }
+ rnp_buffer_destroy(keyid);
+ return false;
+}
+
+TEST_F(rnp_tests, test_ffi_decrypt_pk_unlocked)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char * plaintext = "data1";
+
+ // 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", "data/keyrings/1/secring.gpg"));
+
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ // create encrypt operation
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // add recipients
+ 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);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ rnp_key_handle_destroy(key);
+ // execute the operation
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+
+ // make sure the output file was created
+ assert_true(rnp_file_exists("encrypted"));
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+
+ /* decrypt (unlocked first key, no pass provider) */
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ rnp_key_handle_t defkey = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
+ assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
+ assert_non_null(defkey);
+ assert_rnp_success(rnp_key_unlock(defkey, "password"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ assert_rnp_success(rnp_key_lock(defkey));
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(defkey);
+ // cleanup
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ assert_int_equal(unlink("decrypted"), 0);
+
+ /* decrypt (unlocked second key, no pass provider) */
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
+ assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
+ assert_non_null(defkey);
+ assert_rnp_success(rnp_key_unlock(defkey, "password"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ assert_rnp_success(rnp_key_lock(defkey));
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(defkey);
+ // cleanup
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ assert_int_equal(unlink("decrypted"), 0);
+
+ /* decrypt (unlocked first key, pass provider should not be called) */
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, ffi_asserting_password_provider, NULL));
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
+ assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
+ assert_non_null(defkey);
+ assert_rnp_success(rnp_key_unlock(defkey, "password"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ assert_rnp_success(rnp_key_lock(defkey));
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(defkey);
+ // cleanup
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ assert_int_equal(unlink("decrypted"), 0);
+
+ /* decrypt (unlocked second key, pass provider should not be called) */
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, first_key_password_provider, NULL));
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
+ assert_rnp_success(rnp_key_get_default_key(key, "encrypt", 0, &defkey));
+ assert_non_null(defkey);
+ assert_rnp_success(rnp_key_unlock(defkey, "password"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ assert_rnp_success(rnp_key_lock(defkey));
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(defkey);
+ // cleanup
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ assert_int_equal(unlink("decrypted"), 0);
+
+ // final cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_encrypt_pk_key_provider)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char * plaintext = "data1";
+ uint8_t * primary_sec_key_data = NULL;
+ size_t primary_sec_size = 0;
+ uint8_t * sub_sec_key_data = NULL;
+ size_t sub_sec_size = 0;
+
+ /* first, let's generate some encrypted data */
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_non_null(ffi);
+ // load our keyrings
+ assert_true(
+ load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ assert_non_null(output);
+ // create encrypt operation
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // add recipient 1
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key0-uid2", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ // cleanup
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ key = NULL;
+ // add recipient 2
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ // save the primary key data for later
+ assert_rnp_success(rnp_get_secret_key_data(key, &primary_sec_key_data, &primary_sec_size));
+ assert_non_null(primary_sec_key_data);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ key = NULL;
+ // save the appropriate encrypting subkey for the key provider to use during decryption
+ // later
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8A05B89FAD5ADED1", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_get_secret_key_data(key, &sub_sec_key_data, &sub_sec_size));
+ assert_non_null(sub_sec_key_data);
+ // cleanup
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ key = NULL;
+ // set the data encryption cipher
+ if (cast5_enabled()) {
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ } else {
+ assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
+ }
+ // execute the operation
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ // make sure the output file was created
+ assert_true(rnp_file_exists("encrypted"));
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ op = NULL;
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+ ffi = NULL;
+
+ /* decrypt */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load the primary
+ input = NULL;
+ assert_rnp_success(
+ rnp_input_from_memory(&input, primary_sec_key_data, primary_sec_size, true));
+ assert_non_null(input);
+ assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
+ rnp_input_destroy(input);
+ input = NULL;
+
+ // decrypt (no key to decrypt, should fail)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_int_equal(RNP_ERROR_NO_SUITABLE_KEY, rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // key_data key_data_size secret keyid grip userids
+ const key_tbl_t keydb[] = {
+ {sub_sec_key_data, sub_sec_size, true, "8A05B89FAD5ADED1", NULL, {NULL}}, {0}};
+
+ // decrypt
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(rnp_ffi_set_key_provider(ffi, tbl_getkeycb, (void *) keydb));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ // compare the decrypted file
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ // final cleanup
+ rnp_ffi_destroy(ffi);
+ free(sub_sec_key_data);
+ free(primary_sec_key_data);
+}
+
+TEST_F(rnp_tests, test_ffi_encrypt_and_sign)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ rnp_op_sign_signature_t signsig = NULL;
+ const char * plaintext = "data1";
+ rnp_key_handle_t key = NULL;
+ const uint32_t issued = 1516211899; // Unix epoch, nowish
+ const uint32_t expires = 1000000000; // expires later
+ const uint32_t issued2 = 1516211900; // Unix epoch, nowish
+ const uint32_t expires2 = 2000000000; // expires later
+
+ // 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", "data/keyrings/1/secring.gpg"));
+
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ assert_non_null(output);
+ // create encrypt operation
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // add recipients
+ 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);
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ // set the data encryption cipher
+ if (cast5_enabled()) {
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ } else {
+ assert_rnp_failure(rnp_op_encrypt_set_cipher(op, "CAST5"));
+ assert_rnp_success(rnp_op_encrypt_set_cipher(op, "AES256"));
+ }
+ // enable armoring
+ assert_rnp_failure(rnp_op_encrypt_set_armor(NULL, true));
+ assert_rnp_success(rnp_op_encrypt_set_armor(op, true));
+ // add signature
+ assert_rnp_failure(rnp_op_encrypt_set_hash(NULL, "SHA256"));
+ assert_rnp_failure(rnp_op_encrypt_set_hash(op, NULL));
+ assert_rnp_failure(rnp_op_encrypt_set_hash(op, "WRONG"));
+ assert_rnp_success(rnp_op_encrypt_set_hash(op, "SHA1"));
+ assert_rnp_failure(rnp_op_encrypt_set_creation_time(NULL, 0));
+ assert_rnp_success(rnp_op_encrypt_set_creation_time(op, 0));
+ assert_rnp_failure(rnp_op_encrypt_set_expiration_time(NULL, 0));
+ assert_rnp_success(rnp_op_encrypt_set_expiration_time(op, 0));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid1", &key));
+ assert_rnp_failure(rnp_op_encrypt_add_signature(NULL, key, NULL));
+ assert_rnp_failure(rnp_op_encrypt_add_signature(op, NULL, NULL));
+ assert_rnp_success(rnp_op_encrypt_add_signature(op, key, NULL));
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ // attempt to add signature from the public key
+ assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-p256-pub.asc"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
+ assert_rnp_failure(rnp_op_encrypt_add_signature(op, key, &signsig));
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ // attempt to add signature by the offline secret key
+ assert_true(
+ import_pub_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-no-sign-sub.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ assert_rnp_failure(rnp_op_encrypt_add_signature(op, key, &signsig));
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ // add second signature with different hash/issued/expiration
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid2", &key));
+ assert_rnp_success(rnp_op_encrypt_add_signature(op, key, &signsig));
+ assert_rnp_success(rnp_op_sign_signature_set_creation_time(signsig, issued2));
+ assert_rnp_success(rnp_op_sign_signature_set_expiration_time(signsig, expires2));
+ assert_rnp_failure(rnp_op_sign_signature_set_hash(signsig, NULL));
+ assert_rnp_failure(rnp_op_sign_signature_set_hash(NULL, "SHA512"));
+ assert_rnp_failure(rnp_op_sign_signature_set_hash(signsig, "UNKNOWN"));
+ assert_rnp_success(rnp_op_sign_signature_set_hash(signsig, "SHA512"));
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ // set default sig parameters after the signature is added - those should be picked up
+ assert_rnp_success(rnp_op_encrypt_set_hash(op, "SHA256"));
+ assert_rnp_success(rnp_op_encrypt_set_creation_time(op, issued));
+ assert_rnp_success(rnp_op_encrypt_set_expiration_time(op, expires));
+ // execute the operation
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+
+ // make sure the output file was created
+ assert_true(rnp_file_exists("encrypted"));
+
+ // check whether keys are locked
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
+ const char *fprint = NULL;
+ while (!rnp_identifier_iterator_next(it, &fprint)) {
+ if (!fprint) {
+ break;
+ }
+ SCOPED_TRACE(fprint);
+ rnp_key_handle_t skey = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "fingerprint", fprint, &skey));
+ bool secret = true;
+ assert_rnp_success(rnp_key_have_secret(skey, &secret));
+ if (secret) {
+ bool locked = false;
+ assert_rnp_success(rnp_key_is_locked(skey, &locked));
+ assert_true(locked);
+ }
+ rnp_key_handle_destroy(skey);
+ }
+ rnp_identifier_iterator_destroy(it);
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ op = NULL;
+
+ /* decrypt */
+
+ // decrypt (no pass provider, should fail)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // decrypt (wrong pass, should fail)
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ const char *pass = "wrong1";
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_once, &pass));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // decrypt
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ // compare the decrypted file
+ assert_string_equal(file_to_str("decrypted").c_str(), plaintext);
+ // verify and check signatures
+ rnp_op_verify_t verify;
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "verified"));
+ assert_non_null(output);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // check signatures
+ rnp_op_verify_signature_t sig;
+ size_t sig_count;
+ uint32_t sig_create;
+ uint32_t sig_expires;
+ char * hname = NULL;
+
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
+ assert_int_equal(sig_count, 2);
+ // signature 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_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
+ assert_int_equal(sig_create, issued);
+ assert_int_equal(sig_expires, expires);
+ assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
+ assert_string_equal(hname, "SHA256");
+ rnp_buffer_destroy(hname);
+ hname = NULL;
+ // signature 2
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 1, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
+ assert_int_equal(sig_create, issued2);
+ assert_int_equal(sig_expires, expires2);
+ assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
+ assert_string_equal(hname, "SHA512");
+ rnp_buffer_destroy(hname);
+ hname = NULL;
+ // make sure keys are locked
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
+ while (!rnp_identifier_iterator_next(it, &fprint)) {
+ if (!fprint) {
+ break;
+ }
+ SCOPED_TRACE(fprint);
+ rnp_key_handle_t skey = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "fingerprint", fprint, &skey));
+ bool secret = true;
+ assert_rnp_success(rnp_key_have_secret(skey, &secret));
+ if (secret) {
+ bool locked = false;
+ assert_rnp_success(rnp_key_is_locked(skey, &locked));
+ assert_true(locked);
+ }
+ rnp_key_handle_destroy(skey);
+ }
+ rnp_identifier_iterator_destroy(it);
+ // cleanup
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ // compare the decrypted file
+ assert_string_equal(file_to_str("verified").c_str(), plaintext);
+ // final cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_encrypt_pk_subkey_selection)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char * plaintext = "data1";
+
+ /* check whether a latest subkey is selected for encryption */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ /* case 1: three encryption subkeys, second expired, third has later creation time */
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub02.pgp"));
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ /* create encrypt operation, add recipient and execute */
+ 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-uid0", &key));
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ /* get output */
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, true));
+ assert_true(buf && len);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_op_encrypt_destroy(op);
+ /* decrypt */
+ assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(rnp_input_from_memory(&input, buf, len, true));
+ rnp_buffer_destroy(buf);
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+
+ /* check whether we used correct subkey */
+ size_t count = 0;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 1);
+ rnp_recipient_handle_t recipient = NULL;
+ assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+ assert_non_null(recipient);
+ char *keyid = NULL;
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_non_null(keyid);
+ assert_string_equal(keyid, "8A05B89FAD5ADED1");
+ rnp_buffer_destroy(keyid);
+
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* case 2: only subkeys 1-2, make sure that latest but expired subkey is not selected */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub01.pgp"));
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ /* create encrypt operation, add recipient and execute */
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_rnp_success(rnp_op_encrypt_add_recipient(op, key));
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ /* get output */
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, true));
+ assert_true(buf && len);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_op_encrypt_destroy(op);
+ /* decrypt */
+ assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(rnp_input_from_memory(&input, buf, len, true));
+ rnp_buffer_destroy(buf);
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+
+ /* check whether we used correct subkey */
+ count = 0;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 1);
+ recipient = NULL;
+ assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+ assert_non_null(recipient);
+ keyid = NULL;
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_non_null(keyid);
+ assert_string_equal(keyid, "1ED63EE56FADC34D");
+ rnp_buffer_destroy(keyid);
+
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* case 3: only expired subkey, make sure encryption operation fails */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/key0-sub1.pgp"));
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (uint8_t *) plaintext, strlen(plaintext), false));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ /* create encrypt operation, add recipient and execute */
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_int_equal(rnp_op_encrypt_add_recipient(op, key), RNP_ERROR_NO_SUITABLE_KEY);
+ rnp_key_handle_destroy(key);
+ assert_rnp_failure(rnp_op_encrypt_execute(op));
+ rnp_op_encrypt_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_decrypt_small_rsa)
+{
+ rnp_ffi_t ffi = NULL;
+ const char *plaintext = "data1";
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(import_all_keys(ffi, "data/test_key_validity/rsa_key_small_sig-sec.asc"));
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/data.enc.small-rsa"));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ size_t len = 0;
+ uint8_t *buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 5);
+ assert_int_equal(memcmp(plaintext, buf, 5), 0);
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_decrypt_small_eg)
+{
+ /* make sure unlock and decrypt fails with invalid key */
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(
+ import_all_keys(ffi, "data/test_key_edge_cases/key-eg-small-subgroup-sec.pgp"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "3b8dda452b9f69b4", &key));
+ assert_non_null(key);
+ /* key is not encrypted */
+ assert_rnp_success(rnp_key_unlock(key, NULL));
+ rnp_key_handle_destroy(key);
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-eg-bad"));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_null(&output));
+ 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);
+ /* make sure unlock and decrypt fails with invalid encrypted key */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_true(
+ import_all_keys(ffi, "data/test_key_edge_cases/key-eg-small-subgroup-sec-enc.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "3b072c3bb2d1a8b2", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_unlock(key, "password"));
+ assert_rnp_success(rnp_key_lock(key));
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-eg-bad2"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ 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/src/tests/ffi-key-prop.cpp b/src/tests/ffi-key-prop.cpp
new file mode 100644
index 0000000..95185cf
--- /dev/null
+++ b/src/tests/ffi-key-prop.cpp
@@ -0,0 +1,1406 @@
+/*
+ * Copyright (c) 2021 [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 <sstream>
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+
+TEST_F(rnp_tests, test_ffi_key_set_expiry_multiple_uids)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ /* load key with 3 uids with zero key expiration */
+ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/alice-3-uids.pgp"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ size_t count = 0;
+ 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));
+ assert_true(check_uid_valid(key, 2, true));
+ /* reload */
+ rnp_key_handle_destroy(key);
+ reload_keyrings(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &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(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_key_set_expiration(key, 0xFFFFFFFF));
+ 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 */
+ reload_keyrings(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Caesar <caesar@rnp>", &key));
+ assert_rnp_success(rnp_key_get_expiration(key, &expiry));
+ assert_int_equal(expiry, 0xFFFFFFFF);
+ 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);
+
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ /* load key with 3 uids, including primary, with key expiration */
+ assert_true(
+ import_all_keys(ffi, "data/test_key_edge_cases/alice-3-uids-primary-expiring.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &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));
+ assert_true(check_uid_valid(key, 2, true));
+ rnp_key_handle_destroy(key);
+ /* reload and make sure it is saved */
+ reload_keyrings(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Caesar <caesar@rnp>", &key));
+ assert_rnp_success(rnp_key_get_expiration(key, &expiry));
+ assert_int_equal(expiry, 0);
+ 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));
+ rnp_key_handle_destroy(key);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_primary_uid_conflict)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ /* load key with 1 uid and two certifications: first marks uid primary, but expires key
+ * second marks uid as non-primary, but has zero key expiration */
+ assert_true(
+ import_all_keys(ffi, "data/test_key_edge_cases/key-primary-uid-conflict-pub.pgp"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid_2_sigs", &key));
+ assert_int_equal(get_key_uids(key), 1);
+ assert_int_equal(get_key_expiry(key), 0);
+ assert_true(check_key_valid(key, true));
+ assert_true(check_uid_valid(key, 0, true));
+ assert_true(check_uid_primary(key, 0, false));
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_expired_certification_and_direct_sig)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ /* load key with 2 uids and direct-key signature:
+ * - direct-key sig has 0 key expiration time but expires in 30 seconds
+ * - first uid is not primary, but key expiration is 60 seconds
+ * - second uid is marked as primary, doesn't expire key, but certification expires in 60
+ * seconds */
+ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/key-expired-cert-direct.pgp"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "primary-uid-expired-cert", &key));
+ assert_null(key);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "expired-certifications", &key));
+ assert_non_null(key);
+ assert_int_equal(get_key_uids(key), 2);
+ assert_int_equal(get_key_expiry(key), 60);
+ rnp_signature_handle_t sig = NULL;
+ assert_rnp_success(rnp_key_get_signature_at(key, 0, &sig));
+ assert_non_null(sig);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_EXPIRED);
+ rnp_signature_handle_destroy(sig);
+ assert_true(check_key_valid(key, false));
+ assert_true(check_uid_valid(key, 0, true));
+ assert_true(check_uid_primary(key, 0, false));
+ assert_true(check_uid_valid(key, 1, false));
+ assert_true(check_uid_primary(key, 1, false));
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_25519_tweaked_bits)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* try public key */
+ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/key-25519-non-tweaked.asc"));
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "950EE0CD34613DBA", &sub));
+ bool tweaked = true;
+ assert_rnp_failure(rnp_key_25519_bits_tweaked(NULL, &tweaked));
+ assert_rnp_failure(rnp_key_25519_bits_tweaked(sub, NULL));
+ assert_rnp_failure(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ assert_rnp_failure(rnp_key_25519_bits_tweak(NULL));
+ assert_rnp_failure(rnp_key_25519_bits_tweak(sub));
+ rnp_key_handle_destroy(sub);
+ /* load secret key */
+ assert_true(
+ import_all_keys(ffi, "data/test_key_edge_cases/key-25519-non-tweaked-sec.asc"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "950EE0CD34613DBA", &sub));
+ assert_rnp_failure(rnp_key_25519_bits_tweaked(NULL, &tweaked));
+ assert_rnp_failure(rnp_key_25519_bits_tweaked(sub, NULL));
+ assert_rnp_success(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ assert_false(tweaked);
+ /* protect key and try again */
+ assert_rnp_success(rnp_key_protect(sub, "password", NULL, NULL, NULL, 100000));
+ assert_rnp_failure(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ assert_rnp_success(rnp_key_unlock(sub, "password"));
+ tweaked = true;
+ assert_rnp_success(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ assert_false(tweaked);
+ assert_rnp_success(rnp_key_lock(sub));
+ assert_rnp_failure(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ /* now let's tweak it */
+ assert_rnp_failure(rnp_key_25519_bits_tweak(NULL));
+ assert_rnp_failure(rnp_key_25519_bits_tweak(sub));
+ assert_rnp_success(rnp_key_unlock(sub, "password"));
+ assert_rnp_failure(rnp_key_25519_bits_tweak(sub));
+ assert_rnp_success(rnp_key_unprotect(sub, "password"));
+ assert_rnp_success(rnp_key_25519_bits_tweak(sub));
+ assert_rnp_success(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ assert_true(tweaked);
+ /* export unprotected key */
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "3176FC1486AA2528", &key));
+ auto clearsecdata = export_key(key, true, true);
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_key_protect(sub, "password", NULL, NULL, NULL, 100000));
+ rnp_key_handle_destroy(sub);
+ /* make sure it is exported and saved tweaked and protected */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "3176FC1486AA2528", &key));
+ auto secdata = export_key(key, true, true);
+ rnp_key_handle_destroy(key);
+ reload_keyrings(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "950EE0CD34613DBA", &sub));
+ bool prot = false;
+ assert_rnp_success(rnp_key_is_protected(sub, &prot));
+ assert_true(prot);
+ assert_rnp_success(rnp_key_unlock(sub, "password"));
+ tweaked = false;
+ assert_rnp_success(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ assert_true(tweaked);
+ rnp_key_handle_destroy(sub);
+ rnp_ffi_destroy(ffi);
+ /* import cleartext exported key */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(import_all_keys(ffi, clearsecdata.data(), clearsecdata.size()));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "950EE0CD34613DBA", &sub));
+ prot = true;
+ assert_rnp_success(rnp_key_is_protected(sub, &prot));
+ assert_false(prot);
+ tweaked = false;
+ assert_rnp_success(rnp_key_25519_bits_tweaked(sub, &tweaked));
+ assert_true(tweaked);
+ rnp_key_handle_destroy(sub);
+ rnp_ffi_destroy(ffi);
+ /* import exported key */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(import_all_keys(ffi, secdata.data(), secdata.size()));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "950EE0CD34613DBA", &sub));
+ prot = false;
+ assert_rnp_success(rnp_key_is_protected(sub, &prot));
+ assert_true(prot);
+ assert_rnp_success(rnp_key_unlock(sub, "password"));
+ tweaked = false;
+ 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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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 <alice@rnp>", &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);
+
+ /* offline primary key - must select a subkey */
+ 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", &primary));
+ def_key = NULL;
+ 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, "22F3A217C0E439CB");
+ rnp_buffer_destroy(keyid);
+ rnp_key_handle_destroy(def_key);
+ rnp_key_handle_destroy(primary);
+ /* offline primary key, stored on card - must select a subkey */
+ 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", &primary));
+ def_key = NULL;
+ 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, "22F3A217C0E439CB");
+ rnp_buffer_destroy(keyid);
+ rnp_key_handle_destroy(def_key);
+ rnp_key_handle_destroy(primary);
+ /* offline primary key without the signing subkey - fail */
+ 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-no-sign-sub.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &primary));
+ def_key = NULL;
+ assert_int_equal(rnp_key_get_default_key(primary, "sign", 0, &def_key),
+ RNP_ERROR_NO_SUITABLE_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/src/tests/ffi-key-sig.cpp b/src/tests/ffi-key-sig.cpp
new file mode 100644
index 0000000..01bfdd2
--- /dev/null
+++ b/src/tests/ffi-key-sig.cpp
@@ -0,0 +1,1626 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <sstream>
+#include <rnp/rnp.h>
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+#include "rnp_tests.h"
+#include "support.h"
+
+static bool check_sig_status(json_object *sig,
+ const char * pub,
+ const char * sec,
+ const char * fp);
+
+TEST_F(rnp_tests, test_ffi_key_signatures)
+{
+ rnp_ffi_t ffi = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load key
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p384-pub.asc"));
+ // check primary key
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "242A3AA5EA85F44A", &key));
+ // some edge cases
+ size_t sigs = 0;
+ rnp_signature_handle_t sig = NULL;
+ assert_rnp_failure(rnp_key_get_signature_count(NULL, &sigs));
+ assert_rnp_failure(rnp_key_get_signature_count(key, NULL));
+ assert_rnp_failure(rnp_key_get_signature_at(key, 0, &sig));
+ assert_rnp_failure(rnp_key_get_signature_at(key, 0x10000, &sig));
+ assert_rnp_failure(rnp_key_get_signature_at(NULL, 0x10000, &sig));
+ assert_rnp_failure(rnp_key_get_signature_at(NULL, 0, NULL));
+ // key doesn't have signatures
+ assert_rnp_success(rnp_key_get_signature_count(key, &sigs));
+ assert_int_equal(sigs, 0);
+ // uid must have one signature
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &sigs));
+ assert_int_equal(sigs, 1);
+ assert_rnp_failure(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ char *type = NULL;
+ assert_rnp_failure(rnp_signature_get_type(NULL, &type));
+ assert_rnp_failure(rnp_signature_get_type(sig, NULL));
+ assert_rnp_success(rnp_signature_get_type(sig, &type));
+ assert_string_equal(type, "certification (positive)");
+ rnp_buffer_destroy(type);
+ uint32_t creation = 0;
+ assert_rnp_success(rnp_signature_get_creation(sig, &creation));
+ assert_int_equal(creation, 1549119505);
+ char *alg = NULL;
+ assert_rnp_failure(rnp_signature_get_alg(NULL, &alg));
+ assert_rnp_failure(rnp_signature_get_alg(sig, NULL));
+ assert_rnp_success(rnp_signature_get_alg(sig, &alg));
+ assert_string_equal(alg, "ECDSA");
+ rnp_buffer_destroy(alg);
+ assert_rnp_success(rnp_signature_get_hash_alg(sig, &alg));
+ assert_string_equal(alg, "SHA384");
+ rnp_buffer_destroy(alg);
+ 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);
+ assert_string_equal(keyid, "242A3AA5EA85F44A");
+ rnp_buffer_destroy(keyid);
+ rnp_key_handle_destroy(signer);
+ assert_int_equal(rnp_signature_is_valid(NULL, 0), RNP_ERROR_NULL_POINTER);
+ assert_int_equal(rnp_signature_is_valid(sig, 17), RNP_ERROR_BAD_PARAMETERS);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ assert_rnp_success(rnp_signature_handle_destroy(sig));
+ // subkey must have one signature
+ rnp_key_handle_t subkey = NULL;
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &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));
+ // check signature export
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_signature_export(NULL, output, 0));
+ assert_rnp_failure(rnp_signature_export(sig, NULL, 0));
+ assert_rnp_failure(rnp_signature_export(sig, output, 0x333));
+ assert_rnp_success(rnp_signature_export(sig, output, 0));
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 154);
+ rnp_input_t input;
+ assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));
+ char *json = NULL;
+ assert_rnp_success(rnp_import_signatures(ffi, input, 0, &json));
+ assert_non_null(json);
+ json_object *jso = json_tokener_parse(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_object));
+ json_object *jsigs = NULL;
+ assert_true(json_object_object_get_ex(jso, "sigs", &jsigs));
+ assert_true(json_object_is_type(jsigs, json_type_array));
+ assert_int_equal(json_object_array_length(jsigs), 1);
+ json_object *jsig = json_object_array_get_idx(jsigs, 0);
+ assert_true(check_sig_status(jsig, "none", "none", NULL));
+ json_object_put(jso);
+ rnp_buffer_destroy(json);
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_signature_export(sig, output, RNP_KEY_EXPORT_ARMORED));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 297);
+ std::string data((const char *) buf, len);
+ assert_true(starts_with(data, "-----BEGIN PGP PUBLIC KEY BLOCK-----"));
+ assert_true(ends_with(strip_eol(data), "-----END PGP PUBLIC KEY BLOCK-----"));
+
+ assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));
+ assert_rnp_success(rnp_import_signatures(ffi, input, 0, NULL));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+
+ 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_creation(sig, &creation));
+ assert_int_equal(creation, 1549119513);
+ assert_rnp_success(rnp_signature_get_alg(sig, &alg));
+ assert_string_equal(alg, "ECDSA");
+ rnp_buffer_destroy(alg);
+ assert_rnp_success(rnp_signature_get_hash_alg(sig, &alg));
+ assert_string_equal(alg, "SHA384");
+ rnp_buffer_destroy(alg);
+ assert_rnp_success(rnp_signature_get_keyid(sig, &keyid));
+ assert_non_null(keyid);
+ assert_string_equal(keyid, "242A3AA5EA85F44A");
+ rnp_buffer_destroy(keyid);
+ 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);
+ assert_string_equal(keyid, "242A3AA5EA85F44A");
+ rnp_buffer_destroy(keyid);
+ rnp_key_handle_destroy(signer);
+ 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
+check_import_sigs(rnp_ffi_t ffi, json_object **jso, json_object **sigarr, const char *sigpath)
+{
+ rnp_input_t input = NULL;
+ if (rnp_input_from_path(&input, sigpath)) {
+ return false;
+ }
+ bool res = false;
+ char *sigs = NULL;
+ *jso = NULL;
+
+ if (rnp_import_signatures(ffi, input, 0, &sigs)) {
+ goto done;
+ }
+ if (!sigs) {
+ goto done;
+ }
+
+ *jso = json_tokener_parse(sigs);
+ if (!jso) {
+ goto done;
+ }
+ if (!json_object_is_type(*jso, json_type_object)) {
+ goto done;
+ }
+ if (!json_object_object_get_ex(*jso, "sigs", sigarr)) {
+ goto done;
+ }
+ if (!json_object_is_type(*sigarr, json_type_array)) {
+ goto done;
+ }
+ res = true;
+done:
+ if (!res) {
+ json_object_put(*jso);
+ *jso = NULL;
+ }
+ rnp_input_destroy(input);
+ rnp_buffer_destroy(sigs);
+ return res;
+}
+
+static bool
+check_sig_status(json_object *sig, const char *pub, const char *sec, const char *fp)
+{
+ if (!sig) {
+ return false;
+ }
+ if (!json_object_is_type(sig, json_type_object)) {
+ return false;
+ }
+ json_object *fld = NULL;
+ if (!json_object_object_get_ex(sig, "public", &fld)) {
+ return false;
+ }
+ if (strcmp(json_object_get_string(fld), pub) != 0) {
+ return false;
+ }
+ if (!json_object_object_get_ex(sig, "secret", &fld)) {
+ return false;
+ }
+ if (strcmp(json_object_get_string(fld), sec) != 0) {
+ return false;
+ }
+ if (!fp && json_object_object_get_ex(sig, "signer fingerprint", &fld)) {
+ return false;
+ }
+ if (fp) {
+ if (!json_object_object_get_ex(sig, "signer fingerprint", &fld)) {
+ return false;
+ }
+ if (strcmp(json_object_get_string(fld), fp) != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+TEST_F(rnp_tests, test_ffi_import_signatures)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ char * results = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-pub.asc"));
+ /* find key and check signature count */
+ rnp_key_handle_t key_handle = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ size_t sigcount = 0;
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 0);
+ /* check revocation status */
+ bool revoked = false;
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_false(revoked);
+ /* some import edge cases */
+ assert_rnp_failure(rnp_import_signatures(ffi, NULL, 0, &results));
+ assert_rnp_failure(rnp_import_signatures(NULL, input, 0, &results));
+ assert_rnp_failure(rnp_import_signatures(ffi, input, 0x18, &results));
+ /* import revocation signature */
+ json_object *jso = NULL;
+ json_object *jsosigs = NULL;
+ assert_true(
+ check_import_sigs(ffi, &jso, &jsosigs, "data/test_key_validity/alice-rev.pgp"));
+ assert_int_equal(json_object_array_length(jsosigs), 1);
+ json_object *jsosig = json_object_array_get_idx(jsosigs, 0);
+ assert_true(check_sig_status(
+ jsosig, "new", "unknown key", "73edcc9119afc8e2dbbdcde50451409669ffde3c"));
+ json_object_put(jso);
+ /* key now must become revoked */
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_true(revoked);
+ /* check signature number - it now must be 1 */
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 1);
+ /* check signature type */
+ rnp_signature_handle_t sig = NULL;
+ assert_rnp_success(rnp_key_get_signature_at(key_handle, 0, &sig));
+ char *type = NULL;
+ assert_rnp_success(rnp_signature_get_type(sig, &type));
+ assert_string_equal(type, "key revocation");
+ rnp_buffer_destroy(type);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ uint32_t screate = 0;
+ assert_rnp_success(rnp_signature_get_creation(sig, &screate));
+ assert_int_equal(screate, 1578663151);
+ rnp_signature_handle_destroy(sig);
+ /* check key validity */
+ bool valid = true;
+ assert_rnp_success(rnp_key_is_valid(key_handle, &valid));
+ assert_false(valid);
+ uint32_t till = 0;
+ assert_rnp_success(rnp_key_valid_till(key_handle, &till));
+ assert_int_equal(till, 1578663151);
+ /* check import with NULL results param */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_key_validity/alice-rev.pgp"));
+ assert_rnp_success(rnp_import_signatures(ffi, input, 0, NULL));
+ assert_rnp_success(rnp_input_destroy(input));
+ /* import signature again, making sure it is not duplicated */
+ assert_true(
+ check_import_sigs(ffi, &jso, &jsosigs, "data/test_key_validity/alice-rev.pgp"));
+ assert_int_equal(json_object_array_length(jsosigs), 1);
+ jsosig = json_object_array_get_idx(jsosigs, 0);
+ assert_true(check_sig_status(
+ jsosig, "unchanged", "unknown key", "73edcc9119afc8e2dbbdcde50451409669ffde3c"));
+ json_object_put(jso);
+ /* check signature count, using the same key handle (it must not be changed) */
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 1);
+ rnp_key_handle_destroy(key_handle);
+ /* save and reload keyring, making sure signature is saved */
+ reload_pubring(&ffi);
+ /* find key and check sig count and revocation status */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_true(revoked);
+ assert_rnp_success(rnp_key_is_valid(key_handle, &valid));
+ assert_false(valid);
+ assert_rnp_success(rnp_key_valid_till(key_handle, &till));
+ assert_int_equal(till, 1578663151);
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+
+ /* try to import wrong signature (certification) */
+ assert_true(
+ check_import_sigs(ffi, &jso, &jsosigs, "data/test_key_validity/alice-cert.pgp"));
+ assert_int_equal(json_object_array_length(jsosigs), 1);
+ jsosig = json_object_array_get_idx(jsosigs, 0);
+ assert_true(check_sig_status(jsosig, "none", "none", NULL));
+ json_object_put(jso);
+
+ /* try to import signature for both public and secret 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-pub.asc"));
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
+ assert_true(
+ check_import_sigs(ffi, &jso, &jsosigs, "data/test_key_validity/alice-rev.pgp"));
+ 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);
+
+ /* import direct-key signature (with revocation key subpacket) */
+ assert_true(
+ check_import_sigs(ffi, &jso, &jsosigs, "data/test_key_validity/alice-revoker-sig.pgp"));
+ 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);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 2);
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_true(revoked);
+ assert_rnp_success(rnp_key_get_signature_at(key_handle, 1, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &type));
+ assert_string_equal(type, "direct");
+ rnp_buffer_destroy(type);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+
+ /* load two binary signatures from the file */
+ 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-pub.asc"));
+
+ assert_true(
+ check_import_sigs(ffi, &jso, &jsosigs, "data/test_key_validity/alice-sigs.pgp"));
+ assert_int_equal(json_object_array_length(jsosigs), 2);
+ jsosig = json_object_array_get_idx(jsosigs, 0);
+ assert_true(check_sig_status(
+ jsosig, "new", "unknown key", "73edcc9119afc8e2dbbdcde50451409669ffde3c"));
+ jsosig = json_object_array_get_idx(jsosigs, 1);
+ assert_true(check_sig_status(
+ jsosig, "new", "unknown key", "73edcc9119afc8e2dbbdcde50451409669ffde3c"));
+ json_object_put(jso);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 2);
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_true(revoked);
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+
+ /* load two armored signatures from the single file */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
+
+ assert_true(
+ check_import_sigs(ffi, &jso, &jsosigs, "data/test_key_validity/alice-sigs.asc"));
+ assert_int_equal(json_object_array_length(jsosigs), 2);
+ jsosig = json_object_array_get_idx(jsosigs, 0);
+ /* when secret key is loaded then public copy is created automatically */
+ assert_true(
+ check_sig_status(jsosig, "new", "new", "73edcc9119afc8e2dbbdcde50451409669ffde3c"));
+ jsosig = json_object_array_get_idx(jsosigs, 1);
+ assert_true(
+ check_sig_status(jsosig, "new", "new", "73edcc9119afc8e2dbbdcde50451409669ffde3c"));
+ json_object_put(jso);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 2);
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_true(revoked);
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+ /* try to import signature from key file - must fail */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_key_validity/alice-sec.asc"));
+ results = NULL;
+ assert_rnp_failure(rnp_import_signatures(ffi, input, 0, &results));
+ assert_null(results);
+ assert_rnp_success(rnp_input_destroy(input));
+ /* try to import signatures from stream where second is malformed. Nothing should be
+ * imported. */
+ 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-pub.asc"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_validity/alice-sigs-malf.pgp"));
+ results = NULL;
+ assert_rnp_failure(rnp_import_signatures(ffi, input, 0, &results));
+ assert_null(results);
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 0);
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_false(revoked);
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_export_revocation)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
+
+ rnp_key_handle_t key_handle = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_null(&output));
+ /* check for failure with wrong parameters */
+ assert_rnp_failure(rnp_key_export_revocation(
+ NULL, output, 0, "SHA256", "superseded", "test key revocation"));
+ assert_rnp_failure(rnp_key_export_revocation(key_handle, NULL, 0, "SHA256", NULL, NULL));
+ assert_rnp_failure(
+ rnp_key_export_revocation(key_handle, output, 0x17, "SHA256", NULL, NULL));
+ assert_rnp_failure(
+ rnp_key_export_revocation(key_handle, output, 0, "Wrong hash", NULL, NULL));
+ assert_rnp_failure(
+ rnp_key_export_revocation(key_handle, output, 0, "SHA256", "Wrong reason code", NULL));
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+ /* check for failure with subkey */
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sub-sec.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &key_handle));
+ assert_rnp_success(rnp_key_unlock(key_handle, "password"));
+ assert_rnp_failure(rnp_key_export_revocation(
+ key_handle, output, 0, "SHA256", "superseded", "test key revocation"));
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+ /* try to export revocation having public key only */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ assert_rnp_failure(rnp_key_export_revocation(
+ key_handle, output, 0, "SHA256", "superseded", "test key revocation"));
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+ /* load secret key and export revocation - should succeed with correct password */
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_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_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"));
+ /* correct password provider - must succeed */
+ 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_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"));
+ assert_int_equal(json_object_array_length(jsosigs), 1);
+ json_object *jsosig = json_object_array_get_idx(jsosigs, 0);
+ assert_true(
+ check_sig_status(jsosig, "new", "new", "73edcc9119afc8e2dbbdcde50451409669ffde3c"));
+ json_object_put(jso);
+ /* key now must become revoked */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ bool revoked = false;
+ assert_rnp_success(rnp_key_is_revoked(key_handle, &revoked));
+ assert_true(revoked);
+ /* check signature number - it now must be 1 */
+ size_t sigcount = 0;
+ assert_rnp_success(rnp_key_get_signature_count(key_handle, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+
+ /* check signature contents */
+ pgp_source_t src = {};
+ assert_rnp_success(init_file_src(&src, "alice-revocation.pgp"));
+ pgp_signature_t sig = {};
+ assert_rnp_success(sig.parse(src));
+ src_close(&src);
+ 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(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 <alice@rnp>", &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 <alice@rnp>", &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/"
+
+TEST_F(rnp_tests, test_ffi_sig_validity)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* Case1:
+ * Keys: Alice [pub]
+ * Alice is signed by Basil, but without the Basil's key.
+ * Result: Alice [valid]
+ */
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case1/pubring.gpg"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ bool valid = false;
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_signature_handle_t sig = NULL;
+ /* signature 0: valid self-signature */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ char *sigtype = NULL;
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* signature 1: valid certification from Basil, but without Basil's key */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (generic)");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_KEY_NOT_FOUND);
+ /* let's load Basil's key and make sure signature is now validated and valid */
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "basil-pub.asc"));
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ /* Case2:
+ * Keys: Alice [pub], Basil [pub]
+ * Alice is signed by Basil, Basil is signed by Alice, but Alice's self-signature is
+ * corrupted.
+ * Result: Alice [invalid], Basil [valid]
+ */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case2/pubring.gpg"));
+ /* Alice key */
+ /* we cannot get key by uid since uid is invalid */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ assert_null(key);
+ /* get it via the fingerprint */
+ assert_rnp_success(
+ rnp_locate_key(ffi, "fingerprint", "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C", &key));
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ /* signature 0: corrupted self-signature */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_signature_handle_destroy(sig);
+ /* signature 1: valid certification from Basil */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (generic)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ /* Basil key */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Basil <basil@rnp>", &key));
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ /* signature 0: valid self-signature */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* signature 1: valid certification from Alice */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (generic)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ /* Case3:
+ * Keys: Alice [pub], Basil [pub]
+ * Alice is signed by Basil, but doesn't have self-signature
+ * Result: Alice [invalid]
+ */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case3/pubring.gpg"));
+ /* Alice key */
+ /* cannot locate it via userid since it is invalid */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ assert_null(key);
+ /* let's locate it via the fingerprint */
+ assert_rnp_success(
+ rnp_locate_key(ffi, "fingerprint", "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C", &key));
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ /* signature 0: valid certification from Basil */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (generic)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ /* Case4:
+ * Keys Alice [pub, sub]
+ * Alice subkey has invalid binding signature
+ * Result: Alice [valid], Alice sub [invalid]
+ */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case4/pubring.gpg"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ rnp_key_handle_t sub = NULL;
+ rnp_key_get_subkey_at(key, 0, &sub);
+ rnp_key_get_signature_at(sub, 0, &sig);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_signature_handle_destroy(sig);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* Case5:
+ * Keys Alice [pub, sub], Basil [pub]
+ * Alice subkey has valid binding signature, but from the key Basil
+ * Result: Alice [valid], Alice sub [invalid]
+ */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case5/pubring.gpg"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ rnp_key_get_subkey_at(key, 0, &sub);
+ rnp_key_get_signature_at(sub, 0, &sig);
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "subkey binding");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_signature_handle_destroy(sig);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* Case6:
+ * Keys Alice [pub, sub]
+ * Key Alice has revocation signature by Alice, and subkey doesn't
+ * Result: Alice [invalid], Alice sub [invalid]
+ */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case6/pubring.gpg"));
+ /* check revocation signature */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ rnp_key_get_signature_at(key, 0, &sig);
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "key revocation");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* check subkey binding */
+ rnp_key_get_subkey_at(key, 0, &sub);
+ rnp_key_get_signature_at(sub, 0, &sig);
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "subkey binding");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* Case7:
+ * Keys Alice [pub, sub]
+ * Alice subkey has revocation signature by Alice
+ * Result: Alice [valid], Alice sub [invalid]
+ */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case7/pubring.gpg"));
+ /* check subkey revocation signature */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ rnp_key_get_subkey_at(key, 0, &sub);
+ rnp_key_get_signature_at(sub, 0, &sig);
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "subkey revocation");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* check subkey binding */
+ rnp_key_get_signature_at(sub, 1, &sig);
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "subkey binding");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* Case8:
+ * Keys Alice [pub, sub]
+ * Userid is stripped from the key, but it still has valid subkey binding
+ * Result: Alice [valid], Alice sub[valid]
+ */
+
+ /* not interesting for us at the moment */
+
+ /* Case9:
+ * Keys Alice [pub, sub]
+ * Alice key has two self-signatures, one which expires key and second without key
+ * expiration.
+ * Result: Alice [valid], Alice sub[valid]
+ */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "case9/pubring.gpg"));
+ /* Alice key */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ /* signature 0: valid certification */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* signature 1: valid certification */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ /* another case: expired certification */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "alice-expired-claus-cert.asc"));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "claus-pub.asc"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ /* signature 0: valid certification */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* signature 1: expired claus's certification */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (generic)");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_EXPIRED);
+ uint32_t expires = 0;
+ assert_rnp_success(rnp_signature_get_expiration(sig, &expires));
+ assert_int_equal(expires, 86400);
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_get_signature_type)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/test_key_edge_cases/alice-sig-misc-values.pgp"));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "basil-pub.asc"));
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ rnp_signature_handle_t sig = NULL;
+ /* signature 0: valid self-signature */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ char *sigtype = NULL;
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* signature 1: valid signature by Basil */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (generic)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_is_valid(sig, 0));
+ rnp_signature_handle_destroy(sig);
+ /* signature 2..7: invalid signatures with misc types */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 2, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "standalone");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_VERIFICATION_FAILED);
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 3, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (persona)");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 4, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (casual)");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 5, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "primary key binding");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_VERIFICATION_FAILED);
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 6, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification revocation");
+ rnp_buffer_destroy(sigtype);
+ assert_int_equal(rnp_signature_is_valid(sig, 0), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_signature_handle_destroy(sig);
+
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_remove_signature)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/test_key_edge_cases/alice-sig-misc-values.pgp"));
+ assert_true(import_pub_keys(ffi, KEYSIG_PATH "basil-pub.asc"));
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ size_t count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(key, &count));
+ assert_int_equal(count, 0);
+ rnp_key_handle_t bkey = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Basil <basil@rnp>", &bkey));
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(bkey, &count));
+ assert_int_equal(count, 0);
+
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ rnp_signature_handle_t sig = NULL;
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &count));
+ assert_int_equal(count, 8);
+ /* signature 1: valid signature by Basil */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ assert_rnp_failure(rnp_signature_remove(NULL, sig));
+ assert_rnp_failure(rnp_signature_remove(key, NULL));
+ /* attempt to delete signature from the wrong key */
+ assert_int_equal(rnp_signature_remove(bkey, sig), RNP_ERROR_NO_SIGNATURES_FOUND);
+ assert_rnp_success(rnp_signature_remove(key, sig));
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &count));
+ assert_int_equal(count, 7);
+ /* signature 2: must be moved to position 1 */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 1, &sig));
+ char *sigtype = NULL;
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "standalone");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_remove(key, sig));
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &count));
+ assert_int_equal(count, 6);
+ /* signature 7: must be moved to position 5 */
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 5, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "third-party");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_remove(key, sig));
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &count));
+ assert_int_equal(count, 5);
+ /* check that key and userid are still valid */
+ bool valid = false;
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ valid = false;
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(bkey);
+
+ /* Export key and reload */
+ reload_pubring(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(key, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Basil <basil@rnp>", &bkey));
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(bkey, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &count));
+ assert_int_equal(count, 5);
+ /* delete self-certification and make sure that key/uid become invalid */
+ valid = false;
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ valid = false;
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "certification (positive)");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_remove(key, sig));
+ rnp_signature_handle_destroy(sig);
+ valid = true;
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_false(valid);
+ valid = true;
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(bkey);
+
+ /* Remove subkey's signature */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(load_keys_gpg(ffi, "data/test_key_validity/case7/pubring.gpg"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ count = 0;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 1);
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(sub, &count));
+ assert_int_equal(count, 2);
+ /* check whether key and sub valid: [true, false] since sub is revoked */
+ valid = false;
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_false(valid);
+ assert_rnp_success(rnp_key_get_signature_at(sub, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "subkey revocation");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_success(rnp_signature_remove(sub, sig));
+ rnp_signature_handle_destroy(sig);
+ /* now subkey must become valid with 1 signature */
+ assert_rnp_success(rnp_key_get_signature_count(sub, &count));
+ assert_int_equal(count, 1);
+ valid = false;
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_true(valid);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+ /* reload keys */
+ reload_pubring(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ valid = false;
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_true(valid);
+ assert_rnp_success(rnp_key_get_signature_at(sub, 0, &sig));
+ assert_rnp_success(rnp_signature_get_type(sig, &sigtype));
+ assert_string_equal(sigtype, "subkey binding");
+ rnp_buffer_destroy(sigtype);
+ assert_rnp_failure(rnp_signature_remove(key, sig));
+ assert_rnp_success(rnp_signature_remove(sub, sig));
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_false(valid);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_failure(rnp_signature_remove(sub, sig));
+ assert_rnp_success(rnp_signature_remove(key, sig));
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+ /* save and reload keys without sigs */
+ reload_pubring(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(key, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ count = 255;
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(sub, &count));
+ assert_int_equal(count, 0);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+
+ /* Remove signature from the secret key/subkey */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(load_keys_gpg(ffi,
+ "data/test_key_validity/alice-sub-pub.pgp",
+ "data/test_key_validity/alice-sub-sec.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ /* make sure they are actually secret */
+ bool secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ assert_rnp_success(rnp_key_have_secret(sub, &secret));
+ assert_true(secret);
+ /* remove both signatures and reload */
+ assert_rnp_success(rnp_key_get_signature_at(sub, 0, &sig));
+ assert_rnp_success(rnp_signature_remove(sub, sig));
+ rnp_signature_handle_destroy(sig);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_remove(key, sig));
+ rnp_signature_handle_destroy(sig);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+ reload_keyrings(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(key, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ count = 255;
+ assert_rnp_success(rnp_uid_get_signature_count(uid, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(sub, &secret));
+ assert_true(secret);
+ count = 255;
+ assert_rnp_success(rnp_key_get_signature_count(sub, &count));
+ assert_int_equal(count, 0);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+static std::string
+key_info(rnp_key_handle_t key)
+{
+ bool sec = false;
+ rnp_key_have_secret(key, &sec);
+ bool primary = false;
+ rnp_key_is_primary(key, &primary);
+ std::string res = ":";
+ res += primary ? (sec ? "sec" : "pub") : (sec ? "ssb" : "sub");
+ char *keyid = NULL;
+ rnp_key_get_keyid(key, &keyid);
+ res += std::string("(") + std::string(keyid, 4) + std::string(")");
+ rnp_buffer_destroy(keyid);
+ return res;
+}
+
+static std::string
+sig_info(rnp_signature_handle_t sig)
+{
+ int type = sig->sig->sig.type();
+ char * keyid = NULL;
+ uint32_t sigid = sig->sig->sigid[0] + (sig->sig->sigid[1] << 8);
+ rnp_signature_get_keyid(sig, &keyid);
+ std::stringstream ss;
+ ss << ":sig(" << type << ", " << std::hex << sigid << ", " << std::string(keyid, 4) << ")";
+ rnp_buffer_destroy(keyid);
+ return ss.str();
+}
+
+static std::string
+uid_info(rnp_uid_handle_t uid)
+{
+ std::string res;
+ uint32_t type = 0;
+ rnp_uid_get_type(uid, &type);
+ if (type == RNP_USER_ATTR) {
+ res = ":uid(photo)";
+ } else {
+ char * uidstr = NULL;
+ size_t len = 0;
+ rnp_uid_get_data(uid, (void **) &uidstr, &len);
+ res = ":uid(" + std::string(uidstr, uidstr + len) + ")";
+ rnp_buffer_destroy(uidstr);
+ }
+
+ size_t sigs = 0;
+ rnp_uid_get_signature_count(uid, &sigs);
+ for (size_t i = 0; i < sigs; i++) {
+ rnp_signature_handle_t sig = NULL;
+ rnp_uid_get_signature_at(uid, i, &sig);
+ res += sig_info(sig);
+ rnp_signature_handle_destroy(sig);
+ }
+ return res;
+}
+
+static std::string
+key_packets(rnp_key_handle_t key)
+{
+ std::string res = key_info(key);
+ size_t sigs = 0;
+ rnp_key_get_signature_count(key, &sigs);
+ for (size_t i = 0; i < sigs; i++) {
+ rnp_signature_handle_t sig = NULL;
+ rnp_key_get_signature_at(key, i, &sig);
+ res += sig_info(sig);
+ rnp_signature_handle_destroy(sig);
+ }
+
+ bool primary = false;
+ rnp_key_is_primary(key, &primary);
+ if (!primary) {
+ return res;
+ }
+
+ size_t uids = 0;
+ rnp_key_get_uid_count(key, &uids);
+ for (size_t i = 0; i < uids; i++) {
+ rnp_uid_handle_t uid = NULL;
+ rnp_key_get_uid_handle_at(key, i, &uid);
+ res += uid_info(uid);
+ rnp_uid_handle_destroy(uid);
+ }
+
+ size_t subs = 0;
+ rnp_key_get_subkey_count(key, &subs);
+ for (size_t i = 0; i < subs; i++) {
+ rnp_key_handle_t sub = NULL;
+ rnp_key_get_subkey_at(key, i, &sub);
+ res += key_packets(sub);
+ rnp_key_handle_destroy(sub);
+ }
+ return res;
+}
+
+static void
+sigremove_leave(rnp_ffi_t ffi, void *app_ctx, rnp_signature_handle_t sig, uint32_t *action)
+{
+ assert_true((*(int *) app_ctx) == 48);
+ assert_non_null(sig);
+ assert_non_null(action);
+ assert_non_null(ffi);
+ *action = RNP_KEY_SIGNATURE_KEEP;
+}
+
+static void
+sigremove_unchanged(rnp_ffi_t ffi, void *app_ctx, rnp_signature_handle_t sig, uint32_t *action)
+{
+ assert_true((*(int *) app_ctx) == 48);
+ assert_non_null(sig);
+ assert_non_null(action);
+ assert_non_null(ffi);
+}
+
+static void
+sigremove_remove(rnp_ffi_t ffi, void *app_ctx, rnp_signature_handle_t sig, uint32_t *action)
+{
+ assert_true((*(int *) app_ctx) == 48);
+ *action = RNP_KEY_SIGNATURE_REMOVE;
+}
+
+static void
+sigremove_revocation(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_signature_handle_t sig,
+ uint32_t * action)
+{
+ assert_true((*(int *) app_ctx) == 48);
+ char *type = NULL;
+ assert_rnp_success(rnp_signature_get_type(sig, &type));
+ if (std::string(type).find("revocation") != std::string::npos) {
+ *action = RNP_KEY_SIGNATURE_REMOVE;
+ } else {
+ *action = RNP_KEY_SIGNATURE_KEEP;
+ }
+ rnp_buffer_destroy(type);
+}
+
+TEST_F(rnp_tests, test_ffi_remove_signatures)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* case 1: key Alice with self-signature and certification from the Basil. */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case1/pubring.gpg"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, de9d, 0B2B)");
+ /* rnp_key_remove_signatures corner cases */
+ assert_rnp_failure(rnp_key_remove_signatures(NULL, RNP_KEY_SIGNATURE_INVALID, NULL, NULL));
+ assert_rnp_failure(rnp_key_remove_signatures(NULL, 0, NULL, NULL));
+ assert_rnp_failure(rnp_key_remove_signatures(key, 0, NULL, ffi));
+ /* remove unknown signatures */
+ assert_rnp_success(
+ rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_UNKNOWN_KEY, NULL, NULL));
+ /* signature is deleted since we don't have Basil's key in the keyring */
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451)");
+ /* let's load key and try again */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case1/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/basil-pub.asc"));
+ assert_rnp_success(
+ rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_UNKNOWN_KEY, NULL, NULL));
+ /* now it is not removed */
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, de9d, 0B2B)");
+ /* let's delete non-self sigs */
+ assert_rnp_success(
+ rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_NON_SELF_SIG, NULL, NULL));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451)");
+ rnp_key_handle_destroy(key);
+ /* case 2: alice with corrupted self-signature */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case2/pubring.gpg"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, e530, 0451):sig(16, 2508, 0B2B)");
+ /* remove invalid signature */
+ assert_rnp_success(rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_INVALID, NULL, NULL));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(16, 2508, 0B2B)");
+ /* remove both invalid and non-self signatures */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case2/pubring.gpg"));
+ assert_rnp_success(rnp_key_remove_signatures(
+ key, RNP_KEY_SIGNATURE_INVALID | RNP_KEY_SIGNATURE_NON_SELF_SIG, NULL, NULL));
+ assert_string_equal(key_packets(key).c_str(), ":pub(0451):uid(Alice <alice@rnp>)");
+
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ /* load both keyrings and remove Basil's key */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case1/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case2/pubring.gpg"));
+
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0B2B09F7D7EA6E0E", &key));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0B2B):uid(Basil <basil@rnp>):sig(19, f083, 0B2B):sig(16, a7cd, 0451)");
+
+ assert_rnp_success(rnp_key_remove(key, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SUBKEYS));
+ rnp_key_handle_destroy(key);
+
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, de9d, "
+ "0B2B):sig(19, e530, 0451):sig(16, 2508, 0B2B)");
+ assert_rnp_success(rnp_key_remove_signatures(
+ key, RNP_KEY_SIGNATURE_INVALID | RNP_KEY_SIGNATURE_UNKNOWN_KEY, NULL, NULL));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451)");
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+
+ /* case 4: alice key with invalid subkey bindings (corrupted and non-self) */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case4/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case5/pubring.gpg"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, "
+ "0451):sub(DD23):sig(24, 89c0, 0451):sig(24, 1f6d, 0B2B)");
+ assert_rnp_success(rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_INVALID, NULL, NULL));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sub(DD23)");
+ /* make sure non-self doesn't touch invalid self sigs */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case4/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case5/pubring.gpg"));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, "
+ "0451):sub(DD23):sig(24, 89c0, 0451):sig(24, 1f6d, 0B2B)");
+ assert_rnp_success(
+ rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_NON_SELF_SIG, NULL, NULL));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sub(DD23):sig(24, 89c0, 0451)");
+ /* add subkey with valid subkey binding */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case4/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case5/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/pubring.gpg"));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sub(DD23):sig(24, 89c0, "
+ "0451):sig(24, 1f6d, 0B2B):sub(22F3):sig(24, 3766, 0451)");
+ assert_rnp_success(rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_INVALID, NULL, NULL));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, "
+ "0451):sub(DD23):sub(22F3):sig(24, 3766, 0451)");
+
+ /* load more keys and signatures and check callback usage */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case1/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case2/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case5/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case6/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case7/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case9/pubring.gpg"));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):sig(32, c76f, 0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, "
+ "de9d, 0B2B):sig(19, e530, 0451):sig(16, 2508, 0B2B):sig(19, b22f, 0451):sig(19, 6cd1, "
+ "0451):sub(DD23):sig(24, 1f6d, 0B2B):sig(24, ea55, 0451):sig(40, f001, "
+ "0451):sub(22F3):sig(24, 3766, 0451)");
+ int param = 48;
+ assert_rnp_success(rnp_key_remove_signatures(key,
+ RNP_KEY_SIGNATURE_INVALID |
+ RNP_KEY_SIGNATURE_UNKNOWN_KEY |
+ RNP_KEY_SIGNATURE_NON_SELF_SIG,
+ sigremove_leave,
+ &param));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):sig(32, c76f, 0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, "
+ "de9d, 0B2B):sig(19, e530, 0451):sig(16, 2508, 0B2B):sig(19, b22f, 0451):sig(19, 6cd1, "
+ "0451):sub(DD23):sig(24, 1f6d, 0B2B):sig(24, ea55, 0451):sig(40, f001, "
+ "0451):sub(22F3):sig(24, 3766, 0451)");
+
+ assert_rnp_success(
+ rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_INVALID, sigremove_unchanged, &param));
+ assert_string_equal(
+ key_packets(key).c_str(),
+ ":pub(0451):sig(32, c76f, 0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, "
+ "de9d, 0B2B):sig(16, 2508, 0B2B):sig(19, b22f, 0451):sig(19, 6cd1, "
+ "0451):sub(DD23):sig(24, ea55, 0451):sig(40, f001, 0451):sub(22F3):sig(24, 3766, 0451)");
+
+ assert_rnp_success(rnp_key_remove_signatures(
+ key, RNP_KEY_SIGNATURE_NON_SELF_SIG, sigremove_revocation, &param));
+ assert_string_equal(key_packets(key).c_str(),
+ ":pub(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, de9d, "
+ "0B2B):sig(16, 2508, 0B2B):sig(19, b22f, 0451):sig(19, 6cd1, "
+ "0451):sub(DD23):sig(24, ea55, 0451):sub(22F3):sig(24, 3766, 0451)");
+
+ /* make sure that signature will be removed from the secret key as well */
+ rnp_key_handle_destroy(key);
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sign-sub-sec.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_string_equal(key_packets(key).c_str(),
+ ":sec(0451):uid(Alice <alice@rnp>):sig(19, 8ba5, 0451):sig(16, de9d, "
+ "0B2B):sig(16, 2508, 0B2B):sig(19, b22f, 0451):sig(19, 6cd1, "
+ "0451):sub(DD23):sig(24, ea55, 0451):ssb(22F3):sig(24, 3766, 0451)");
+ assert_rnp_success(
+ rnp_key_remove_signatures(key, RNP_KEY_SIGNATURE_INVALID, sigremove_remove, &param));
+ assert_string_equal(key_packets(key).c_str(),
+ ":sec(0451):uid(Alice <alice@rnp>):sub(DD23):ssb(22F3)");
+ rnp_key_handle_destroy(key);
+
+ /* reload keyring, making sure changes are saved */
+ reload_keyrings(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_string_equal(key_packets(key).c_str(),
+ ":sec(0451):uid(Alice <alice@rnp>):sub(DD23):ssb(22F3)");
+ rnp_key_handle_destroy(key);
+ /* load data and delete signatures on subkey */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case6/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case7/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/pubring.gpg"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case9/pubring.gpg"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &key));
+ assert_string_equal(key_packets(key).c_str(),
+ ":sub(DD23):sig(24, ea55, 0451):sig(40, f001, 0451)");
+ assert_rnp_success(rnp_key_remove_signatures(key, 0, sigremove_revocation, &param));
+ assert_string_equal(key_packets(key).c_str(), ":sub(DD23):sig(24, ea55, 0451)");
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_rsa_small_sig)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/rsa_key_small_sig-pub.asc"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "ED23B0105947F283", &key));
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ bool valid = false;
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_key_critical_notations)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* Load key with 2 unknown critical notations in certification */
+ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/key-critical-notations.pgp"));
+ rnp_key_handle_t key = NULL;
+ /* key is valid since it has valid subkey binding, but userid is not valid */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "critical-key", &key));
+ assert_null(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "ddc610bb7b8f689c", &key));
+ assert_non_null(key);
+ assert_true(check_key_valid(key, true));
+ /* uid is not valid, as certification has unknown critical notation */
+ assert_true(check_uid_valid(key, 0, false));
+ assert_true(check_sub_valid(key, 0, true));
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* Load key with unknown critical notations in both certification and binding */
+ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/key-sub-crit-note-pub.pgp"));
+ /* key is not valid, as well as sub and uid */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "critical_notation", &key));
+ assert_null(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "9988c1bcb55391d6", &key));
+ assert_non_null(key);
+ assert_true(check_key_valid(key, false));
+ assert_true(check_uid_valid(key, 0, false));
+ assert_true(check_sub_valid(key, 0, false));
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+
+ /* Verify data signature with unknown critical notation */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed.crit-notation"));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ size_t sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ rnp_op_verify_signature_t sig = NULL;
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_int_equal(rnp_op_verify_signature_get_status(sig), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_import_invalid_issuer)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* public key + secret subkey with invalid signer's keyfp */
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-sub-sig-fp.pgp"));
+ char * keys = NULL;
+ uint32_t flags =
+ RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_SINGLE;
+ assert_rnp_success(rnp_import_keys(ffi, input, flags, &keys));
+ rnp_input_destroy(input);
+ rnp_buffer_destroy(keys);
+
+ /* public key + secret subkey with invalid signer's keyid */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-sub-sig-keyid.pgp"));
+ assert_rnp_success(rnp_import_keys(ffi, input, flags, &keys));
+ rnp_input_destroy(input);
+ rnp_buffer_destroy(keys);
+
+ rnp_ffi_destroy(ffi);
+}
diff --git a/src/tests/ffi-key.cpp b/src/tests/ffi-key.cpp
new file mode 100644
index 0000000..2933d68
--- /dev/null
+++ b/src/tests/ffi-key.cpp
@@ -0,0 +1,4442 @@
+/*
+ * 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 <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+#include "str-utils.h"
+#ifndef RNP_USE_STD_REGEX
+#include <regex.h>
+#else
+#include <regex>
+#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_sec_key_offline_operations)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* generate subkey for offline secret key */
+ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/alice-s2k-101-1-subs.pgp"));
+ rnp_key_handle_t primary = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &primary));
+ rnp_op_generate_t subop = NULL;
+ assert_rnp_failure(rnp_op_generate_subkey_create(&subop, ffi, primary, "ECDSA"));
+ /* unlock/unprotect offline secret key */
+ assert_rnp_failure(rnp_key_unlock(primary, "password"));
+ assert_rnp_failure(rnp_key_unprotect(primary, "password"));
+ /* add userid */
+ assert_int_equal(rnp_key_add_uid(primary, "new_uid", "SHA256", 2147317200, 0x00, false),
+ RNP_ERROR_NO_SUITABLE_KEY);
+ rnp_key_handle_destroy(primary);
+ /* generate subkey for offline secret key on card */
+ 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"));
+ primary = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &primary));
+ subop = NULL;
+ assert_rnp_failure(rnp_op_generate_subkey_create(&subop, ffi, primary, "ECDSA"));
+ rnp_key_handle_destroy(primary);
+
+ 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);
+ 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);
+ /* key bits */
+ assert_rnp_success(rnp_key_get_bits(subkey, &bits));
+ assert_int_equal(bits, 255);
+ /* curve */
+ curve = NULL;
+ assert_rnp_success(rnp_key_get_curve(subkey, &curve));
+ assert_int_equal(strcasecmp(curve, "Curve25519"), 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_sm2)
+{
+ 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 sm2 key */
+ rnp_key_handle_t key = NULL;
+ if (!sm2_enabled()) {
+ assert_rnp_failure(rnp_generate_key_sm2(ffi, "sm2", NULL, &key));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+ return;
+ }
+ assert_rnp_success(rnp_generate_key_sm2(ffi, "sm2", 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, "SM2"), 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);
+ /* curve */
+ char *curve = NULL;
+ assert_rnp_success(rnp_key_get_curve(key, &curve));
+ assert_int_equal(strcasecmp(curve, "SM2 P-256"), 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, "sm2");
+ 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, "SM2"), 0);
+ rnp_buffer_destroy(alg);
+ /* key 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, "SM2 P-256"), 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_ex)
+{
+ 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));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "123"));
+
+ /* Generate RSA key with misc options set */
+ rnp_op_generate_t keygen = NULL;
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_failure(rnp_op_generate_set_dsa_qbits(keygen, 256));
+ /* key usage */
+ assert_rnp_success(rnp_op_generate_clear_usage(keygen));
+ assert_rnp_failure(rnp_op_generate_add_usage(keygen, "usage"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "sign"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "encrypt"));
+ /* preferred ciphers */
+ assert_rnp_success(rnp_op_generate_clear_pref_ciphers(keygen));
+ assert_rnp_failure(rnp_op_generate_add_pref_cipher(keygen, "unknown"));
+ assert_true(!rnp_op_generate_add_pref_cipher(keygen, "BLOWFISH") == blowfish_enabled());
+ assert_rnp_success(rnp_op_generate_clear_pref_ciphers(keygen));
+ assert_rnp_success(rnp_op_generate_add_pref_cipher(keygen, "CAMELLIA256"));
+ assert_rnp_success(rnp_op_generate_add_pref_cipher(keygen, "AES256"));
+ /* preferred compression algorithms */
+ assert_rnp_success(rnp_op_generate_clear_pref_compression(keygen));
+ assert_rnp_failure(rnp_op_generate_add_pref_compression(keygen, "unknown"));
+ assert_rnp_success(rnp_op_generate_add_pref_compression(keygen, "zlib"));
+ assert_rnp_success(rnp_op_generate_clear_pref_compression(keygen));
+ assert_rnp_success(rnp_op_generate_add_pref_compression(keygen, "zip"));
+ assert_rnp_success(rnp_op_generate_add_pref_compression(keygen, "zlib"));
+ /* preferred hash algorithms */
+ assert_rnp_success(rnp_op_generate_clear_pref_hashes(keygen));
+ assert_rnp_failure(rnp_op_generate_add_pref_hash(keygen, "unknown"));
+ assert_rnp_success(rnp_op_generate_add_pref_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_clear_pref_hashes(keygen));
+ assert_rnp_success(rnp_op_generate_add_pref_hash(keygen, "SHA512"));
+ assert_rnp_success(rnp_op_generate_add_pref_hash(keygen, "SHA256"));
+ /* key expiration */
+ assert_rnp_success(rnp_op_generate_set_expiration(keygen, 60 * 60 * 24 * 100));
+ assert_rnp_success(rnp_op_generate_set_expiration(keygen, 60 * 60 * 24 * 300));
+ /* preferred key server */
+ assert_rnp_success(rnp_op_generate_set_pref_keyserver(keygen, NULL));
+ assert_rnp_success(rnp_op_generate_set_pref_keyserver(keygen, "hkp://first.server/"));
+ assert_rnp_success(rnp_op_generate_set_pref_keyserver(keygen, "hkp://second.server/"));
+ /* user id */
+ assert_rnp_failure(rnp_op_generate_set_userid(keygen, NULL));
+ assert_rnp_success(rnp_op_generate_set_userid(keygen, "userid_cleared"));
+ assert_rnp_success(rnp_op_generate_set_userid(keygen, "userid"));
+ /* protection */
+ assert_rnp_failure(rnp_op_generate_set_protection_cipher(keygen, NULL));
+ assert_rnp_failure(rnp_op_generate_set_protection_cipher(keygen, "unknown"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES256"));
+ assert_rnp_failure(rnp_op_generate_set_protection_hash(keygen, NULL));
+ assert_rnp_failure(rnp_op_generate_set_protection_hash(keygen, "unknown"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA256"));
+ assert_rnp_success(rnp_op_generate_set_protection_iterations(keygen, 65536));
+ assert_rnp_failure(rnp_op_generate_set_protection_mode(keygen, NULL));
+ assert_rnp_failure(rnp_op_generate_set_protection_mode(keygen, "unknown"));
+ assert_rnp_success(rnp_op_generate_set_protection_mode(keygen, "cfb"));
+ /* now execute keygen operation */
+ assert_rnp_success(rnp_op_generate_set_request_password(keygen, true));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check key usage */
+ 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_false(flag);
+ assert_rnp_success(rnp_key_allows_usage(key, "encrypt", &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_allows_usage(key, "authenticate", &flag));
+ assert_false(flag);
+ /* check key creation and expiration */
+ uint32_t create = 0;
+ assert_rnp_success(rnp_key_get_creation(key, &create));
+ assert_true((create != 0) && (create <= time(NULL)));
+ uint32_t expiry = 0;
+ assert_rnp_success(rnp_key_get_expiration(key, &expiry));
+ assert_true(expiry == 60 * 60 * 24 * 300);
+ uint32_t till = 0;
+ assert_rnp_success(rnp_key_valid_till(key, &till));
+ assert_int_equal(till, create + expiry);
+ /* check whether key is encrypted */
+ assert_rnp_success(rnp_key_is_protected(key, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_is_locked(key, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_unlock(key, "123"));
+ assert_rnp_success(rnp_key_is_locked(key, &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_lock(key));
+
+ /* generate DSA subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "DSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1536));
+ assert_rnp_success(rnp_op_generate_set_dsa_qbits(keygen, 224));
+ /* key flags */
+ assert_rnp_failure(rnp_op_generate_add_usage(keygen, "encrypt"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "certify"));
+ /* these should not work for subkey */
+ assert_rnp_failure(rnp_op_generate_clear_pref_ciphers(keygen));
+ assert_rnp_failure(rnp_op_generate_add_pref_cipher(keygen, "AES256"));
+ assert_rnp_failure(rnp_op_generate_clear_pref_compression(keygen));
+ assert_rnp_failure(rnp_op_generate_add_pref_compression(keygen, "zlib"));
+ assert_rnp_failure(rnp_op_generate_clear_pref_hashes(keygen));
+ assert_rnp_failure(rnp_op_generate_add_pref_hash(keygen, "unknown"));
+ assert_rnp_failure(rnp_op_generate_set_pref_keyserver(keygen, "hkp://first.server/"));
+ assert_rnp_failure(rnp_op_generate_set_userid(keygen, "userid"));
+ /* key expiration */
+ assert_rnp_success(rnp_op_generate_set_expiration(keygen, 60 * 60 * 24 * 300));
+ /* key protection */
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES256"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA256"));
+ assert_rnp_success(rnp_op_generate_set_protection_iterations(keygen, 65536));
+ assert_rnp_success(rnp_op_generate_set_request_password(keygen, true));
+ /* now generate the subkey */
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t subkey = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check subkey usage */
+ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_allows_usage(subkey, "certify", &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_allows_usage(subkey, "encrypt", &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_allows_usage(subkey, "authenticate", &flag));
+ assert_false(flag);
+ /* check subkey creation and expiration */
+ create = 0;
+ assert_rnp_success(rnp_key_get_creation(subkey, &create));
+ assert_true((create != 0) && (create <= time(NULL)));
+ expiry = 0;
+ assert_rnp_success(rnp_key_get_expiration(subkey, &expiry));
+ assert_true(expiry == 60 * 60 * 24 * 300);
+ /* check whether subkey is encrypted */
+ assert_rnp_success(rnp_key_is_protected(subkey, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_is_locked(subkey, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_unlock(subkey, "123"));
+ assert_rnp_success(rnp_key_is_locked(subkey, &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_lock(subkey));
+ /* destroy key handle */
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* generate RSA sign/encrypt subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "sign"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "encrypt"));
+ assert_rnp_success(rnp_op_generate_set_expiration(keygen, 0));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ /* set bits for iterations instead of exact iterations number */
+ assert_rnp_success(rnp_op_generate_set_protection_iterations(keygen, 12));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check subkey usage */
+ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag));
+ assert_true(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);
+ /* check whether subkey is encrypted - it should not */
+ assert_rnp_success(rnp_key_is_protected(subkey, &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* generate ElGamal subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ELGAMAL"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_failure(rnp_op_generate_add_usage(keygen, "sign"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "encrypt"));
+ assert_rnp_success(rnp_op_generate_set_expiration(keygen, 0));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check subkey usage */
+ 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));
+
+ /* generate ECDSA subkeys for each curve */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_failure(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_failure(rnp_op_generate_set_dsa_qbits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "sign"));
+ assert_rnp_failure(rnp_op_generate_add_usage(keygen, "encrypt"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-256"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check subkey usage */
+ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag));
+ assert_true(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_false(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_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-384"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-521"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ if (brainpool_enabled()) {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "brainpoolP256r1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ } else {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "brainpoolP256r1"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ }
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ if (brainpool_enabled()) {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "brainpoolP384r1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ } else {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "brainpoolP384r1"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ }
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ if (brainpool_enabled()) {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "brainpoolP512r1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ } else {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "brainpoolP512r1"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ }
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "secp256k1"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* These curves will not work with ECDSA*/
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "Ed25519"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "Curve25519"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDSA"));
+ if (!sm2_enabled()) {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "SM2 P-256"));
+ } else {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "SM2 P-256"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ }
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+
+ /* Add EDDSA subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "EDDSA"));
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "secp256k1"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "sign"));
+ assert_rnp_failure(rnp_op_generate_add_usage(keygen, "encrypt"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check subkey usage */
+ assert_rnp_success(rnp_key_allows_usage(subkey, "sign", &flag));
+ assert_true(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_false(flag);
+ assert_rnp_success(rnp_key_allows_usage(subkey, "authenticate", &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* Add ECDH subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDH"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-256"));
+ assert_rnp_failure(rnp_op_generate_add_usage(keygen, "sign"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "encrypt"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check subkey usage */
+ 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));
+
+ /* Add ECDH x25519 subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ECDH"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "Curve25519"));
+ assert_rnp_failure(rnp_op_generate_add_usage(keygen, "sign"));
+ assert_rnp_success(rnp_op_generate_add_usage(keygen, "encrypt"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* now check subkey usage */
+ 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));
+
+ /* Add SM2 subkey */
+ if (!sm2_enabled()) {
+ keygen = NULL;
+ assert_rnp_failure(rnp_op_generate_subkey_create(&keygen, ffi, key, "SM2"));
+ } else {
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "SM2"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "AES128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "SHA1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ }
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_key_generate_expiry_32bit)
+{
+ 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));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "123"));
+
+ /* Generate RSA key with creation + expiration > 32 bit */
+ rnp_op_generate_t keygen = NULL;
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ /* key expiration */
+ assert_rnp_success(rnp_op_generate_set_expiration(keygen, UINT32_MAX));
+ /* now execute keygen operation */
+ assert_rnp_success(rnp_op_generate_set_request_password(keygen, true));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* check key creation and expiration */
+ uint32_t create = 0;
+ assert_rnp_success(rnp_key_get_creation(key, &create));
+ assert_true((create != 0) && (create <= time(NULL)));
+ uint32_t expiry = 0;
+ assert_rnp_success(rnp_key_get_expiration(key, &expiry));
+ assert_true(expiry == UINT32_MAX);
+ uint32_t till = 0;
+ assert_rnp_success(rnp_key_valid_till(key, &till));
+ assert_int_equal(till, UINT32_MAX - 1);
+ uint64_t till64 = 0;
+ assert_rnp_success(rnp_key_valid_till64(key, &till64));
+ assert_int_equal(till64, (uint64_t) create + expiry);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* Load key with creation + expiration == UINT32_MAX */
+ assert_true(import_pub_keys(ffi, "data/test_key_edge_cases/key-create-expiry-32bit.asc"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "60eac9ddf0d9ac9f", &key));
+ /* check key creation and expiration */
+ create = 0;
+ assert_rnp_success(rnp_key_get_creation(key, &create));
+ assert_int_equal(create, 1619611313);
+ expiry = 0;
+ assert_rnp_success(rnp_key_get_expiration(key, &expiry));
+ assert_true(expiry == UINT32_MAX - create);
+ till = 0;
+ assert_rnp_success(rnp_key_valid_till(key, &till));
+ assert_int_equal(till, UINT32_MAX - 1);
+ till64 = 0;
+ assert_rnp_success(rnp_key_valid_till64(key, &till64));
+ assert_int_equal(till64, UINT32_MAX);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_key_generate_algnamecase)
+{
+ 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));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "123"));
+
+ /* Generate RSA key with misc options set */
+ rnp_op_generate_t keygen = NULL;
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "rsa"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+
+ /* generate DSA subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "dsa"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1536));
+ /* now generate the subkey */
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t subkey = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* destroy key handle */
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* generate ElGamal subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "elgamal"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* generate ECDSA subkeys for each curve */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_failure(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-256"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-384"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-521"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ if (brainpool_enabled()) {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "brainpoolP256r1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ } else {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "brainpoolP256r1"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ }
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ if (brainpool_enabled()) {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "brainpoolP384r1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ } else {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "brainpoolP384r1"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ }
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ if (brainpool_enabled()) {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "brainpoolP512r1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ } else {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "brainpoolP512r1"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ }
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "secp256k1"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* These curves will not work with ECDSA */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "Ed25519"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "Curve25519"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdsa"));
+ if (!sm2_enabled()) {
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "SM2 P-256"));
+ } else {
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "SM2 P-256"));
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ }
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+
+ /* Add EDDSA subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "eddsa"));
+ assert_rnp_failure(rnp_op_generate_set_curve(keygen, "secp256k1"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* Add ECDH subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdh"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "NIST P-256"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* Add ECDH x25519 subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "ecdh"));
+ assert_rnp_success(rnp_op_generate_set_curve(keygen, "Curve25519"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+
+ /* Add SM2 subkey */
+ if (!sm2_enabled()) {
+ keygen = NULL;
+ assert_rnp_failure(rnp_op_generate_subkey_create(&keygen, ffi, key, "sm2"));
+ } else {
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "sm2"));
+ assert_rnp_success(rnp_op_generate_set_protection_cipher(keygen, "aes128"));
+ assert_rnp_success(rnp_op_generate_set_protection_hash(keygen, "sha1"));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ }
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_key_generate_protection)
+{
+ 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));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "123"));
+
+ /* Generate key and subkey without protection */
+ rnp_op_generate_t keygen = NULL;
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* check whether key is encrypted */
+ bool flag = true;
+ assert_rnp_success(rnp_key_is_protected(key, &flag));
+ assert_false(flag);
+ /* generate subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t subkey = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_is_protected(subkey, &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* Generate RSA key with password */
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_set_protection_password(keygen, "password"));
+ /* Line below should not change password from 'password' to '123' */
+ assert_rnp_success(rnp_op_generate_set_request_password(keygen, true));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* check whether key is encrypted */
+ assert_rnp_success(rnp_key_is_protected(key, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_is_locked(key, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_unlock(key, "password"));
+ assert_rnp_success(rnp_key_is_locked(key, &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_lock(key));
+ /* generate subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_set_protection_password(keygen, "password"));
+ /* this should fail since primary key is locked */
+ assert_rnp_failure(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_key_unlock(key, "password"));
+ /* now it should work */
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ subkey = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_is_protected(subkey, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_is_locked(subkey, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_unlock(subkey, "password"));
+ assert_rnp_success(rnp_key_is_locked(subkey, &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* Generate RSA key via password request */
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_set_request_password(keygen, true));
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ /* check whether key is encrypted */
+ assert_rnp_success(rnp_key_is_protected(key, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_is_locked(key, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_unlock(key, "123"));
+ assert_rnp_success(rnp_key_is_locked(key, &flag));
+ assert_false(flag);
+ assert_rnp_success(rnp_key_lock(key));
+ /* generate subkey */
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ assert_rnp_success(rnp_op_generate_set_request_password(keygen, true));
+ /* this should succeed since password for primary key is returned via provider */
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ subkey = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &subkey));
+ assert_non_null(subkey);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ assert_rnp_success(rnp_key_is_protected(subkey, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_is_locked(subkey, &flag));
+ assert_true(flag);
+ assert_rnp_success(rnp_key_unlock(subkey, "123"));
+ assert_rnp_success(rnp_key_is_locked(subkey, &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_keygen_json_sub_pass_required)
+{
+ 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));
+ assert_non_null(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;
+
+ // protect+lock the primary key
+ assert_rnp_success(rnp_key_protect(primary, "pass123", NULL, NULL, NULL, 0));
+ assert_rnp_success(rnp_key_lock(primary));
+ rnp_key_handle_destroy(primary);
+ primary = 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 (no ffi_string_password_provider, should fail)
+ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, NULL));
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, NULL, NULL));
+ assert_rnp_failure(rnp_generate_key_json(ffi, json.c_str(), &results));
+
+ // generate the subkey (wrong pass, should fail)
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrong"));
+ assert_rnp_failure(rnp_generate_key_json(ffi, json.c_str(), &results));
+
+ // generate the subkey
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass123"));
+ 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_int_equal(RNP_SUCCESS, rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(2, count);
+ assert_int_equal(RNP_SUCCESS, rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(2, count);
+
+ // check some key properties
+ check_key_properties(sub, false, true, true);
+
+ // cleanup
+ rnp_key_handle_destroy(primary);
+ rnp_key_handle_destroy(sub);
+ rnp_ffi_destroy(ffi);
+}
+
+/** get the value of a (potentially nested) field in a json object
+ *
+ * Note that this does not support JSON arrays, only objects.
+ *
+ * @param jso the json object to search within. This should be an object, not a string,
+ * array, etc.
+ * @param field the field to retrieve. The format is "first.second.third".
+ * @return a pointer to the located json object, or NULL
+ **/
+static json_object *
+get_json_obj(json_object *jso, const char *field)
+{
+ const char *start = field;
+ const char *end;
+ char buf[32];
+
+ do {
+ end = strchr(start, '.');
+
+ size_t len = end ? (end - start) : strlen(start);
+ if (len >= sizeof(buf)) {
+ return NULL;
+ }
+ memcpy(buf, start, len);
+ buf[len] = '\0';
+
+ if (!json_object_object_get_ex(jso, buf, &jso)) {
+ return NULL;
+ }
+
+ start = end + 1;
+ } while (end);
+ return jso;
+}
+
+/* This test loads a keyring and converts the keys to JSON,
+ * then validates some properties.
+ *
+ * We could just do a simple strcmp, but that would depend
+ * on json-c sorting the keys consistently, across versions,
+ * etc.
+ */
+TEST_F(rnp_tests, test_ffi_key_to_json)
+{
+ rnp_ffi_t ffi = NULL;
+ char * pub_format = NULL;
+ char * pub_path = NULL;
+ char * sec_format = NULL;
+ char * sec_path = NULL;
+ rnp_key_handle_t key = NULL;
+ char * json = NULL;
+ json_object * jso = NULL;
+
+ // detect the formats+paths
+ assert_rnp_success(rnp_detect_homedir_info(
+ "data/keyrings/5", &pub_format, &pub_path, &sec_format, &sec_path));
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, pub_format, sec_format));
+ // load our keyrings
+ assert_true(load_keys_gpg(ffi, pub_path, sec_path));
+ // free formats+paths
+ rnp_buffer_destroy(pub_format);
+ pub_format = NULL;
+ rnp_buffer_destroy(pub_path);
+ pub_path = NULL;
+ rnp_buffer_destroy(sec_format);
+ sec_format = NULL;
+ rnp_buffer_destroy(sec_path);
+ sec_path = NULL;
+
+ // locate key (primary)
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0E33FD46FF10F19C", &key));
+ assert_non_null(key);
+ // convert to JSON
+ json = NULL;
+ assert_rnp_success(rnp_key_to_json(key, 0xff, &json));
+ assert_non_null(json);
+ // parse it back in
+ jso = json_tokener_parse(json);
+ assert_non_null(jso);
+ // validate some properties
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "type")), "ECDSA"));
+ assert_int_equal(json_object_get_int(get_json_obj(jso, "length")), 256);
+ assert_true(
+ rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "curve")), "NIST P-256"));
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "keyid")),
+ "0E33FD46FF10F19C"));
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "fingerprint")),
+ "B6B5E497A177551ECB8862200E33FD46FF10F19C"));
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "grip")),
+ "20A48B3C61525DCDF8B3B9D82C6BBCF4D8BFB5E5"));
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "revoked")), false);
+ assert_int_equal(json_object_get_int64(get_json_obj(jso, "creation time")), 1511313500);
+ assert_int_equal(json_object_get_int64(get_json_obj(jso, "expiration")), 0);
+ // usage
+ assert_int_equal(json_object_array_length(get_json_obj(jso, "usage")), 2);
+ assert_true(rnp::str_case_eq(
+ json_object_get_string(json_object_array_get_idx(get_json_obj(jso, "usage"), 0)),
+ "sign"));
+ assert_true(rnp::str_case_eq(
+ json_object_get_string(json_object_array_get_idx(get_json_obj(jso, "usage"), 1)),
+ "certify"));
+ // primary key grip
+ assert_null(get_json_obj(jso, "primary key grip"));
+ // subkey grips
+ assert_int_equal(json_object_array_length(get_json_obj(jso, "subkey grips")), 1);
+ assert_true(rnp::str_case_eq(
+ json_object_get_string(json_object_array_get_idx(get_json_obj(jso, "subkey grips"), 0)),
+ "FFFA72FC225214DC712D0127172EE13E88AF93B4"));
+ // public key
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "public key.present")), true);
+ assert_true(
+ rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "public key.mpis.point")),
+ "04B0C6F2F585C1EEDF805C4492CB683839D5EAE6246420780F063D558"
+ "A33F607876BE6F818A665722F8204653CC4DCFAD4F4765521AC8A6E9F"
+ "793CEBAE8600BEEF"));
+ // secret key
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "secret key.present")), true);
+ assert_true(
+ rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "secret key.mpis.x")),
+ "46DE93CA439735F36B9CF228F10D8586DA824D88BBF4E24566D5312D061802C8"));
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "secret key.locked")), false);
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "secret key.protected")),
+ false);
+ // userids
+ assert_int_equal(json_object_array_length(get_json_obj(jso, "userids")), 1);
+ assert_true(rnp::str_case_eq(
+ json_object_get_string(json_object_array_get_idx(get_json_obj(jso, "userids"), 0)),
+ "test0"));
+ // signatures
+ assert_int_equal(json_object_array_length(get_json_obj(jso, "signatures")), 1);
+ json_object *jsosig = json_object_array_get_idx(get_json_obj(jso, "signatures"), 0);
+ assert_int_equal(json_object_get_int(get_json_obj(jsosig, "userid")), 0);
+ // TODO: other properties of signature
+ // cleanup
+ json_object_put(jso);
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ rnp_buffer_destroy(json);
+ json = NULL;
+
+ // locate key (sub)
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "074131BC8D16C5C9", &key));
+ assert_non_null(key);
+ // convert to JSON
+ assert_rnp_success(rnp_key_to_json(key, 0xff, &json));
+ assert_non_null(json);
+ // parse it back in
+ jso = json_tokener_parse(json);
+ assert_non_null(jso);
+ // validate some properties
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "type")), "ECDH"));
+ assert_int_equal(json_object_get_int(get_json_obj(jso, "length")), 256);
+ assert_true(
+ rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "curve")), "NIST P-256"));
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "keyid")),
+ "074131BC8D16C5C9"));
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "fingerprint")),
+ "481E6A41B10ECD71A477DB02074131BC8D16C5C9"));
+ // ECDH-specific
+ assert_true(
+ rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "kdf hash")), "SHA256"));
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "key wrap cipher")),
+ "AES128"));
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "grip")),
+ "FFFA72FC225214DC712D0127172EE13E88AF93B4"));
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "revoked")), false);
+ assert_int_equal(json_object_get_int64(get_json_obj(jso, "creation time")), 1511313500);
+ assert_int_equal(json_object_get_int64(get_json_obj(jso, "expiration")), 0);
+ // usage
+ assert_int_equal(json_object_array_length(get_json_obj(jso, "usage")), 1);
+ assert_true(rnp::str_case_eq(
+ json_object_get_string(json_object_array_get_idx(get_json_obj(jso, "usage"), 0)),
+ "encrypt"));
+ // primary key grip
+ assert_true(rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "primary key grip")),
+ "20A48B3C61525DCDF8B3B9D82C6BBCF4D8BFB5E5"));
+ // subkey grips
+ assert_null(get_json_obj(jso, "subkey grips"));
+ // public key
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "public key.present")), true);
+ assert_true(rnp::str_case_eq(
+ json_object_get_string(get_json_obj(jso, "public key.mpis.point")),
+ "04E2746BA4D180011B17A6909EABDBF2F3733674FBE00B20A3B857C2597233651544150B"
+ "896BCE7DCDF47C49FC1E12D5AD86384D26336A48A18845940A3F65F502"));
+ // secret key
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "secret key.present")), true);
+ assert_true(
+ rnp::str_case_eq(json_object_get_string(get_json_obj(jso, "secret key.mpis.x")),
+ "DF8BEB7272117AD7AFE2B7E882453113059787FBC785C82F78624EE7EF2117FB"));
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "secret key.locked")), false);
+ assert_int_equal(json_object_get_boolean(get_json_obj(jso, "secret key.protected")),
+ false);
+ // userids
+ assert_null(get_json_obj(jso, "userids"));
+ // signatures
+ assert_int_equal(json_object_array_length(get_json_obj(jso, "signatures")), 1);
+ jsosig = json_object_array_get_idx(get_json_obj(jso, "signatures"), 0);
+ assert_null(get_json_obj(jsosig, "userid"));
+ // TODO: other properties of signature
+ // cleanup
+ json_object_put(jso);
+ rnp_key_handle_destroy(key);
+ rnp_buffer_destroy(json);
+
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_iter)
+{
+ rnp_ffi_t ffi = NULL;
+ char * pub_format = NULL;
+ char * pub_path = NULL;
+ char * sec_format = NULL;
+ char * sec_path = NULL;
+
+ // detect the formats+paths
+ assert_rnp_success(rnp_detect_homedir_info(
+ "data/keyrings/1", &pub_format, &pub_path, &sec_format, &sec_path));
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, pub_format, sec_format));
+
+ // test invalid identifier type
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_failure(rnp_identifier_iterator_create(ffi, &it, "keyidz"));
+ assert_null(it);
+ }
+
+ // test empty rings
+ // keyid
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "keyid"));
+ assert_non_null(it);
+ const char *ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ assert_null(ident);
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+ // grip
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "grip"));
+ assert_non_null(it);
+ const char *ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ assert_null(ident);
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+ // userid
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "userid"));
+ assert_non_null(it);
+ const char *ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ assert_null(ident);
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+ // fingerprint
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
+ assert_non_null(it);
+ const char *ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ assert_null(ident);
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+
+ // load our keyrings
+ assert_true(load_keys_gpg(ffi, pub_path, sec_path));
+ // free formats+paths
+ rnp_buffer_destroy(pub_format);
+ pub_format = NULL;
+ rnp_buffer_destroy(pub_path);
+ pub_path = NULL;
+ rnp_buffer_destroy(sec_format);
+ sec_format = NULL;
+ rnp_buffer_destroy(sec_path);
+ sec_path = NULL;
+
+ // keyid
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "keyid"));
+ assert_non_null(it);
+ {
+ static const char *expected[] = {"7BC6709B15C23A4A",
+ "1ED63EE56FADC34D",
+ "1D7E8A5393C997A8",
+ "8A05B89FAD5ADED1",
+ "2FCADF05FFA501BB",
+ "54505A936A4A970E",
+ "326EF111425D14A5"};
+ size_t i = 0;
+ const char * ident = NULL;
+ do {
+ ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ if (ident) {
+ assert_true(rnp::str_case_eq(expected[i], ident));
+ i++;
+ }
+ } while (ident);
+ assert_int_equal(i, ARRAY_SIZE(expected));
+ }
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+
+ // grip
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "grip"));
+ assert_non_null(it);
+ {
+ static const char *expected[] = {"66D6A0800A3FACDE0C0EB60B16B3669ED380FDFA",
+ "D9839D61EDAF0B3974E0A4A341D6E95F3479B9B7",
+ "B1CC352FEF9A6BD4E885B5351840EF9306D635F0",
+ "E7C8860B70DC727BED6DB64C633683B41221BB40",
+ "B2A7F6C34AA2C15484783E9380671869A977A187",
+ "43C01D6D96BE98C3C87FE0F175870ED92DE7BE45",
+ "8082FE753013923972632550838A5F13D81F43B9"};
+ size_t i = 0;
+ const char * ident = NULL;
+ do {
+ ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ if (ident) {
+ assert_true(rnp::str_case_eq(expected[i], ident));
+ i++;
+ }
+ } while (ident);
+ assert_int_equal(i, ARRAY_SIZE(expected));
+ }
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+
+ // userid
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "userid"));
+ assert_non_null(it);
+ {
+ static const char *expected[] = {
+ "key0-uid0", "key0-uid1", "key0-uid2", "key1-uid0", "key1-uid2", "key1-uid1"};
+ size_t i = 0;
+ const char *ident = NULL;
+ do {
+ ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ if (ident) {
+ assert_true(rnp::str_case_eq(expected[i], ident));
+ i++;
+ }
+ } while (ident);
+ assert_int_equal(i, ARRAY_SIZE(expected));
+ }
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+
+ // fingerprint
+ {
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
+ assert_non_null(it);
+ {
+ static const char *expected[] = {"E95A3CBF583AA80A2CCC53AA7BC6709B15C23A4A",
+ "E332B27CAF4742A11BAA677F1ED63EE56FADC34D",
+ "C5B15209940A7816A7AF3FB51D7E8A5393C997A8",
+ "5CD46D2A0BD0B8CFE0B130AE8A05B89FAD5ADED1",
+ "BE1C4AB951F4C2F6B604C7F82FCADF05FFA501BB",
+ "A3E94DE61A8CB229413D348E54505A936A4A970E",
+ "57F8ED6E5C197DB63C60FFAF326EF111425D14A5"};
+ size_t i = 0;
+ const char * ident = NULL;
+ do {
+ ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ if (ident) {
+ assert_true(rnp::str_case_eq(expected[i], ident));
+ i++;
+ }
+ } while (ident);
+ assert_int_equal(i, ARRAY_SIZE(expected));
+ }
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ }
+
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+void
+check_loaded_keys(const char * format,
+ bool armored,
+ uint8_t * buf,
+ size_t buf_len,
+ const char * id_type,
+ const std::vector<std::string> &expected_ids,
+ bool secret)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_identifier_iterator_t it = NULL;
+ const char * identifier = NULL;
+
+ if (armored) {
+ assert_memory_equal("-----", buf, 5);
+ } else {
+ assert_memory_not_equal("-----", buf, 5);
+ }
+
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, format, format));
+
+ // load our keyrings
+ assert_rnp_success(rnp_input_from_memory(&input, buf, buf_len, true));
+ assert_rnp_success(rnp_load_keys(
+ ffi, format, input, secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS));
+ rnp_input_destroy(input);
+ input = NULL;
+
+ std::vector<std::string> ids;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, id_type));
+ do {
+ identifier = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &identifier));
+ if (identifier) {
+ rnp_key_handle_t key = NULL;
+ bool expected_secret = secret;
+ bool expected_public = !secret;
+ bool result;
+ assert_rnp_success(rnp_locate_key(ffi, id_type, identifier, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_have_secret(key, &result));
+ assert_int_equal(result, expected_secret);
+ assert_rnp_success(rnp_key_have_public(key, &result));
+ assert_int_equal(result, expected_public);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ ids.push_back(identifier);
+ }
+ } while (identifier);
+ assert_true(ids == expected_ids);
+ rnp_identifier_iterator_destroy(it);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_export)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_output_t output = NULL;
+ rnp_key_handle_t key = NULL;
+ uint8_t * buf = NULL;
+ size_t buf_len = 0;
+
+ // setup FFI
+ test_ffi_init(&ffi);
+
+ // primary pub only
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+
+ // export
+ assert_rnp_success(rnp_key_export(key, output, RNP_KEY_EXPORT_PUBLIC));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+
+ // check results
+ check_loaded_keys("GPG", false, buf, buf_len, "keyid", {"2FCADF05FFA501BB"}, false);
+
+ // cleanup
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // primary sec only (armored)
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+
+ // export
+ assert_rnp_success(
+ rnp_key_export(key, output, RNP_KEY_EXPORT_SECRET | RNP_KEY_EXPORT_ARMORED));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+
+ // check results
+ check_loaded_keys("GPG", true, buf, buf_len, "keyid", {"2FCADF05FFA501BB"}, true);
+
+ // cleanup
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // primary pub and subs
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+
+ // export
+ assert_rnp_success(
+ rnp_key_export(key, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+
+ // check results
+ check_loaded_keys("GPG",
+ false,
+ buf,
+ buf_len,
+ "keyid",
+ {"2FCADF05FFA501BB", "54505A936A4A970E", "326EF111425D14A5"},
+ false);
+
+ // cleanup
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // primary sec and subs (armored)
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+
+ // export
+ assert_rnp_success(rnp_key_export(key,
+ output,
+ RNP_KEY_EXPORT_SECRET | RNP_KEY_EXPORT_SUBKEYS |
+ RNP_KEY_EXPORT_ARMORED));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+
+ // check results
+ check_loaded_keys("GPG",
+ true,
+ buf,
+ buf_len,
+ "keyid",
+ {"2FCADF05FFA501BB", "54505A936A4A970E", "326EF111425D14A5"},
+ true);
+
+ // cleanup
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // sub pub
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "54505A936A4A970E", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+
+ // export
+ assert_rnp_success(
+ rnp_key_export(key, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_ARMORED));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+
+ // check results
+ check_loaded_keys(
+ "GPG", true, buf, buf_len, "keyid", {"2FCADF05FFA501BB", "54505A936A4A970E"}, false);
+
+ // cleanup
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // sub sec
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "54505A936A4A970E", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+
+ // export
+ assert_rnp_success(
+ rnp_key_export(key, output, RNP_KEY_EXPORT_SECRET | RNP_KEY_EXPORT_ARMORED));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+
+ // check results
+ check_loaded_keys(
+ "GPG", true, buf, buf_len, "keyid", {"2FCADF05FFA501BB", "54505A936A4A970E"}, true);
+
+ // cleanup
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+static bool
+check_import_keys_ex(rnp_ffi_t ffi,
+ json_object **jso,
+ uint32_t flags,
+ rnp_input_t input,
+ size_t rescount,
+ size_t pubcount,
+ size_t seccount)
+{
+ bool res = false;
+ char * keys = NULL;
+ size_t keycount = 0;
+ json_object *keyarr = NULL;
+ *jso = NULL;
+
+ if (rnp_import_keys(ffi, input, flags, &keys)) {
+ goto done;
+ }
+ if (rnp_get_public_key_count(ffi, &keycount) || (keycount != pubcount)) {
+ goto done;
+ }
+ if (rnp_get_secret_key_count(ffi, &keycount) || (keycount != seccount)) {
+ goto done;
+ }
+ if (!keys) {
+ goto done;
+ }
+
+ *jso = json_tokener_parse(keys);
+ if (!jso) {
+ goto done;
+ }
+ if (!json_object_is_type(*jso, json_type_object)) {
+ goto done;
+ }
+ if (!json_object_object_get_ex(*jso, "keys", &keyarr)) {
+ goto done;
+ }
+ if (!json_object_is_type(keyarr, json_type_array)) {
+ goto done;
+ }
+ if ((size_t) json_object_array_length(keyarr) != rescount) {
+ goto done;
+ }
+ res = true;
+done:
+ if (!res) {
+ json_object_put(*jso);
+ *jso = NULL;
+ }
+ rnp_buffer_destroy(keys);
+ return res;
+}
+
+static bool
+check_import_keys(rnp_ffi_t ffi,
+ json_object **jso,
+ const char * keypath,
+ size_t rescount,
+ size_t pubcount,
+ size_t seccount)
+{
+ rnp_input_t input = NULL;
+
+ if (rnp_input_from_path(&input, keypath)) {
+ return false;
+ }
+ bool res = check_import_keys_ex(ffi,
+ jso,
+ RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS,
+ input,
+ rescount,
+ pubcount,
+ seccount);
+ rnp_input_destroy(input);
+ return res;
+}
+
+static bool
+check_key_status(
+ json_object *jso, size_t idx, const char *pub, const char *sec, const char *fp)
+{
+ if (!jso) {
+ return false;
+ }
+ if (!json_object_is_type(jso, json_type_object)) {
+ return false;
+ }
+ json_object *keys = NULL;
+ if (!json_object_object_get_ex(jso, "keys", &keys)) {
+ return false;
+ }
+ if (!json_object_is_type(keys, json_type_array)) {
+ return false;
+ }
+ json_object *key = json_object_array_get_idx(keys, idx);
+ if (!json_object_is_type(key, json_type_object)) {
+ return false;
+ }
+ json_object *fld = NULL;
+ if (!json_object_object_get_ex(key, "public", &fld)) {
+ return false;
+ }
+ if (strcmp(json_object_get_string(fld), pub) != 0) {
+ return false;
+ }
+ if (!json_object_object_get_ex(key, "secret", &fld)) {
+ return false;
+ }
+ if (strcmp(json_object_get_string(fld), sec) != 0) {
+ return false;
+ }
+ if (!json_object_object_get_ex(key, "fingerprint", &fld)) {
+ return false;
+ }
+ if (strcmp(json_object_get_string(fld), fp) != 0) {
+ return false;
+ }
+ return true;
+}
+
+TEST_F(rnp_tests, test_ffi_keys_import)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // some edge cases
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-both.asc"));
+ assert_rnp_failure(rnp_import_keys(NULL, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ assert_rnp_failure(rnp_import_keys(ffi, NULL, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ assert_rnp_failure(rnp_import_keys(ffi, input, 0, NULL));
+ assert_rnp_failure(rnp_import_keys(ffi, input, 0x31, NULL));
+ // load just public keys
+ assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ rnp_input_destroy(input);
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 3);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ // load just secret keys from pubkey file
+ assert_true(import_sec_keys(ffi, "data/test_stream_key_merge/key-pub.asc"));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+ // load both public and secret keys by specifying just secret (it will create pub part)
+ assert_true(import_sec_keys(ffi, "data/test_stream_key_merge/key-sec.asc"));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 3);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 3);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ // import just a public key without subkeys
+ json_object *jso = NULL;
+ assert_true(check_import_keys(
+ ffi, &jso, "data/test_stream_key_merge/key-pub-just-key.pgp", 1, 1, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "090bd712a1166be572252c3c9747d2a6b3a63124"));
+ json_object_put(jso);
+ // import just subkey 1
+ assert_true(check_import_keys(
+ ffi, &jso, "data/test_stream_key_merge/key-pub-just-subkey-1.pgp", 1, 2, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "51b45a4c74917272e4e34180af1114a47f5f5b28"));
+ json_object_put(jso);
+ // import just subkey 2 without sigs
+ assert_true(check_import_keys(
+ ffi, &jso, "data/test_stream_key_merge/key-pub-just-subkey-2-no-sigs.pgp", 1, 3, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "5fe514a54816e1b331686c2c16cd16f267ccdd4f"));
+ json_object_put(jso);
+ // import subkey 2 with sigs
+ assert_true(check_import_keys(
+ ffi, &jso, "data/test_stream_key_merge/key-pub-just-subkey-2.pgp", 1, 3, 0));
+ assert_true(
+ check_key_status(jso, 0, "updated", "none", "5fe514a54816e1b331686c2c16cd16f267ccdd4f"));
+ json_object_put(jso);
+ // import first uid
+ assert_true(
+ check_import_keys(ffi, &jso, "data/test_stream_key_merge/key-pub-uid-1.pgp", 1, 3, 0));
+ assert_true(
+ check_key_status(jso, 0, "updated", "none", "090bd712a1166be572252c3c9747d2a6b3a63124"));
+ json_object_put(jso);
+ // import the whole key
+ assert_true(
+ check_import_keys(ffi, &jso, "data/test_stream_key_merge/key-pub.pgp", 3, 3, 0));
+ assert_true(
+ check_key_status(jso, 0, "updated", "none", "090bd712a1166be572252c3c9747d2a6b3a63124"));
+ assert_true(check_key_status(
+ jso, 1, "unchanged", "none", "51b45a4c74917272e4e34180af1114a47f5f5b28"));
+ assert_true(check_key_status(
+ jso, 2, "unchanged", "none", "5fe514a54816e1b331686c2c16cd16f267ccdd4f"));
+ json_object_put(jso);
+ // import the first secret subkey
+ assert_true(check_import_keys(
+ ffi, &jso, "data/test_stream_key_merge/key-sec-just-subkey-1.pgp", 1, 3, 1));
+ assert_true(check_key_status(
+ jso, 0, "unchanged", "new", "51b45a4c74917272e4e34180af1114a47f5f5b28"));
+ json_object_put(jso);
+ // import the second secret subkey
+ assert_true(check_import_keys(
+ ffi, &jso, "data/test_stream_key_merge/key-sec-just-subkey-2-no-sigs.pgp", 1, 3, 2));
+ assert_true(check_key_status(
+ jso, 0, "unchanged", "new", "5fe514a54816e1b331686c2c16cd16f267ccdd4f"));
+ json_object_put(jso);
+ // import the whole secret key
+ assert_true(
+ check_import_keys(ffi, &jso, "data/test_stream_key_merge/key-sec.pgp", 3, 3, 3));
+ assert_true(check_key_status(
+ jso, 0, "unchanged", "new", "090bd712a1166be572252c3c9747d2a6b3a63124"));
+ assert_true(check_key_status(
+ jso, 1, "unchanged", "unchanged", "51b45a4c74917272e4e34180af1114a47f5f5b28"));
+ assert_true(check_key_status(
+ jso, 2, "unchanged", "unchanged", "5fe514a54816e1b331686c2c16cd16f267ccdd4f"));
+ json_object_put(jso);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_elgamal4096)
+{
+ rnp_ffi_t ffi = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* load public key */
+ json_object *jso = NULL;
+ assert_true(
+ check_import_keys(ffi, &jso, "data/test_key_edge_cases/key-eg-4096-pub.pgp", 2, 2, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "6541db10cdfcdba89db2dffea8f0408eb3369d8e"));
+ assert_true(
+ check_key_status(jso, 1, "new", "none", "c402a09b74acd0c11efc0527a3d630b457a0b15b"));
+ json_object_put(jso);
+ /* load secret key */
+ assert_true(
+ check_import_keys(ffi, &jso, "data/test_key_edge_cases/key-eg-4096-sec.pgp", 2, 2, 2));
+ assert_true(check_key_status(
+ jso, 0, "unchanged", "new", "6541db10cdfcdba89db2dffea8f0408eb3369d8e"));
+ assert_true(check_key_status(
+ jso, 1, "unchanged", "new", "c402a09b74acd0c11efc0527a3d630b457a0b15b"));
+ json_object_put(jso);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_malformed_keys_import)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* import keys with bad key0-uid0 certification, first without flag */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/pubring-malf-cert.pgp"));
+ assert_rnp_failure(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ rnp_input_destroy(input);
+ size_t keycount = 255;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+ /* now try with RNP_LOAD_SAVE_PERMISSIVE */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/pubring-malf-cert.pgp"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_PERMISSIVE, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 7);
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_non_null(key);
+ size_t uidcount = 255;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uidcount));
+ assert_int_equal(uidcount, 3);
+ size_t subcount = 255;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &subcount));
+ assert_int_equal(subcount, 3);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fcadf05ffa501bb", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* import keys with bad key0-sub0 binding */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/pubring-malf-key0-sub0-bind.pgp"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_PERMISSIVE, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 7);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_non_null(key);
+ uidcount = 255;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uidcount));
+ assert_int_equal(uidcount, 3);
+ subcount = 255;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &subcount));
+ assert_int_equal(subcount, 3);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fcadf05ffa501bb", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* import keys with bad key0-sub0 packet */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/pubring-malf-key0-sub0.pgp"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_PERMISSIVE, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 6);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_non_null(key);
+ uidcount = 255;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uidcount));
+ assert_int_equal(uidcount, 3);
+ subcount = 255;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &subcount));
+ assert_int_equal(subcount, 2);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fcadf05ffa501bb", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* import keys with bad key0 packet */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/pubring-malf-key0.pgp"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_PERMISSIVE, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 3);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_null(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fcadf05ffa501bb", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* import secret keys with bad key1 packet - public should be added as well */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/secring-malf-key1.pgp"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PERMISSIVE, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 7);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 4);
+
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_non_null(key);
+ bool secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "326ef111425d14a5", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_false(secret);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* import secret keys with bad key0 packet */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/secring-malf-key0.pgp"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_PERMISSIVE, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 7);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 7);
+
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "key1-uid2", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "326ef111425d14a5", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ /* import unprotected secret key with wrong crc */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_false(
+ import_sec_keys(ffi, "data/test_key_edge_cases/key-25519-tweaked-wrong-crc.asc"));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+
+ /* cleanup */
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_iterated_key_import)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ uint32_t flags =
+ RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | RNP_LOAD_SAVE_SINGLE;
+
+ /* two primary keys with attached subkeys in binary keyring */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
+ json_object *jso = NULL;
+ assert_true(check_import_keys_ex(ffi, &jso, flags, input, 4, 4, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a"));
+ assert_true(
+ check_key_status(jso, 1, "new", "none", "e332b27caf4742a11baa677f1ed63ee56fadc34d"));
+ assert_true(
+ check_key_status(jso, 2, "new", "none", "c5b15209940a7816a7af3fb51d7e8a5393c997a8"));
+ assert_true(
+ check_key_status(jso, 3, "new", "none", "5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1"));
+ json_object_put(jso);
+
+ assert_true(check_import_keys_ex(ffi, &jso, flags, input, 3, 7, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb"));
+ assert_true(
+ check_key_status(jso, 1, "new", "none", "a3e94de61a8cb229413d348e54505a936a4a970e"));
+ assert_true(
+ check_key_status(jso, 2, "new", "none", "57f8ed6e5c197db63c60ffaf326ef111425d14a5"));
+ json_object_put(jso);
+
+ char *results = NULL;
+ assert_int_equal(RNP_ERROR_EOF, rnp_import_keys(ffi, input, flags, &results));
+ assert_null(results);
+ rnp_input_destroy(input);
+
+ /* public + secret key, armored separately */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-both.asc"));
+ assert_true(check_import_keys_ex(ffi, &jso, flags, input, 3, 3, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "090bd712a1166be572252c3c9747d2a6b3a63124"));
+ assert_true(
+ check_key_status(jso, 1, "new", "none", "51b45a4c74917272e4e34180af1114a47f5f5b28"));
+ assert_true(
+ check_key_status(jso, 2, "new", "none", "5fe514a54816e1b331686c2c16cd16f267ccdd4f"));
+ json_object_put(jso);
+
+ assert_true(check_import_keys_ex(ffi, &jso, flags, input, 3, 3, 3));
+ assert_true(check_key_status(
+ jso, 0, "unchanged", "new", "090bd712a1166be572252c3c9747d2a6b3a63124"));
+ assert_true(check_key_status(
+ jso, 1, "unchanged", "new", "51b45a4c74917272e4e34180af1114a47f5f5b28"));
+ assert_true(check_key_status(
+ jso, 2, "unchanged", "new", "5fe514a54816e1b331686c2c16cd16f267ccdd4f"));
+ json_object_put(jso);
+
+ assert_int_equal(RNP_ERROR_EOF, rnp_import_keys(ffi, input, flags, &results));
+ assert_null(results);
+ rnp_input_destroy(input);
+
+ /* public keyring, enarmored */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg.asc"));
+ flags |= RNP_LOAD_SAVE_PERMISSIVE;
+ assert_true(check_import_keys_ex(ffi, &jso, flags, input, 4, 4, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "e95a3cbf583aa80a2ccc53aa7bc6709b15c23a4a"));
+ assert_true(
+ check_key_status(jso, 1, "new", "none", "e332b27caf4742a11baa677f1ed63ee56fadc34d"));
+ assert_true(
+ check_key_status(jso, 2, "new", "none", "c5b15209940a7816a7af3fb51d7e8a5393c997a8"));
+ assert_true(
+ check_key_status(jso, 3, "new", "none", "5cd46d2a0bd0b8cfe0b130ae8a05b89fad5aded1"));
+ json_object_put(jso);
+
+ assert_true(check_import_keys_ex(ffi, &jso, flags, input, 3, 7, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "be1c4ab951f4c2f6b604c7f82fcadf05ffa501bb"));
+ assert_true(
+ check_key_status(jso, 1, "new", "none", "a3e94de61a8cb229413d348e54505a936a4a970e"));
+ assert_true(
+ check_key_status(jso, 2, "new", "none", "57f8ed6e5c197db63c60ffaf326ef111425d14a5"));
+ json_object_put(jso);
+
+ results = NULL;
+ assert_int_equal(RNP_ERROR_EOF, rnp_import_keys(ffi, input, flags, &results));
+ assert_null(results);
+ rnp_input_destroy(input);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_stripped_keys_import)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* load stripped key as keyring */
+ assert_true(load_keys_gpg(ffi, "data/test_key_validity/case8/pubring.gpg"));
+ /* validate signatures - must succeed */
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_validity/case8/message.txt.asc"));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_op_verify_signature_t sig;
+ /* signature 1 - by primary key */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ /* signature 2 - by subkey */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 1, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ rnp_op_verify_destroy(verify);
+
+ /* load stripped key by parts via import */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/primary.pgp"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/subkey.pgp"));
+ /* validate signatures - must be valid */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_validity/case8/message.txt.asc"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ /* signature 1 - by primary key */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ /* signature 2 - by subkey */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 1, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ rnp_op_verify_destroy(verify);
+
+ /* load stripped key with subkey first */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/subkey.pgp"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/primary.pgp"));
+ /* validate signatures - must be valid */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_validity/case8/message.txt.asc"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ /* signature 1 - by primary key */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ /* signature 2 - by subkey */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 1, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ rnp_op_verify_destroy(verify);
+
+ /* load stripped key without subkey binding */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/primary.pgp"));
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case8/subkey-no-sig.pgp"));
+ /* validate signatures - must be invalid */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_validity/case8/message.txt.asc"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ /* signature 1 - by primary key */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_int_equal(rnp_op_verify_signature_get_status(sig), RNP_ERROR_SIGNATURE_INVALID);
+ /* signature 2 - by subkey */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 1, &sig));
+ assert_int_equal(rnp_op_verify_signature_get_status(sig), RNP_ERROR_SIGNATURE_INVALID);
+ rnp_op_verify_destroy(verify);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_import_edge_cases)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* key with empty packets - must fail with bad format */
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/key-empty-packets.pgp"));
+ char *results = NULL;
+ assert_int_equal(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, &results),
+ RNP_ERROR_BAD_FORMAT);
+ assert_null(results);
+ rnp_input_destroy(input);
+
+ /* key with empty uid - must succeed */
+ json_object *jso = NULL;
+ assert_true(
+ check_import_keys(ffi, &jso, "data/test_key_edge_cases/key-empty-uid.pgp", 1, 1, 0));
+ assert_true(
+ check_key_status(jso, 0, "new", "none", "753d5b947e9a2b2e01147c1fc972affd358bf887"));
+ json_object_put(jso);
+
+ /* key with experimental signature subpackets - must succeed and append uid and signature
+ */
+ assert_true(check_import_keys(
+ ffi, &jso, "data/test_key_edge_cases/key-subpacket-101-110.pgp", 1, 1, 0));
+ assert_true(
+ check_key_status(jso, 0, "updated", "none", "753d5b947e9a2b2e01147c1fc972affd358bf887"));
+ json_object_put(jso);
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "C972AFFD358BF887", &key));
+ size_t count = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 2);
+ char *uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid));
+ assert_string_equal(uid, "");
+ rnp_buffer_destroy(uid);
+ assert_rnp_success(rnp_key_get_uid_at(key, 1, &uid));
+ assert_string_equal(uid, "NoUID");
+ rnp_buffer_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ /* key with malformed signature - must fail */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/key-malf-sig.pgp"));
+ assert_int_equal(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, &results),
+ RNP_ERROR_BAD_FORMAT);
+ assert_null(results);
+ rnp_input_destroy(input);
+
+ /* revoked key without revocation reason signature subpacket */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-rev-no-reason.pgp"));
+ assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, &results));
+ rnp_input_destroy(input);
+ assert_non_null(results);
+ rnp_buffer_destroy(results);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_rnp_success(rnp_key_get_revocation_reason(key, &results));
+ assert_string_equal(results, "No reason specified");
+ rnp_buffer_destroy(results);
+ bool revoked = false;
+ assert_rnp_success(rnp_key_is_revoked(key, &revoked));
+ assert_true(revoked);
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+
+ /* revoked subkey without revocation reason signature subpacket */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-sub-rev-no-reason.pgp"));
+ assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, &results));
+ rnp_input_destroy(input);
+ assert_non_null(results);
+ rnp_buffer_destroy(results);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_int_equal(rnp_key_get_revocation_reason(key, &results), RNP_ERROR_BAD_PARAMETERS);
+ revoked = true;
+ assert_rnp_success(rnp_key_is_revoked(key, &revoked));
+ assert_false(revoked);
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &key));
+ assert_rnp_success(rnp_key_get_revocation_reason(key, &results));
+ assert_string_equal(results, "No reason specified");
+ rnp_buffer_destroy(results);
+ revoked = false;
+ assert_rnp_success(rnp_key_is_revoked(key, &revoked));
+ assert_true(revoked);
+ rnp_key_handle_destroy(key);
+
+ /* key with two subkeys with same material but different creation time */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-2-subs-same-grip.pgp"));
+ assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, &results));
+ rnp_input_destroy(input);
+ assert_non_null(results);
+ rnp_buffer_destroy(results);
+ count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 3);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ char *keyid = NULL;
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "DD23CEB7FEBEFF17");
+ rnp_buffer_destroy(keyid);
+ char *fp = NULL;
+ assert_rnp_success(rnp_key_get_primary_fprint(sub, &fp));
+ assert_string_equal(fp, "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C");
+ rnp_buffer_destroy(fp);
+ rnp_key_handle_destroy(sub);
+ assert_rnp_success(rnp_key_get_subkey_at(key, 1, &sub));
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "C2E7FDCC9CD59FB5");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_key_get_primary_fprint(sub, &fp));
+ assert_string_equal(fp, "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C");
+ rnp_buffer_destroy(fp);
+ rnp_key_handle_destroy(sub);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub));
+ assert_rnp_success(rnp_key_get_primary_fprint(sub, &fp));
+ assert_string_equal(fp, "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C");
+ rnp_buffer_destroy(fp);
+ rnp_key_handle_destroy(sub);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "C2E7FDCC9CD59FB5", &sub));
+ assert_rnp_success(rnp_key_get_primary_fprint(sub, &fp));
+ assert_string_equal(fp, "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C");
+ rnp_buffer_destroy(fp);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* two keys with subkeys with same material but different creation time */
+ assert_true(import_pub_keys(ffi, "data/test_key_edge_cases/alice-2-keys-same-grip.pgp"));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 4);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "DD23CEB7FEBEFF17");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_key_get_primary_fprint(sub, &fp));
+ assert_string_equal(fp, "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C");
+ rnp_buffer_destroy(fp);
+ rnp_key_handle_destroy(sub);
+ assert_rnp_success(rnp_key_get_subkey_at(key, 1, &sub));
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "C2E7FDCC9CD59FB5");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_key_get_primary_fprint(sub, &fp));
+ assert_string_equal(fp, "73EDCC9119AFC8E2DBBDCDE50451409669FFDE3C");
+ rnp_buffer_destroy(fp);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+ /* subkey should belong to original key */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "467A2DE826ABA0DB", &key));
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 0);
+ rnp_key_handle_destroy(key);
+
+ /* key with signing subkey, where primary binding has different from subkey binding hash
+ * algorithm */
+ assert_true(import_pub_keys(ffi, "data/test_key_edge_cases/key-binding-hash-alg.asc"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "F81A30AA5DCBD01E", &key));
+ bool valid = false;
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ assert_true(key->pub->valid());
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD716516A7249711", &sub));
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_true(valid);
+ assert_true(sub->pub->valid());
+ rnp_key_handle_destroy(sub);
+
+ /* key and subkey both has 0 key expiration with corresponding subpacket */
+ assert_true(import_pub_keys(ffi, "data/test_key_edge_cases/key-sub-0-expiry.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "6EFF45F2201AC5F8", &key));
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ assert_true(key->pub->valid());
+ uint32_t expiry = 0;
+ assert_rnp_success(rnp_key_valid_till(key, &expiry));
+ assert_int_equal(expiry, 0xffffffff);
+ uint64_t expiry64 = 0;
+ assert_rnp_success(rnp_key_valid_till64(key, &expiry64));
+ assert_int_equal(expiry64, UINT64_MAX);
+
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "74F971795A5DDBC9", &sub));
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_true(valid);
+ assert_true(sub->pub->valid());
+ assert_rnp_success(rnp_key_valid_till(sub, &expiry));
+ assert_int_equal(expiry, 0xffffffff);
+ assert_rnp_success(rnp_key_valid_till64(sub, &expiry64));
+ assert_int_equal(expiry64, UINT64_MAX);
+ rnp_key_handle_destroy(sub);
+
+ /* key/subkey with expiration times in unhashed subpackets */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_true(import_pub_keys(ffi, "data/test_key_edge_cases/key-unhashed-subpkts.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7BC6709B15C23A4A", &key));
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ assert_true(key->pub->valid());
+ assert_rnp_success(rnp_key_get_expiration(key, &expiry));
+ assert_int_equal(expiry, 0);
+ assert_rnp_success(rnp_key_valid_till(key, &expiry));
+ assert_int_equal(expiry, 0xffffffff);
+ assert_rnp_success(rnp_key_valid_till64(key, &expiry64));
+ assert_int_equal(expiry64, UINT64_MAX);
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ED63EE56FADC34D", &sub));
+ assert_true(sub->pub->valid());
+ expiry = 100;
+ assert_rnp_success(rnp_key_get_expiration(sub, &expiry));
+ assert_int_equal(expiry, 0);
+ rnp_key_handle_destroy(sub);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_import_gpg_s2k)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* secret subkeys, exported via gpg --export-secret-subkeys (no primary secret key data) */
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-1-subs.pgp"));
+ assert_rnp_success(rnp_import_keys(
+ ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ rnp_input_destroy(input);
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ bool secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ bool locked = false;
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+ char *type = NULL;
+ assert_rnp_success(rnp_key_get_protection_type(key, &type));
+ assert_string_equal(type, "GPG-None");
+ rnp_buffer_destroy(type);
+ assert_rnp_failure(rnp_key_unlock(key, "password"));
+ size_t count = 0;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ /* signing secret subkey */
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ char *keyid = NULL;
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "22F3A217C0E439CB");
+ rnp_buffer_destroy(keyid);
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(sub, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_true(locked);
+ 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_unlock(sub, "password"));
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_false(locked);
+ rnp_key_handle_destroy(sub);
+ /* encrypting secret subkey */
+ assert_rnp_success(rnp_key_get_subkey_at(key, 1, &sub));
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "DD23CEB7FEBEFF17");
+ rnp_buffer_destroy(keyid);
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(sub, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_true(locked);
+ 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_unlock(sub, "password"));
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_false(locked);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* save keyrings and reload */
+ reload_keyrings(&ffi);
+
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(key, &type));
+ assert_string_equal(type, "GPG-None");
+ rnp_buffer_destroy(type);
+ count = 0;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ /* signing secret subkey */
+ sub = NULL;
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ keyid = NULL;
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "22F3A217C0E439CB");
+ rnp_buffer_destroy(keyid);
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(sub, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(sub, &type));
+ assert_string_equal(type, "Encrypted-Hashed");
+ rnp_buffer_destroy(type);
+ rnp_key_handle_destroy(sub);
+ /* encrypting secret subkey */
+ assert_rnp_success(rnp_key_get_subkey_at(key, 1, &sub));
+ assert_rnp_success(rnp_key_get_keyid(sub, &keyid));
+ assert_string_equal(keyid, "DD23CEB7FEBEFF17");
+ rnp_buffer_destroy(keyid);
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(sub, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(sub, &type));
+ assert_string_equal(type, "Encrypted-Hashed");
+ rnp_buffer_destroy(type);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* secret subkeys, and primary key stored on the smartcard by gpg */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-2-card.pgp"));
+ assert_rnp_success(rnp_import_keys(
+ ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(key, &type));
+ assert_string_equal(type, "GPG-Smartcard");
+ rnp_buffer_destroy(type);
+ assert_rnp_failure(rnp_key_unlock(key, "password"));
+ count = 0;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ /* signing secret subkey */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22F3A217C0E439CB", &sub));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(sub, &type));
+ assert_string_equal(type, "Encrypted-Hashed");
+ rnp_buffer_destroy(type);
+ rnp_key_handle_destroy(sub);
+ /* encrypting secret subkey */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(sub, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(sub, &type));
+ assert_string_equal(type, "Encrypted-Hashed");
+ rnp_buffer_destroy(type);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* save keyrings and reload */
+ reload_keyrings(&ffi);
+
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ count = 0;
+ 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_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ rnp_key_handle_destroy(key);
+
+ /* load key with too large gpg_serial_len */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp"));
+ assert_rnp_success(rnp_import_keys(
+ ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(key, &type));
+ assert_string_equal(type, "GPG-Smartcard");
+ rnp_buffer_destroy(type);
+ assert_rnp_failure(rnp_key_unlock(key, "password"));
+ rnp_key_handle_destroy(key);
+
+ /* secret subkeys, and primary key stored with unknown gpg s2k */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-3.pgp"));
+ assert_rnp_success(rnp_import_keys(
+ ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(key, &type));
+ assert_string_equal(type, "Unknown");
+ rnp_buffer_destroy(type);
+ assert_rnp_failure(rnp_key_unlock(key, "password"));
+ count = 0;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ rnp_key_handle_destroy(key);
+
+ /* save keyrings and reload */
+ reload_keyrings(&ffi);
+
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ count = 0;
+ 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_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ rnp_key_handle_destroy(key);
+
+ /* secret subkeys, and primary key stored with unknown s2k */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-unknown.pgp"));
+ assert_rnp_success(rnp_import_keys(
+ ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ locked = false;
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_get_protection_type(key, &type));
+ assert_string_equal(type, "Unknown");
+ rnp_buffer_destroy(type);
+ assert_rnp_failure(rnp_key_unlock(key, "password"));
+ count = 0;
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ rnp_key_handle_destroy(key);
+
+ /* save keyrings and reload */
+ reload_keyrings(&ffi);
+
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669FFDE3C", &key));
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ count = 0;
+ 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_subkey_count(key, &count));
+ assert_int_equal(count, 2);
+ rnp_key_handle_destroy(key);
+
+ rnp_ffi_destroy(ffi);
+}
+
+static bool
+check_key_autocrypt(rnp_output_t memout,
+ const std::string &keyid,
+ const std::string &subid,
+ const std::string &uid,
+ bool base64 = false)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_ffi_create(&ffi, "GPG", "GPG");
+
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ if (rnp_output_memory_get_buf(memout, &buf, &len, false) || !buf || !len) {
+ return false;
+ }
+ if (!import_all_keys(ffi, buf, len, base64 ? RNP_LOAD_SAVE_BASE64 : 0)) {
+ return false;
+ }
+ size_t count = 0;
+ rnp_get_public_key_count(ffi, &count);
+ if (count != 2) {
+ return false;
+ }
+ rnp_get_secret_key_count(ffi, &count);
+ if (count != 0) {
+ return false;
+ }
+ rnp_key_handle_t key = NULL;
+ if (rnp_locate_key(ffi, "keyid", keyid.c_str(), &key) || !key) {
+ return false;
+ }
+ rnp_key_handle_t sub = NULL;
+ if (rnp_locate_key(ffi, "keyid", subid.c_str(), &sub) || !sub) {
+ return false;
+ }
+ if (!key->pub->valid() || !sub->pub->valid()) {
+ return false;
+ }
+ if ((key->pub->sig_count() != 1) || (sub->pub->sig_count() != 1)) {
+ return false;
+ }
+ if (!key->pub->can_sign() || !sub->pub->can_encrypt()) {
+ return false;
+ }
+ if ((key->pub->uid_count() != 1) || (key->pub->get_uid(0).str != uid)) {
+ return false;
+ }
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+ rnp_ffi_destroy(ffi);
+ return true;
+}
+
+TEST_F(rnp_tests, test_ffi_key_export_autocrypt)
+{
+ rnp_ffi_t ffi = NULL;
+ test_ffi_init(&ffi);
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));
+
+ /* edge cases */
+ assert_rnp_failure(rnp_key_export_autocrypt(key, NULL, NULL, NULL, 0));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(key, sub, NULL, output, 17));
+ assert_rnp_failure(rnp_key_export_autocrypt(NULL, sub, "key0-uid0", output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(key, sub, NULL, output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(key, key, NULL, output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(key, key, "key0-uid0", output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(sub, sub, "key0-uid0", output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(sub, key, "key0-uid0", output, 0));
+ assert_int_equal(output->dst.writeb, 0);
+
+ /* export key + uid1 + sub2 */
+ assert_rnp_success(rnp_key_export_autocrypt(key, sub, "key0-uid1", output, 0));
+ assert_true(
+ check_key_autocrypt(output, "7bc6709b15c23a4a", "8a05b89fad5aded1", "key0-uid1"));
+ rnp_output_destroy(output);
+
+ /* export key + uid0 + sub1 (fail) */
+ rnp_key_handle_destroy(sub);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1d7e8a5393c997a8", &sub));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(key, sub, "key0-uid0", output, 0));
+ assert_int_equal(output->dst.writeb, 0);
+ rnp_key_handle_destroy(sub);
+
+ /* export key without specifying subkey */
+ assert_rnp_success(rnp_key_export_autocrypt(key, NULL, "key0-uid2", output, 0));
+ assert_true(
+ check_key_autocrypt(output, "7bc6709b15c23a4a", "8a05b89fad5aded1", "key0-uid2"));
+ rnp_output_destroy(output);
+
+ /* export base64-encoded key */
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(
+ rnp_key_export_autocrypt(key, NULL, "key0-uid2", output, RNP_KEY_EXPORT_BASE64));
+ /* Make sure it is base64-encoded */
+ const std::string reg = "^[A-Za-z0-9\\+\\/]+={0,2}$";
+ uint8_t * buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ std::string val((char *) buf, (char *) buf + len);
+#ifndef RNP_USE_STD_REGEX
+ static regex_t r;
+ regmatch_t matches[1];
+ assert_int_equal(regcomp(&r, reg.c_str(), REG_EXTENDED), 0);
+ assert_int_equal(regexec(&r, val.c_str(), 1, matches, 0), 0);
+#else
+ static std::regex re(reg, std::regex_constants::extended | std::regex_constants::icase);
+ std::smatch result;
+ assert_true(std::regex_search(val, result, re));
+#endif
+ /* Fails to load without base64 flag */
+ assert_false(import_all_keys(ffi, buf, len));
+ /* Now should succeed */
+ assert_true(
+ check_key_autocrypt(output, "7bc6709b15c23a4a", "8a05b89fad5aded1", "key0-uid2", true));
+ rnp_output_destroy(output);
+
+ /* remove first subkey and export again */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &sub));
+ assert_rnp_success(rnp_key_remove(sub, RNP_KEY_REMOVE_PUBLIC));
+ rnp_key_handle_destroy(sub);
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_key_export_autocrypt(key, NULL, "key0-uid0", output, 0));
+ assert_true(
+ check_key_autocrypt(output, "7bc6709b15c23a4a", "8a05b89fad5aded1", "key0-uid0"));
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+
+ /* primary key with encrypting capability, make sure subkey is exported */
+ 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/encrypting-primary.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "92091b7b76c50017", &key));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_key_export_autocrypt(
+ key, NULL, "encrypting primary <encrypting_primary@rnp>", output, 0));
+ assert_true(check_key_autocrypt(output,
+ "92091b7b76c50017",
+ "c2e243e872c1fe50",
+ "encrypting primary <encrypting_primary@rnp>"));
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+
+ /* export key with single uid 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-sub-pub.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_key_export_autocrypt(key, NULL, NULL, output, 0));
+ assert_true(check_key_autocrypt(
+ output, "0451409669ffde3c", "dd23ceb7febeff17", "Alice <alice@rnp>"));
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+
+ /* export key with sign-only subkey: fail */
+ 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_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "22f3a217c0e439cb", &sub));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(key, sub, NULL, output, 0));
+ assert_int_equal(output->dst.writeb, 0);
+ assert_rnp_failure(rnp_key_export_autocrypt(key, NULL, NULL, output, 0));
+ assert_int_equal(output->dst.writeb, 0);
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+
+ /* export key without subkey: fail */
+ 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-pub.asc"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_key_export_autocrypt(key, NULL, NULL, output, 0));
+ assert_int_equal(output->dst.writeb, 0);
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+
+ /* export secret key: make sure public is exported */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ 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_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_key_export_autocrypt(key, NULL, NULL, output, 0));
+ assert_true(check_key_autocrypt(
+ output, "0451409669ffde3c", "dd23ceb7febeff17", "Alice <alice@rnp>"));
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+
+ /* make sure that only self-certification is exported */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ /* load key alice with 2 self-sigs, one of those is expired */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case9/pubring.gpg"));
+ /* add one corrupted alice's signature and one valid from Basil */
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/case2/pubring.gpg"));
+
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "0451409669ffde3c", &key));
+ assert_int_equal(key->pub->sig_count(), 4);
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_key_export_autocrypt(key, NULL, NULL, output, 0));
+ assert_true(check_key_autocrypt(
+ output, "0451409669ffde3c", "dd23ceb7febeff17", "Alice <alice@rnp>"));
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_keys_import_autocrypt)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_load/ecc-25519-pub.b64"));
+ /* no base64 flag */
+ assert_rnp_failure(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ rnp_input_destroy(input);
+ /* enable base64 flag */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_load/ecc-25519-pub.b64"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_BASE64, NULL));
+ rnp_input_destroy(input);
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 1);
+ /* load other files, with different base64 formatting */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_load/ecc-25519-pub-2.b64"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_BASE64, NULL));
+ rnp_input_destroy(input);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 1);
+
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_load/ecc-25519-pub-3.b64"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_BASE64, NULL));
+ rnp_input_destroy(input);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 1);
+
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_load/ecc-25519-pub-4.b64"));
+ assert_rnp_failure(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_BASE64, NULL));
+ rnp_input_destroy(input);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 0);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_load/ecc-p256k1-pub.b64"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_BASE64, NULL));
+ rnp_input_destroy(input);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 2);
+
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_load/ecc-p256k1-pub-2.b64"));
+ assert_rnp_success(
+ rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_BASE64, NULL));
+ rnp_input_destroy(input);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keycount));
+ assert_int_equal(keycount, 2);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_keys_load_armored_spaces)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ const char *key = R"key(
+ -----BEGIN PGP PUBLIC KEY BLOCK-----
+
+mDMEXLO69BYJKwYBBAHaRw8BAQdAWsoBwHOLMrbp7ykSSCD7FYG7tMYT74aLn5wh
+Q63nmJC0BmVjZHNhMIiQBBMWCAA4FiEEMuxFQcPhApFLtGbaEJXD7W1DwDsFAlyz
+uvQCGwMFCwkIBwIGFQoJCAsCBBYCAwECHgECF4AACgkQEJXD7W1DwDs/cwD+PQt4
+GnDUFFW2omo7XJh6AUUC4eUnKQoMWoD3iwYetCwA/1leV7sUdsvs5wvkp+LJVDTW
+dbpkwTCmBVbAmazgea0B
+=omFJ
+-----END PGP PUBLIC KEY BLOCK-----
+)key";
+
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_memory(&input, (uint8_t *) key, strlen(key), false));
+ assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ rnp_input_destroy(input);
+ size_t keys = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &keys));
+ assert_int_equal(keys, 1);
+ rnp_ffi_destroy(ffi);
+}
+
+/* Functions below are used to demonstrate how to check whether key has weak MD5/SHA1
+ * signatures, and may be reused later in FFI code */
+static bool
+is_self_signature(const char *keyid, rnp_signature_handle_t sig)
+{
+ char *signer = NULL;
+ rnp_signature_get_keyid(sig, &signer);
+ if (!signer) {
+ return false;
+ }
+ bool result = !strcmp(keyid, signer);
+ rnp_buffer_destroy(signer);
+ return result;
+}
+
+static bool
+is_weak_signature(rnp_ffi_t ffi, rnp_signature_handle_t sig)
+{
+ char * hash = NULL;
+ uint32_t creation = 0;
+ rnp_signature_get_hash_alg(sig, &hash);
+ rnp_signature_get_creation(sig, &creation);
+ /* This approach would be more general, however hardcoding MD5/SHA1 may be used as well */
+ uint32_t flags = RNP_SECURITY_VERIFY_KEY;
+ uint32_t level = 0;
+ rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, hash, creation, &flags, NULL, &level);
+ bool res = level < RNP_SECURITY_DEFAULT;
+ if (res) {
+ printf(
+ "Detected weak signature with %s hash, created at %zu\n", hash, (size_t) creation);
+ }
+ rnp_buffer_destroy(hash);
+ return res;
+}
+
+static const std::string
+get_uid_str(rnp_uid_handle_t uid)
+{
+ uint32_t type = 0;
+ rnp_uid_get_type(uid, &type);
+ switch (type) {
+ case RNP_USER_ID: {
+ void * data = NULL;
+ size_t len = 0;
+ rnp_uid_get_data(uid, &data, &len);
+ std::string res((const char *) data, (const char *) data + len);
+ rnp_buffer_destroy(data);
+ return res;
+ }
+ case RNP_USER_ATTR:
+ return "photo";
+ default:
+ return "Unknown";
+ }
+}
+
+static size_t
+key_weak_self_signatures_count(rnp_ffi_t ffi, rnp_key_handle_t key)
+{
+ char *keyid = NULL;
+ rnp_key_get_keyid(key, &keyid);
+ bool valid = false;
+ rnp_key_is_valid(key, &valid);
+ printf(
+ "Key %s is %s, checking for the weak signatures.\n", keyid, valid ? "valid" : "invalid");
+ /* Check direct-key signatures */
+ size_t res = 0;
+ size_t count = 0;
+ rnp_key_get_signature_count(key, &count);
+ for (size_t i = 0; i < count; i++) {
+ rnp_signature_handle_t sig = NULL;
+ rnp_key_get_signature_at(key, i, &sig);
+ if (is_self_signature(keyid, sig) && is_weak_signature(ffi, sig)) {
+ printf("Key %s has weak direct-key signature at index %zu.\n", keyid, i);
+ res++;
+ }
+ rnp_signature_handle_destroy(sig);
+ }
+ /* Check certifications */
+ size_t uidcount = 0;
+ rnp_key_get_uid_count(key, &uidcount);
+ for (size_t i = 0; i < uidcount; i++) {
+ rnp_uid_handle_t uid = NULL;
+ rnp_key_get_uid_handle_at(key, i, &uid);
+ count = 0;
+ rnp_uid_get_signature_count(uid, &count);
+ for (size_t j = 0; j < count; j++) {
+ rnp_signature_handle_t sig = NULL;
+ rnp_uid_get_signature_at(uid, j, &sig);
+ if (is_self_signature(keyid, sig) && is_weak_signature(ffi, sig)) {
+ auto uidstr = get_uid_str(uid);
+ printf("Uid %s of the key %s has weak self-certification at index %zu.\n",
+ uidstr.c_str(),
+ keyid,
+ j);
+ res++;
+ }
+ rnp_signature_handle_destroy(sig);
+ }
+ rnp_uid_handle_destroy(uid);
+ }
+ /* Check subkeys */
+ size_t subcount = 0;
+ rnp_key_get_subkey_count(key, &subcount);
+ for (size_t i = 0; i < subcount; i++) {
+ rnp_key_handle_t subkey = NULL;
+ rnp_key_get_subkey_at(key, i, &subkey);
+ count = 0;
+ rnp_key_get_signature_count(subkey, &count);
+ for (size_t j = 0; j < count; j++) {
+ rnp_signature_handle_t sig = NULL;
+ rnp_key_get_signature_at(subkey, j, &sig);
+ if (is_self_signature(keyid, sig) && is_weak_signature(ffi, sig)) {
+ char *subid = NULL;
+ rnp_key_get_keyid(subkey, &subid);
+ printf("Subkey %s of the key %s has weak binding signature at index %zu.\n",
+ subid,
+ keyid,
+ j);
+ res++;
+ rnp_buffer_destroy(subid);
+ }
+ rnp_signature_handle_destroy(sig);
+ }
+ rnp_key_handle_destroy(subkey);
+ }
+ rnp_buffer_destroy(keyid);
+ return res;
+}
+
+TEST_F(rnp_tests, test_ffi_sha1_self_signatures)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* This key has SHA1 self signature, made before the cut-off date */
+ assert_true(import_pub_keys(ffi, "data/test_stream_key_load/rsa-rsa-pub.asc"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fb9179118898e8b", &key));
+ /* Check key validity */
+ bool valid = false;
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ size_t count = 0;
+ /* Check uid validity */
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 1);
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ /* Check subkey validity */
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 1);
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_true(valid);
+ /* Check weak signature count */
+ assert_int_equal(key_weak_self_signatures_count(ffi, key), 0);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+
+ /* Check the key which has SHA1 self signature, made after the cut-off date */
+ assert_rnp_success(rnp_set_timestamp(ffi, SHA1_KEY_FROM + 10));
+ assert_true(import_pub_keys(ffi, "data/test_forged_keys/eddsa-2024-pub.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "980e3741f632212c", &key));
+ /* Check key validity */
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_false(valid);
+ /* Check uid validity */
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ rnp_uid_handle_destroy(uid);
+ /* Check subkey validity */
+ assert_rnp_success(rnp_key_get_subkey_count(key, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_false(valid);
+ /* Check weak signature count */
+ assert_int_equal(key_weak_self_signatures_count(ffi, key), 2);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ /* Now allow the SHA1 hash */
+ assert_rnp_success(rnp_add_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "SHA1",
+ RNP_SECURITY_OVERRIDE,
+ SHA1_KEY_FROM + 1,
+ RNP_SECURITY_DEFAULT));
+
+ assert_true(import_pub_keys(ffi, "data/test_forged_keys/eddsa-2024-pub.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "980e3741f632212c", &key));
+ /* Check key validity */
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ /* Check uid validity */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ /* Check subkey validity */
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_true(valid);
+ /* Check weak signature count */
+ assert_int_equal(key_weak_self_signatures_count(ffi, key), 0);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ /* Check the key which has MD5 self signature, made after the cut-off date */
+ assert_true(import_pub_keys(ffi, "data/test_forged_keys/eddsa-2012-md5-pub.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8801eafbd906bd21", &key));
+ /* Check key validity */
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_false(valid);
+ /* Check uid validity */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ rnp_uid_handle_destroy(uid);
+ /* Check subkey validity */
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ assert_rnp_success(rnp_key_is_valid(sub, &valid));
+ assert_false(valid);
+ /* Check weak signature count */
+ assert_int_equal(key_weak_self_signatures_count(ffi, key), 2);
+ rnp_key_handle_destroy(sub);
+ rnp_key_handle_destroy(key);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_reprotect_keys)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* Cast5-encrypted keys */
+ assert_true(
+ load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring-cast5.gpg"));
+
+ rnp_identifier_iterator_t it = NULL;
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
+ assert_non_null(it);
+ const char *ident = NULL;
+ do {
+ ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ if (!ident) {
+ break;
+ }
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "fingerprint", ident, &key));
+ if (cast5_enabled()) {
+ assert_rnp_success(rnp_key_unprotect(key, "password"));
+ assert_rnp_success(rnp_key_protect(key, "password", "AES256", NULL, NULL, 65536));
+ } else {
+ assert_rnp_failure(rnp_key_unprotect(key, "password"));
+ }
+ rnp_key_handle_destroy(key);
+ } while (1);
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ /* AES-encrypted keys */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_true(
+ load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg", "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(rnp_identifier_iterator_create(ffi, &it, "fingerprint"));
+ assert_non_null(it);
+ do {
+ ident = NULL;
+ assert_rnp_success(rnp_identifier_iterator_next(it, &ident));
+ if (!ident) {
+ break;
+ }
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "fingerprint", ident, &key));
+ assert_rnp_success(rnp_key_unprotect(key, "password"));
+ if (cast5_enabled()) {
+ assert_rnp_success(rnp_key_protect(key, "password", "CAST5", NULL, NULL, 65536));
+ } else {
+ assert_rnp_success(rnp_key_protect(key, "password", "AES128", NULL, NULL, 65536));
+ }
+ rnp_key_handle_destroy(key);
+ } while (1);
+ assert_rnp_success(rnp_identifier_iterator_destroy(it));
+ rnp_ffi_destroy(ffi);
+}
diff --git a/src/tests/ffi-uid.cpp b/src/tests/ffi-uid.cpp
new file mode 100644
index 0000000..c8aada9
--- /dev/null
+++ b/src/tests/ffi-uid.cpp
@@ -0,0 +1,379 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+TEST_F(rnp_tests, test_ffi_uid_properties)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/test_uid_validity/key-uids-pub.pgp"));
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "F6E741D1DF582D90", &key));
+ assert_non_null(key);
+
+ size_t uids = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uids));
+ assert_int_equal(uids, 4);
+
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_failure(rnp_key_get_uid_handle_at(NULL, 0, &uid));
+ assert_rnp_failure(rnp_key_get_uid_handle_at(key, 0, NULL));
+ assert_rnp_failure(rnp_key_get_uid_handle_at(key, 100, &uid));
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_non_null(uid);
+
+ uint32_t uid_type = 0;
+ assert_rnp_failure(rnp_uid_get_type(NULL, &uid_type));
+ assert_rnp_failure(rnp_uid_get_type(uid, NULL));
+ assert_rnp_success(rnp_uid_get_type(uid, &uid_type));
+ assert_int_equal(uid_type, RNP_USER_ID);
+
+ size_t size = 0;
+ void * data = NULL;
+ assert_rnp_failure(rnp_uid_get_data(NULL, &data, &size));
+ assert_rnp_failure(rnp_uid_get_data(uid, NULL, &size));
+ assert_rnp_failure(rnp_uid_get_data(uid, &data, NULL));
+ assert_rnp_success(rnp_uid_get_data(uid, &data, &size));
+ assert_int_equal(size, 12);
+ assert_int_equal(memcmp(data, "userid-valid", size), 0);
+ rnp_buffer_destroy(data);
+
+ bool primary = false;
+ assert_rnp_failure(rnp_uid_is_primary(NULL, &primary));
+ assert_rnp_failure(rnp_uid_is_primary(uid, NULL));
+ assert_rnp_success(rnp_uid_is_primary(uid, &primary));
+ assert_true(primary);
+ rnp_uid_handle_destroy(uid);
+
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid));
+ assert_rnp_success(rnp_uid_get_data(uid, &data, &size));
+ assert_int_equal(size, 14);
+ assert_int_equal(memcmp(data, "userid-expired", size), 0);
+ rnp_buffer_destroy(data);
+ assert_rnp_success(rnp_uid_is_primary(uid, &primary));
+ assert_false(primary);
+ rnp_uid_handle_destroy(uid);
+
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 2, &uid));
+ assert_rnp_success(rnp_uid_get_data(uid, &data, &size));
+ assert_int_equal(size, 14);
+ assert_int_equal(memcmp(data, "userid-invalid", size), 0);
+ rnp_buffer_destroy(data);
+ assert_rnp_success(rnp_uid_is_primary(uid, &primary));
+ assert_false(primary);
+ rnp_uid_handle_destroy(uid);
+
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 3, &uid));
+ assert_rnp_success(rnp_uid_get_type(uid, &uid_type));
+ assert_int_equal(uid_type, RNP_USER_ATTR);
+ assert_rnp_success(rnp_uid_get_data(uid, &data, &size));
+ assert_int_equal(size, 3038);
+ rnp_buffer_destroy(data);
+ assert_rnp_success(rnp_uid_is_primary(uid, &primary));
+ assert_false(primary);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_uid_validity)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/test_uid_validity/key-uids-with-invalid.pgp"));
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "F6E741D1DF582D90", &key));
+ assert_non_null(key);
+
+ size_t uids = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uids));
+ assert_int_equal(uids, 4);
+
+ /* userid 0 : valid */
+ rnp_uid_handle_t uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ bool valid = false;
+ assert_rnp_failure(rnp_uid_is_valid(NULL, &valid));
+ assert_rnp_failure(rnp_uid_is_valid(uid, NULL));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ /* userid 1 : self-sig marks key as expired, but uid is still valid */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ /* userid 2 : invalid (malformed signature data) */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 2, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ rnp_uid_handle_destroy(uid);
+
+ /* userid 3: valid userattr */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 3, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+
+ /* Try to locate key via all uids */
+ rnp_key_handle_t newkey = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-valid", &newkey));
+ assert_non_null(newkey);
+ rnp_key_handle_destroy(newkey);
+ newkey = NULL;
+ /* even if signature marks key as expired uid is still valid and usable */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-expired", &newkey));
+ assert_non_null(newkey);
+ rnp_key_handle_destroy(newkey);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-invalid", &newkey));
+ assert_null(newkey);
+
+ /* Now import key with valid signature for the userid 2 */
+ assert_true(import_pub_keys(ffi, "data/test_uid_validity/key-uids-pub.pgp"));
+ uids = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uids));
+ assert_int_equal(uids, 4);
+
+ /* userid 0 : valid */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ /* userid 1 : key is expired via self-cert, but uid is valid */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ /* userid 2 : valid */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 2, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ /* userid 3 : valid userattr */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 3, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+
+ /* now we should be able to locate key via userid-invalid */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-invalid", &newkey));
+ assert_non_null(newkey);
+ rnp_key_handle_destroy(newkey);
+
+ /* Now import key with revoked primary userid */
+ assert_true(import_pub_keys(ffi, "data/test_uid_validity/key-uids-revoked-valid.pgp"));
+ uids = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uids));
+ assert_int_equal(uids, 4);
+
+ /* userid 0 : invalid since it is revoked */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ bool primary = true;
+ assert_rnp_success(rnp_uid_is_primary(uid, &primary));
+ assert_false(primary);
+ rnp_uid_handle_destroy(uid);
+
+ /* Primary userid now should be userid-expired */
+ char *uid_str = NULL;
+ assert_rnp_success(rnp_key_get_primary_uid(key, &uid_str));
+ assert_non_null(uid_str);
+ assert_string_equal(uid_str, "userid-expired");
+ rnp_buffer_destroy(uid_str);
+
+ /* We should not be able to find key via userid-valid */
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-valid", &newkey));
+ assert_null(newkey);
+
+ rnp_key_handle_destroy(key);
+
+ /* Load expired key with single uid: still has primary uid as it has valid self-cert */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, "data/test_uid_validity/key-expired.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "4BE147BB22DF1E60", &key));
+ assert_non_null(key);
+ uid_str = NULL;
+ assert_rnp_success(rnp_key_get_primary_uid(key, &uid_str));
+ assert_string_equal(uid_str, "test1");
+ rnp_buffer_destroy(uid_str);
+ rnp_key_handle_destroy(key);
+
+ /* UID with expired self-certification signature */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, "data/test_uid_validity/key-uid-expired-sig.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "expired_uid_sig", &key));
+ assert_null(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "129195E05B0943CB", &key));
+ assert_non_null(key);
+ uid_str = NULL;
+ assert_rnp_failure(rnp_key_get_primary_uid(key, &uid_str));
+ assert_null(uid_str);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ rnp_uid_handle_destroy(uid);
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ /* key is valid since there is a subkey with valid binding signature */
+ assert_true(valid);
+ rnp_key_handle_destroy(key);
+
+ /* UID with expired self-certification on primary uid signature */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(import_pub_keys(ffi, "data/test_uid_validity/key-uid-prim-expired-sig.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "expired_prim_uid_sig", &key));
+ assert_null(key);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "non_prim_uid", &key));
+ assert_non_null(key);
+ uid_str = NULL;
+ assert_rnp_success(rnp_key_get_primary_uid(key, &uid_str));
+ assert_non_null(uid_str);
+ assert_string_equal(uid_str, "non_prim_uid");
+ rnp_buffer_destroy(uid_str);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_false(valid);
+ rnp_uid_handle_destroy(uid);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid));
+ assert_rnp_success(rnp_uid_is_valid(uid, &valid));
+ assert_true(valid);
+ rnp_uid_handle_destroy(uid);
+ rnp_key_handle_destroy(key);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_remove_uid)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/test_uid_validity/key-uids-pub.pgp"));
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-valid", &key));
+ size_t count = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 4);
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_key_get_subkey_at(key, 0, &sub));
+ rnp_uid_handle_t uid = NULL;
+ /* delete last userattr */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 3, &uid));
+ assert_rnp_failure(rnp_uid_remove(NULL, uid));
+ assert_rnp_failure(rnp_uid_remove(key, NULL));
+ assert_rnp_failure(rnp_uid_remove(sub, uid));
+ assert_rnp_success(rnp_uid_remove(key, uid));
+ assert_rnp_success(rnp_uid_handle_destroy(uid));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 3);
+ /* delete uid in the middle, userid-expired */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid));
+ char *uidstr = NULL;
+ assert_rnp_success(rnp_key_get_uid_at(key, 1, &uidstr));
+ assert_string_equal(uidstr, "userid-expired");
+ rnp_buffer_destroy(uidstr);
+ assert_rnp_success(rnp_uid_remove(key, uid));
+ assert_rnp_success(rnp_uid_handle_destroy(uid));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 2);
+ /* delete first uid */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uidstr));
+ assert_string_equal(uidstr, "userid-valid");
+ rnp_buffer_destroy(uidstr);
+ assert_rnp_success(rnp_uid_remove(key, uid));
+ assert_rnp_success(rnp_uid_handle_destroy(uid));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 1);
+ /* delete last and only uid */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid));
+ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uidstr));
+ assert_string_equal(uidstr, "userid-invalid");
+ rnp_buffer_destroy(uidstr);
+ assert_rnp_success(rnp_uid_remove(key, uid));
+ assert_rnp_success(rnp_uid_handle_destroy(uid));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 0);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+ /* now let's reload pubring to make sure that they are removed */
+ reload_pubring(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-valid", &key));
+ assert_null(key);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "f6e741d1df582d90", &key));
+ count = 255;
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 0);
+ rnp_key_handle_destroy(key);
+
+ /* delete userids of the secret key and reload */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_true(load_keys_gpg(ffi,
+ "data/test_uid_validity/key-uids-pub.pgp",
+ "data/test_uid_validity/key-uids-sec.pgp"));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-valid", &key));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 4);
+ bool secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ /* remove userid-expired */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid));
+ assert_rnp_success(rnp_key_get_uid_at(key, 1, &uidstr));
+ assert_string_equal(uidstr, "userid-expired");
+ rnp_buffer_destroy(uidstr);
+ assert_rnp_success(rnp_uid_remove(key, uid));
+ assert_rnp_success(rnp_uid_handle_destroy(uid));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 3);
+ /* remove userid-invalid */
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid));
+ assert_rnp_success(rnp_key_get_uid_at(key, 1, &uidstr));
+ assert_string_equal(uidstr, "userid-invalid");
+ rnp_buffer_destroy(uidstr);
+ assert_rnp_success(rnp_uid_remove(key, uid));
+ assert_rnp_success(rnp_uid_handle_destroy(uid));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 2);
+ rnp_key_handle_destroy(key);
+ /* reload */
+ reload_keyrings(&ffi);
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "userid-valid", &key));
+ assert_rnp_success(rnp_key_get_uid_count(key, &count));
+ assert_int_equal(count, 2);
+ secret = false;
+ assert_rnp_success(rnp_key_have_secret(key, &secret));
+ assert_true(secret);
+ rnp_key_handle_destroy(key);
+
+ rnp_ffi_destroy(ffi);
+}
diff --git a/src/tests/ffi.cpp b/src/tests/ffi.cpp
new file mode 100644
index 0000000..1e75871
--- /dev/null
+++ b/src/tests/ffi.cpp
@@ -0,0 +1,6025 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <fstream>
+#include <vector>
+#include <string>
+#include <set>
+#include <utility>
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include "librepgp/stream-common.h"
+#include "librepgp/stream-packet.h"
+#include "librepgp/stream-sig.h"
+#include <json.h>
+#include "file-utils.h"
+#include "str-utils.h"
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+
+TEST_F(rnp_tests, test_ffi_homedir)
+{
+ rnp_ffi_t ffi = NULL;
+ char * pub_format = NULL;
+ char * pub_path = NULL;
+ char * sec_format = NULL;
+ char * sec_path = NULL;
+
+ // get the default homedir (not a very thorough test)
+ {
+ assert_rnp_failure(rnp_get_default_homedir(NULL));
+ char *homedir = NULL;
+ assert_rnp_success(rnp_get_default_homedir(&homedir));
+ assert_non_null(homedir);
+ rnp_buffer_destroy(homedir);
+ }
+
+ // check NULL params
+ assert_rnp_failure(
+ rnp_detect_homedir_info(NULL, &pub_format, &pub_path, &sec_format, &sec_path));
+ assert_rnp_failure(
+ rnp_detect_homedir_info("data/keyrings/1", NULL, &pub_path, &sec_format, &sec_path));
+ assert_rnp_failure(
+ rnp_detect_homedir_info("data/keyrings/1", &pub_format, NULL, &sec_format, &sec_path));
+ assert_rnp_failure(
+ rnp_detect_homedir_info("data/keyrings/1", &pub_format, &pub_path, NULL, &sec_path));
+ assert_rnp_failure(
+ rnp_detect_homedir_info("data/keyrings/1", &pub_format, &pub_path, &sec_format, NULL));
+ // detect the formats+paths
+ assert_rnp_success(rnp_detect_homedir_info(
+ "data/keyrings/1", &pub_format, &pub_path, &sec_format, &sec_path));
+ // check formats
+ assert_string_equal(pub_format, "GPG");
+ assert_string_equal(sec_format, "GPG");
+ // check paths
+ assert_string_equal(pub_path, "data/keyrings/1/pubring.gpg");
+ assert_string_equal(sec_path, "data/keyrings/1/secring.gpg");
+ rnp_buffer_destroy(pub_format);
+ rnp_buffer_destroy(pub_path);
+ rnp_buffer_destroy(sec_format);
+ rnp_buffer_destroy(sec_path);
+// detect windows-style slashes
+#ifdef _WIN32
+ assert_rnp_success(rnp_detect_homedir_info(
+ "data\\keyrings\\1", &pub_format, &pub_path, &sec_format, &sec_path));
+ // check formats
+ assert_string_equal(pub_format, "GPG");
+ assert_string_equal(sec_format, "GPG");
+ // check paths
+ assert_string_equal(pub_path, "data\\keyrings\\1\\pubring.gpg");
+ assert_string_equal(sec_path, "data\\keyrings\\1\\secring.gpg");
+ rnp_buffer_destroy(pub_format);
+ rnp_buffer_destroy(pub_path);
+ rnp_buffer_destroy(sec_format);
+ rnp_buffer_destroy(sec_path);
+#endif
+ // detect with the trailing slash
+ assert_rnp_success(rnp_detect_homedir_info(
+ "data/keyrings/1/", &pub_format, &pub_path, &sec_format, &sec_path));
+ // check formats
+ assert_string_equal(pub_format, "GPG");
+ assert_string_equal(sec_format, "GPG");
+ // check paths
+ assert_string_equal(pub_path, "data/keyrings/1/pubring.gpg");
+ assert_string_equal(sec_path, "data/keyrings/1/secring.gpg");
+ // setup FFI with wrong parameters
+ assert_rnp_failure(rnp_ffi_create(NULL, "GPG", "GPG"));
+ assert_rnp_failure(rnp_ffi_create(&ffi, "GPG", NULL));
+ assert_rnp_failure(rnp_ffi_create(&ffi, NULL, "GPG"));
+ assert_rnp_failure(rnp_ffi_create(&ffi, "ZZG", "GPG"));
+ assert_rnp_failure(rnp_ffi_create(&ffi, "GPG", "ZZG"));
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, pub_format, sec_format));
+ // load our keyrings
+ assert_true(load_keys_gpg(ffi, pub_path, sec_path));
+ // free formats+paths
+ rnp_buffer_destroy(pub_format);
+ pub_format = NULL;
+ rnp_buffer_destroy(pub_path);
+ pub_path = NULL;
+ rnp_buffer_destroy(sec_format);
+ sec_format = NULL;
+ rnp_buffer_destroy(sec_path);
+ sec_path = NULL;
+ // check key counts
+ size_t count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+
+ // detect the formats+paths
+ assert_rnp_success(rnp_detect_homedir_info(
+ "data/keyrings/3", &pub_format, &pub_path, &sec_format, &sec_path));
+ // check formats
+ assert_string_equal(pub_format, "KBX");
+ assert_string_equal(sec_format, "G10");
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, pub_format, sec_format));
+ // load our keyrings
+ assert_true(load_keys_kbx_g10(ffi, pub_path, sec_path));
+ // free formats+paths
+ rnp_buffer_destroy(pub_format);
+ pub_format = NULL;
+ rnp_buffer_destroy(pub_path);
+ pub_path = NULL;
+ rnp_buffer_destroy(sec_format);
+ sec_format = NULL;
+ rnp_buffer_destroy(sec_path);
+ sec_path = NULL;
+ // check 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 grip (1)
+ rnp_key_handle_t key = NULL;
+ assert_int_equal(
+ RNP_SUCCESS,
+ rnp_locate_key(ffi, "grip", "63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59", &key));
+ assert_non_null(key);
+ char *grip = NULL;
+ assert_rnp_success(rnp_key_get_grip(key, &grip));
+ assert_non_null(grip);
+ assert_string_equal(grip, "63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59");
+ rnp_buffer_destroy(grip);
+ grip = NULL;
+ rnp_key_handle_destroy(key);
+ key = NULL;
+ // check grip (2)
+ assert_int_equal(
+ RNP_SUCCESS,
+ rnp_locate_key(ffi, "grip", "7EAB41A2F46257C36F2892696F5A2F0432499AD3", &key));
+ assert_non_null(key);
+ grip = NULL;
+ assert_rnp_success(rnp_key_get_grip(key, &grip));
+ assert_non_null(grip);
+ assert_string_equal(grip, "7EAB41A2F46257C36F2892696F5A2F0432499AD3");
+ rnp_buffer_destroy(grip);
+ grip = NULL;
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ key = NULL;
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_detect_key_format)
+{
+ char *format = NULL;
+
+ // Wrong parameters
+ auto data = file_to_vec("data/keyrings/1/pubring.gpg");
+ assert_rnp_failure(rnp_detect_key_format(NULL, data.size(), &format));
+ assert_rnp_failure(rnp_detect_key_format(data.data(), 0, &format));
+ assert_rnp_failure(rnp_detect_key_format(data.data(), data.size(), NULL));
+ free(format);
+
+ // GPG
+ format = NULL;
+ data = file_to_vec("data/keyrings/1/pubring.gpg");
+ assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
+ assert_string_equal(format, "GPG");
+ free(format);
+ format = NULL;
+
+ // GPG
+ data = file_to_vec("data/keyrings/1/secring.gpg");
+ assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
+ assert_string_equal(format, "GPG");
+ free(format);
+ format = NULL;
+
+ // GPG (armored)
+ data = file_to_vec("data/keyrings/4/rsav3-p.asc");
+ assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
+ assert_string_equal(format, "GPG");
+ free(format);
+ format = NULL;
+
+ // KBX
+ data = file_to_vec("data/keyrings/3/pubring.kbx");
+ assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
+ assert_string_equal(format, "KBX");
+ free(format);
+ format = NULL;
+
+ // G10
+ data = file_to_vec(
+ "data/keyrings/3/private-keys-v1.d/63E59092E4B1AE9F8E675B2F98AA2B8BD9F4EA59.key");
+ assert_rnp_success(rnp_detect_key_format(data.data(), data.size(), &format));
+ assert_string_equal(format, "G10");
+ free(format);
+ format = NULL;
+
+ // invalid
+ assert_rnp_success(rnp_detect_key_format((uint8_t *) "ABC", 3, &format));
+ assert_null(format);
+}
+
+TEST_F(rnp_tests, test_ffi_load_keys)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ size_t count;
+
+ /* load public keys from pubring */
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load pubring
+ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
+ // again
+ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
+ // check counts
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(0, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+
+ /* load public keys from secring */
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load
+ assert_true(load_keys_gpg(ffi, "data/keyrings/1/secring.gpg"));
+ // check counts
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(0, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+
+ /* load secret keys from secring */
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load secring
+ assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
+ // again
+ assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
+ // check counts
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(0, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+
+ /* load secret keys from pubring */
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load pubring
+ assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/pubring.gpg"));
+ // check counts
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(0, count);
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(0, count);
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+
+ /* concatenate the pubring and secrings into a single buffer */
+ auto pub_buf = file_to_vec("data/keyrings/1/pubring.gpg");
+ auto sec_buf = file_to_vec("data/keyrings/1/secring.gpg");
+ auto buf = pub_buf;
+ buf.reserve(buf.size() + sec_buf.size());
+ buf.insert(buf.end(), sec_buf.begin(), sec_buf.end());
+
+ /* load secret keys from pubring */
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load
+ assert_rnp_success(rnp_input_from_memory(&input, buf.data(), buf.size(), true));
+ assert_non_null(input);
+ // try wrong parameters
+ assert_rnp_failure(rnp_load_keys(NULL, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_failure(rnp_load_keys(ffi, NULL, input, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_failure(rnp_load_keys(ffi, "GPG", NULL, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_failure(rnp_load_keys(ffi, "WRONG", input, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_failure(rnp_load_keys(ffi, "WRONG", input, 0));
+ assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
+ rnp_input_destroy(input);
+ input = NULL;
+ // again
+ assert_rnp_success(rnp_input_from_memory(&input, buf.data(), buf.size(), true));
+ assert_non_null(input);
+ assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_SECRET_KEYS));
+ rnp_input_destroy(input);
+ input = NULL;
+ // check counts
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+}
+
+TEST_F(rnp_tests, test_ffi_clear_keys)
+{
+ rnp_ffi_t ffi = NULL;
+ size_t pub_count;
+ size_t sec_count;
+
+ // setup FFI
+ test_ffi_init(&ffi);
+ // check counts
+ assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
+ assert_int_equal(7, pub_count);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
+ assert_int_equal(7, sec_count);
+ // clear public keys
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
+ assert_int_equal(pub_count, 0);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
+ assert_int_equal(sec_count, 7);
+ // clear secret keys
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
+ assert_int_equal(pub_count, 0);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
+ assert_int_equal(sec_count, 0);
+ // load public and clear secret keys
+ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
+ assert_int_equal(pub_count, 7);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
+ assert_int_equal(sec_count, 0);
+ // load secret keys and clear all
+ assert_true(load_keys_gpg(ffi, "", "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
+ assert_int_equal(7, pub_count);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
+ assert_int_equal(7, sec_count);
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &pub_count));
+ assert_int_equal(pub_count, 0);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &sec_count));
+ assert_int_equal(sec_count, 0);
+ // attempt to clear NULL ffi
+ assert_rnp_failure(rnp_unload_keys(NULL, RNP_KEY_UNLOAD_SECRET));
+ // attempt to pass wrong flags
+ assert_rnp_failure(rnp_unload_keys(ffi, 255));
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+}
+
+TEST_F(rnp_tests, test_ffi_save_keys)
+{
+ rnp_ffi_t ffi = NULL;
+ // setup FFI
+ test_ffi_init(&ffi);
+ char *temp_dir = make_temp_dir();
+ // save pubring
+ auto pub_path = rnp::path::append(temp_dir, "pubring.gpg");
+ assert_false(rnp::path::exists(pub_path));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_path(&output, pub_path.c_str()));
+ assert_rnp_failure(rnp_save_keys(NULL, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_failure(rnp_save_keys(ffi, NULL, output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_failure(rnp_save_keys(ffi, "GPG", NULL, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_failure(rnp_save_keys(ffi, "WRONG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_failure(rnp_save_keys(ffi, "GPG", output, 0));
+ assert_rnp_success(rnp_save_keys(ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(pub_path));
+ // save secring
+ auto sec_path = rnp::path::append(temp_dir, "secring.gpg");
+ assert_false(rnp::path::exists(sec_path));
+ assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
+ assert_rnp_success(rnp_save_keys(ffi, "GPG", output, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(sec_path));
+ // save pubring && secring
+ auto both_path = rnp::path::append(temp_dir, "bothring.gpg");
+ assert_false(rnp::path::exists(both_path));
+ assert_rnp_success(rnp_output_to_path(&output, both_path.c_str()));
+ assert_int_equal(
+ RNP_SUCCESS,
+ rnp_save_keys(
+ ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(both_path));
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+ // start over (read from the saved locations)
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load pubring & secring
+ assert_true(load_keys_gpg(ffi, pub_path, sec_path));
+ // check the counts
+ size_t count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ count = 0;
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+ // load both keyrings from the single file
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load pubring
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_path(&input, both_path.c_str()));
+ assert_non_null(input);
+ assert_int_equal(
+ RNP_SUCCESS,
+ rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
+ rnp_input_destroy(input);
+ input = NULL;
+ // check the counts. We should get both secret and public keys, since public keys are
+ // extracted from the secret ones.
+ count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ count = 0;
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
+ // load pubring & secring
+ assert_true(load_keys_kbx_g10(
+ ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d"));
+ // save pubring
+ pub_path = rnp::path::append(temp_dir, "pubring.kbx");
+ assert_rnp_success(rnp_output_to_path(&output, pub_path.c_str()));
+ assert_rnp_success(rnp_save_keys(ffi, "KBX", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(pub_path));
+ // save secring to file - will fail for G10
+ sec_path = rnp::path::append(temp_dir, "secring.file");
+ assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
+ assert_rnp_failure(rnp_save_keys(ffi, "G10", output, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ // save secring
+ sec_path = rnp::path::append(temp_dir, "private-keys-v1.d");
+ assert_false(rnp::path::exists(sec_path, true));
+ assert_int_equal(0, RNP_MKDIR(sec_path.c_str(), S_IRWXU));
+ assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
+ assert_rnp_success(rnp_save_keys(ffi, "G10", output, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(sec_path, true));
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+ // start over (read from the saved locations)
+ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
+ // load pubring & secring
+ assert_true(load_keys_kbx_g10(ffi, pub_path, sec_path));
+ // check the counts
+ count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(2, count);
+ count = 0;
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(2, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+
+ // final cleanup
+ clean_temp_dir(temp_dir);
+ free(temp_dir);
+}
+
+TEST_F(rnp_tests, test_ffi_load_save_keys_to_utf8_path)
+{
+ const char kbx_pubring_utf8_filename[] = "pubring_\xC2\xA2.kbx";
+ const char g10_secring_utf8_dirname[] = "private-keys-\xC2\xA2.d";
+ const char utf8_filename[] = "bothring_\xC2\xA2.gpg";
+
+ // setup FFI
+ rnp_ffi_t ffi = NULL;
+ test_ffi_init(&ffi);
+ auto temp_dir = make_temp_dir();
+ // save pubring && secring
+ auto both_path = rnp::path::append(temp_dir, utf8_filename);
+ assert_false(rnp::path::exists(both_path));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_path(&output, both_path.c_str()));
+ assert_rnp_success(rnp_save_keys(
+ ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(both_path));
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+ // start over (read from the saved locations)
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load both keyrings from the single file
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_path(&input, both_path.c_str()));
+ assert_non_null(input);
+ assert_rnp_success(
+ rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS));
+ rnp_input_destroy(input);
+ input = NULL;
+ // check the counts. We should get both secret and public keys, since public keys are
+ // extracted from the secret ones.
+ size_t count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ count = 0;
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(7, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
+ // load pubring
+ assert_true(load_keys_kbx_g10(
+ ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d"));
+ // save pubring
+ auto pub_path = rnp::path::append(temp_dir, kbx_pubring_utf8_filename);
+ assert_rnp_success(rnp_output_to_path(&output, pub_path.c_str()));
+ assert_rnp_success(rnp_save_keys(ffi, "KBX", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(pub_path));
+ // save secring
+ auto sec_path = rnp::path::append(temp_dir, g10_secring_utf8_dirname);
+ assert_false(rnp::path::exists(sec_path, true));
+ assert_int_equal(0, RNP_MKDIR(sec_path.c_str(), S_IRWXU));
+ assert_rnp_success(rnp_output_to_path(&output, sec_path.c_str()));
+ assert_rnp_success(rnp_save_keys(ffi, "G10", output, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_true(rnp::path::exists(sec_path, true));
+ // cleanup
+ rnp_ffi_destroy(ffi);
+ ffi = NULL;
+ // start over (read from the saved locations)
+ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
+ // load pubring & secring
+ assert_true(load_keys_kbx_g10(ffi, pub_path, sec_path));
+ // check the counts
+ count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(2, count);
+ count = 0;
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(2, count);
+ // cleanup
+ rnp_ffi_destroy(ffi);
+
+ // final cleanup
+ clean_temp_dir(temp_dir);
+ free(temp_dir);
+}
+
+static size_t
+get_longest_line_length(const std::string &str, const std::set<std::string> lines_to_skip)
+{
+ // eol could be \n or \r\n
+ size_t index = 0;
+ size_t max_len = 0;
+ for (;;) {
+ auto new_index = str.find('\n', index);
+ if (new_index == std::string::npos) {
+ break;
+ }
+ size_t line_length = new_index - index;
+ if (str[new_index - 1] == '\r') {
+ line_length--;
+ }
+ if (line_length > max_len &&
+ lines_to_skip.find(str.substr(index, line_length)) == lines_to_skip.end()) {
+ max_len = line_length;
+ }
+ index = new_index + 1;
+ }
+ return max_len;
+}
+
+TEST_F(rnp_tests, test_ffi_add_userid)
+{
+ rnp_ffi_t ffi = NULL;
+ char * results = NULL;
+ size_t count = 0;
+ rnp_uid_handle_t uid;
+ rnp_signature_handle_t sig;
+ char * hash_alg_name = NULL;
+
+ const char *new_userid = "my new userid <user@example.com>";
+ const char *default_hash_userid = "default hash <user@example.com";
+ const char *ripemd_hash_userid = "ripemd160 <user@example.com";
+
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(rnp_ffi_set_key_provider(ffi, unused_getkeycb, 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);
+ rnp_buffer_destroy(results);
+ 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);
+
+ rnp_key_handle_t key_handle = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "test0", &key_handle));
+ assert_non_null(key_handle);
+
+ assert_rnp_success(rnp_key_get_uid_count(key_handle, &count));
+ assert_int_equal(1, count);
+
+ // protect+lock the key
+ if (!sm2_enabled()) {
+ assert_rnp_failure(rnp_key_protect(key_handle, "pass", "SM4", "CFB", "SM3", 999999));
+ assert_rnp_success(
+ rnp_key_protect(key_handle, "pass", "AES128", "CFB", "SHA256", 999999));
+ } else {
+ assert_rnp_success(rnp_key_protect(key_handle, "pass", "SM4", "CFB", "SM3", 999999));
+ }
+ assert_rnp_success(rnp_key_lock(key_handle));
+
+ // add with NULL parameters
+ assert_rnp_failure(rnp_key_add_uid(NULL, new_userid, NULL, 2147317200, 0x00, false));
+ assert_rnp_failure(rnp_key_add_uid(key_handle, NULL, NULL, 2147317200, 0x00, false));
+
+ // add the userid (no pass provider, should fail)
+ assert_int_equal(
+ RNP_ERROR_BAD_PASSWORD,
+ rnp_key_add_uid(key_handle, new_userid, "SHA256", 2147317200, 0x00, false));
+
+ // actually add the userid
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass"));
+ // attempt to add empty uid
+ assert_rnp_failure(rnp_key_add_uid(key_handle, "", NULL, 2147317200, 0, false));
+ // add with default hash algorithm
+ assert_rnp_success(
+ rnp_key_add_uid(key_handle, default_hash_userid, NULL, 2147317200, 0, false));
+ // check whether key was locked back
+ bool locked = false;
+ assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
+ assert_true(locked);
+ // check if default hash was used
+ assert_rnp_success(rnp_key_get_uid_handle_at(key_handle, 1, &uid));
+ assert_rnp_success(rnp_uid_get_signature_at(uid, 0, &sig));
+ assert_rnp_success(rnp_signature_get_hash_alg(sig, &hash_alg_name));
+ assert_int_equal(strcasecmp(hash_alg_name, DEFAULT_HASH_ALG), 0);
+ rnp_buffer_destroy(hash_alg_name);
+ hash_alg_name = NULL;
+ assert_rnp_success(rnp_signature_handle_destroy(sig));
+ assert_rnp_success(rnp_uid_handle_destroy(uid));
+
+ assert_int_equal(
+ RNP_SUCCESS, rnp_key_add_uid(key_handle, new_userid, "SHA256", 2147317200, 0x00, false));
+
+ int uid_count_expected = 3;
+ int res =
+ rnp_key_add_uid(key_handle, ripemd_hash_userid, "RIPEMD160", 2147317200, 0, false);
+ if (ripemd160_enabled()) {
+ assert_rnp_success(res);
+ uid_count_expected++;
+ } else {
+ assert_rnp_failure(res);
+ }
+
+ assert_rnp_success(rnp_key_get_uid_count(key_handle, &count));
+ assert_int_equal(uid_count_expected, count);
+
+ rnp_key_handle_t key_handle2 = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", new_userid, &key_handle2));
+ assert_non_null(key_handle2);
+
+ rnp_key_handle_destroy(key_handle);
+ rnp_key_handle_destroy(key_handle2);
+ rnp_ffi_destroy(ffi);
+}
+
+static bool
+file_equals(const char *filename, const void *data, size_t len)
+{
+ pgp_source_t msrc = {};
+ bool res = false;
+
+ if (file_to_mem_src(&msrc, filename)) {
+ return false;
+ }
+
+ res = (msrc.size == len) && !memcmp(mem_src_get_memory(&msrc), data, len);
+ src_close(&msrc);
+ return res;
+}
+
+static void
+test_ffi_init_sign_file_input(rnp_input_t *input, rnp_output_t *output)
+{
+ const char *plaintext = "this is some data that will be signed";
+
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(input, "plaintext"));
+ assert_non_null(*input);
+ assert_rnp_success(rnp_output_to_path(output, "signed"));
+ assert_non_null(*output);
+}
+
+static void
+test_ffi_init_sign_memory_input(rnp_input_t *input, rnp_output_t *output)
+{
+ const char *plaintext = "this is some data that will be signed";
+
+ assert_rnp_success(
+ rnp_input_from_memory(input, (uint8_t *) plaintext, strlen(plaintext), true));
+ assert_non_null(*input);
+ if (output) {
+ assert_rnp_success(rnp_output_to_memory(output, 0));
+ assert_non_null(*output);
+ }
+}
+
+static void
+test_ffi_init_verify_file_input(rnp_input_t *input, rnp_output_t *output)
+{
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(input, "signed"));
+ assert_non_null(*input);
+ assert_rnp_success(rnp_output_to_path(output, "recovered"));
+ assert_non_null(*output);
+}
+
+static void
+test_ffi_init_verify_detached_file_input(rnp_input_t *input, rnp_input_t *signature)
+{
+ assert_rnp_success(rnp_input_from_path(input, "plaintext"));
+ assert_non_null(*input);
+ assert_rnp_success(rnp_input_from_path(signature, "signed"));
+ assert_non_null(*signature);
+}
+
+static void
+test_ffi_init_verify_memory_input(rnp_input_t * input,
+ rnp_output_t *output,
+ uint8_t * signed_buf,
+ size_t signed_len)
+{
+ // create input+output
+ assert_rnp_success(rnp_input_from_memory(input, signed_buf, signed_len, false));
+ assert_non_null(*input);
+ assert_rnp_success(rnp_output_to_memory(output, 0));
+ assert_non_null(*output);
+}
+
+static void
+test_ffi_setup_signatures(rnp_ffi_t *ffi, rnp_op_sign_t *op)
+{
+ rnp_key_handle_t key = NULL;
+ rnp_op_sign_signature_t sig = NULL;
+ // set signature times
+ const uint32_t issued = 1516211899; // Unix epoch, nowish
+ const uint32_t expires = 1000000000; // expires later
+ const uint32_t issued2 = 1516211900; // Unix epoch, nowish
+ const uint32_t expires2 = 2000000000; // expires later
+
+ assert_rnp_success(rnp_op_sign_set_armor(*op, true));
+ assert_rnp_success(rnp_op_sign_set_hash(*op, "SHA256"));
+ assert_rnp_success(rnp_op_sign_set_creation_time(*op, issued));
+ assert_rnp_success(rnp_op_sign_set_expiration_time(*op, expires));
+
+ // set pass provider
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(*ffi, ffi_string_password_provider, (void *) "password"));
+
+ // set first signature key
+ assert_rnp_success(rnp_locate_key(*ffi, "userid", "key0-uid2", &key));
+ assert_rnp_success(rnp_op_sign_add_signature(*op, key, NULL));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ key = NULL;
+ // set second signature key
+ assert_rnp_success(rnp_locate_key(*ffi, "userid", "key0-uid1", &key));
+ assert_rnp_success(rnp_op_sign_add_signature(*op, key, &sig));
+ assert_rnp_success(rnp_op_sign_signature_set_creation_time(sig, issued2));
+ assert_rnp_success(rnp_op_sign_signature_set_expiration_time(sig, expires2));
+ assert_rnp_success(rnp_op_sign_signature_set_hash(sig, "SHA512"));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+}
+
+static void
+test_ffi_check_signatures(rnp_op_verify_t *verify)
+{
+ rnp_op_verify_signature_t sig;
+ size_t sig_count;
+ uint32_t sig_create;
+ uint32_t sig_expires;
+ char * hname = NULL;
+ const uint32_t issued = 1516211899; // Unix epoch, nowish
+ const uint32_t expires = 1000000000; // expires later
+ const uint32_t issued2 = 1516211900; // Unix epoch, nowish
+ const uint32_t expires2 = 2000000000; // expires later
+
+ assert_rnp_success(rnp_op_verify_get_signature_count(*verify, &sig_count));
+ assert_int_equal(sig_count, 2);
+ // first signature
+ 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_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
+ assert_int_equal(sig_create, issued);
+ assert_int_equal(sig_expires, expires);
+ assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
+ assert_string_equal(hname, "SHA256");
+ rnp_buffer_destroy(hname);
+ // second signature
+ assert_rnp_success(rnp_op_verify_get_signature_at(*verify, 1, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ assert_rnp_success(rnp_op_verify_signature_get_times(sig, &sig_create, &sig_expires));
+ assert_int_equal(sig_create, issued2);
+ assert_int_equal(sig_expires, expires2);
+ assert_rnp_success(rnp_op_verify_signature_get_hash(sig, &hname));
+ assert_string_equal(hname, "SHA512");
+ rnp_buffer_destroy(hname);
+}
+
+static bool
+test_ffi_check_recovered()
+{
+ pgp_source_t msrc1 = {};
+ pgp_source_t msrc2 = {};
+ bool res = false;
+
+ if (file_to_mem_src(&msrc1, "recovered")) {
+ return false;
+ }
+
+ if (file_to_mem_src(&msrc2, "plaintext")) {
+ goto finish;
+ }
+
+ res = (msrc1.size == msrc2.size) &&
+ !memcmp(mem_src_get_memory(&msrc1), mem_src_get_memory(&msrc2), msrc1.size);
+finish:
+ src_close(&msrc1);
+ src_close(&msrc2);
+ return res;
+}
+
+TEST_F(rnp_tests, test_ffi_signatures_memory)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t op = NULL;
+ rnp_op_verify_t verify;
+ uint8_t * signed_buf;
+ size_t signed_len;
+ uint8_t * verified_buf;
+ size_t verified_len;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ // init input
+ test_ffi_init_sign_memory_input(&input, &output);
+ // create signature operation
+ assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ // execute the operation
+ assert_rnp_success(rnp_op_sign_execute(op));
+ // make sure the output file was created
+ assert_rnp_failure(rnp_output_memory_get_buf(NULL, &signed_buf, &signed_len, true));
+ assert_rnp_failure(rnp_output_memory_get_buf(output, NULL, &signed_len, true));
+ assert_rnp_failure(rnp_output_memory_get_buf(output, &signed_buf, NULL, true));
+ assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
+ assert_non_null(signed_buf);
+ assert_true(signed_len > 0);
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_sign_destroy(op));
+ op = NULL;
+
+ /* now verify */
+ // make sure it is correctly armored
+ assert_int_equal(memcmp(signed_buf, "-----BEGIN PGP MESSAGE-----", 27), 0);
+ // create input and output
+ test_ffi_init_verify_memory_input(&input, &output, signed_buf, signed_len);
+ // call verify
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // check signatures
+ test_ffi_check_signatures(&verify);
+ // get output
+ assert_rnp_success(rnp_output_memory_get_buf(output, &verified_buf, &verified_len, true));
+ assert_non_null(verified_buf);
+ assert_true(verified_len > 0);
+ // cleanup
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+ rnp_buffer_destroy(signed_buf);
+ rnp_buffer_destroy(verified_buf);
+}
+
+TEST_F(rnp_tests, test_ffi_signatures)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t op = NULL;
+ rnp_op_verify_t verify;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ // init file input
+ test_ffi_init_sign_file_input(&input, &output);
+ // create signature operation
+ assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ // execute the operation
+ assert_rnp_success(rnp_op_sign_execute(op));
+ // make sure the output file was created
+ assert_true(rnp_file_exists("signed"));
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_sign_destroy(op));
+ op = NULL;
+
+ /* now verify */
+
+ // create input and output
+ test_ffi_init_verify_file_input(&input, &output);
+ // call verify
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // check signatures
+ test_ffi_check_signatures(&verify);
+ // cleanup
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+ // check output
+ assert_true(test_ffi_check_recovered());
+}
+
+TEST_F(rnp_tests, test_ffi_signatures_detached_memory)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_input_t signature = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t op = NULL;
+ rnp_op_verify_t verify;
+ uint8_t * signed_buf;
+ size_t signed_len;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ // init input
+ test_ffi_init_sign_memory_input(&input, &output);
+ // create signature operation
+ assert_rnp_success(rnp_op_sign_detached_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ // execute the operation
+ assert_rnp_success(rnp_op_sign_execute(op));
+ assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
+ assert_non_null(signed_buf);
+ assert_true(signed_len > 0);
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_sign_destroy(op));
+ op = NULL;
+
+ /* now verify */
+ // make sure it is correctly armored
+ assert_int_equal(memcmp(signed_buf, "-----BEGIN PGP SIGNATURE-----", 29), 0);
+ // create input and output
+ test_ffi_init_sign_memory_input(&input, NULL);
+ assert_rnp_success(rnp_input_from_memory(&signature, signed_buf, signed_len, true));
+ assert_non_null(signature);
+ // call verify
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // check signatures
+ test_ffi_check_signatures(&verify);
+ // cleanup
+ rnp_buffer_destroy(signed_buf);
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_input_destroy(signature));
+ signature = NULL;
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_signatures_detached)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_input_t signature = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t op = NULL;
+ rnp_op_verify_t verify;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ // init file input
+ test_ffi_init_sign_file_input(&input, &output);
+ // create signature operation
+ assert_rnp_success(rnp_op_sign_detached_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ // execute the operation
+ assert_rnp_success(rnp_op_sign_execute(op));
+ // make sure the output file was created
+ assert_true(rnp_file_exists("signed"));
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_sign_destroy(op));
+ op = NULL;
+
+ /* now verify */
+
+ // create input and output
+ test_ffi_init_verify_detached_file_input(&input, &signature);
+ // call verify
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // check signatures
+ test_ffi_check_signatures(&verify);
+ // cleanup
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_input_destroy(signature));
+ signature = NULL;
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_signatures_dump)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_input_t signature = NULL;
+ rnp_op_verify_t verify;
+
+ /* init ffi and inputs */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ load_keys_gpg(ffi, "data/test_stream_signatures/pub.asc");
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
+ assert_rnp_success(
+ rnp_input_from_path(&signature, "data/test_stream_signatures/source.txt.sig"));
+ /* call verify detached to obtain signatures */
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ /* get signature and check it */
+ 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);
+ /* get signature handle */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ rnp_signature_handle_t sighandle = NULL;
+ assert_rnp_success(rnp_op_verify_signature_get_handle(sig, &sighandle));
+ assert_non_null(sighandle);
+ /* check signature type */
+ char *sigtype = NULL;
+ assert_rnp_success(rnp_signature_get_type(sighandle, &sigtype));
+ assert_string_equal(sigtype, "binary");
+ rnp_buffer_destroy(sigtype);
+ /* attempt to validate it via wrong function */
+ assert_int_equal(rnp_signature_is_valid(sighandle, 0), RNP_ERROR_BAD_PARAMETERS);
+ /* cleanup, making sure that sighandle doesn't depend on verify */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_input_destroy(signature));
+ /* check whether getters work on sighandle: algorithm */
+ char *alg = NULL;
+ assert_rnp_success(rnp_signature_get_alg(sighandle, &alg));
+ assert_non_null(alg);
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ /* keyid */
+ char *keyid = NULL;
+ assert_rnp_success(rnp_signature_get_keyid(sighandle, &keyid));
+ assert_non_null(keyid);
+ assert_string_equal(keyid, "5873BD738E575398");
+ rnp_buffer_destroy(keyid);
+ /* creation time */
+ uint32_t create = 0;
+ assert_rnp_success(rnp_signature_get_creation(sighandle, &create));
+ assert_int_equal(create, 1522241943);
+ /* hash algorithm */
+ assert_rnp_success(rnp_signature_get_hash_alg(sighandle, &alg));
+ assert_non_null(alg);
+ assert_string_equal(alg, "SHA256");
+ rnp_buffer_destroy(alg);
+ /* now dump signature packet to json */
+ char *json = NULL;
+ assert_rnp_success(rnp_signature_packet_to_json(sighandle, 0, &json));
+ json_object *jso = json_tokener_parse(json);
+ rnp_buffer_destroy(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ assert_int_equal(json_object_array_length(jso), 1);
+ /* check the signature packet dump */
+ json_object *pkt = json_object_array_get_idx(jso, 0);
+ /* check helper functions */
+ assert_false(check_json_field_int(pkt, "unknown", 4));
+ assert_false(check_json_field_int(pkt, "version", 5));
+ assert_true(check_json_field_int(pkt, "version", 4));
+ assert_true(check_json_field_int(pkt, "type", 0));
+ assert_true(check_json_field_str(pkt, "type.str", "Signature of a binary document"));
+ assert_true(check_json_field_int(pkt, "algorithm", 1));
+ assert_true(check_json_field_str(pkt, "algorithm.str", "RSA (Encrypt or Sign)"));
+ assert_true(check_json_field_int(pkt, "hash algorithm", 8));
+ assert_true(check_json_field_str(pkt, "hash algorithm.str", "SHA256"));
+ assert_true(check_json_field_str(pkt, "lbits", "816e"));
+ json_object *subpkts = NULL;
+ assert_true(json_object_object_get_ex(pkt, "subpackets", &subpkts));
+ assert_non_null(subpkts);
+ assert_true(json_object_is_type(subpkts, json_type_array));
+ assert_int_equal(json_object_array_length(subpkts), 3);
+ /* subpacket 0 */
+ json_object *subpkt = json_object_array_get_idx(subpkts, 0);
+ assert_true(check_json_field_int(subpkt, "type", 33));
+ assert_true(check_json_field_str(subpkt, "type.str", "issuer fingerprint"));
+ assert_true(check_json_field_int(subpkt, "length", 21));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(
+ check_json_field_str(subpkt, "fingerprint", "7a60e671179f9b920f6478a25873bd738e575398"));
+ /* subpacket 1 */
+ subpkt = json_object_array_get_idx(subpkts, 1);
+ assert_true(check_json_field_int(subpkt, "type", 2));
+ assert_true(check_json_field_str(subpkt, "type.str", "signature creation time"));
+ assert_true(check_json_field_int(subpkt, "length", 4));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(check_json_field_int(subpkt, "creation time", 1522241943));
+ /* subpacket 2 */
+ subpkt = json_object_array_get_idx(subpkts, 2);
+ assert_true(check_json_field_int(subpkt, "type", 16));
+ assert_true(check_json_field_str(subpkt, "type.str", "issuer key ID"));
+ assert_true(check_json_field_int(subpkt, "length", 8));
+ assert_true(check_json_field_bool(subpkt, "hashed", false));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(check_json_field_str(subpkt, "issuer keyid", "5873bd738e575398"));
+ json_object_put(jso);
+ rnp_signature_handle_destroy(sighandle);
+ /* check text-mode detached signature */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
+ assert_rnp_success(
+ rnp_input_from_path(&signature, "data/test_stream_signatures/source.txt.text.sig"));
+ /* call verify detached to obtain signatures */
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ /* get signature and check it */
+ sig_count = 0;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
+ assert_int_equal(sig_count, 1);
+ /* get signature handle */
+ 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_op_verify_signature_get_handle(sig, &sighandle));
+ assert_non_null(sighandle);
+ /* check signature type */
+ assert_rnp_success(rnp_signature_get_type(sighandle, &sigtype));
+ assert_string_equal(sigtype, "text");
+ rnp_buffer_destroy(sigtype);
+ /* attempt to validate it via wrong function */
+ assert_int_equal(rnp_signature_is_valid(sighandle, 0), RNP_ERROR_BAD_PARAMETERS);
+ /* cleanup, making sure that sighandle doesn't depend on verify */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_input_destroy(signature));
+ /* check whether getters work on sighandle: algorithm */
+ assert_rnp_success(rnp_signature_get_alg(sighandle, &alg));
+ assert_non_null(alg);
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ /* keyid */
+ assert_rnp_success(rnp_signature_get_keyid(sighandle, &keyid));
+ assert_non_null(keyid);
+ assert_string_equal(keyid, "5873BD738E575398");
+ rnp_buffer_destroy(keyid);
+ /* creation time */
+ assert_rnp_success(rnp_signature_get_creation(sighandle, &create));
+ assert_int_equal(create, 1608118321);
+ /* hash algorithm */
+ assert_rnp_success(rnp_signature_get_hash_alg(sighandle, &alg));
+ assert_non_null(alg);
+ assert_string_equal(alg, "SHA256");
+ rnp_buffer_destroy(alg);
+ /* now dump signature packet to json */
+ assert_rnp_success(rnp_signature_packet_to_json(sighandle, 0, &json));
+ jso = json_tokener_parse(json);
+ rnp_buffer_destroy(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ assert_int_equal(json_object_array_length(jso), 1);
+ /* check the signature packet dump */
+ pkt = json_object_array_get_idx(jso, 0);
+ /* check helper functions */
+ assert_false(check_json_field_int(pkt, "unknown", 4));
+ assert_false(check_json_field_int(pkt, "version", 5));
+ assert_true(check_json_field_int(pkt, "version", 4));
+ assert_true(check_json_field_int(pkt, "type", 1));
+ assert_true(
+ check_json_field_str(pkt, "type.str", "Signature of a canonical text document"));
+ assert_true(check_json_field_int(pkt, "algorithm", 1));
+ assert_true(check_json_field_str(pkt, "algorithm.str", "RSA (Encrypt or Sign)"));
+ assert_true(check_json_field_int(pkt, "hash algorithm", 8));
+ assert_true(check_json_field_str(pkt, "hash algorithm.str", "SHA256"));
+ assert_true(check_json_field_str(pkt, "lbits", "1037"));
+ subpkts = NULL;
+ assert_true(json_object_object_get_ex(pkt, "subpackets", &subpkts));
+ assert_non_null(subpkts);
+ assert_true(json_object_is_type(subpkts, json_type_array));
+ assert_int_equal(json_object_array_length(subpkts), 3);
+ /* subpacket 0 */
+ subpkt = json_object_array_get_idx(subpkts, 0);
+ assert_true(check_json_field_int(subpkt, "type", 33));
+ assert_true(check_json_field_str(subpkt, "type.str", "issuer fingerprint"));
+ assert_true(check_json_field_int(subpkt, "length", 21));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(
+ check_json_field_str(subpkt, "fingerprint", "7a60e671179f9b920f6478a25873bd738e575398"));
+ /* subpacket 1 */
+ subpkt = json_object_array_get_idx(subpkts, 1);
+ assert_true(check_json_field_int(subpkt, "type", 2));
+ assert_true(check_json_field_str(subpkt, "type.str", "signature creation time"));
+ assert_true(check_json_field_int(subpkt, "length", 4));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(check_json_field_int(subpkt, "creation time", 1608118321));
+ /* subpacket 2 */
+ subpkt = json_object_array_get_idx(subpkts, 2);
+ assert_true(check_json_field_int(subpkt, "type", 16));
+ assert_true(check_json_field_str(subpkt, "type.str", "issuer key ID"));
+ assert_true(check_json_field_int(subpkt, "length", 8));
+ assert_true(check_json_field_bool(subpkt, "hashed", false));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(check_json_field_str(subpkt, "issuer keyid", "5873bd738e575398"));
+ json_object_put(jso);
+ rnp_signature_handle_destroy(sighandle);
+
+ /* attempt to validate a timestamp signature instead of detached */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
+ assert_rnp_success(
+ rnp_input_from_path(&signature, "data/test_stream_signatures/signature-timestamp.asc"));
+ /* call verify detached to obtain signatures */
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
+ assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
+ /* get signature and check it */
+ sig_count = 0;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
+ assert_int_equal(sig_count, 1);
+ /* get signature handle */
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_int_equal(rnp_op_verify_signature_get_status(sig), RNP_ERROR_SIGNATURE_INVALID);
+ assert_rnp_success(rnp_op_verify_signature_get_handle(sig, &sighandle));
+ assert_non_null(sighandle);
+ /* check signature type */
+ assert_rnp_success(rnp_signature_get_type(sighandle, &sigtype));
+ assert_string_equal(sigtype, "timestamp");
+ rnp_buffer_destroy(sigtype);
+ /* attempt to validate it via wrong function */
+ assert_int_equal(rnp_signature_is_valid(sighandle, 0), RNP_ERROR_BAD_PARAMETERS);
+ /* cleanup, making sure that sighandle doesn't depend on verify */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_input_destroy(signature));
+ /* check whether getters work on sighandle: algorithm */
+ assert_rnp_success(rnp_signature_get_alg(sighandle, &alg));
+ assert_non_null(alg);
+ assert_string_equal(alg, "DSA");
+ rnp_buffer_destroy(alg);
+ /* keyid */
+ assert_rnp_success(rnp_signature_get_keyid(sighandle, &keyid));
+ assert_non_null(keyid);
+ assert_string_equal(keyid, "2D727CC768697734");
+ rnp_buffer_destroy(keyid);
+ /* creation time */
+ assert_rnp_success(rnp_signature_get_creation(sighandle, &create));
+ assert_int_equal(create, 1535389094);
+ /* hash algorithm */
+ assert_rnp_success(rnp_signature_get_hash_alg(sighandle, &alg));
+ assert_non_null(alg);
+ assert_string_equal(alg, "SHA512");
+ rnp_buffer_destroy(alg);
+ /* now dump signature packet to json */
+ assert_rnp_success(rnp_signature_packet_to_json(sighandle, 0, &json));
+ jso = json_tokener_parse(json);
+ rnp_buffer_destroy(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ assert_int_equal(json_object_array_length(jso), 1);
+ /* check the signature packet dump */
+ pkt = json_object_array_get_idx(jso, 0);
+ /* check helper functions */
+ assert_false(check_json_field_int(pkt, "unknown", 4));
+ assert_false(check_json_field_int(pkt, "version", 5));
+ assert_true(check_json_field_int(pkt, "version", 4));
+ assert_true(check_json_field_int(pkt, "type", 0x40));
+ assert_true(check_json_field_str(pkt, "type.str", "Timestamp signature"));
+ assert_true(check_json_field_int(pkt, "algorithm", 17));
+ assert_true(check_json_field_str(pkt, "algorithm.str", "DSA"));
+ assert_true(check_json_field_int(pkt, "hash algorithm", 10));
+ assert_true(check_json_field_str(pkt, "hash algorithm.str", "SHA512"));
+ assert_true(check_json_field_str(pkt, "lbits", "2727"));
+ subpkts = NULL;
+ assert_true(json_object_object_get_ex(pkt, "subpackets", &subpkts));
+ assert_non_null(subpkts);
+ assert_true(json_object_is_type(subpkts, json_type_array));
+ assert_int_equal(json_object_array_length(subpkts), 7);
+ /* subpacket 0 */
+ subpkt = json_object_array_get_idx(subpkts, 0);
+ assert_true(check_json_field_int(subpkt, "type", 2));
+ assert_true(check_json_field_str(subpkt, "type.str", "signature creation time"));
+ assert_true(check_json_field_int(subpkt, "length", 4));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", true));
+ assert_true(check_json_field_int(subpkt, "creation time", 1535389094));
+ /* subpacket 1 */
+ subpkt = json_object_array_get_idx(subpkts, 1);
+ assert_true(check_json_field_int(subpkt, "type", 7));
+ assert_true(check_json_field_str(subpkt, "type.str", "revocable"));
+ assert_true(check_json_field_int(subpkt, "length", 1));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", true));
+ assert_true(check_json_field_bool(subpkt, "revocable", false));
+ /* subpacket 2 */
+ subpkt = json_object_array_get_idx(subpkts, 2);
+ assert_true(check_json_field_int(subpkt, "type", 16));
+ assert_true(check_json_field_str(subpkt, "type.str", "issuer key ID"));
+ assert_true(check_json_field_int(subpkt, "length", 8));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", true));
+ assert_true(check_json_field_str(subpkt, "issuer keyid", "2d727cc768697734"));
+ /* subpacket 3 */
+ subpkt = json_object_array_get_idx(subpkts, 3);
+ assert_true(check_json_field_int(subpkt, "type", 20));
+ assert_true(check_json_field_str(subpkt, "type.str", "notation data"));
+ assert_true(check_json_field_int(subpkt, "length", 51));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(check_json_field_bool(subpkt, "human", true));
+ assert_true(check_json_field_str(subpkt, "name", "serialnumber@dots.testdomain.test"));
+ assert_true(check_json_field_str(subpkt, "value", "TEST000001"));
+ /* subpacket 4 */
+ subpkt = json_object_array_get_idx(subpkts, 4);
+ assert_true(check_json_field_int(subpkt, "type", 26));
+ assert_true(check_json_field_str(subpkt, "type.str", "policy URI"));
+ assert_true(check_json_field_int(subpkt, "length", 44));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(
+ check_json_field_str(subpkt, "uri", "https://policy.testdomain.test/timestamping/"));
+ /* subpacket 5 */
+ subpkt = json_object_array_get_idx(subpkts, 5);
+ assert_true(check_json_field_int(subpkt, "type", 32));
+ assert_true(check_json_field_str(subpkt, "type.str", "embedded signature"));
+ assert_true(check_json_field_int(subpkt, "length", 105));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", true));
+ json_object *embsig = NULL;
+ assert_true(json_object_object_get_ex(subpkt, "signature", &embsig));
+ assert_true(check_json_field_int(embsig, "version", 4));
+ assert_true(check_json_field_int(embsig, "type", 0));
+ assert_true(check_json_field_str(embsig, "type.str", "Signature of a binary document"));
+ assert_true(check_json_field_int(embsig, "algorithm", 17));
+ assert_true(check_json_field_str(embsig, "algorithm.str", "DSA"));
+ assert_true(check_json_field_int(embsig, "hash algorithm", 10));
+ assert_true(check_json_field_str(embsig, "hash algorithm.str", "SHA512"));
+ assert_true(check_json_field_str(embsig, "lbits", "a386"));
+ /* subpacket 6 */
+ subpkt = json_object_array_get_idx(subpkts, 6);
+ assert_true(check_json_field_int(subpkt, "type", 33));
+ assert_true(check_json_field_str(subpkt, "type.str", "issuer fingerprint"));
+ assert_true(check_json_field_int(subpkt, "length", 21));
+ assert_true(check_json_field_bool(subpkt, "hashed", true));
+ assert_true(check_json_field_bool(subpkt, "critical", false));
+ assert_true(
+ check_json_field_str(subpkt, "fingerprint", "a0ff4590bb6122edef6e3c542d727cc768697734"));
+ json_object_put(jso);
+ rnp_signature_handle_destroy(sighandle);
+
+ /* cleanup ffi */
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_locate_key)
+{
+ 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"));
+
+ // keyid
+ {
+ static const char *ids[] = {"7BC6709B15C23A4A",
+ "1ED63EE56FADC34D",
+ "1D7E8A5393C997A8",
+ "8A05B89FAD5ADED1",
+ "2FCADF05FFA501BB",
+ "54505A936A4A970E",
+ "326EF111425D14A5"};
+ for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
+ const char * id = ids[i];
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", id, &key));
+ assert_non_null(key);
+ rnp_key_handle_destroy(key);
+ }
+ // invalid - value did not change
+ {
+ rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
+ assert_rnp_failure(rnp_locate_key(ffi, "keyid", "invalid-keyid", &key));
+ assert_true(key == (rnp_key_handle_t) 0x111);
+ }
+ // valid but non-existent - null returned
+ {
+ rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "AAAAAAAAAAAAAAAA", &key));
+ assert_null(key);
+ }
+ }
+
+ // userid
+ {
+ static const char *ids[] = {
+ "key0-uid0", "key0-uid1", "key0-uid2", "key1-uid0", "key1-uid2", "key1-uid1"};
+ for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
+ const char * id = ids[i];
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", id, &key));
+ assert_non_null(key);
+ rnp_key_handle_destroy(key);
+ }
+ // valid but non-existent
+ {
+ rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "bad-userid", &key));
+ assert_null(key);
+ }
+ }
+
+ // fingerprint
+ {
+ static const char *ids[] = {"E95A3CBF583AA80A2CCC53AA7BC6709B15C23A4A",
+ "E332B27CAF4742A11BAA677F1ED63EE56FADC34D",
+ "C5B15209940A7816A7AF3FB51D7E8A5393C997A8",
+ "5CD46D2A0BD0B8CFE0B130AE8A05B89FAD5ADED1",
+ "BE1C4AB951F4C2F6B604C7F82FCADF05FFA501BB",
+ "A3E94DE61A8CB229413D348E54505A936A4A970E",
+ "57F8ED6E5C197DB63C60FFAF326EF111425D14A5"};
+ for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
+ const char * id = ids[i];
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "fingerprint", id, &key));
+ assert_non_null(key);
+ rnp_key_handle_destroy(key);
+ }
+ // invalid
+ {
+ rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
+ assert_rnp_failure(rnp_locate_key(ffi, "fingerprint", "invalid-fpr", &key));
+ assert_true(key == (rnp_key_handle_t) 0x111);
+ }
+ // valid but non-existent
+ {
+ rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
+ assert_rnp_success(rnp_locate_key(
+ ffi, "fingerprint", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", &key));
+ assert_null(key);
+ }
+ }
+
+ // grip
+ {
+ static const char *ids[] = {"66D6A0800A3FACDE0C0EB60B16B3669ED380FDFA",
+ "D9839D61EDAF0B3974E0A4A341D6E95F3479B9B7",
+ "B1CC352FEF9A6BD4E885B5351840EF9306D635F0",
+ "E7C8860B70DC727BED6DB64C633683B41221BB40",
+ "B2A7F6C34AA2C15484783E9380671869A977A187",
+ "43C01D6D96BE98C3C87FE0F175870ED92DE7BE45",
+ "8082FE753013923972632550838A5F13D81F43B9"};
+ for (size_t i = 0; i < ARRAY_SIZE(ids); i++) {
+ const char * id = ids[i];
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "grip", id, &key));
+ assert_non_null(key);
+ rnp_key_handle_destroy(key);
+ }
+ // invalid
+ {
+ rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
+ assert_rnp_failure(rnp_locate_key(ffi, "grip", "invalid-fpr", &key));
+ assert_true(key == (rnp_key_handle_t) 0x111);
+ }
+ // valid but non-existent
+ {
+ rnp_key_handle_t key = (rnp_key_handle_t) 0x111;
+ assert_rnp_success(
+ rnp_locate_key(ffi, "grip", "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", &key));
+ assert_null(key);
+ }
+ }
+
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_signatures_detached_memory_g10)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_input_t input_sig = NULL;
+ rnp_output_t output = NULL;
+ rnp_key_handle_t key = NULL;
+ rnp_op_sign_t opsign = NULL;
+ rnp_op_verify_t opverify = NULL;
+ const char * data = "my data";
+ uint8_t * sig = NULL;
+ size_t sig_len = 0;
+
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "KBX", "G10"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ // load our keyrings
+ assert_true(load_keys_kbx_g10(
+ ffi, "data/keyrings/3/pubring.kbx", "data/keyrings/3/private-keys-v1.d"));
+
+ // find our signing key
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "4BE147BB22DF1E60", &key));
+ assert_non_null(key);
+
+ // create our input
+ assert_rnp_success(rnp_input_from_memory(&input, (uint8_t *) data, strlen(data), false));
+ assert_non_null(input);
+ // create our output
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+ // create the signing operation
+ assert_rnp_success(rnp_op_sign_detached_create(&opsign, ffi, input, output));
+ assert_non_null(opsign);
+
+ // add the signer
+ assert_rnp_success(rnp_op_sign_add_signature(opsign, key, NULL));
+ // execute the signing operation
+ assert_rnp_success(rnp_op_sign_execute(opsign));
+ // get the resulting signature
+ assert_rnp_success(rnp_output_memory_get_buf(output, &sig, &sig_len, true));
+ assert_non_null(sig);
+ assert_int_not_equal(0, sig_len);
+ // cleanup
+ rnp_op_sign_destroy(opsign);
+ opsign = NULL;
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+
+ // verify
+ // create our data input
+ assert_rnp_success(rnp_input_from_memory(&input, (uint8_t *) data, strlen(data), false));
+ assert_non_null(input);
+ // create our signature input
+ assert_rnp_success(rnp_input_from_memory(&input_sig, sig, sig_len, true));
+ assert_non_null(input_sig);
+ // create our operation
+ assert_rnp_success(rnp_op_verify_detached_create(&opverify, ffi, input, input_sig));
+ assert_non_null(opverify);
+ // execute the verification
+ assert_rnp_success(rnp_op_verify_execute(opverify));
+ // cleanup
+ rnp_op_verify_destroy(opverify);
+ opverify = NULL;
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_input_destroy(input_sig);
+ input_sig = NULL;
+
+ // verify (tamper with signature)
+ // create our data input
+ assert_rnp_success(rnp_input_from_memory(&input, (uint8_t *) data, strlen(data), false));
+ assert_non_null(input);
+ // create our signature input
+ sig[sig_len - 5] ^= 0xff;
+ assert_rnp_success(rnp_input_from_memory(&input_sig, sig, sig_len, true));
+ assert_non_null(input_sig);
+ // create our operation
+ assert_rnp_success(rnp_op_verify_detached_create(&opverify, ffi, input, input_sig));
+ assert_non_null(opverify);
+ // execute the verification
+ assert_rnp_failure(rnp_op_verify_execute(opverify));
+ // cleanup
+ rnp_op_verify_destroy(opverify);
+ opverify = NULL;
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_input_destroy(input_sig);
+ input_sig = NULL;
+
+ // cleanup
+ rnp_buffer_destroy(sig);
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_enarmor_dearmor)
+{
+ std::string data;
+
+ // enarmor plain message
+ const std::string msg("this is a test");
+ data.clear();
+ {
+ uint8_t * buf = NULL;
+ size_t buf_size = 0;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) msg.data(), msg.size(), true));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ assert_rnp_success(rnp_enarmor(input, output, "message"));
+
+ rnp_output_memory_get_buf(output, &buf, &buf_size, false);
+ data = std::string(buf, buf + buf_size);
+ assert_true(starts_with(data, "-----BEGIN PGP MESSAGE-----\r\n"));
+ assert_true(ends_with(data, "-----END PGP MESSAGE-----\r\n"));
+
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+ {
+ uint8_t * buf = NULL;
+ size_t buf_size = 0;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) data.data(), data.size(), true));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ assert_rnp_success(rnp_dearmor(input, output));
+
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
+ std::string dearmored(buf, buf + buf_size);
+ assert_true(msg == dearmored);
+
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+
+ // enarmor public key
+ data.clear();
+ {
+ uint8_t * buf = NULL;
+ size_t buf_size = 0;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // enarmor
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ assert_rnp_success(rnp_enarmor(input, output, NULL));
+
+ rnp_output_memory_get_buf(output, &buf, &buf_size, false);
+ data = std::string(buf, buf + buf_size);
+ assert_true(starts_with(data, "-----BEGIN PGP PUBLIC KEY BLOCK-----\r\n"));
+ assert_true(ends_with(data, "-----END PGP PUBLIC KEY BLOCK-----\r\n"));
+
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+ // dearmor public key
+ {
+ uint8_t * buf = NULL;
+ size_t buf_size = 0;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) data.data(), data.size(), true));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ assert_rnp_success(rnp_dearmor(input, output));
+
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
+ std::string dearmored(buf, buf + buf_size);
+ std::ifstream inf("data/keyrings/1/pubring.gpg", std::ios::binary | std::ios::ate);
+ std::string from_disk(inf.tellg(), ' ');
+ inf.seekg(0);
+ inf.read(&from_disk[0], from_disk.size());
+ inf.close();
+ assert_true(dearmored == from_disk);
+
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+ // test truncated armored data
+ {
+ std::ifstream keyf("data/test_stream_key_load/rsa-rsa-pub.asc",
+ std::ios::binary | std::ios::ate);
+ std::string keystr(keyf.tellg(), ' ');
+ keyf.seekg(0);
+ keyf.read(&keystr[0], keystr.size());
+ keyf.close();
+ for (size_t sz = keystr.size() - 2; sz > 0; sz--) {
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) keystr.data(), sz, true));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+ }
+}
+
+TEST_F(rnp_tests, test_ffi_dearmor_edge_cases)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/long_header_line.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/empty_header_line.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(rnp_input_from_path(
+ &input, "data/test_stream_armor/64k_whitespace_before_armored_message.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* Armor header starts and fits in the first 1024 bytes of the input. Prepended by
+ * whitespaces. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/1024_peek_buf.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/blank_line_with_whitespace.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/duplicate_header_line.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/long_header_line_1024.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/long_header_line_64k.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/long_header_nameline_64k.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* Armored message encoded in a single >64k text line */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/message_64k_oneline.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 68647);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_header_line.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* invalid, > 127 (negative char), preceding the armor header - just warning */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_header.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dearmor(input, output));
+ buf = NULL;
+ len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 2226);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* invalid, > 127, base64 chars at positions 1..4 */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_1.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_2.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_3.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_base64_4.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* invalid, > 127 base64 char in the crc */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_chars_crc.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* too short armor header */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/too_short_header.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* wrong base64 padding */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_armor/wrong_b64_trailer.asc"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_dearmor(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+}
+
+TEST_F(rnp_tests, test_ffi_customized_enarmor)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_output_t armor_layer = NULL;
+ const std::string msg("this is a test long enough to have more than 76 characters in "
+ "enarmored representation");
+ std::set<std::string> lines_to_skip{"-----BEGIN PGP MESSAGE-----",
+ "-----END PGP MESSAGE-----"};
+
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "message"));
+ // should fail when trying to set line length on non-armor output
+ assert_rnp_failure(rnp_output_armor_set_line_length(output, 64));
+ // should fail when trying to set zero line length
+ assert_rnp_failure(rnp_output_armor_set_line_length(armor_layer, 0));
+ // should fail when trying to set line length less than the minimum allowed 16
+ assert_rnp_failure(rnp_output_armor_set_line_length(armor_layer, 15));
+ assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, 16));
+ assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, 76));
+ // should fail when trying to set line length greater than the maximum allowed 76
+ assert_rnp_failure(rnp_output_armor_set_line_length(armor_layer, 77));
+ assert_rnp_success(rnp_output_destroy(armor_layer));
+ assert_rnp_success(rnp_output_destroy(output));
+
+ for (size_t llen = 16; llen <= 76; llen++) {
+ std::string data;
+ uint8_t * buf = NULL;
+ size_t buf_size = 0;
+
+ input = NULL;
+ output = NULL;
+ armor_layer = NULL;
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) msg.data(), msg.size(), true));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "message"));
+ assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));
+ assert_rnp_success(rnp_output_pipe(input, armor_layer));
+ assert_rnp_success(rnp_output_finish(armor_layer));
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
+ data = std::string(buf, buf + buf_size);
+ auto effective_llen = get_longest_line_length(data, lines_to_skip);
+ assert_int_equal(llen / 4, effective_llen / 4);
+ assert_true(llen >= effective_llen);
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(armor_layer));
+ assert_rnp_success(rnp_output_destroy(output));
+
+ // test that the dearmored message is correct
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) data.data(), data.size(), true));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ assert_rnp_success(rnp_dearmor(input, output));
+
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
+ std::string dearmored(buf, buf + buf_size);
+ assert_true(msg == dearmored);
+
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ }
+}
+
+TEST_F(rnp_tests, test_ffi_version)
+{
+ const uint32_t version = rnp_version();
+ const uint32_t major = rnp_version_major(version);
+ const uint32_t minor = rnp_version_minor(version);
+ const uint32_t patch = rnp_version_patch(version);
+
+ // reconstruct the version string
+ assert_string_equal(fmt("%d.%d.%d", major, minor, patch).c_str(), rnp_version_string());
+
+ // full version string should probably be at least as long as regular version string
+ assert_true(strlen(rnp_version_string_full()) >= strlen(rnp_version_string()));
+
+ // reconstruct the version value
+ assert_int_equal(version, rnp_version_for(major, minor, patch));
+
+ // check out-of-range handling
+ assert_int_equal(0, rnp_version_for(1024, 0, 0));
+ assert_int_equal(0, rnp_version_for(0, 1024, 0));
+ assert_int_equal(0, rnp_version_for(0, 0, 1024));
+
+ // check component extraction again
+ assert_int_equal(rnp_version_major(rnp_version_for(5, 4, 3)), 5);
+ assert_int_equal(rnp_version_minor(rnp_version_for(5, 4, 3)), 4);
+ assert_int_equal(rnp_version_patch(rnp_version_for(5, 4, 3)), 3);
+
+ // simple comparisons
+ assert_true(rnp_version_for(1, 0, 1) > rnp_version_for(1, 0, 0));
+ assert_true(rnp_version_for(1, 1, 0) > rnp_version_for(1, 0, 1023));
+ assert_true(rnp_version_for(2, 0, 0) > rnp_version_for(1, 1023, 1023));
+
+ // commit timestamp
+ const uint64_t timestamp = rnp_version_commit_timestamp();
+ assert_true(!timestamp || (timestamp >= 1639439116));
+}
+
+TEST_F(rnp_tests, test_ffi_backend_version)
+{
+ assert_non_null(rnp_backend_string());
+ assert_non_null(rnp_backend_version());
+
+ assert_true(strlen(rnp_backend_string()) > 0 && strlen(rnp_backend_string()) < 255);
+ assert_true(strlen(rnp_backend_version()) > 0 && strlen(rnp_backend_version()) < 255);
+}
+
+void check_loaded_keys(const char * format,
+ bool armored,
+ uint8_t * buf,
+ size_t buf_len,
+ const char * id_type,
+ const std::vector<std::string> &expected_ids,
+ bool secret);
+
+TEST_F(rnp_tests, test_ffi_key_export_customized_enarmor)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_output_t output = NULL;
+ rnp_output_t armor_layer = NULL;
+ rnp_key_handle_t key = NULL;
+ uint8_t * buf = NULL;
+ size_t buf_len = 0;
+ std::set<std::string> lines_to_skip{"-----BEGIN PGP PUBLIC KEY BLOCK-----",
+ "-----END PGP PUBLIC KEY BLOCK-----",
+ "-----BEGIN PGP PRIVATE KEY BLOCK-----",
+ "-----END PGP PRIVATE KEY BLOCK-----"};
+ // setup FFI
+ test_ffi_init(&ffi);
+
+ for (size_t llen = 16; llen <= 76; llen++) {
+ // primary pub only
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+ assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "public key"));
+ assert_non_null(armor_layer);
+ assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));
+
+ // export
+ assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_PUBLIC));
+ assert_rnp_success(rnp_output_finish(armor_layer));
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+ std::string data = std::string(buf, buf + buf_len);
+ auto effective_llen = get_longest_line_length(data, lines_to_skip);
+ assert_int_equal(llen / 4, effective_llen / 4);
+ assert_true(llen >= effective_llen);
+
+ // check results
+ check_loaded_keys("GPG", true, buf, buf_len, "keyid", {"2FCADF05FFA501BB"}, false);
+
+ // cleanup
+ rnp_output_destroy(armor_layer);
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // primary sec only
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+ assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "secret key"));
+ assert_non_null(armor_layer);
+ assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));
+
+ // export
+ assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_SECRET));
+ assert_rnp_success(rnp_output_finish(armor_layer));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+ std::string data = std::string(buf, buf + buf_len);
+ auto effective_llen = get_longest_line_length(data, lines_to_skip);
+ assert_int_equal(llen / 4, effective_llen / 4);
+ assert_true(llen >= effective_llen);
+
+ // check results
+ check_loaded_keys("GPG", true, buf, buf_len, "keyid", {"2FCADF05FFA501BB"}, true);
+
+ // cleanup
+ rnp_output_destroy(armor_layer);
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // sub pub
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "54505A936A4A970E", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+ assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "public key"));
+ assert_non_null(armor_layer);
+ assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));
+
+ // export
+ assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_PUBLIC));
+ assert_rnp_success(rnp_output_finish(armor_layer));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+ std::string data = std::string(buf, buf + buf_len);
+ auto effective_llen = get_longest_line_length(data, lines_to_skip);
+ assert_int_equal(llen / 4, effective_llen / 4);
+ assert_true(llen >= effective_llen);
+
+ // check results
+ check_loaded_keys("GPG",
+ true,
+ buf,
+ buf_len,
+ "keyid",
+ {"2FCADF05FFA501BB", "54505A936A4A970E"},
+ false);
+
+ // cleanup
+ rnp_output_destroy(armor_layer);
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+
+ // sub sec
+ {
+ // locate key
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "54505A936A4A970E", &key));
+ assert_non_null(key);
+
+ // create output
+ output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_non_null(output);
+ assert_rnp_success(rnp_output_to_armor(output, &armor_layer, "secret key"));
+ assert_non_null(armor_layer);
+ assert_rnp_success(rnp_output_armor_set_line_length(armor_layer, llen));
+
+ // export
+ assert_rnp_success(rnp_key_export(key, armor_layer, RNP_KEY_EXPORT_SECRET));
+ assert_rnp_success(rnp_output_finish(armor_layer));
+
+ // get output
+ buf = NULL;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_len, false));
+ assert_non_null(buf);
+ std::string data = std::string(buf, buf + buf_len);
+ auto effective_llen = get_longest_line_length(data, lines_to_skip);
+ assert_int_equal(llen / 4, effective_llen / 4);
+ assert_true(llen >= effective_llen);
+
+ // check results
+ check_loaded_keys("GPG",
+ true,
+ buf,
+ buf_len,
+ "keyid",
+ {"2FCADF05FFA501BB", "54505A936A4A970E"},
+ true);
+
+ // cleanup
+ rnp_output_destroy(armor_layer);
+ rnp_output_destroy(output);
+ rnp_key_handle_destroy(key);
+ }
+ }
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_dump)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_key_handle_t key = NULL;
+ char * json = NULL;
+ json_object * jso = NULL;
+
+ // setup FFI
+ test_ffi_init(&ffi);
+
+ // locate key
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ // dump public key and check results
+ assert_rnp_success(rnp_key_packets_to_json(
+ key, false, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
+ assert_non_null(json);
+ jso = json_tokener_parse(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ json_object_put(jso);
+ rnp_buffer_destroy(json);
+
+ // dump secret key and check results
+ assert_rnp_success(rnp_key_packets_to_json(
+ key, true, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
+ assert_non_null(json);
+ jso = json_tokener_parse(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ json_object_put(jso);
+ rnp_buffer_destroy(json);
+
+ // cleanup
+ rnp_key_handle_destroy(key);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_dump_edge_cases)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* secret key, stored on gpg card, with too large card serial len */
+ rnp_input_t input = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp"));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
+ rnp_input_destroy(input);
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ std::string dstr(buf, buf + len);
+ assert_true(
+ dstr.find("card serial number: 0x000102030405060708090a0b0c0d0e0f (16 bytes)") !=
+ std::string::npos);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-2-card-len.pgp"));
+ char *json = NULL;
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ rnp_input_destroy(input);
+ dstr = json;
+ assert_true(dstr.find("\"card serial number\":\"000102030405060708090a0b0c0d0e0f\"") !=
+ std::string::npos);
+ rnp_buffer_destroy(json);
+
+ /* secret key, stored with unknown gpg s2k */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-3.pgp"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ dstr = std::string(buf, buf + len);
+ assert_true(dstr.find("Unknown experimental s2k: 0x474e5503 (4 bytes)") !=
+ std::string::npos);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-3.pgp"));
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ rnp_input_destroy(input);
+ dstr = json;
+ assert_true(dstr.find("\"unknown experimental\":\"474e5503\"") != std::string::npos);
+ rnp_buffer_destroy(json);
+
+ /* secret key, stored with unknown s2k */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-unknown.pgp"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
+ rnp_input_destroy(input);
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ dstr = std::string(buf, buf + len);
+ assert_true(dstr.find("Unknown experimental s2k: 0x554e4b4e (4 bytes)") !=
+ std::string::npos);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_edge_cases/alice-s2k-101-unknown.pgp"));
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ rnp_input_destroy(input);
+ dstr = json;
+ assert_true(dstr.find("\"unknown experimental\":\"554e4b4e\"") != std::string::npos);
+ rnp_buffer_destroy(json);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_userid_dump_has_no_special_chars)
+{
+ rnp_ffi_t ffi = NULL;
+ char * json = NULL;
+ json_object *jso = NULL;
+ const char * trackers[] = {
+ "userid\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f@rnp",
+ "userid\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e\x1f@rnp"};
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ for (int i = 0; i < 2; i++) {
+ // generate RSA key
+ rnp_op_generate_t keygen = NULL;
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ // user id
+ assert_rnp_success(rnp_op_generate_set_userid(keygen, trackers[0]));
+ // now execute keygen operation
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ keygen = NULL;
+
+ // dump public key and check results
+ assert_rnp_success(rnp_key_packets_to_json(
+ key, false, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
+ assert_non_null(json);
+ for (char c = 1; c < 0x20; c++) {
+ if (c != '\n') {
+ assert_null(strchr(json, c));
+ }
+ }
+ jso = json_tokener_parse(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ json_object_put(jso);
+ rnp_buffer_destroy(json);
+
+ // cleanup
+ rnp_key_handle_destroy(key);
+ }
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_pkt_dump)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ char * json = NULL;
+ json_object *jso = NULL;
+
+ // setup FFI
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ // setup input
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
+
+ // try with wrong parameters
+ assert_rnp_failure(rnp_dump_packets_to_json(input, 0, NULL));
+ assert_rnp_failure(rnp_dump_packets_to_json(NULL, 0, &json));
+ assert_rnp_failure(rnp_dump_packets_to_json(input, 117, &json));
+ // dump
+ assert_rnp_success(rnp_dump_packets_to_json(
+ input, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
+ rnp_input_destroy(input);
+ input = NULL;
+ assert_non_null(json);
+
+ // check results
+ jso = json_tokener_parse(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ /* make sure that correct number of packets dumped */
+ assert_int_equal(json_object_array_length(jso), 35);
+ json_object_put(jso);
+ rnp_buffer_destroy(json);
+
+ // setup input and output
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ // try with wrong parameters
+ assert_rnp_failure(rnp_dump_packets_to_output(input, NULL, 0));
+ assert_rnp_failure(rnp_dump_packets_to_output(NULL, output, 0));
+ assert_rnp_failure(rnp_dump_packets_to_output(input, output, 117));
+ // dump
+ assert_rnp_success(
+ rnp_dump_packets_to_output(input, output, RNP_DUMP_MPI | RNP_DUMP_RAW | RNP_DUMP_GRIP));
+
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ /* make sure output is not cut */
+ assert_true(len > 45000);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ // dump data with marker packet
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(
+ rnp_dump_packets_to_output(input, output, RNP_DUMP_MPI | RNP_DUMP_RAW | RNP_DUMP_GRIP));
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ buf[len - 1] = '\0';
+ assert_non_null(strstr((char *) buf, "contents: PGP"));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ // dump data with marker packet to json
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
+ assert_rnp_success(rnp_dump_packets_to_json(
+ input, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
+ assert_non_null(strstr(json, "\"contents\":\"PGP\""));
+ rnp_buffer_destroy(json);
+ rnp_input_destroy(input);
+
+ // dump data with malformed marker packet
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.marker.malf"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(
+ rnp_dump_packets_to_output(input, output, RNP_DUMP_MPI | RNP_DUMP_RAW | RNP_DUMP_GRIP));
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ buf[len - 1] = '\0';
+ assert_non_null(strstr((char *) buf, "contents: invalid"));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ // dump data with malformed marker packet to json
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.marker.malf"));
+ assert_rnp_success(rnp_dump_packets_to_json(
+ input, RNP_JSON_DUMP_MPI | RNP_JSON_DUMP_RAW | RNP_JSON_DUMP_GRIP, &json));
+ assert_non_null(strstr(json, "\"contents\":\"invalid\""));
+ rnp_buffer_destroy(json);
+ rnp_input_destroy(input);
+
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_rsa_v3_dump)
+{
+ rnp_input_t input = NULL;
+ char * json = NULL;
+
+ /* dump rsav3 key to json via FFI */
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/4/rsav3-p.asc"));
+ assert_rnp_success(rnp_dump_packets_to_json(input, RNP_JSON_DUMP_GRIP, &json));
+ rnp_input_destroy(input);
+ /* parse dump */
+ json_object *jso = json_tokener_parse(json);
+ rnp_buffer_destroy(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ json_object *rsapkt = json_object_array_get_idx(jso, 0);
+ assert_non_null(rsapkt);
+ assert_true(json_object_is_type(rsapkt, json_type_object));
+ /* check algorithm string */
+ json_object *fld = NULL;
+ assert_true(json_object_object_get_ex(rsapkt, "algorithm.str", &fld));
+ assert_non_null(fld);
+ const char *str = json_object_get_string(fld);
+ assert_non_null(str);
+ assert_string_equal(str, "RSA (Encrypt or Sign)");
+ /* check fingerprint */
+ fld = NULL;
+ assert_true(json_object_object_get_ex(rsapkt, "fingerprint", &fld));
+ assert_non_null(fld);
+ str = json_object_get_string(fld);
+ assert_non_null(str);
+ assert_string_equal(str, "06a044022bb5aa7991077466aeba2ce7");
+ json_object_put(jso);
+}
+
+TEST_F(rnp_tests, test_ffi_load_userattr)
+{
+ rnp_ffi_t ffi = NULL;
+
+ // init ffi and load key
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-25519-photo-pub.asc"));
+ // check userid 0 : ecc-25519
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "cc786278981b0728", &key));
+ assert_non_null(key);
+ size_t uid_count = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uid_count));
+ assert_int_equal(uid_count, 2);
+ char *uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid));
+ assert_string_equal(uid, "ecc-25519");
+ rnp_buffer_destroy(uid);
+ // check userattr 1, must be text instead of binary JPEG data
+ assert_rnp_success(rnp_key_get_uid_at(key, 1, &uid));
+ assert_string_equal(uid, "(photo)");
+ rnp_buffer_destroy(uid);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_revocations)
+{
+ rnp_ffi_t ffi = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load key with revoked userid
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-revoked-uid.asc"));
+ // check userid 0 : ecc-p256
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
+ assert_non_null(key);
+ size_t uid_count = 0;
+ assert_rnp_success(rnp_key_get_uid_count(key, &uid_count));
+ assert_int_equal(uid_count, 2);
+ char *uid = NULL;
+ assert_rnp_success(rnp_key_get_uid_at(key, 0, &uid));
+ assert_string_equal(uid, "ecc-p256");
+ rnp_buffer_destroy(uid);
+ rnp_uid_handle_t uid_handle = NULL;
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 0, &uid_handle));
+ assert_non_null(uid_handle);
+ bool revoked = true;
+ assert_rnp_failure(rnp_uid_is_revoked(NULL, &revoked));
+ assert_rnp_failure(rnp_uid_is_revoked(uid_handle, NULL));
+ assert_rnp_success(rnp_uid_is_revoked(uid_handle, &revoked));
+ assert_false(revoked);
+ rnp_signature_handle_t sig = (rnp_signature_handle_t) 0xdeadbeef;
+ assert_rnp_failure(rnp_uid_get_revocation_signature(NULL, &sig));
+ assert_rnp_failure(rnp_uid_get_revocation_signature(uid_handle, NULL));
+ assert_rnp_success(rnp_uid_get_revocation_signature(uid_handle, &sig));
+ assert_null(sig);
+ assert_rnp_success(rnp_uid_handle_destroy(uid_handle));
+ // check userid 1: ecc-p256-revoked
+ assert_rnp_success(rnp_key_get_uid_at(key, 1, &uid));
+ assert_string_equal(uid, "ecc-p256-revoked");
+ rnp_buffer_destroy(uid);
+ assert_rnp_success(rnp_key_get_uid_handle_at(key, 1, &uid_handle));
+ assert_non_null(uid_handle);
+ assert_rnp_success(rnp_uid_is_revoked(uid_handle, &revoked));
+ assert_true(revoked);
+ assert_rnp_success(rnp_uid_get_revocation_signature(uid_handle, &sig));
+ assert_non_null(sig);
+ uint32_t creation = 0;
+ assert_rnp_success(rnp_signature_get_creation(sig, &creation));
+ assert_int_equal(creation, 1556630215);
+ assert_rnp_success(rnp_signature_handle_destroy(sig));
+ assert_rnp_success(rnp_uid_handle_destroy(uid_handle));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ // load key with revoked subkey
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-revoked-sub.asc"));
+ // key is not revoked
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
+ assert_rnp_success(rnp_key_is_revoked(key, &revoked));
+ assert_false(revoked);
+ assert_rnp_failure(rnp_key_get_revocation_signature(NULL, &sig));
+ assert_rnp_failure(rnp_key_get_revocation_signature(key, NULL));
+ assert_rnp_success(rnp_key_get_revocation_signature(key, &sig));
+ assert_null(sig);
+ bool valid = false;
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_true(valid);
+ uint32_t till = 0;
+ assert_rnp_success(rnp_key_valid_till(key, &till));
+ assert_int_equal(till, 0xFFFFFFFF);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ // subkey is revoked
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "37E285E9E9851491", &key));
+ assert_rnp_success(rnp_key_is_revoked(key, &revoked));
+ assert_true(revoked);
+ char *reason = NULL;
+ assert_rnp_success(rnp_key_get_revocation_reason(key, &reason));
+ assert_string_equal(reason, "Subkey revocation test.");
+ rnp_buffer_destroy(reason);
+ assert_rnp_success(rnp_key_is_superseded(key, &revoked));
+ assert_false(revoked);
+ assert_rnp_success(rnp_key_is_compromised(key, &revoked));
+ assert_true(revoked);
+ assert_rnp_success(rnp_key_is_retired(key, &revoked));
+ assert_false(revoked);
+ assert_rnp_success(rnp_key_get_revocation_signature(key, &sig));
+ assert_non_null(sig);
+ assert_rnp_success(rnp_signature_get_creation(sig, &creation));
+ assert_int_equal(creation, 1556630749);
+ assert_rnp_success(rnp_signature_handle_destroy(sig));
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_false(valid);
+ assert_rnp_success(rnp_key_valid_till(key, &till));
+ assert_int_equal(till, 0);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ // load revoked key
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC));
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-revoked-key.asc"));
+ // key is revoked
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &key));
+ assert_rnp_success(rnp_key_is_revoked(key, &revoked));
+ assert_true(revoked);
+ reason = NULL;
+ assert_rnp_success(rnp_key_get_revocation_reason(key, &reason));
+ assert_string_equal(reason, "Superseded key test.");
+ rnp_buffer_destroy(reason);
+ assert_rnp_success(rnp_key_is_superseded(key, &revoked));
+ assert_true(revoked);
+ assert_rnp_success(rnp_key_is_compromised(key, &revoked));
+ assert_false(revoked);
+ assert_rnp_success(rnp_key_is_retired(key, &revoked));
+ assert_false(revoked);
+ assert_rnp_success(rnp_key_get_revocation_signature(key, &sig));
+ assert_non_null(sig);
+ assert_rnp_success(rnp_signature_get_creation(sig, &creation));
+ assert_int_equal(creation, 1556799806);
+ assert_rnp_success(rnp_signature_handle_destroy(sig));
+ assert_rnp_success(rnp_key_is_valid(key, &valid));
+ assert_false(valid);
+ assert_rnp_success(rnp_key_valid_till(key, &till));
+ assert_int_equal(till, 1556799806);
+ uint64_t till64 = 0;
+ assert_rnp_success(rnp_key_valid_till64(key, &till64));
+ assert_int_equal(till64, 1556799806);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ // cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+#define KEY_OUT_PATH "exported-key.asc"
+
+TEST_F(rnp_tests, test_ffi_file_output)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // load two keys
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-pub.asc"));
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p521-pub.asc"));
+
+ rnp_key_handle_t k256 = NULL;
+ rnp_key_handle_t k521 = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &k256));
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p521", &k521));
+
+ rnp_output_t output = NULL;
+ // test output to path - must overwrite if exists
+ assert_rnp_success(rnp_output_to_path(&output, KEY_OUT_PATH));
+ assert_rnp_success(rnp_key_export(
+ k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_true(rnp_file_exists(KEY_OUT_PATH));
+ off_t sz = file_size(KEY_OUT_PATH);
+ assert_rnp_success(rnp_output_to_path(&output, KEY_OUT_PATH));
+ assert_rnp_success(rnp_key_export(
+ k521, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_true(rnp_file_exists(KEY_OUT_PATH));
+ assert_true(sz != file_size(KEY_OUT_PATH));
+ sz = file_size(KEY_OUT_PATH);
+ // test output to file - will fail without overwrite
+ assert_rnp_failure(rnp_output_to_file(&output, KEY_OUT_PATH, 0));
+ // fail with wrong flags
+ assert_rnp_failure(rnp_output_to_file(&output, KEY_OUT_PATH, 0x100));
+ // test output to random file - will succeed on creation and export but fail on finish.
+ assert_rnp_success(rnp_output_to_file(&output, KEY_OUT_PATH, RNP_OUTPUT_FILE_RANDOM));
+ assert_true(file_size(KEY_OUT_PATH) == sz);
+ assert_rnp_success(
+ rnp_key_export(k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS));
+ assert_rnp_failure(rnp_output_finish(output));
+ assert_rnp_success(rnp_output_destroy(output));
+ // test output with random + overwrite - will succeed
+ assert_rnp_success(rnp_output_to_file(
+ &output, KEY_OUT_PATH, RNP_OUTPUT_FILE_RANDOM | RNP_OUTPUT_FILE_OVERWRITE));
+ assert_true(file_size(KEY_OUT_PATH) == sz);
+ assert_rnp_success(
+ rnp_key_export(k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS));
+ assert_rnp_success(rnp_output_finish(output));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_true(file_size(KEY_OUT_PATH) != sz);
+ sz = file_size(KEY_OUT_PATH);
+ // test output with just overwrite - will succeed
+ assert_rnp_success(rnp_output_to_file(&output, KEY_OUT_PATH, RNP_OUTPUT_FILE_OVERWRITE));
+ assert_true(file_size(KEY_OUT_PATH) == 0);
+ assert_rnp_success(
+ rnp_key_export(k521, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_SUBKEYS));
+ assert_rnp_success(rnp_output_finish(output));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_true(file_size(KEY_OUT_PATH) != sz);
+ assert_int_equal(rnp_unlink(KEY_OUT_PATH), 0);
+ // cleanup
+ assert_rnp_success(rnp_key_handle_destroy(k256));
+ assert_rnp_success(rnp_key_handle_destroy(k521));
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_stdout_output)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ assert_true(load_keys_gpg(ffi, "data/test_stream_key_load/ecc-p256-pub.asc"));
+
+ rnp_key_handle_t k256 = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "ecc-p256", &k256));
+
+ rnp_output_t output = NULL;
+ assert_rnp_failure(rnp_output_to_stdout(NULL));
+ assert_rnp_success(rnp_output_to_stdout(&output));
+ assert_rnp_success(rnp_key_export(
+ k256, output, RNP_KEY_EXPORT_PUBLIC | RNP_KEY_EXPORT_ARMORED | RNP_KEY_EXPORT_SUBKEYS));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_key_handle_destroy(k256));
+ rnp_ffi_destroy(ffi);
+}
+
+/* shrink the length to 1 packet
+ * set packet length type as PGP_PTAG_OLD_LEN_1 and remove one octet from length header
+ */
+static std::vector<uint8_t>
+shrink_len_2_to_1(const std::vector<uint8_t> &src)
+{
+ std::vector<uint8_t> dst = std::vector<uint8_t>();
+ dst.reserve(src.size() - 1);
+ dst.insert(dst.end(),
+ PGP_PTAG_ALWAYS_SET | (PGP_PKT_PUBLIC_KEY << PGP_PTAG_OF_CONTENT_TAG_SHIFT) |
+ PGP_PTAG_OLD_LEN_1);
+ // make sure the most significant octet of 2-octet length is actually zero
+ if (src[1] != 0) {
+ throw std::invalid_argument("src");
+ }
+ dst.insert(dst.end(), src[2]);
+ dst.insert(dst.end(), src.begin() + 3, src.end());
+ return dst;
+}
+
+/*
+ * fake a packet with len = 0xEEEE
+ */
+static std::vector<uint8_t>
+fake_len_EEEE(const std::vector<uint8_t> &src)
+{
+ std::vector<uint8_t> dst = std::vector<uint8_t>(src);
+ dst[1] = 0xEE;
+ dst[2] = 0xEE;
+ return dst;
+}
+
+/*
+ * fake a packet with len = 0x00
+ */
+static std::vector<uint8_t>
+fake_len_0(const std::vector<uint8_t> &src)
+{
+ std::vector<uint8_t> dst = shrink_len_2_to_1(src);
+ // erase subsequent octets for the packet to correspond the length
+ uint8_t old_length = dst[1];
+ dst.erase(dst.begin() + 2, dst.begin() + 2 + old_length);
+ dst[1] = 0;
+ return dst;
+}
+
+/* extend the length to 4 octets (preserving the value)
+ * set packet length type as PGP_PTAG_OLD_LEN_4 and set 4 octet length instead of 2
+ */
+static std::vector<uint8_t>
+extend_len_2_to_4(const std::vector<uint8_t> &src)
+{
+ std::vector<uint8_t> dst = std::vector<uint8_t>();
+ dst.reserve(src.size() + 2);
+ dst.insert(dst.end(), src.begin(), src.begin() + 3);
+ dst[0] &= ~PGP_PTAG_OF_LENGTH_TYPE_MASK;
+ dst[0] |= PGP_PTAG_OLD_LEN_4;
+ dst.insert(dst.begin() + 1, 2, 0);
+ dst.insert(dst.end(), src.begin() + 3, src.end());
+ return dst;
+}
+
+static bool
+import_public_keys_from_vector(std::vector<uint8_t> keyring)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_ffi_create(&ffi, "GPG", "GPG");
+ bool res = import_pub_keys(ffi, &keyring[0], keyring.size());
+ rnp_ffi_destroy(ffi);
+ return res;
+}
+
+TEST_F(rnp_tests, test_ffi_import_keys_check_pktlen)
+{
+ std::vector<uint8_t> keyring = file_to_vec("data/keyrings/2/pubring.gpg");
+ // check tag
+ // we are assuming that original key uses old format and packet length type is
+ // PGP_PTAG_OLD_LEN_2
+ assert_true(keyring.size() >= 5);
+ uint8_t expected_tag = PGP_PTAG_ALWAYS_SET |
+ (PGP_PKT_PUBLIC_KEY << PGP_PTAG_OF_CONTENT_TAG_SHIFT) |
+ PGP_PTAG_OLD_LEN_2;
+ assert_int_equal(expected_tag, 0x99);
+ assert_int_equal(keyring[0], expected_tag);
+ // original file can be loaded correctly
+ assert_true(import_public_keys_from_vector(keyring));
+ {
+ // Shrink the packet length to 1 octet
+ std::vector<uint8_t> keyring_valid_1 = shrink_len_2_to_1(keyring);
+ assert_int_equal(keyring_valid_1.size(), keyring.size() - 1);
+ assert_true(import_public_keys_from_vector(keyring_valid_1));
+ }
+ {
+ // get invalid key with length 0
+ std::vector<uint8_t> keyring_invalid_0 = fake_len_0(keyring);
+ assert_false(import_public_keys_from_vector(keyring_invalid_0));
+ }
+ {
+ // get invalid key with length 0xEEEE
+ std::vector<uint8_t> keyring_invalid_EEEE = fake_len_EEEE(keyring);
+ assert_int_equal(keyring_invalid_EEEE.size(), keyring.size());
+ assert_false(import_public_keys_from_vector(keyring_invalid_EEEE));
+ }
+ {
+ std::vector<uint8_t> keyring_len_4 = extend_len_2_to_4(keyring);
+ assert_int_equal(keyring_len_4.size(), keyring.size() + 2);
+ assert_true(import_public_keys_from_vector(keyring_len_4));
+ // get invalid key with length 0xEEEEEEEE
+ keyring_len_4[1] = 0xEE;
+ keyring_len_4[2] = 0xEE;
+ keyring_len_4[3] = 0xEE;
+ keyring_len_4[4] = 0xEE;
+ assert_false(import_public_keys_from_vector(keyring_len_4));
+ }
+}
+
+TEST_F(rnp_tests, test_ffi_calculate_iterations)
+{
+ size_t iterations = 0;
+ assert_rnp_failure(rnp_calculate_iterations(NULL, 500, &iterations));
+ assert_rnp_failure(rnp_calculate_iterations("SHA256", 500, NULL));
+ assert_rnp_failure(rnp_calculate_iterations("WRONG", 500, &iterations));
+ assert_rnp_success(rnp_calculate_iterations("SHA256", 500, &iterations));
+ assert_true(iterations > 65536);
+}
+
+static bool
+check_features(const char *type, const char *json, size_t count)
+{
+ size_t got_count = 0;
+
+ json_object *features = json_tokener_parse(json);
+ if (!features) {
+ return false;
+ }
+ bool res = false;
+ if (!json_object_is_type(features, json_type_array)) {
+ goto done;
+ }
+ got_count = json_object_array_length(features);
+ if (got_count != count) {
+ RNP_LOG("wrong feature count for %s: expected %zu, got %zu", type, count, got_count);
+ goto done;
+ }
+ for (size_t i = 0; i < count; i++) {
+ json_object *val = json_object_array_get_idx(features, i);
+ const char * str = json_object_get_string(val);
+ bool supported = false;
+ if (!str || rnp_supports_feature(type, str, &supported) || !supported) {
+ goto done;
+ }
+ }
+
+ res = true;
+done:
+ json_object_put(features);
+ return res;
+}
+
+TEST_F(rnp_tests, test_ffi_supported_features)
+{
+ char *features = NULL;
+ /* some edge cases */
+ assert_rnp_failure(rnp_supported_features(NULL, &features));
+ assert_rnp_failure(rnp_supported_features("something", NULL));
+ assert_rnp_failure(rnp_supported_features(RNP_FEATURE_SYMM_ALG, NULL));
+ assert_rnp_failure(rnp_supported_features("something", &features));
+ /* symmetric algorithms */
+ assert_rnp_success(rnp_supported_features("Symmetric Algorithm", &features));
+ assert_non_null(features);
+ bool has_sm2 = sm2_enabled();
+ bool has_tf = twofish_enabled();
+ bool has_brainpool = brainpool_enabled();
+ bool has_idea = idea_enabled();
+ assert_true(
+ check_features(RNP_FEATURE_SYMM_ALG,
+ features,
+ 7 + has_sm2 + has_tf + has_idea + blowfish_enabled() + cast5_enabled()));
+ rnp_buffer_destroy(features);
+ bool supported = false;
+ assert_rnp_failure(rnp_supports_feature(NULL, "IDEA", &supported));
+ assert_rnp_failure(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, NULL, &supported));
+ assert_rnp_failure(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", NULL));
+ assert_rnp_failure(rnp_supports_feature("WRONG", "IDEA", &supported));
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", &supported));
+ assert_true(supported == has_idea);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "TRIPLEDES", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAST5", &supported));
+ assert_int_equal(supported, cast5_enabled());
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "BLOWFISH", &supported));
+ assert_int_equal(supported, blowfish_enabled());
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "AES128", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "AES192", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "AES256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "TWOFISH", &supported));
+ assert_true(supported == has_tf);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAMELLIA128", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAMELLIA192", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAMELLIA256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "SM4", &supported));
+ assert_true(supported == has_sm2);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "idea", &supported));
+ assert_true(supported == has_idea);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "tripledes", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "cast5", &supported));
+ assert_true(supported == cast5_enabled());
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "blowfish", &supported));
+ assert_true(supported == blowfish_enabled());
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "aes128", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "aes192", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "aes256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "twofish", &supported));
+ assert_true(supported == has_tf);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "camellia128", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "camellia192", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "camellia256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "sm4", &supported));
+ assert_true(supported == has_sm2);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "wrong", &supported));
+ assert_false(supported);
+ /* aead algorithms */
+ bool has_eax = aead_eax_enabled();
+ bool has_ocb = aead_ocb_enabled();
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_AEAD_ALG, &features));
+ assert_non_null(features);
+ assert_true(check_features(RNP_FEATURE_AEAD_ALG, features, 1 + has_eax + has_ocb));
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "eax", &supported));
+ assert_true(supported == has_eax);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "ocb", &supported));
+ assert_true(supported == has_ocb);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "none", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "wrong", &supported));
+ assert_false(supported);
+ /* protection mode */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_PROT_MODE, &features));
+ assert_non_null(features);
+ assert_true(check_features(RNP_FEATURE_PROT_MODE, features, 1));
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PROT_MODE, "cfb", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PROT_MODE, "wrong", &supported));
+ assert_false(supported);
+ /* public key algorithm */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_PK_ALG, &features));
+ assert_non_null(features);
+ assert_true(check_features(RNP_FEATURE_PK_ALG, features, 6 + has_sm2));
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "RSA", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "DSA", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ELGAMAL", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ECDSA", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ECDH", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "EDDSA", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "SM2", &supported));
+ assert_true(supported == has_sm2);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "rsa", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "dsa", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "elgamal", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ecdsa", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "ecdh", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "eddsa", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "sm2", &supported));
+ assert_true(supported == has_sm2);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_PK_ALG, "wrong", &supported));
+ assert_false(supported);
+ /* hash algorithm */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_HASH_ALG, &features));
+ assert_non_null(features);
+ assert_true(
+ check_features(RNP_FEATURE_HASH_ALG, features, 8 + has_sm2 + ripemd160_enabled()));
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "MD5", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA1", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "RIPEMD160", &supported));
+ assert_true(supported == ripemd160_enabled());
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA384", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA512", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA224", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA3-256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SHA3-512", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "SM3", &supported));
+ assert_true(supported == has_sm2);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "md5", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha1", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "ripemd160", &supported));
+ assert_true(supported == ripemd160_enabled());
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha384", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha512", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha224", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha3-256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sha3-512", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "sm3", &supported));
+ assert_true(supported == has_sm2);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "wrong", &supported));
+ assert_false(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_HASH_ALG, "CRC24", &supported));
+ assert_false(supported);
+ /* compression algorithm */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_COMP_ALG, &features));
+ assert_non_null(features);
+ assert_true(check_features(RNP_FEATURE_COMP_ALG, features, 4));
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "Uncompressed", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "Zlib", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "ZIP", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "BZIP2", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_COMP_ALG, "wrong", &supported));
+ assert_false(supported);
+ /* elliptic curve */
+ assert_rnp_success(rnp_supported_features(RNP_FEATURE_CURVE, &features));
+ assert_non_null(features);
+ assert_true(check_features(RNP_FEATURE_CURVE, features, 6 + has_sm2 + 3 * has_brainpool));
+ rnp_buffer_destroy(features);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "NIST P-256", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "NIST P-384", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "NIST P-521", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "ed25519", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "curve25519", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP256r1", &supported));
+ assert_true(supported == has_brainpool);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP384r1", &supported));
+ assert_true(supported == has_brainpool);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP512r1", &supported));
+ assert_true(supported == has_brainpool);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "secp256k1", &supported));
+ assert_true(supported);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "SM2 P-256", &supported));
+ assert_true(supported == has_sm2);
+ assert_rnp_success(rnp_supports_feature(RNP_FEATURE_CURVE, "wrong", &supported));
+ assert_false(supported);
+}
+
+TEST_F(rnp_tests, test_ffi_output_to_armor)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_output_t memory = NULL;
+ rnp_output_t armor = NULL;
+ rnp_input_t input = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2FCADF05FFA501BB", &key));
+ assert_non_null(key);
+
+ assert_rnp_success(rnp_output_to_memory(&memory, 0));
+ /* some edge cases */
+ assert_rnp_failure(rnp_output_to_armor(NULL, &armor, "message"));
+ assert_null(armor);
+ assert_rnp_failure(rnp_output_to_armor(memory, NULL, "message"));
+ assert_null(armor);
+ assert_rnp_failure(rnp_output_to_armor(memory, &armor, "wrong"));
+ assert_null(armor);
+ /* export raw key to armored stream with 'message' header */
+ assert_rnp_success(rnp_output_to_armor(memory, &armor, "message"));
+ assert_rnp_success(rnp_key_export(key, armor, RNP_KEY_EXPORT_PUBLIC));
+ assert_rnp_success(rnp_output_destroy(armor));
+ uint8_t *buf = NULL;
+ size_t buf_len = 0;
+ /* check contents to make sure it is correct armored stream */
+ assert_rnp_success(rnp_output_memory_get_buf(memory, &buf, &buf_len, false));
+ assert_non_null(buf);
+ const char *hdr = "-----BEGIN PGP MESSAGE-----";
+ assert_true(buf_len > strlen(hdr));
+ assert_int_equal(strncmp((char *) buf, hdr, strlen(hdr)), 0);
+ assert_rnp_success(rnp_input_from_memory(&input, buf, buf_len, false));
+ rnp_output_t memory2 = NULL;
+ assert_rnp_success(rnp_output_to_memory(&memory2, 0));
+ assert_rnp_success(rnp_dearmor(input, memory2));
+ rnp_output_destroy(memory2);
+ rnp_input_destroy(input);
+
+ rnp_key_handle_destroy(key);
+ rnp_output_destroy(memory);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_rnp_guess_contents)
+{
+ char * msgt = NULL;
+ rnp_input_t input = NULL;
+ assert_rnp_failure(rnp_guess_contents(NULL, &msgt));
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/issue1188/armored_revocation_signature.pgp"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_int_equal(strcmp(msgt, "signature"), 0);
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-pub.pgp"));
+ assert_rnp_failure(rnp_guess_contents(input, NULL));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "public key");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_merge/key-pub-just-subkey-1.pgp"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "public key");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-pub.asc"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "public key");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-sec.pgp"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "secret key");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_key_merge/key-sec.asc"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "secret key");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_key_merge/key-sec-just-subkey-1.pgp"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "secret key");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_z/128mb.zip"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "message");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_z/4gb.bzip2.asc"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "message");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.sig"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "signature");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.sig.asc"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "signature");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.asc.asc"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "cleartext");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_stream_signatures/source.txt"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "unknown");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "message");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.wrong-armor.asc"));
+ assert_rnp_success(rnp_guess_contents(input, &msgt));
+ assert_string_equal(msgt, "unknown");
+ rnp_buffer_destroy(msgt);
+ rnp_input_destroy(input);
+}
+
+TEST_F(rnp_tests, test_ffi_literal_filename)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t op = NULL;
+ uint8_t * signed_buf;
+ size_t signed_len;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ // init input
+ test_ffi_init_sign_memory_input(&input, &output);
+ // create signature operation
+ assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ // setup filename and modification time
+ assert_rnp_success(rnp_op_sign_set_file_name(op, "checkleak.dat"));
+ assert_rnp_success(rnp_op_sign_set_file_name(op, NULL));
+ assert_rnp_success(rnp_op_sign_set_file_name(op, "testfile.dat"));
+ assert_rnp_success(rnp_op_sign_set_file_mtime(op, 12345678));
+ // execute the operation
+ assert_rnp_success(rnp_op_sign_execute(op));
+ // make sure the output file was created
+ assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
+ assert_non_null(signed_buf);
+ assert_true(signed_len > 0);
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_sign_destroy(op));
+ op = NULL;
+
+ // check the resulting stream for correct name/time
+ assert_rnp_success(rnp_input_from_memory(&input, signed_buf, signed_len, false));
+ char *json = NULL;
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ assert_non_null(json);
+
+ std::string jstr = json;
+ assert_true(jstr.find("\"filename\":\"testfile.dat\"") != std::string::npos);
+ assert_true(jstr.find("\"timestamp\":12345678") != std::string::npos);
+
+ assert_rnp_success(rnp_input_destroy(input));
+ rnp_buffer_destroy(signed_buf);
+ rnp_buffer_destroy(json);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_op_set_hash)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t op = NULL;
+ uint8_t * signed_buf;
+ size_t signed_len;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ // init input
+ test_ffi_init_sign_memory_input(&input, &output);
+ // create signature operation
+ assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ // make sure it doesn't fail on NULL hash value
+ assert_rnp_failure(rnp_op_sign_set_hash(op, NULL));
+ assert_rnp_failure(rnp_op_sign_set_hash(op, "Unknown"));
+ assert_rnp_success(rnp_op_sign_set_hash(op, "SHA256"));
+ // execute the operation with wrong password
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrong"));
+ assert_int_equal(rnp_op_sign_execute(op), RNP_ERROR_BAD_PASSWORD);
+ assert_rnp_success(rnp_op_sign_destroy(op));
+ // execute the operation with valid password
+ assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ assert_rnp_success(rnp_op_sign_execute(op));
+ // make sure the output file was created
+ assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
+ assert_non_null(signed_buf);
+ assert_true(signed_len > 0);
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_op_sign_destroy(op));
+
+ rnp_buffer_destroy(signed_buf);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_op_set_compression)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t op = NULL;
+ uint8_t * signed_buf;
+ size_t signed_len;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ // init input
+ test_ffi_init_sign_memory_input(&input, &output);
+ // create signature operation
+ assert_rnp_success(rnp_op_sign_create(&op, ffi, input, output));
+ // setup signature(s)
+ test_ffi_setup_signatures(&ffi, &op);
+ // make sure it doesn't fail on NULL compression algorithm value
+ assert_rnp_failure(rnp_op_sign_set_compression(op, NULL, 6));
+ assert_rnp_failure(rnp_op_sign_set_compression(op, "Unknown", 6));
+ assert_rnp_failure(rnp_op_sign_set_compression(NULL, "ZLib", 6));
+ assert_rnp_success(rnp_op_sign_set_compression(op, "ZLib", 6));
+ // execute the operation
+ assert_rnp_success(rnp_op_sign_execute(op));
+ // make sure the output file was created
+ assert_rnp_success(rnp_output_memory_get_buf(output, &signed_buf, &signed_len, true));
+ assert_non_null(signed_buf);
+ assert_true(signed_len > 0);
+
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_op_sign_destroy(op));
+
+ rnp_buffer_destroy(signed_buf);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_aead_params)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char *plaintext = "Some data to encrypt using the AEAD-EAX and AEAD-OCB encryption.";
+
+ // setup FFI
+ test_ffi_init(&ffi);
+
+ // write out some data
+ str_to_file("plaintext", plaintext);
+ // create input+output
+ assert_rnp_success(rnp_input_from_path(&input, "plaintext"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "encrypted"));
+ assert_non_null(output);
+ // create encrypt operation
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // setup AEAD params
+ assert_rnp_failure(rnp_op_encrypt_set_aead(NULL, "OCB"));
+ assert_rnp_failure(rnp_op_encrypt_set_aead(op, NULL));
+ assert_rnp_failure(rnp_op_encrypt_set_aead(op, "WRONG"));
+ if (!aead_ocb_enabled()) {
+ assert_rnp_failure(rnp_op_encrypt_set_aead(op, "OCB"));
+ } else {
+ assert_rnp_success(rnp_op_encrypt_set_aead(op, "OCB"));
+ }
+ assert_rnp_failure(rnp_op_encrypt_set_aead_bits(NULL, 10));
+ assert_rnp_failure(rnp_op_encrypt_set_aead_bits(op, -1));
+ assert_rnp_failure(rnp_op_encrypt_set_aead_bits(op, 60));
+ assert_rnp_failure(rnp_op_encrypt_set_aead_bits(op, 17));
+ assert_rnp_success(rnp_op_encrypt_set_aead_bits(op, 10));
+ // add password (using all defaults)
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "pass1", NULL, 0, NULL));
+ // setup compression
+ assert_rnp_failure(rnp_op_encrypt_set_compression(NULL, "ZLIB", 6));
+ assert_rnp_failure(rnp_op_encrypt_set_compression(op, NULL, 6));
+ assert_rnp_failure(rnp_op_encrypt_set_compression(op, "WRONG", 6));
+ assert_rnp_success(rnp_op_encrypt_set_compression(op, "ZLIB", 6));
+ // set filename and mtime
+ assert_rnp_failure(rnp_op_encrypt_set_file_name(NULL, "filename"));
+ assert_rnp_success(rnp_op_encrypt_set_file_name(op, NULL));
+ assert_rnp_success(rnp_op_encrypt_set_file_name(op, "filename"));
+ assert_rnp_failure(rnp_op_encrypt_set_file_mtime(NULL, 1000));
+ assert_rnp_success(rnp_op_encrypt_set_file_mtime(op, 1000));
+ // execute the operation
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ // make sure the output file was created
+ assert_true(rnp_file_exists("encrypted"));
+ // cleanup
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ assert_rnp_success(rnp_output_destroy(output));
+ output = NULL;
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ op = NULL;
+
+ // list packets
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ char *json = NULL;
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ assert_rnp_success(rnp_input_destroy(input));
+ input = NULL;
+ json_object *jso = json_tokener_parse(json);
+ rnp_buffer_destroy(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ /* check the symmetric-key encrypted session key packet */
+ json_object *pkt = json_object_array_get_idx(jso, 0);
+ assert_true(check_json_pkt_type(pkt, PGP_PKT_SK_SESSION_KEY));
+ if (!aead_ocb_enabled()) {
+ // if AEAD is not enabled then v4 encrypted packet will be created
+ assert_true(check_json_field_int(pkt, "version", 4));
+ assert_true(check_json_field_str(pkt, "algorithm.str", "AES-256"));
+ } else {
+ assert_true(check_json_field_int(pkt, "version", 5));
+ assert_true(check_json_field_str(pkt, "aead algorithm.str", "OCB"));
+ }
+ /* check the aead-encrypted packet */
+ pkt = json_object_array_get_idx(jso, 1);
+ if (!aead_ocb_enabled()) {
+ assert_true(check_json_pkt_type(pkt, PGP_PKT_SE_IP_DATA));
+ } else {
+ assert_true(check_json_pkt_type(pkt, PGP_PKT_AEAD_ENCRYPTED));
+ assert_true(check_json_field_int(pkt, "version", 1));
+ assert_true(check_json_field_str(pkt, "aead algorithm.str", "OCB"));
+ assert_true(check_json_field_int(pkt, "chunk size", 10));
+ }
+ json_object_put(jso);
+
+ /* decrypt */
+ assert_rnp_success(rnp_input_from_path(&input, "encrypted"));
+ assert_non_null(input);
+ assert_rnp_success(rnp_output_to_path(&output, "decrypted"));
+ assert_non_null(output);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "pass1"));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ // cleanup
+ rnp_input_destroy(input);
+ input = NULL;
+ rnp_output_destroy(output);
+ output = NULL;
+ // compare the decrypted file
+ assert_true(file_equals("decrypted", plaintext, strlen(plaintext)));
+ rnp_unlink("decrypted");
+
+ // final cleanup
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_detached_verify_input)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ /* verify detached signature via rnp_op_verify_create - should not crash */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_stream_signatures/source.txt.sig"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_detached_cleartext_signed_input)
+{
+ rnp_ffi_t ffi = NULL;
+ test_ffi_init(&ffi);
+ /* verify detached signature with cleartext input - must fail */
+ rnp_input_t inputmsg = NULL;
+ assert_rnp_success(rnp_input_from_path(&inputmsg, "data/test_messages/message.txt"));
+ rnp_input_t inputsig = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&inputsig, "data/test_messages/message.txt.cleartext-signed"));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, inputmsg, inputsig));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(inputmsg);
+ rnp_input_destroy(inputsig);
+ /* verify detached signature with signed/embedded input - must fail */
+ assert_rnp_success(rnp_input_from_path(&inputmsg, "data/test_messages/message.txt"));
+ assert_rnp_success(
+ rnp_input_from_path(&inputsig, "data/test_messages/message.txt.empty.sig"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, inputmsg, inputsig));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(inputmsg);
+ rnp_input_destroy(inputsig);
+ /* verify detached signature as a whole message - must fail */
+ assert_rnp_success(rnp_input_from_path(&inputmsg, "data/test_messages/message.txt.sig"));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, inputmsg, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ rnp_op_verify_destroy(verify);
+ rnp_output_destroy(output);
+ rnp_input_destroy(inputmsg);
+
+ rnp_ffi_destroy(ffi);
+}
+
+static bool
+check_signature(rnp_op_verify_t op, size_t idx, rnp_result_t status)
+{
+ rnp_op_verify_signature_t sig = NULL;
+ if (rnp_op_verify_get_signature_at(op, idx, &sig)) {
+ return false;
+ }
+ return rnp_op_verify_signature_get_status(sig) == status;
+}
+
+TEST_F(rnp_tests, test_ffi_op_verify_sig_count)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // init ffi
+ test_ffi_init(&ffi);
+
+ /* signed message */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_failure(rnp_op_verify_create(NULL, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_create(&verify, NULL, input, output));
+ assert_rnp_failure(rnp_op_verify_create(&verify, ffi, NULL, output));
+ assert_rnp_failure(rnp_op_verify_create(&verify, ffi, input, NULL));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(NULL));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ size_t sigcount = 0;
+ assert_rnp_failure(rnp_op_verify_get_signature_count(verify, NULL));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed with unknown key */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed.unknown"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_KEY_NOT_FOUND));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed with malformed signature (bad version) */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed.malfsig"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_UNKNOWN));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed with invalid signature (modified hash alg) */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed.invsig"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed without the signature */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed.nosig"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 0);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* detached signature */
+ rnp_input_t source = NULL;
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig"));
+ assert_rnp_failure(rnp_op_verify_detached_create(NULL, ffi, source, input));
+ assert_rnp_failure(rnp_op_verify_detached_create(&verify, NULL, source, input));
+ assert_rnp_failure(rnp_op_verify_detached_create(&verify, ffi, NULL, input));
+ assert_rnp_failure(rnp_op_verify_detached_create(&verify, ffi, source, NULL));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* detached text-mode signature */
+ source = NULL;
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig-text"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ source = NULL;
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt.crlf"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig-text"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* detached text-mode signature with trailing CR characters */
+ source = NULL;
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&source, "data/test_messages/message-trailing-cr.txt"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message-trailing-cr.txt.sig-text"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* detached text-mode signature with CRLF on 32k boundary */
+ source = NULL;
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&source, "data/test_messages/message-32k-crlf.txt"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message-32k-crlf.txt.sig"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* embedded text-mode signature with CRLF on 32k boundary */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message-32k-crlf.txt.gpg"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* malformed detached signature */
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.sig.malf"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 0);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* malformed detached signature, wrong bitlen in MPI */
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/message.txt"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.sig.wrong-mpi-bitlen"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* encrypted message */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.encrypted"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ }
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 0);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* encrypted and signed message */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-encrypted"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* cleartext signed message */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.cleartext-signed"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* cleartext signed message without newline */
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(
+ &input, "data/test_messages/message.txt.cleartext-signed-nonewline"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 0);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* cleartext signed with malformed signature (wrong mpi len) */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.cleartext-malf"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_int_equal(rnp_op_verify_execute(verify), RNP_ERROR_SIGNATURE_INVALID);
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_UNKNOWN));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* cleartext signed without the signature */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.cleartext-nosig"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 0);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed message without compression */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-no-z"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed and password-encrypted data with 0 compression algo */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-sym-none-z"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed message with one-pass with wrong version */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-no-z-malf"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* encrypted and signed message with marker packet */
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.marker"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* encrypted and signed message with marker packet, armored */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.marker.asc"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* encrypted and signed message with malformed marker packet */
+ sigcount = 255;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.marker.malf"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* sha1 detached signature over the collision-suspicious data */
+ /* allow sha1 temporary */
+ rnp::SecurityRule allow_sha1(
+ rnp::FeatureType::Hash, PGP_HASH_SHA1, rnp::SecurityLevel::Default, 1547856001);
+ global_ctx.profile.add_rule(allow_sha1);
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/shattered-1.pdf"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/shattered-1.pdf.sig"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* sha1 detached signature over the document with collision*/
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&source, "data/test_messages/shattered-2.pdf"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/shattered-1.pdf.sig"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, source, input));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(source);
+ rnp_input_destroy(input);
+
+ /* sha1 attached signature over the collision-suspicious data */
+ sigcount = 255;
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/shattered-2.pdf.gpg"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* remove sha1 rule */
+ assert_true(global_ctx.profile.del_rule(allow_sha1));
+
+ /* signed message with key which is now expired */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ import_pub_keys(ffi, "data/test_messages/expired_signing_key-pub.asc");
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "30FC0D776915BA44", &key));
+ uint64_t till = 0;
+ assert_rnp_success(rnp_key_valid_till64(key, &till));
+ assert_int_equal(till, 1623424417);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-expired-key"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed message with subkey which is now expired */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ import_pub_keys(ffi, "data/test_messages/expired_signing_sub-pub.asc");
+ key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "D93A47FD93191FD1", &key));
+ till = 0;
+ assert_rnp_success(rnp_key_valid_till64(key, &till));
+ assert_int_equal(till, 1623933507);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-expired-sub"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed message with md5 hash */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_PUBLIC | RNP_KEY_UNLOAD_SECRET));
+ assert_true(load_keys_gpg(ffi, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed.md5"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed message with md5 hash before the cut-off date */
+ assert_true(import_all_keys(ffi, "data/test_key_edge_cases/key-rsa-2001-pub.asc"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-md5-before"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed message with md5 hash right after the cut-off date */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-md5-after"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* signed message with sha1 hash */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed.sha1"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message signed with sha1 before the cut-off date */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-sha1-before"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_SUCCESS));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message signed with sha1 right after the cut-off date */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.signed-sha1-after"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ sigcount = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sigcount));
+ assert_int_equal(sigcount, 1);
+ assert_true(check_signature(verify, 0, RNP_ERROR_SIGNATURE_INVALID));
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_op_verify_get_protection_info)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ /* message just signed */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ char *mode = NULL;
+ char *cipher = NULL;
+ bool valid = true;
+ assert_rnp_failure(rnp_op_verify_get_protection_info(NULL, &mode, &cipher, &valid));
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, NULL, NULL));
+ assert_string_equal(mode, "none");
+ rnp_buffer_destroy(mode);
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, NULL, &cipher, NULL));
+ assert_string_equal(cipher, "none");
+ rnp_buffer_destroy(cipher);
+ valid = true;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, NULL, NULL, &valid));
+ assert_false(valid);
+ assert_rnp_failure(rnp_op_verify_get_protection_info(verify, NULL, NULL, NULL));
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "none");
+ assert_string_equal(cipher, "none");
+ assert_false(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message without MDC */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-no-mdc"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ mode = NULL;
+ cipher = NULL;
+ valid = true;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "cfb");
+ assert_string_equal(cipher, "AES256");
+ assert_false(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message with MDC */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.enc-mdc"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ mode = NULL;
+ cipher = NULL;
+ valid = false;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "cfb-mdc");
+ assert_string_equal(cipher, "AES256");
+ assert_true(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message with AEAD-OCB */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ if (!aead_ocb_enabled() || aead_ocb_aes_only()) {
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ }
+ mode = NULL;
+ cipher = NULL;
+ valid = false;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "aead-ocb");
+ assert_string_equal(cipher, "CAMELLIA192");
+ assert_true(valid == (aead_ocb_enabled() && !aead_ocb_aes_only()));
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message with AEAD-OCB AES-192 */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb-aes"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ if (!aead_ocb_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ }
+ mode = NULL;
+ cipher = NULL;
+ valid = false;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "aead-ocb");
+ assert_string_equal(cipher, "AES192");
+ assert_true(valid == aead_ocb_enabled());
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* modified message with AEAD-OCB */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb-malf"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ mode = NULL;
+ cipher = NULL;
+ valid = false;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "aead-ocb");
+ assert_string_equal(cipher, "CAMELLIA192");
+ assert_false(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message with AEAD-EAX */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ }
+ mode = NULL;
+ cipher = NULL;
+ valid = false;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "aead-eax");
+ assert_string_equal(cipher, "AES256");
+ assert_true(valid == aead_eax_enabled());
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* modified message with AEAD-EAX */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax-malf"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ mode = NULL;
+ cipher = NULL;
+ valid = false;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "aead-eax");
+ assert_string_equal(cipher, "AES256");
+ assert_false(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ rnp_ffi_destroy(ffi);
+}
+
+static bool
+getpasscb_for_key(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ if (!key) {
+ return false;
+ }
+ char *keyid = NULL;
+ rnp_key_get_keyid(key, &keyid);
+ if (!keyid) {
+ return false;
+ }
+ const char *pass = "password";
+ if (strcmp(keyid, (const char *) app_ctx)) {
+ pass = "wrongpassword";
+ }
+ size_t pass_len = strlen(pass);
+ rnp_buffer_destroy(keyid);
+
+ if (pass_len >= buf_len) {
+ return false;
+ }
+ memcpy(buf, pass, pass_len + 1);
+ return true;
+}
+
+TEST_F(rnp_tests, test_ffi_op_verify_recipients_info)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // init ffi
+ test_ffi_init(&ffi);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ /* message just signed */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.signed"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ /* check filename and mtime */
+ char * filename = NULL;
+ uint32_t mtime = 0;
+ assert_rnp_failure(rnp_op_verify_get_file_info(NULL, &filename, &mtime));
+ assert_rnp_success(rnp_op_verify_get_file_info(verify, &filename, &mtime));
+ assert_string_equal(filename, "message.txt");
+ assert_int_equal(mtime, 1571991574);
+ rnp_buffer_destroy(filename);
+ filename = NULL;
+ assert_rnp_success(rnp_op_verify_get_file_info(verify, &filename, NULL));
+ assert_string_equal(filename, "message.txt");
+ rnp_buffer_destroy(filename);
+ mtime = 0;
+ assert_rnp_success(rnp_op_verify_get_file_info(verify, NULL, &mtime));
+ assert_int_equal(mtime, 1571991574);
+ /* rnp_op_verify_get_recipient_count */
+ assert_rnp_failure(rnp_op_verify_get_recipient_count(verify, NULL));
+ size_t count = 255;
+ assert_rnp_failure(rnp_op_verify_get_recipient_count(NULL, &count));
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 0);
+ /* rnp_op_verify_get_recipient_at */
+ rnp_recipient_handle_t recipient = NULL;
+ assert_rnp_failure(rnp_op_verify_get_recipient_at(NULL, 0, &recipient));
+ assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 0, NULL));
+ assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+ assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 10, &recipient));
+ /* rnp_op_verify_get_used_recipient */
+ assert_rnp_failure(rnp_op_verify_get_used_recipient(NULL, &recipient));
+ assert_rnp_failure(rnp_op_verify_get_used_recipient(verify, NULL));
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_null(recipient);
+ /* rnp_op_verify_get_symenc_count */
+ assert_rnp_failure(rnp_op_verify_get_symenc_count(verify, NULL));
+ count = 255;
+ assert_rnp_failure(rnp_op_verify_get_symenc_count(NULL, &count));
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 0);
+ /* rnp_op_verify_get_symenc_at */
+ rnp_symenc_handle_t symenc = NULL;
+ assert_rnp_failure(rnp_op_verify_get_symenc_at(NULL, 0, &symenc));
+ assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 0, NULL));
+ assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 10, &symenc));
+ /* rnp_op_verify_get_used_symenc */
+ assert_rnp_failure(rnp_op_verify_get_used_symenc(NULL, &symenc));
+ assert_rnp_failure(rnp_op_verify_get_used_symenc(verify, NULL));
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ assert_null(symenc);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message without MDC: single recipient */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-no-mdc"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_failure(rnp_op_verify_get_recipient_at(verify, 1, &recipient));
+ assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+ assert_non_null(recipient);
+ char *alg = NULL;
+ assert_rnp_failure(rnp_recipient_get_alg(NULL, &alg));
+ assert_rnp_failure(rnp_recipient_get_alg(recipient, NULL));
+ assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ char *keyid = NULL;
+ assert_rnp_failure(rnp_recipient_get_keyid(NULL, &keyid));
+ assert_rnp_failure(rnp_recipient_get_keyid(recipient, NULL));
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "8A05B89FAD5ADED1");
+ rnp_buffer_destroy(keyid);
+ recipient = NULL;
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_non_null(recipient);
+ alg = NULL;
+ assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ keyid = NULL;
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "8A05B89FAD5ADED1");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 0);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message with AEAD-OCB: single password */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ if (!aead_ocb_enabled() || aead_ocb_aes_only()) {
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ }
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_failure(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ assert_non_null(symenc);
+ char *cipher = NULL;
+ assert_rnp_failure(rnp_symenc_get_cipher(symenc, NULL));
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "CAMELLIA192");
+ rnp_buffer_destroy(cipher);
+ char *aead = NULL;
+ assert_rnp_failure(rnp_symenc_get_aead_alg(symenc, NULL));
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+ assert_string_equal(aead, "OCB");
+ rnp_buffer_destroy(aead);
+ char *hash = NULL;
+ assert_rnp_failure(rnp_symenc_get_hash_alg(symenc, NULL));
+ assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+ assert_string_equal(hash, "SHA1");
+ rnp_buffer_destroy(hash);
+ char *s2k = NULL;
+ assert_rnp_failure(rnp_symenc_get_s2k_type(symenc, NULL));
+ assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+ assert_string_equal(s2k, "Iterated and salted");
+ rnp_buffer_destroy(s2k);
+ uint32_t iterations = 0;
+ assert_rnp_failure(rnp_symenc_get_s2k_iterations(symenc, NULL));
+ assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+ assert_int_equal(iterations, 30408704);
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ if (!aead_ocb_enabled() || aead_ocb_aes_only()) {
+ assert_null(symenc);
+ } else {
+ assert_non_null(symenc);
+ cipher = NULL;
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "CAMELLIA192");
+ rnp_buffer_destroy(cipher);
+ aead = NULL;
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+ assert_string_equal(aead, "OCB");
+ rnp_buffer_destroy(aead);
+ hash = NULL;
+ assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+ assert_string_equal(hash, "SHA1");
+ rnp_buffer_destroy(hash);
+ s2k = NULL;
+ assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+ assert_string_equal(s2k, "Iterated and salted");
+ rnp_buffer_destroy(s2k);
+ iterations = 0;
+ assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+ assert_int_equal(iterations, 30408704);
+ }
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message with AEAD-OCB-AES: single password */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-ocb-aes"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ if (!aead_ocb_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ }
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ assert_non_null(symenc);
+ cipher = NULL;
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES192");
+ rnp_buffer_destroy(cipher);
+ aead = NULL;
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+ assert_string_equal(aead, "OCB");
+ rnp_buffer_destroy(aead);
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ if (!aead_ocb_enabled()) {
+ assert_null(symenc);
+ } else {
+ assert_non_null(symenc);
+ cipher = NULL;
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES192");
+ rnp_buffer_destroy(cipher);
+ aead = NULL;
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+ assert_string_equal(aead, "OCB");
+ rnp_buffer_destroy(aead);
+ }
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* modified message with AEAD-EAX: one recipient and one password, decrypt with recipient
+ */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax-malf"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+ assert_non_null(recipient);
+ alg = NULL;
+ assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ keyid = NULL;
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "1ED63EE56FADC34D");
+ rnp_buffer_destroy(keyid);
+ recipient = NULL;
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ if (!aead_eax_enabled()) {
+ assert_null(recipient);
+ } else {
+ assert_non_null(recipient);
+ assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "1ED63EE56FADC34D");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_non_null(recipient);
+ alg = NULL;
+ assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ keyid = NULL;
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "1ED63EE56FADC34D");
+ rnp_buffer_destroy(keyid);
+ recipient = NULL;
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_non_null(recipient);
+ assert_rnp_success(rnp_recipient_get_alg(recipient, &alg));
+ assert_string_equal(alg, "RSA");
+ rnp_buffer_destroy(alg);
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "1ED63EE56FADC34D");
+ rnp_buffer_destroy(keyid);
+ }
+
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ assert_non_null(symenc);
+ cipher = NULL;
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES256");
+ rnp_buffer_destroy(cipher);
+ aead = NULL;
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+ assert_string_equal(aead, "EAX");
+ rnp_buffer_destroy(aead);
+ hash = NULL;
+ assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+ assert_string_equal(hash, "SHA256");
+ rnp_buffer_destroy(hash);
+ s2k = NULL;
+ assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+ assert_string_equal(s2k, "Iterated and salted");
+ rnp_buffer_destroy(s2k);
+ iterations = 0;
+ assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+ assert_int_equal(iterations, 3932160);
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ assert_null(symenc);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message with AEAD-EAX: one recipient and one password, decrypt with password */
+ assert_rnp_success(rnp_unload_keys(ffi, RNP_KEY_UNLOAD_SECRET));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-aead-eax"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ }
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_null(recipient);
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ assert_non_null(symenc);
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ if (!aead_eax_enabled()) {
+ assert_null(symenc);
+ } else {
+ assert_non_null(symenc);
+ cipher = NULL;
+ assert_rnp_success(rnp_symenc_get_cipher(symenc, &cipher));
+ assert_string_equal(cipher, "AES256");
+ rnp_buffer_destroy(cipher);
+ aead = NULL;
+ assert_rnp_success(rnp_symenc_get_aead_alg(symenc, &aead));
+ assert_string_equal(aead, "EAX");
+ rnp_buffer_destroy(aead);
+ hash = NULL;
+ assert_rnp_success(rnp_symenc_get_hash_alg(symenc, &hash));
+ assert_string_equal(hash, "SHA256");
+ rnp_buffer_destroy(hash);
+ s2k = NULL;
+ assert_rnp_success(rnp_symenc_get_s2k_type(symenc, &s2k));
+ assert_string_equal(s2k, "Iterated and salted");
+ rnp_buffer_destroy(s2k);
+ iterations = 0;
+ assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+ assert_int_equal(iterations, 3932160);
+ }
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* message encrypted to 3 recipients and 2 passwords: password1, password2 */
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "wrongpassword"));
+ assert_true(import_sec_keys(ffi, "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 3);
+ assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 0, &recipient));
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "1ED63EE56FADC34D");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 1, &recipient));
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "8A05B89FAD5ADED1");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_op_verify_get_recipient_at(verify, 2, &recipient));
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "54505A936A4A970E");
+ rnp_buffer_destroy(keyid);
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_null(recipient);
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 2);
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 0, &symenc));
+ assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+ assert_int_equal(iterations, 3932160);
+ assert_rnp_success(rnp_op_verify_get_symenc_at(verify, 1, &symenc));
+ assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+ assert_int_equal(iterations, 3276800);
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ assert_null(symenc);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password2"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ assert_rnp_success(rnp_symenc_get_s2k_iterations(symenc, &iterations));
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_null(recipient);
+ assert_int_equal(iterations, 3276800);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, getpasscb_for_key, (void *) "8A05B89FAD5ADED1"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-3key-2p"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ assert_rnp_success(rnp_op_verify_get_used_symenc(verify, &symenc));
+ assert_null(symenc);
+ assert_rnp_success(rnp_op_verify_get_used_recipient(verify, &recipient));
+ assert_rnp_success(rnp_recipient_get_keyid(recipient, &keyid));
+ assert_string_equal(keyid, "8A05B89FAD5ADED1");
+ rnp_buffer_destroy(keyid);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_secret_sig_import)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
+ rnp_key_handle_t key_handle = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key_handle));
+ bool locked = false;
+ /* unlock secret key */
+ assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
+ assert_true(locked);
+ assert_rnp_success(rnp_key_unlock(key_handle, "password"));
+ assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
+ assert_false(locked);
+ /* import revocation signature */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_key_validity/alice-rev.pgp"));
+ assert_rnp_success(rnp_import_signatures(ffi, input, 0, NULL));
+ assert_rnp_success(rnp_input_destroy(input));
+ /* make sure that key is still unlocked */
+ assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
+ assert_false(locked);
+ /* import subkey */
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sub-sec.pgp"));
+ /* make sure that primary key is still unlocked */
+ assert_rnp_success(rnp_key_is_locked(key_handle, &locked));
+ assert_false(locked);
+ /* unlock subkey and make sure it is unlocked after revocation */
+ rnp_key_handle_t sub_handle = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "DD23CEB7FEBEFF17", &sub_handle));
+ assert_rnp_success(rnp_key_unlock(sub_handle, "password"));
+ assert_rnp_success(rnp_key_is_locked(sub_handle, &locked));
+ assert_false(locked);
+ assert_rnp_success(rnp_key_revoke(sub_handle, 0, "SHA256", "retired", "Custom reason"));
+ assert_rnp_success(rnp_key_is_locked(sub_handle, &locked));
+ assert_false(locked);
+ assert_rnp_success(rnp_key_handle_destroy(sub_handle));
+ assert_rnp_success(rnp_key_handle_destroy(key_handle));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+static bool
+getpasscb_fail(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ return false;
+}
+
+static bool
+getpasscb_context(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ strncpy(buf, pgp_context, buf_len - 1);
+ return true;
+}
+
+static bool
+getpasscb_keyid(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ if (!key) {
+ return false;
+ }
+ char *keyid = NULL;
+ if (rnp_key_get_keyid(key, &keyid)) {
+ return false;
+ }
+ strncpy(buf, keyid, buf_len - 1);
+ rnp_buffer_destroy(keyid);
+ return true;
+}
+
+TEST_F(rnp_tests, test_ffi_rnp_request_password)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ /* check wrong parameters cases */
+ char *password = NULL;
+ assert_rnp_failure(rnp_request_password(ffi, NULL, "sign", &password));
+ assert_null(password);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_failure(rnp_request_password(NULL, NULL, "sign", &password));
+ assert_rnp_failure(rnp_request_password(ffi, NULL, "sign", NULL));
+ /* now it should succeed */
+ assert_rnp_success(rnp_request_password(ffi, NULL, "sign", &password));
+ assert_string_equal(password, "password");
+ rnp_buffer_destroy(password);
+ /* let's try failing password provider */
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_fail, NULL));
+ assert_rnp_failure(rnp_request_password(ffi, NULL, "sign", &password));
+ /* let's try to return request context */
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_context, NULL));
+ assert_rnp_success(rnp_request_password(ffi, NULL, "custom context", &password));
+ assert_string_equal(password, "custom context");
+ rnp_buffer_destroy(password);
+ /* let's check whether key is correctly passed to handler */
+ assert_true(import_sec_keys(ffi, "data/test_key_validity/alice-sec.asc"));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_ffi_set_pass_provider(ffi, getpasscb_keyid, NULL));
+ assert_rnp_success(rnp_request_password(ffi, key, NULL, &password));
+ assert_string_equal(password, "0451409669FFDE3C");
+ rnp_buffer_destroy(password);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_mdc_8k_boundary)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+
+ test_ffi_init(&ffi);
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ /* correctly process two messages */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_1.pgp"));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ /* check signature */
+ size_t sig_count = 0;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &sig_count));
+ assert_int_equal(sig_count, 1);
+ rnp_op_verify_signature_t sig = NULL;
+ assert_rnp_success(rnp_op_verify_get_signature_at(verify, 0, &sig));
+ assert_rnp_success(rnp_op_verify_signature_get_status(sig));
+ /* cleanup */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_2.pgp"));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ /* check signature */
+ sig_count = 0;
+ 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));
+ /* cleanup */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+
+ /* let it gracefully fail on message 1 with the last byte cut */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_cut1.pgp"));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ /* cleanup */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+
+ /* let it gracefully fail on message 1 with the last 22 bytes (MDC size) cut */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message_mdc_8k_cut22.pgp"));
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ /* cleanup */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_ffi_decrypt_wrong_mpi_bits)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // init ffi
+ test_ffi_init(&ffi);
+
+ /* 1024 bitcount instead of 1023 */
+ rnp_op_verify_t op = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-1"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(op));
+ }
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* 1025 bitcount instead of 1023 */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-2"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(op));
+ }
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* 1031 bitcount instead of 1023 */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-3"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(op));
+ }
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* 1040 bitcount instead of 1023 */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-4"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(op));
+ }
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* 1017 bitcount instead of 1023 */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-malf-5"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ if (!aead_eax_enabled()) {
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ } else {
+ assert_rnp_success(rnp_op_verify_execute(op));
+ }
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_decrypt_edge_cases)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ test_ffi_init(&ffi);
+
+ /* unknown algorithm in public-key encrypted session key */
+ rnp_op_verify_t op = NULL;
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.txt.enc-wrong-alg"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* endless recursive compression packets, 'quine'.
+ * Generated using the code by Taylor R. Campbell */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.zlib-quine"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.zlib-quine"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.zlib-quine"));
+ char *json = NULL;
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ assert_non_null(json);
+ rnp_buffer_destroy(json);
+ rnp_input_destroy(input);
+
+ /* 128 levels of compression - fail decryption*/
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr.128-rounds"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* but dumping will succeed */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr.128-rounds"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr.128-rounds"));
+ json = NULL;
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ assert_non_null(json);
+ rnp_buffer_destroy(json);
+ rnp_input_destroy(input);
+
+ /* 32 levels of compression + encryption */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr-encr.32-rounds"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(op));
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr-encr.32-rounds"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr-encr.32-rounds"));
+ json = NULL;
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ assert_non_null(json);
+ rnp_buffer_destroy(json);
+ rnp_input_destroy(input);
+
+ /* 31 levels of compression + encryption */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr-encr.31-rounds"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_op_verify_create(&op, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(op));
+ rnp_op_verify_destroy(op);
+ rnp_input_destroy(input);
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(len, 7);
+ assert_int_equal(memcmp(buf, "message", 7), 0);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr-encr.31-rounds"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_dump_packets_to_output(input, output, 0));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_messages/message.compr-encr.31-rounds"));
+ json = NULL;
+ assert_rnp_success(rnp_dump_packets_to_json(input, 0, &json));
+ assert_non_null(json);
+ rnp_buffer_destroy(json);
+ rnp_input_destroy(input);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_key_remove)
+{
+ rnp_ffi_t ffi = NULL;
+ test_ffi_init(&ffi);
+
+ rnp_key_handle_t key0 = NULL;
+ rnp_key_handle_t key0_sub0 = NULL;
+ rnp_key_handle_t key0_sub1 = NULL;
+ rnp_key_handle_t key0_sub2 = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key0));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1d7e8a5393c997a8", &key0_sub1));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &key0_sub2));
+
+ /* edge cases */
+ assert_rnp_failure(rnp_key_remove(NULL, RNP_KEY_REMOVE_PUBLIC));
+ assert_rnp_failure(rnp_key_remove(key0, 0));
+ /* make sure we correctly remove public and secret keys */
+ bool pub = false;
+ assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
+ assert_true(pub);
+ bool sec = false;
+ assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
+ assert_true(sec);
+ assert_rnp_success(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_PUBLIC));
+ pub = true;
+ assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
+ assert_false(pub);
+ sec = false;
+ assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
+ assert_true(sec);
+ assert_rnp_failure(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_PUBLIC));
+ rnp_key_handle_destroy(key0_sub2);
+ /* locate it back */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &key0_sub2));
+ assert_non_null(key0_sub2);
+ pub = true;
+ assert_rnp_success(rnp_key_have_public(key0_sub2, &pub));
+ assert_false(pub);
+ sec = false;
+ assert_rnp_success(rnp_key_have_secret(key0_sub2, &sec));
+ assert_true(sec);
+
+ pub = false;
+ assert_rnp_success(rnp_key_have_public(key0_sub0, &pub));
+ assert_true(pub);
+ sec = false;
+ assert_rnp_success(rnp_key_have_secret(key0_sub0, &sec));
+ assert_true(sec);
+ assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
+ pub = false;
+ assert_rnp_success(rnp_key_have_public(key0_sub0, &pub));
+ assert_true(pub);
+ sec = true;
+ assert_rnp_success(rnp_key_have_secret(key0_sub0, &sec));
+ assert_false(sec);
+ assert_rnp_failure(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
+ rnp_key_handle_destroy(key0_sub0);
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
+ assert_non_null(key0_sub0);
+
+ size_t count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 6);
+ count = 0;
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 6);
+
+ /* while there are 2 public and 1 secret subkey, this calculates only public */
+ assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
+ assert_int_equal(count, 2);
+
+ assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_PUBLIC));
+ assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
+ assert_int_equal(count, 1);
+ count = 0;
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 5);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 6);
+
+ assert_rnp_success(rnp_key_remove(key0_sub2, RNP_KEY_REMOVE_SECRET));
+ assert_rnp_success(rnp_key_get_subkey_count(key0, &count));
+ assert_int_equal(count, 1);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 5);
+
+ assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 4);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 4);
+
+ rnp_key_handle_destroy(key0_sub1);
+ /* key0_sub1 should be left in keyring */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1d7e8a5393c997a8", &key0_sub1));
+ pub = false;
+ assert_rnp_success(rnp_key_have_public(key0_sub1, &pub));
+ assert_true(pub);
+ sec = false;
+ assert_rnp_success(rnp_key_have_secret(key0_sub1, &sec));
+ assert_true(sec);
+
+ rnp_key_handle_destroy(key0);
+ rnp_key_handle_destroy(key0_sub0);
+ rnp_key_handle_destroy(key0_sub1);
+ rnp_key_handle_destroy(key0_sub2);
+
+ /* let's import keys back */
+ assert_true(import_pub_keys(ffi, "data/keyrings/1/pubring.gpg"));
+ assert_true(import_sec_keys(ffi, "data/keyrings/1/secring.gpg"));
+
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 7);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 7);
+
+ /* now try to remove the whole key */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key0));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "1ed63ee56fadc34d", &key0_sub0));
+
+ assert_rnp_failure(
+ rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
+ assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_SECRET));
+ assert_rnp_success(rnp_key_remove(key0_sub0, RNP_KEY_REMOVE_PUBLIC));
+
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 6);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 6);
+
+ assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 6);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 3);
+
+ assert_rnp_success(rnp_key_remove(key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SUBKEYS));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 3);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 3);
+
+ rnp_key_handle_destroy(key0);
+ rnp_key_handle_destroy(key0_sub0);
+
+ /* delete the second key all at once */
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "2fcadf05ffa501bb", &key0));
+ assert_rnp_success(rnp_key_remove(
+ key0, RNP_KEY_REMOVE_PUBLIC | RNP_KEY_REMOVE_SECRET | RNP_KEY_REMOVE_SUBKEYS));
+ assert_rnp_success(rnp_get_public_key_count(ffi, &count));
+ assert_int_equal(count, 0);
+ assert_rnp_success(rnp_get_secret_key_count(ffi, &count));
+ assert_int_equal(count, 0);
+ rnp_key_handle_destroy(key0);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_literal_packet)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // init ffi
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* try rnp_decrypt() */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.literal"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ rnp_output_memory_get_buf(output, &buf, &len, false);
+ std::string out;
+ out.assign((char *) buf, len);
+ assert_true(out == file_to_str("data/test_messages/message.txt"));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* try rnp_op_verify() */
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_messages/message.txt.literal"));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ rnp_output_memory_get_buf(output, &buf, &len, false);
+ out.assign((char *) buf, len);
+ assert_true(out == file_to_str("data/test_messages/message.txt"));
+ char *mode = NULL;
+ char *cipher = NULL;
+ bool valid = true;
+ assert_rnp_success(rnp_op_verify_get_protection_info(verify, &mode, &cipher, &valid));
+ assert_string_equal(mode, "none");
+ assert_string_equal(cipher, "none");
+ assert_false(valid);
+ rnp_buffer_destroy(mode);
+ rnp_buffer_destroy(cipher);
+ size_t count = 255;
+ assert_rnp_success(rnp_op_verify_get_signature_count(verify, &count));
+ assert_int_equal(count, 0);
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_recipient_count(verify, &count));
+ assert_int_equal(count, 0);
+ count = 255;
+ assert_rnp_success(rnp_op_verify_get_symenc_count(verify, &count));
+ assert_int_equal(count, 0);
+ rnp_op_verify_destroy(verify);
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ rnp_ffi_destroy(ffi);
+}
+
+/* This test checks that any exceptions thrown by the internal library
+ * will not propagate beyond the FFI boundary.
+ * In this case we (ab)use a callback to mimic this scenario.
+ */
+TEST_F(rnp_tests, test_ffi_exception)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ // bad_alloc -> RNP_ERROR_OUT_OF_MEMORY
+ {
+ auto reader = [](void *app_ctx, void *buf, size_t len, size_t *read) {
+ throw std::bad_alloc();
+ return true;
+ };
+ assert_rnp_success(rnp_input_from_callback(&input, reader, NULL, NULL));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_int_equal(RNP_ERROR_OUT_OF_MEMORY, rnp_output_pipe(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+
+ // runtime_error -> RNP_ERROR_GENERIC
+ {
+ auto reader = [](void *app_ctx, void *buf, size_t len, size_t *read) {
+ throw std::runtime_error("");
+ return true;
+ };
+ assert_rnp_success(rnp_input_from_callback(&input, reader, NULL, NULL));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_int_equal(RNP_ERROR_GENERIC, rnp_output_pipe(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+
+ // everything else -> RNP_ERROR_GENERIC
+ {
+ auto reader = [](void *app_ctx, void *buf, size_t len, size_t *read) {
+ throw 5;
+ return true;
+ };
+ assert_rnp_success(rnp_input_from_callback(&input, reader, NULL, NULL));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_int_equal(RNP_ERROR_GENERIC, rnp_output_pipe(input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ }
+}
+
+TEST_F(rnp_tests, test_ffi_key_protection_change)
+{
+ rnp_ffi_t ffi = NULL;
+ test_ffi_init(&ffi);
+
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ rnp_key_handle_t sub = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));
+
+ assert_rnp_success(rnp_key_unprotect(key, "password"));
+ assert_rnp_success(rnp_key_unprotect(sub, "password"));
+
+ bool protect = true;
+ assert_rnp_success(rnp_key_is_protected(key, &protect));
+ assert_false(protect);
+ protect = true;
+ assert_rnp_success(rnp_key_is_protected(sub, &protect));
+ assert_false(protect);
+
+ assert_rnp_success(rnp_key_protect(key, "password2", "Camellia128", NULL, "SHA1", 0));
+ assert_rnp_success(rnp_key_protect(sub, "password2", "Camellia256", NULL, "SHA512", 0));
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(key, &protect));
+ assert_true(protect);
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(sub, &protect));
+ assert_true(protect);
+
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+
+ reload_keyrings(&ffi);
+
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));
+
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(key, &protect));
+ assert_true(protect);
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(sub, &protect));
+ assert_true(protect);
+
+ char *cipher = NULL;
+ assert_rnp_success(rnp_key_get_protection_cipher(key, &cipher));
+ assert_string_equal(cipher, "CAMELLIA128");
+ rnp_buffer_destroy(cipher);
+ char *hash = NULL;
+ assert_rnp_success(rnp_key_get_protection_hash(key, &hash));
+ assert_string_equal(hash, "SHA1");
+ rnp_buffer_destroy(hash);
+ cipher = NULL;
+ assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher));
+ assert_string_equal(cipher, "CAMELLIA256");
+ rnp_buffer_destroy(cipher);
+ hash = NULL;
+ assert_rnp_success(rnp_key_get_protection_hash(sub, &hash));
+ assert_string_equal(hash, "SHA512");
+ rnp_buffer_destroy(hash);
+
+ assert_rnp_failure(rnp_key_unlock(key, "password"));
+ assert_rnp_failure(rnp_key_unlock(sub, "password"));
+
+ assert_rnp_success(rnp_key_unlock(key, "password2"));
+ assert_rnp_success(rnp_key_unlock(sub, "password2"));
+
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(key, &protect));
+ assert_true(protect);
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(sub, &protect));
+ assert_true(protect);
+
+ assert_rnp_success(rnp_key_protect(key, "password3", "AES256", NULL, "SHA512", 10000000));
+ assert_rnp_success(rnp_key_protect(sub, "password3", "AES128", NULL, "SHA1", 10000000));
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(key, &protect));
+ assert_true(protect);
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(sub, &protect));
+ assert_true(protect);
+
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+
+ reload_keyrings(&ffi);
+
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7bc6709b15c23a4a", &key));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "8a05b89fad5aded1", &sub));
+
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(key, &protect));
+ assert_true(protect);
+ protect = false;
+ assert_rnp_success(rnp_key_is_protected(sub, &protect));
+ assert_true(protect);
+
+ cipher = NULL;
+ assert_rnp_success(rnp_key_get_protection_cipher(key, &cipher));
+ assert_string_equal(cipher, "AES256");
+ rnp_buffer_destroy(cipher);
+ hash = NULL;
+ assert_rnp_success(rnp_key_get_protection_hash(key, &hash));
+ assert_string_equal(hash, "SHA512");
+ rnp_buffer_destroy(hash);
+ size_t iterations = 0;
+ assert_rnp_success(rnp_key_get_protection_iterations(key, &iterations));
+ assert_true(iterations >= 10000000);
+ cipher = NULL;
+ assert_rnp_success(rnp_key_get_protection_cipher(sub, &cipher));
+ assert_string_equal(cipher, "AES128");
+ rnp_buffer_destroy(cipher);
+ hash = NULL;
+ assert_rnp_success(rnp_key_get_protection_hash(sub, &hash));
+ assert_string_equal(hash, "SHA1");
+ rnp_buffer_destroy(hash);
+ iterations = 0;
+ assert_rnp_success(rnp_key_get_protection_iterations(sub, &iterations));
+ assert_true(iterations >= 10000000);
+
+ assert_rnp_failure(rnp_key_unlock(key, "password"));
+ assert_rnp_failure(rnp_key_unlock(sub, "password"));
+
+ assert_rnp_success(rnp_key_unlock(key, "password3"));
+ assert_rnp_success(rnp_key_unlock(sub, "password3"));
+
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(sub);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_ffi_set_log_fd)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_failure(rnp_ffi_set_log_fd(NULL, 0));
+ assert_rnp_failure(rnp_ffi_set_log_fd(ffi, 100));
+ int file_fd = rnp_open("tests.txt", O_RDWR | O_CREAT | O_TRUNC, 0777);
+ assert_true(file_fd > 0);
+ assert_rnp_success(rnp_ffi_set_log_fd(ffi, file_fd));
+ rnp_input_t input = NULL;
+ const char *msg = "hello";
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) msg, strlen(msg), true));
+ char *saved_env = NULL;
+ if (getenv(RNP_LOG_CONSOLE)) {
+ saved_env = strdup(getenv(RNP_LOG_CONSOLE));
+ }
+ setenv(RNP_LOG_CONSOLE, "1", 1);
+ assert_rnp_failure(rnp_load_keys(ffi, "GPG", input, 119));
+ assert_rnp_success(rnp_input_destroy(input));
+ rnp_ffi_destroy(ffi);
+ if (saved_env) {
+ assert_int_equal(0, setenv(RNP_LOG_CONSOLE, saved_env, 1));
+ free(saved_env);
+ } else {
+ unsetenv(RNP_LOG_CONSOLE);
+ }
+#ifndef _WIN32
+ assert_int_equal(fcntl(file_fd, F_GETFD), -1);
+ assert_int_equal(errno, EBADF);
+#endif
+ char buffer[128] = {0};
+ file_fd = rnp_open("tests.txt", O_RDONLY, 0);
+ int64_t rres = read(file_fd, buffer, sizeof(buffer));
+ assert_true(rres > 0);
+ assert_non_null(strstr(buffer, "unexpected flags remaining: 0x"));
+ close(file_fd);
+}
+
+TEST_F(rnp_tests, test_ffi_security_profile)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_ffi_create(&ffi, "GPG", "GPG");
+ /* check predefined rules */
+ uint32_t flags = 0;
+ uint64_t from = 0;
+ uint32_t level = 0;
+ assert_rnp_failure(
+ rnp_get_security_rule(NULL, RNP_FEATURE_HASH_ALG, "SHA1", 0, &flags, &from, &level));
+ assert_rnp_failure(rnp_get_security_rule(ffi, NULL, "SHA1", 0, &flags, &from, &level));
+ assert_rnp_failure(
+ rnp_get_security_rule(ffi, RNP_FEATURE_SYMM_ALG, "AES256", 0, &flags, &from, &level));
+ assert_rnp_failure(
+ rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "Unknown", 0, &flags, &from, &level));
+ assert_rnp_failure(
+ rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", 0, NULL, NULL, NULL));
+ /* default rule */
+ from = 256;
+ assert_rnp_success(
+ rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA256", 0, &flags, &from, &level));
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ assert_int_equal(from, 0);
+ /* MD5 */
+ from = 256;
+ assert_rnp_success(
+ rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "MD5", 0, &flags, &from, &level));
+ assert_int_equal(from, 0);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, MD5_FROM);
+ assert_int_equal(level, RNP_SECURITY_INSECURE);
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", MD5_FROM - 1, &flags, &from, &level));
+ assert_int_equal(from, 0);
+ assert_int_equal(flags, 0);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ /* Override it */
+ assert_rnp_failure(rnp_add_security_rule(
+ NULL, RNP_FEATURE_HASH_ALG, "MD5", RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
+ assert_rnp_failure(rnp_add_security_rule(
+ ffi, RNP_FEATURE_SYMM_ALG, "MD5", RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
+ assert_rnp_failure(
+ rnp_add_security_rule(ffi, NULL, "MD5", RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
+ assert_rnp_failure(rnp_add_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, NULL, RNP_SECURITY_OVERRIDE, 0, RNP_SECURITY_DEFAULT));
+ assert_rnp_failure(rnp_add_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_OVERRIDE | 0x17,
+ 0,
+ RNP_SECURITY_DEFAULT));
+ assert_rnp_failure(
+ rnp_add_security_rule(ffi, RNP_FEATURE_HASH_ALG, "MD5", RNP_SECURITY_OVERRIDE, 0, 25));
+ assert_rnp_success(rnp_add_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_OVERRIDE,
+ MD5_FROM - 1,
+ RNP_SECURITY_DEFAULT));
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, MD5_FROM - 1);
+ assert_int_equal(flags, RNP_SECURITY_OVERRIDE);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ /* Remove and check back */
+ size_t removed = 0;
+ assert_rnp_failure(rnp_remove_security_rule(NULL,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_DEFAULT,
+ RNP_SECURITY_OVERRIDE,
+ MD5_FROM - 1,
+ &removed));
+ assert_rnp_failure(rnp_remove_security_rule(ffi,
+ RNP_FEATURE_SYMM_ALG,
+ "MD5",
+ RNP_SECURITY_DEFAULT,
+ RNP_SECURITY_OVERRIDE,
+ MD5_FROM - 1,
+ &removed));
+ assert_rnp_success(rnp_remove_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "SHA256",
+ RNP_SECURITY_DEFAULT,
+ RNP_SECURITY_OVERRIDE,
+ MD5_FROM - 1,
+ &removed));
+ assert_int_equal(removed, 0);
+ removed = 1;
+ assert_rnp_success(rnp_remove_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_INSECURE,
+ RNP_SECURITY_OVERRIDE,
+ MD5_FROM - 1,
+ &removed));
+ assert_int_equal(removed, 0);
+ removed = 1;
+ assert_rnp_success(rnp_remove_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", RNP_SECURITY_DEFAULT, 0, MD5_FROM - 1, &removed));
+ assert_int_equal(removed, 0);
+ removed = 1;
+ assert_rnp_success(rnp_remove_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_DEFAULT,
+ RNP_SECURITY_OVERRIDE,
+ MD5_FROM,
+ &removed));
+ assert_int_equal(removed, 0);
+ assert_rnp_success(rnp_remove_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_DEFAULT,
+ RNP_SECURITY_OVERRIDE,
+ MD5_FROM - 1,
+ &removed));
+ assert_int_equal(removed, 1);
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, MD5_FROM);
+ assert_int_equal(level, RNP_SECURITY_INSECURE);
+ assert_int_equal(flags, 0);
+ /* Add for data sigs only */
+ assert_rnp_success(rnp_add_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_VERIFY_DATA,
+ MD5_FROM + 1,
+ RNP_SECURITY_DEFAULT));
+ flags = RNP_SECURITY_VERIFY_DATA;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, MD5_FROM + 1);
+ assert_int_equal(flags, RNP_SECURITY_VERIFY_DATA);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ /* Add for key sigs only */
+ assert_rnp_success(rnp_add_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_VERIFY_KEY,
+ MD5_FROM + 2,
+ RNP_SECURITY_DEFAULT));
+ flags = RNP_SECURITY_VERIFY_KEY;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, MD5_FROM + 2);
+ assert_int_equal(flags, RNP_SECURITY_VERIFY_KEY);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ /* Remove added two rules */
+ assert_rnp_success(rnp_remove_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_DEFAULT,
+ RNP_SECURITY_VERIFY_DATA,
+ MD5_FROM + 1,
+ &removed));
+ assert_int_equal(removed, 1);
+ assert_rnp_success(rnp_remove_security_rule(ffi,
+ RNP_FEATURE_HASH_ALG,
+ "MD5",
+ RNP_SECURITY_DEFAULT,
+ RNP_SECURITY_VERIFY_KEY,
+ MD5_FROM + 2,
+ &removed));
+ assert_int_equal(removed, 1);
+ /* Remove all */
+ removed = 0;
+ assert_rnp_failure(rnp_remove_security_rule(ffi, NULL, NULL, 0, 0x17, 0, &removed));
+ assert_rnp_success(rnp_remove_security_rule(ffi, NULL, NULL, 0, 0, 0, &removed));
+ assert_int_equal(removed, 3);
+ rnp_ffi_destroy(ffi);
+ rnp_ffi_create(&ffi, "GPG", "GPG");
+ /* Remove all rules for hash */
+ assert_rnp_failure(
+ rnp_remove_security_rule(ffi, RNP_FEATURE_SYMM_ALG, NULL, 0, 0, 0, &removed));
+ removed = 0;
+ assert_rnp_success(
+ rnp_remove_security_rule(ffi, RNP_FEATURE_HASH_ALG, NULL, 0, 0, 0, &removed));
+ assert_int_equal(removed, 3);
+ rnp_ffi_destroy(ffi);
+ rnp_ffi_create(&ffi, "GPG", "GPG");
+ /* Remove all rules for specific hash */
+ assert_rnp_success(rnp_remove_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "MD5", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
+ assert_int_equal(removed, 1);
+ assert_rnp_success(rnp_remove_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "SHA1", 0, RNP_SECURITY_REMOVE_ALL, 0, &removed));
+ assert_int_equal(removed, 2);
+ rnp_ffi_destroy(ffi);
+ rnp_ffi_create(&ffi, "GPG", "GPG");
+ /* SHA1 - ancient times */
+ from = 256;
+ flags = 0;
+ assert_rnp_success(
+ rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", 0, &flags, &from, &level));
+ assert_int_equal(from, 0);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ assert_int_equal(flags, 0);
+ /* SHA1 - now, data verify disabled, key sig verify is enabled */
+ flags = 0;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "SHA1", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, SHA1_DATA_FROM);
+ assert_int_equal(level, RNP_SECURITY_INSECURE);
+ assert_int_equal(flags, RNP_SECURITY_VERIFY_DATA);
+ flags = 0;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "SHA1", SHA1_DATA_FROM - 1, &flags, &from, &level));
+ assert_int_equal(from, 0);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ flags = RNP_SECURITY_VERIFY_DATA;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "SHA1", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, SHA1_DATA_FROM);
+ assert_int_equal(level, RNP_SECURITY_INSECURE);
+ assert_int_equal(flags, RNP_SECURITY_VERIFY_DATA);
+ flags = RNP_SECURITY_VERIFY_KEY;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "SHA1", time(NULL), &flags, &from, &level));
+ assert_int_equal(from, 0);
+ assert_int_equal(level, RNP_SECURITY_DEFAULT);
+ assert_int_equal(flags, 0);
+ flags = RNP_SECURITY_VERIFY_KEY;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "SHA1", SHA1_KEY_FROM + 5, &flags, &from, &level));
+ assert_int_equal(from, SHA1_KEY_FROM);
+ assert_int_equal(level, RNP_SECURITY_INSECURE);
+ assert_int_equal(flags, RNP_SECURITY_VERIFY_KEY);
+ flags = 0;
+ assert_rnp_success(rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, "SHA1", SHA1_KEY_FROM + 5, &flags, &from, &level));
+ assert_int_equal(from, SHA1_KEY_FROM);
+ assert_int_equal(level, RNP_SECURITY_INSECURE);
+ assert_int_equal(flags, RNP_SECURITY_VERIFY_KEY);
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_result_to_string)
+{
+ const char * result_string = NULL;
+ rnp_result_t code;
+ std::set<std::string> stringset;
+
+ result_string = rnp_result_to_string(RNP_SUCCESS);
+ assert_string_equal(result_string, "Success");
+
+ /* Cover all defined error code ranges,
+ * check that each defined
+ * code has corresponding unique string */
+
+ std::vector<std::pair<rnp_result_t, rnp_result_t>> error_codes = {
+ {RNP_ERROR_GENERIC, RNP_ERROR_NULL_POINTER},
+ {RNP_ERROR_ACCESS, RNP_ERROR_WRITE},
+ {RNP_ERROR_BAD_STATE, RNP_ERROR_SIGNATURE_UNKNOWN},
+ {RNP_ERROR_NOT_ENOUGH_DATA, RNP_ERROR_EOF}};
+
+ for (auto &range : error_codes) {
+ for (code = range.first; code <= range.second; code++) {
+ result_string = rnp_result_to_string(code);
+
+ assert_int_not_equal(strcmp("Unsupported error code", result_string), 0);
+
+ auto search = stringset.find(result_string);
+
+ /* Make sure returned error string is not already returned for other codes */
+ assert_true(search == stringset.end());
+
+ stringset.insert(result_string);
+ }
+ }
+}
diff --git a/src/tests/file-utils.cpp b/src/tests/file-utils.cpp
new file mode 100644
index 0000000..508bd5c
--- /dev/null
+++ b/src/tests/file-utils.cpp
@@ -0,0 +1,73 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp_tests.h"
+#include "file-utils.h"
+
+TEST_F(rnp_tests, test_rnp_mkstemp)
+{
+#ifdef _WIN32
+ const char tmpl[17] = "test-file.XXXXXX";
+ char buf[17];
+ const int size = 20;
+ int fds[size];
+ std::string filenames[size];
+ for (int i = 0; i < size; i++) {
+ memcpy(buf, tmpl, sizeof(buf));
+ int fd = rnp_mkstemp(buf);
+ assert_int_not_equal(-1, fd);
+ fds[i] = fd;
+ filenames[i] = buf;
+ }
+ assert_false(filenames[0] == filenames[1]);
+ for (int i = 0; i < size; i++) {
+ if (fds[i] != -1) {
+ assert_int_equal(0, close(fds[i]));
+ assert_int_equal(0, unlink(filenames[i].c_str()));
+ }
+ }
+#endif
+}
+
+TEST_F(rnp_tests, test_rnp_access)
+{
+#ifdef _WIN32
+ /* Assume we are running as non-Administrator user */
+ assert_int_equal(0, rnp_access("C:\\Windows", F_OK));
+ assert_int_equal(0, rnp_access("C:\\Windows", R_OK));
+ assert_int_equal(0, rnp_access("C:\\Windows", W_OK));
+ assert_int_equal(0, rnp_access("C:\\Windows\\System32\\User32.dll", F_OK));
+ assert_int_equal(0, rnp_access("C:\\Windows\\System32\\User32.dll", R_OK));
+ /* Should fail, but unfortunately _waccess() works this way */
+ assert_int_equal(0, rnp_access("C:\\Windows\\System32\\User32.dll", W_OK));
+#else
+ /* Assume we are running as non-root and root directory is non-writeable for us */
+ assert_int_equal(0, rnp_access("/", F_OK));
+ assert_int_equal(0, rnp_access("/", R_OK));
+ assert_int_equal(-1, rnp_access("/", W_OK));
+ assert_int_equal(0, rnp_access("/tmp", R_OK | W_OK));
+#endif
+}
diff --git a/src/tests/fuzz_dump.cpp b/src/tests/fuzz_dump.cpp
new file mode 100644
index 0000000..7ad1415
--- /dev/null
+++ b/src/tests/fuzz_dump.cpp
@@ -0,0 +1,55 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+extern "C" int dump_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_dump/"
+
+TEST_F(rnp_tests, test_fuzz_dump)
+{
+ auto data =
+ file_to_vec(DATA_PATH "clusterfuzz-testcase-minimized-fuzz_dump-5757362284265472");
+ time_t start = time(NULL);
+ assert_int_equal(dump_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+ assert_true(time(NULL) - start <= 1800);
+
+ data = file_to_vec(DATA_PATH "timeout-7e498daecad7ee646371a466d4a317c59fe7db89");
+ start = time(NULL);
+ assert_int_equal(dump_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+ assert_true(time(NULL) - start <= 30);
+
+ data = file_to_vec(DATA_PATH "timeout-6462239459115008");
+ start = time(NULL);
+ assert_int_equal(dump_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+ assert_true(time(NULL) - start <= 30);
+
+ data = file_to_vec(DATA_PATH "outofmemory-5570076898623488");
+ assert_int_equal(dump_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/fuzz_keyimport.cpp b/src/tests/fuzz_keyimport.cpp
new file mode 100644
index 0000000..d485ed4
--- /dev/null
+++ b/src/tests/fuzz_keyimport.cpp
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+extern "C" int keyimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_keyimport/"
+
+TEST_F(rnp_tests, test_fuzz_keyimport)
+{
+ auto data = file_to_vec(DATA_PATH "crash_25f06f13b48d58a5faf6c36fae7fcbd958359199");
+ assert_int_equal(keyimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "crash_e932261875271ccf497715de56adf7caf30ca8a7");
+ assert_int_equal(keyimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "crash_37e8ed57ee47c1991b387fa0506f361f9cd9c663");
+ assert_int_equal(keyimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "leak_11307b70cc609c93fc3a49d37f3a31166df50f44");
+ assert_int_equal(keyimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "timeout_9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051");
+ assert_int_equal(keyimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "leak_371b211d7e9cf9857befcf06c7da74835e249ee7");
+ assert_int_equal(keyimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "timeout-9c10372fe9ebdcdb0b6e275d05f8af4f4e3d6051");
+ assert_int_equal(keyimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/fuzz_keyring.cpp b/src/tests/fuzz_keyring.cpp
new file mode 100644
index 0000000..fd94a67
--- /dev/null
+++ b/src/tests/fuzz_keyring.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+extern "C" int keyring_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_keyring/"
+
+TEST_F(rnp_tests, test_fuzz_keyring)
+{
+ auto data = file_to_vec(DATA_PATH "leak-542d4e51506e3e9d34c9b243e608a964dabfdb21");
+ assert_int_equal(data.size(), 540);
+ assert_int_equal(keyring_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "crash-7ff10f10a95b78461d6f3578f5f99e870c792b9f");
+ assert_int_equal(keyring_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ /* Issue 25292 in oss-fuzz: rnp:fuzz_keyring: Stack-buffer-overflow in stream_write_key */
+ data = file_to_vec(DATA_PATH "crash-8619144979e56d07ab4890bf564b90271ae9b1c9");
+ assert_int_equal(keyring_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ /* Issue 25302 in oss-fuzz: rnp:fuzz_keyring: Direct-leak in Botan::HashFunction::create */
+ data = file_to_vec(DATA_PATH "leak-5ee77f7ae99d7815d069afe037c42f4887193215");
+ assert_int_equal(keyring_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "timeout-6140201111519232");
+ assert_int_equal(keyring_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/fuzz_keyring_g10.cpp b/src/tests/fuzz_keyring_g10.cpp
new file mode 100644
index 0000000..2323034
--- /dev/null
+++ b/src/tests/fuzz_keyring_g10.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+int keyring_g10_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_keyring_g10/"
+
+TEST_F(rnp_tests, test_fuzz_keyring_g10)
+{
+ auto data = file_to_vec(DATA_PATH "crash_c9cabce6f8d7b36fde0306c86ce81c4f554cbd2a");
+ assert_int_equal(keyring_g10_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "crash_4ec166859e821aee27350dcde3e9c06b07a677f7");
+ assert_int_equal(keyring_g10_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "crash_5528625325932544");
+ assert_int_equal(keyring_g10_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/fuzz_keyring_kbx.cpp b/src/tests/fuzz_keyring_kbx.cpp
new file mode 100644
index 0000000..fbaaf24
--- /dev/null
+++ b/src/tests/fuzz_keyring_kbx.cpp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+extern "C" int keyring_kbx_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_keyring_kbx/"
+
+TEST_F(rnp_tests, test_fuzz_keyring_kbx)
+{
+ auto data = file_to_vec(DATA_PATH "leak-52c65c00b53997178f4cd9defa0343573ea8dda6");
+ assert_int_equal(keyring_kbx_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ /* Issue 25386 in oss-fuzz: rnp:fuzz_keyring_kbx: Heap-buffer-overflow in
+ * rnp_key_store_kbx_from_src */
+ data = file_to_vec(DATA_PATH "crash-5526a2e13255018c857ce493c28ce7108b8b2987");
+ assert_int_equal(keyring_kbx_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ /* Issue 25388 in oss-fuzz: rnp:fuzz_keyring_kbx: Heap-buffer-overflow in mem_src_read */
+ data = file_to_vec(DATA_PATH "crash-b894a2f79f7d38a16ae0ee8d74972336aa3f5798");
+ assert_int_equal(keyring_kbx_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ /* Leak found during CI run */
+ data = file_to_vec(DATA_PATH "leak-b02cd1c6b70c10a8a673a34ba3770b39468b7ddf");
+ assert_int_equal(keyring_kbx_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/fuzz_sigimport.cpp b/src/tests/fuzz_sigimport.cpp
new file mode 100644
index 0000000..4de7997
--- /dev/null
+++ b/src/tests/fuzz_sigimport.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+
+extern "C" int sigimport_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_sigimport/"
+
+TEST_F(rnp_tests, test_fuzz_sigimport)
+{
+ /* issue 39578 in oss-fuzz: timeout */
+ auto data = file_to_vec(DATA_PATH "timeout-821848a7b6b667fc41e5ff130415b3efd22ed118");
+ assert_int_equal(sigimport_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/fuzz_verify.cpp b/src/tests/fuzz_verify.cpp
new file mode 100644
index 0000000..43ca22f
--- /dev/null
+++ b/src/tests/fuzz_verify.cpp
@@ -0,0 +1,49 @@
+/*
+ * Copyright (c) 2021, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include <librepgp/stream-write.h>
+
+extern "C" int verify_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_verify/"
+
+TEST_F(rnp_tests, test_fuzz_verify)
+{
+ auto data = file_to_vec(DATA_PATH "timeout-25b8c9d824c8eb492c827689795748298a2b0a46");
+ assert_int_equal(verify_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "timeout-c2aff538c73b447bca689005e9762840b5a022d0");
+ assert_int_equal(verify_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "timeout-5229070269153280");
+ assert_int_equal(verify_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "timeout-6613852539453440");
+ assert_int_equal(verify_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/fuzz_verify_detached.cpp b/src/tests/fuzz_verify_detached.cpp
new file mode 100644
index 0000000..37bb976
--- /dev/null
+++ b/src/tests/fuzz_verify_detached.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include <librepgp/stream-write.h>
+
+extern "C" int verify_detached_LLVMFuzzerTestOneInput(const uint8_t *data, size_t size);
+
+#define DATA_PATH "data/test_fuzz_verify_detached/"
+
+TEST_F(rnp_tests, test_fuzz_verify_detached)
+{
+ auto data = file_to_vec(
+ DATA_PATH "clusterfuzz-testcase-minimized-fuzz_verify_detached-5092660526972928");
+ assert_int_equal(verify_detached_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "outofmemory-23094cb781b2cf6d1749ebac8bd0576e51440498-z");
+ assert_int_equal(verify_detached_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+
+ data = file_to_vec(DATA_PATH "outofmemory-dea88a4aa4ab5fec1291446db702ee893d5559cf");
+ assert_int_equal(verify_detached_LLVMFuzzerTestOneInput(data.data(), data.size()), 0);
+}
diff --git a/src/tests/generatekey.cpp b/src/tests/generatekey.cpp
new file mode 100644
index 0000000..b846ebb
--- /dev/null
+++ b/src/tests/generatekey.cpp
@@ -0,0 +1,1243 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp.h"
+#include <rekey/rnp_key_store.h>
+#include <rnp/rnpcfg.h>
+#include <rnpkeys/rnpkeys.h>
+
+#include "rnp_tests.h"
+#include "support.h"
+#include "crypto/common.h"
+#include "crypto.h"
+#include "pgp-key.h"
+#include "librepgp/stream-ctx.h"
+#include "librepgp/stream-sig.h"
+#include "librepgp/stream-key.h"
+#include "defaults.h"
+#include <fstream>
+
+static bool
+generate_test_key(const char *keystore, const char *userid, const char *hash, const char *home)
+{
+ cli_rnp_t rnp;
+ int pipefd[2] = {-1, -1};
+ bool res = false;
+ size_t keycount = 0;
+
+ /* Initialize the cli rnp structure and generate key */
+ if (!setup_cli_rnp_common(&rnp, keystore, home, pipefd)) {
+ return false;
+ }
+
+ std::vector<rnp_key_handle_t> keys;
+ /* Generate the key */
+ cli_set_default_rsa_key_desc(rnp.cfg(), hash);
+ if (!cli_rnp_generate_key(&rnp, userid)) {
+ goto done;
+ }
+
+ if (!rnp.load_keyrings(true)) {
+ goto done;
+ }
+ if (rnp_get_public_key_count(rnp.ffi, &keycount) || (keycount != 2)) {
+ goto done;
+ }
+ if (rnp_get_secret_key_count(rnp.ffi, &keycount) || (keycount != 2)) {
+ goto done;
+ }
+ if (!cli_rnp_keys_matching_string(
+ &rnp, keys, userid ? userid : "", CLI_SEARCH_SUBKEYS_AFTER)) {
+ goto done;
+ }
+ if (keys.size() != 2) {
+ goto done;
+ }
+ res = true;
+done:
+ if (pipefd[0] != -1) {
+ close(pipefd[0]);
+ }
+ clear_key_handles(keys);
+ rnp.end();
+ return res;
+}
+
+static bool
+hash_supported(const std::string &hash)
+{
+ if (!sm2_enabled() && lowercase(hash) == "sm3") {
+ return false;
+ }
+ return true;
+}
+
+static bool
+hash_secure(rnp_ffi_t ffi, const std::string &hash, uint32_t action)
+{
+ uint32_t flags = action;
+ uint32_t level = 0;
+ rnp_get_security_rule(
+ ffi, RNP_FEATURE_HASH_ALG, hash.c_str(), global_ctx.time(), &flags, NULL, &level);
+ return level == RNP_SECURITY_DEFAULT;
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_testSignature)
+{
+ /* Set the UserId = custom value.
+ * Execute the Generate-key command to generate a new pair of private/public
+ * key
+ * Sign a message, then verify it
+ */
+
+ const char *hashAlg[] = {"SHA1",
+ "SHA224",
+ "SHA256",
+ "SHA384",
+ "SHA512",
+ "SM3",
+ "sha1",
+ "sha224",
+ "sha256",
+ "sha384",
+ "sha512",
+ "sm3",
+ NULL};
+ int pipefd[2] = {-1, -1};
+ char memToSign[] = "A simple test message";
+ cli_rnp_t rnp;
+
+ std::ofstream out("dummyfile.dat");
+ out << memToSign;
+ out.close();
+
+ for (int i = 0; hashAlg[i] != NULL; i++) {
+ std::string userId = std::string("sigtest_") + hashAlg[i];
+ /* Generate key for test */
+ assert_true(
+ generate_test_key(RNP_KEYSTORE_GPG, userId.c_str(), DEFAULT_HASH_ALG, NULL));
+
+ for (unsigned int cleartext = 0; cleartext <= 1; ++cleartext) {
+ for (unsigned int armored = 0; armored <= 1; ++armored) {
+ if (cleartext && !armored) {
+ // This combination doesn't make sense
+ continue;
+ }
+ /* Setup password input and rnp structure */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, pipefd));
+ /* Load keyring */
+ assert_true(rnp.load_keyrings(true));
+ size_t seccount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &seccount));
+ assert_true(seccount > 0);
+
+ /* Setup signing context */
+ rnp_cfg &cfg = rnp.cfg();
+ cfg.load_defaults();
+ cfg.set_bool(CFG_ARMOR, armored);
+ cfg.set_bool(CFG_SIGN_NEEDED, true);
+ cfg.set_str(CFG_HASH, hashAlg[i]);
+ cfg.set_int(CFG_ZLEVEL, 0);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
+ cfg.set_bool(CFG_CLEARTEXT, cleartext);
+ cfg.add_str(CFG_SIGNERS, userId);
+
+ /* Sign the file */
+ if (!hash_supported(hashAlg[i])) {
+ assert_false(cli_rnp_protect_file(&rnp));
+ rnp.end();
+ assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), -1);
+ continue;
+ }
+ assert_true(cli_rnp_protect_file(&rnp));
+ if (pipefd[0] != -1) {
+ close(pipefd[0]);
+ pipefd[0] = -1;
+ }
+
+ /* Verify the file */
+ cfg.clear();
+ cfg.load_defaults();
+ cfg.set_bool(CFG_OVERWRITE, true);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat.pgp");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.verify");
+ if (!hash_secure(rnp.ffi, hashAlg[i], RNP_SECURITY_VERIFY_DATA)) {
+ assert_false(cli_rnp_process_file(&rnp));
+ rnp.end();
+ assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), 0);
+ continue;
+ }
+ assert_true(cli_rnp_process_file(&rnp));
+
+ /* Ensure signature verification passed */
+ std::string verify = file_to_str("dummyfile.verify");
+ if (cleartext) {
+ verify = strip_eol(verify);
+ }
+ assert_true(verify == memToSign);
+
+ /* Corrupt the signature if not armored/cleartext */
+ if (!cleartext && !armored) {
+ std::fstream verf("dummyfile.dat.pgp",
+ std::ios_base::binary | std::ios_base::out |
+ std::ios_base::in);
+ off_t versize = file_size("dummyfile.dat.pgp");
+ verf.seekg(versize - 10, std::ios::beg);
+ char sigch = 0;
+ verf.read(&sigch, 1);
+ sigch = sigch ^ 0xff;
+ verf.seekg(versize - 10, std::ios::beg);
+ verf.write(&sigch, 1);
+ verf.close();
+ assert_false(cli_rnp_process_file(&rnp));
+ }
+
+ rnp.end();
+ assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), 0);
+ rnp_unlink("dummyfile.verify");
+ }
+ }
+ }
+ assert_int_equal(rnp_unlink("dummyfile.dat"), 0);
+}
+
+static bool
+cipher_supported(const std::string &cipher)
+{
+ if (!sm2_enabled() && lowercase(cipher) == "sm4") {
+ return false;
+ }
+ if (!twofish_enabled() && lowercase(cipher) == "twofish") {
+ return false;
+ }
+ if (!blowfish_enabled() && lowercase(cipher) == "blowfish") {
+ return false;
+ }
+ if (!cast5_enabled() && lowercase(cipher) == "cast5") {
+ return false;
+ }
+ return true;
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_testEncryption)
+{
+ const char *cipherAlg[] = {
+ "BLOWFISH", "TWOFISH", "CAST5", "TRIPLEDES", "AES128", "AES192",
+ "AES256", "CAMELLIA128", "CAMELLIA192", "CAMELLIA256", "SM4", "blowfish",
+ "twofish", "cast5", "tripledes", "aes128", "aes192", "aes256",
+ "camellia128", "camellia192", "camellia256", "sm4", NULL};
+
+ cli_rnp_t rnp = {};
+ char memToEncrypt[] = "A simple test message";
+ int pipefd[2] = {-1, -1};
+ const char *userid = "ciphertest";
+
+ std::ofstream out("dummyfile.dat");
+ out << memToEncrypt;
+ out.close();
+
+ assert_true(generate_test_key(RNP_KEYSTORE_GPG, userid, "SHA256", NULL));
+ for (int i = 0; cipherAlg[i] != NULL; i++) {
+ for (unsigned int armored = 0; armored <= 1; ++armored) {
+ /* Set up rnp and encrypt the dataa */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, NULL));
+ /* Load keyring */
+ assert_true(rnp.load_keyrings(false));
+ size_t seccount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &seccount));
+ assert_true(seccount == 0);
+ /* Set the cipher and armored flags */
+ rnp_cfg &cfg = rnp.cfg();
+ cfg.load_defaults();
+ cfg.set_bool(CFG_ARMOR, armored);
+ cfg.set_bool(CFG_ENCRYPT_PK, true);
+ cfg.set_int(CFG_ZLEVEL, 0);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
+ cfg.set_str(CFG_CIPHER, cipherAlg[i]);
+ cfg.add_str(CFG_RECIPIENTS, userid);
+ /* Encrypt the file */
+ bool supported = cipher_supported(cipherAlg[i]);
+ assert_true(cli_rnp_protect_file(&rnp) == supported);
+ rnp.end();
+ if (!supported) {
+ continue;
+ }
+
+ /* Set up rnp again and decrypt the file */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, pipefd));
+ /* Load the keyrings */
+ assert_true(rnp.load_keyrings(true));
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &seccount));
+ assert_true(seccount > 0);
+ /* Setup the decryption context and decrypt */
+ rnp_cfg &newcfg = rnp.cfg();
+ newcfg.load_defaults();
+ newcfg.set_bool(CFG_OVERWRITE, true);
+ newcfg.set_str(CFG_INFILE, "dummyfile.dat.pgp");
+ newcfg.set_str(CFG_OUTFILE, "dummyfile.decrypt");
+ assert_true(cli_rnp_process_file(&rnp));
+ rnp.end();
+ if (pipefd[0] != -1) {
+ close(pipefd[0]);
+ }
+
+ /* Ensure plaintext recovered */
+ std::string decrypt = file_to_str("dummyfile.decrypt");
+ assert_true(decrypt == memToEncrypt);
+ assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), 0);
+ assert_int_equal(rnp_unlink("dummyfile.decrypt"), 0);
+ }
+ }
+ assert_int_equal(rnp_unlink("dummyfile.dat"), 0);
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_verifySupportedHashAlg)
+{
+ /* Generate key for each of the hash algorithms. Check whether key was generated
+ * successfully */
+
+ const char *hashAlg[] = {"MD5",
+ "SHA1",
+ "SHA256",
+ "SHA384",
+ "SHA512",
+ "SHA224",
+ "SM3",
+ "md5",
+ "sha1",
+ "sha256",
+ "sha384",
+ "sha512",
+ "sha224",
+ "sm3"};
+ const char *keystores[] = {RNP_KEYSTORE_GPG, RNP_KEYSTORE_GPG21, RNP_KEYSTORE_KBX};
+ cli_rnp_t rnp = {};
+
+ for (size_t i = 0; i < ARRAY_SIZE(hashAlg); i++) {
+ const char *keystore = keystores[i % ARRAY_SIZE(keystores)];
+ /* Setting up rnp again and decrypting memory */
+ printf("keystore: %s, hashalg %s\n", keystore, hashAlg[i]);
+ /* Generate key with specified hash algorithm */
+ bool supported = hash_supported(hashAlg[i]);
+ assert_true(generate_test_key(keystore, hashAlg[i], hashAlg[i], NULL) == supported);
+ if (!supported) {
+ continue;
+ }
+ /* Load and check key */
+ assert_true(setup_cli_rnp_common(&rnp, keystore, NULL, NULL));
+ /* Loading the keyrings */
+ assert_true(rnp.load_keyrings(true));
+ /* Some minor checks */
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_true(keycount > 0);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_true(keycount > 0);
+ rnp_key_handle_t handle = NULL;
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", hashAlg[i], &handle));
+ if (hash_secure(rnp.ffi, hashAlg[i], RNP_SECURITY_VERIFY_KEY)) {
+ assert_non_null(handle);
+ bool valid = false;
+ rnp_key_is_valid(handle, &valid);
+ assert_true(valid);
+ } else {
+ assert_null(handle);
+ }
+ rnp_key_handle_destroy(handle);
+ rnp.end();
+ delete_recursively(".rnp");
+ }
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_verifyUserIdOption)
+{
+ /* Set the UserId = custom value.
+ * Execute the Generate-key command to generate a new keypair
+ * Verify the key was generated with the correct UserId. */
+
+ const char *userIds[] = {"rnpkeys_generatekey_verifyUserIdOption_MD5",
+ "rnpkeys_generatekey_verifyUserIdOption_SHA-1",
+ "rnpkeys_generatekey_verifyUserIdOption_RIPEMD160",
+ "rnpkeys_generatekey_verifyUserIdOption_SHA256",
+ "rnpkeys_generatekey_verifyUserIdOption_SHA384",
+ "rnpkeys_generatekey_verifyUserIdOption_SHA512",
+ "rnpkeys_generatekey_verifyUserIdOption_SHA224"};
+
+ const char *keystores[] = {RNP_KEYSTORE_GPG, RNP_KEYSTORE_GPG21, RNP_KEYSTORE_KBX};
+ cli_rnp_t rnp = {};
+
+ for (size_t i = 0; i < ARRAY_SIZE(userIds); i++) {
+ const char *keystore = keystores[i % ARRAY_SIZE(keystores)];
+ /* Generate key with specified hash algorithm */
+ assert_true(generate_test_key(keystore, userIds[i], "SHA256", NULL));
+
+ /* Initialize the basic RNP structure. */
+ assert_true(setup_cli_rnp_common(&rnp, keystore, NULL, NULL));
+ /* Load the newly generated rnp key*/
+ assert_true(rnp.load_keyrings(true));
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_true(keycount > 0);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_true(keycount > 0);
+
+ rnp_key_handle_t handle = NULL;
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userIds[i], &handle));
+ assert_non_null(handle);
+ rnp_key_handle_destroy(handle);
+ rnp.end();
+ delete_recursively(".rnp");
+ }
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_verifykeyHomeDirOption)
+{
+ /* Try to generate keypair in different home directories */
+ cli_rnp_t rnp = {};
+
+ /* Initialize the rnp structure. */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, NULL, NULL));
+
+ /* Pubring and secring should not exist yet */
+ assert_false(path_rnp_file_exists(".rnp/pubring.gpg", NULL));
+ assert_false(path_rnp_file_exists(".rnp/secring.gpg", NULL));
+
+ /* Ensure the key was generated. */
+ assert_true(generate_test_key(RNP_KEYSTORE_GPG, NULL, "SHA256", NULL));
+
+ /* Pubring and secring should now exist */
+ assert_true(path_rnp_file_exists(".rnp/pubring.gpg", NULL));
+ assert_true(path_rnp_file_exists(".rnp/secring.gpg", NULL));
+
+ /* Loading keyrings and checking whether they have correct key */
+ assert_true(rnp.load_keyrings(true));
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+
+ std::string userid =
+ fmt("RSA (Encrypt or Sign) 1024-bit key <%s@localhost>", getenv_logname());
+ rnp_key_handle_t handle = NULL;
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle));
+ assert_non_null(handle);
+ rnp_key_handle_destroy(handle);
+ rnp.end();
+
+ /* Now we start over with a new home. When home is specified explicitly then it should
+ * include .rnp as well */
+ std::string newhome = "newhome/.rnp";
+ path_mkdir(0700, "newhome", NULL);
+ path_mkdir(0700, newhome.c_str(), NULL);
+
+ /* Initialize the rnp structure. */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, newhome.c_str(), NULL));
+
+ /* Pubring and secring should not exist yet */
+ assert_false(path_rnp_file_exists(newhome.c_str(), "pubring.gpg", NULL));
+ assert_false(path_rnp_file_exists(newhome.c_str(), "secring.gpg", NULL));
+
+ /* Ensure the key was generated. */
+ assert_true(generate_test_key(RNP_KEYSTORE_GPG, "newhomekey", "SHA256", newhome.c_str()));
+
+ /* Pubring and secring should now exist */
+ assert_true(path_rnp_file_exists(newhome.c_str(), "pubring.gpg", NULL));
+ assert_true(path_rnp_file_exists(newhome.c_str(), "secring.gpg", NULL));
+
+ /* Loading keyrings and checking whether they have correct key */
+ assert_true(rnp.load_keyrings(true));
+ keycount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+
+ /* We should not find this key */
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle));
+ assert_null(handle);
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", "newhomekey", &handle));
+ assert_non_null(handle);
+ rnp_key_handle_destroy(handle);
+ rnp.end(); // Free memory and other allocated resources.
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_verifykeyKBXHomeDirOption)
+{
+ /* Try to generate keypair in different home directories for KBX keystorage */
+ const char *newhome = "newhome";
+ cli_rnp_t rnp = {};
+
+ /* Initialize the rnp structure. */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_KBX, NULL, NULL));
+ /* Pubring and secring should not exist yet */
+ assert_false(path_rnp_file_exists(".rnp/pubring.kbx", NULL));
+ assert_false(path_rnp_file_exists(".rnp/secring.kbx", NULL));
+ assert_false(path_rnp_file_exists(".rnp/pubring.gpg", NULL));
+ assert_false(path_rnp_file_exists(".rnp/secring.gpg", NULL));
+ /* Ensure the key was generated. */
+ assert_true(generate_test_key(RNP_KEYSTORE_KBX, NULL, "SHA256", NULL));
+ /* Pubring and secring should now exist, but only for the KBX */
+ assert_true(path_rnp_file_exists(".rnp/pubring.kbx", NULL));
+ assert_true(path_rnp_file_exists(".rnp/secring.kbx", NULL));
+ assert_false(path_rnp_file_exists(".rnp/pubring.gpg", NULL));
+ assert_false(path_rnp_file_exists(".rnp/secring.gpg", NULL));
+
+ /* Loading keyrings and checking whether they have correct key */
+ assert_true(rnp.load_keyrings(true));
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ std::string userid =
+ fmt("RSA (Encrypt or Sign) 1024-bit key <%s@localhost>", getenv_logname());
+ rnp_key_handle_t handle = NULL;
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle));
+ assert_non_null(handle);
+ rnp_key_handle_destroy(handle);
+ rnp.end();
+
+ /* Now we start over with a new home. */
+ path_mkdir(0700, newhome, NULL);
+ /* Initialize the rnp structure. */
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_KBX, newhome, NULL));
+ /* Pubring and secring should not exist yet */
+ assert_false(path_rnp_file_exists(newhome, "pubring.kbx", NULL));
+ assert_false(path_rnp_file_exists(newhome, "secring.kbx", NULL));
+ assert_false(path_rnp_file_exists(newhome, "pubring.gpg", NULL));
+ assert_false(path_rnp_file_exists(newhome, "secring.gpg", NULL));
+
+ /* Ensure the key was generated. */
+ assert_true(generate_test_key(RNP_KEYSTORE_KBX, "newhomekey", "SHA256", newhome));
+ /* Pubring and secring should now exist, but only for the KBX */
+ assert_true(path_rnp_file_exists(newhome, "pubring.kbx", NULL));
+ assert_true(path_rnp_file_exists(newhome, "secring.kbx", NULL));
+ assert_false(path_rnp_file_exists(newhome, "pubring.gpg", NULL));
+ assert_false(path_rnp_file_exists(newhome, "secring.gpg", NULL));
+ /* Loading keyrings and checking whether they have correct key */
+ assert_true(rnp.load_keyrings(true));
+ keycount = 0;
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ /* We should not find this key */
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", userid.c_str(), &handle));
+ assert_null(handle);
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "userid", "newhomekey", &handle));
+ assert_non_null(handle);
+ rnp_key_handle_destroy(handle);
+ rnp.end();
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_verifykeyHomeDirNoPermission)
+{
+ const char *nopermsdir = "noperms";
+ path_mkdir(0000, nopermsdir, NULL);
+/* Try to generate key in the directory and make sure generation fails */
+#ifndef _WIN32
+ assert_false(generate_test_key(RNP_KEYSTORE_GPG, NULL, "SHA256", nopermsdir));
+#else
+ /* There are no permissions for mkdir() under the Windows */
+ assert_true(generate_test_key(RNP_KEYSTORE_GPG, NULL, "SHA256", nopermsdir));
+#endif
+}
+
+static bool
+ask_expert_details(cli_rnp_t *ctx, rnp_cfg &ops, const char *rsp)
+{
+ /* Run tests*/
+ bool ret = false;
+ int pipefd[2] = {-1, -1};
+ int user_input_pipefd[2] = {-1, -1};
+ size_t rsp_len;
+
+ if (pipe(pipefd) == -1) {
+ return false;
+ }
+ ops.set_int(CFG_PASSFD, pipefd[0]);
+ write_pass_to_pipe(pipefd[1], 2);
+ close(pipefd[1]);
+ if (!rnpkeys_init(*ctx, ops)) {
+ close(pipefd[0]); // otherwise will be closed via passfp
+ goto end;
+ }
+
+ /* Write response to fd */
+ if (pipe(user_input_pipefd) == -1) {
+ goto end;
+ }
+ rsp_len = strlen(rsp);
+ for (size_t i = 0; i < rsp_len;) {
+ i += write(user_input_pipefd[1], rsp + i, rsp_len - i);
+ }
+ close(user_input_pipefd[1]);
+
+ /* Mock user-input*/
+ ctx->cfg().set_int(CFG_USERINPUTFD, user_input_pipefd[0]);
+
+ if (!rnp_cmd(ctx, CMD_GENERATE_KEY, NULL)) {
+ ret = false;
+ goto end;
+ }
+ ops.copy(ctx->cfg());
+ ret = true;
+end:
+ /* Close & clean fd*/
+ if (user_input_pipefd[0]) {
+ close(user_input_pipefd[0]);
+ }
+ return ret;
+}
+
+static bool
+check_key_props(cli_rnp_t * rnp,
+ const char *uid,
+ const char *primary_alg,
+ const char *sub_alg,
+ const char *primary_curve,
+ const char *sub_curve,
+ int bits,
+ int sub_bits,
+ const char *hash)
+{
+ rnp_key_handle_t key = NULL;
+ rnp_key_handle_t subkey = NULL;
+ rnp_signature_handle_t sig = NULL;
+ uint32_t kbits = 0;
+ char * str = NULL;
+ bool res = false;
+
+ /* check primary key properties */
+ if (rnp_locate_key(rnp->ffi, "userid", uid, &key) || !key) {
+ return false;
+ }
+ if (rnp_key_get_alg(key, &str) || strcmp(str, primary_alg)) {
+ goto done;
+ }
+ rnp_buffer_destroy(str);
+ str = NULL;
+
+ if (primary_curve && (rnp_key_get_curve(key, &str) || strcmp(str, primary_curve))) {
+ goto done;
+ }
+ rnp_buffer_destroy(str);
+ str = NULL;
+ if (bits && (rnp_key_get_bits(key, &kbits) || (bits != (int) kbits))) {
+ goto done;
+ }
+
+ /* check subkey properties */
+ if (!sub_alg) {
+ res = true;
+ goto done;
+ }
+
+ if (rnp_key_get_subkey_at(key, 0, &subkey)) {
+ goto done;
+ }
+
+ if (rnp_key_get_alg(subkey, &str) || strcmp(str, sub_alg)) {
+ goto done;
+ }
+ rnp_buffer_destroy(str);
+ str = NULL;
+
+ if (sub_curve && (rnp_key_get_curve(subkey, &str) || strcmp(str, sub_curve))) {
+ goto done;
+ }
+ rnp_buffer_destroy(str);
+ str = NULL;
+ if (sub_bits && (rnp_key_get_bits(subkey, &kbits) || (sub_bits != (int) kbits))) {
+ goto done;
+ }
+
+ if (rnp_key_get_signature_at(subkey, 0, &sig) || !sig) {
+ goto done;
+ }
+ if (rnp_signature_get_hash_alg(sig, &str) || strcmp(str, hash)) {
+ goto done;
+ }
+
+ res = true;
+done:
+ rnp_signature_handle_destroy(sig);
+ rnp_key_handle_destroy(key);
+ rnp_key_handle_destroy(subkey);
+ rnp_buffer_destroy(str);
+ return res;
+}
+
+static bool
+check_cfg_props(const rnp_cfg &cfg,
+ const char * primary_alg,
+ const char * sub_alg,
+ const char * primary_curve,
+ const char * sub_curve,
+ int bits,
+ int sub_bits)
+{
+ if (cfg.get_str(CFG_KG_PRIMARY_ALG) != primary_alg) {
+ return false;
+ }
+ if (cfg.get_str(CFG_KG_SUBKEY_ALG) != sub_alg) {
+ return false;
+ }
+ if (primary_curve && (cfg.get_str(CFG_KG_PRIMARY_CURVE) != primary_curve)) {
+ return false;
+ }
+ if (sub_curve && (cfg.get_str(CFG_KG_SUBKEY_CURVE) != sub_curve)) {
+ return false;
+ }
+ if (bits && (cfg.get_int(CFG_KG_PRIMARY_BITS) != bits)) {
+ return false;
+ }
+ if (sub_bits && (cfg.get_int(CFG_KG_SUBKEY_BITS) != sub_bits)) {
+ return false;
+ }
+ return true;
+}
+
+TEST_F(rnp_tests, rnpkeys_generatekey_testExpertMode)
+{
+ cli_rnp_t rnp;
+ rnp_cfg ops;
+
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_int(CFG_S2K_ITER, 1);
+
+ /* ecdsa/ecdh p256 keypair */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_ecdsa_p256");
+ assert_true(ask_expert_details(&rnp, ops, "19\n1\n"));
+ assert_false(check_cfg_props(ops, "ECDH", "ECDH", "NIST P-256", "NIST P-256", 0, 0));
+ assert_false(check_cfg_props(ops, "ECDSA", "ECDSA", "NIST P-256", "NIST P-256", 0, 0));
+ assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-256", 0, 0));
+ assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-384", 0, 0));
+ assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 1024, 0));
+ assert_false(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 0, 1024));
+ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 0, 0));
+ assert_true(check_key_props(
+ &rnp, "expert_ecdsa_p256", "ECDSA", "ECDH", "NIST P-256", "NIST P-256", 0, 0, "SHA256"));
+ rnp.end();
+
+ /* ecdsa/ecdh p384 keypair */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_ecdsa_p384");
+ assert_true(ask_expert_details(&rnp, ops, "19\n2\n"));
+ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0));
+ assert_false(check_key_props(
+ &rnp, "expert_ecdsa_p256", "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0, "SHA384"));
+ assert_true(check_key_props(
+ &rnp, "expert_ecdsa_p384", "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0, "SHA384"));
+ rnp.end();
+
+ /* ecdsa/ecdh p521 keypair */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_ecdsa_p521");
+ assert_true(ask_expert_details(&rnp, ops, "19\n3\n"));
+ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-521", "NIST P-521", 0, 0));
+ assert_true(check_key_props(
+ &rnp, "expert_ecdsa_p521", "ECDSA", "ECDH", "NIST P-521", "NIST P-521", 0, 0, "SHA512"));
+ rnp.end();
+
+ /* ecdsa/ecdh brainpool256 keypair */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_ecdsa_bp256");
+ assert_true(ask_expert_details(&rnp, ops, "19\n4\n"));
+ if (brainpool_enabled()) {
+ assert_true(
+ check_cfg_props(ops, "ECDSA", "ECDH", "brainpoolP256r1", "brainpoolP256r1", 0, 0));
+ assert_true(check_key_props(&rnp,
+ "expert_ecdsa_bp256",
+ "ECDSA",
+ "ECDH",
+ "brainpoolP256r1",
+ "brainpoolP256r1",
+ 0,
+ 0,
+ "SHA256"));
+ } else {
+ /* secp256k1 will be selected instead */
+ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "secp256k1", "secp256k1", 0, 0));
+ assert_true(check_key_props(&rnp,
+ "expert_ecdsa_bp256",
+ "ECDSA",
+ "ECDH",
+ "secp256k1",
+ "secp256k1",
+ 0,
+ 0,
+ "SHA256"));
+ }
+ rnp.end();
+
+ /* ecdsa/ecdh brainpool384 keypair */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_ecdsa_bp384");
+ if (brainpool_enabled()) {
+ assert_true(ask_expert_details(&rnp, ops, "19\n5\n"));
+ assert_true(
+ check_cfg_props(ops, "ECDSA", "ECDH", "brainpoolP384r1", "brainpoolP384r1", 0, 0));
+ assert_true(check_key_props(&rnp,
+ "expert_ecdsa_bp384",
+ "ECDSA",
+ "ECDH",
+ "brainpoolP384r1",
+ "brainpoolP384r1",
+ 0,
+ 0,
+ "SHA384"));
+ } else {
+ assert_false(ask_expert_details(&rnp, ops, "19\n5\n"));
+ }
+ rnp.end();
+
+ /* ecdsa/ecdh brainpool512 keypair */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_ecdsa_bp512");
+ if (brainpool_enabled()) {
+ assert_true(ask_expert_details(&rnp, ops, "19\n6\n"));
+ assert_true(
+ check_cfg_props(ops, "ECDSA", "ECDH", "brainpoolP512r1", "brainpoolP512r1", 0, 0));
+ assert_true(check_key_props(&rnp,
+ "expert_ecdsa_bp512",
+ "ECDSA",
+ "ECDH",
+ "brainpoolP512r1",
+ "brainpoolP512r1",
+ 0,
+ 0,
+ "SHA512"));
+ } else {
+ assert_false(ask_expert_details(&rnp, ops, "19\n6\n"));
+ }
+ rnp.end();
+
+ /* ecdsa/ecdh secp256k1 keypair */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_ecdsa_p256k1");
+ if (brainpool_enabled()) {
+ assert_true(ask_expert_details(&rnp, ops, "19\n7\n"));
+ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "secp256k1", "secp256k1", 0, 0));
+ assert_true(check_key_props(&rnp,
+ "expert_ecdsa_p256k1",
+ "ECDSA",
+ "ECDH",
+ "secp256k1",
+ "secp256k1",
+ 0,
+ 0,
+ "SHA256"));
+ } else {
+ assert_false(ask_expert_details(&rnp, ops, "19\n7\n"));
+ }
+ rnp.end();
+
+ /* eddsa/x25519 keypair */
+ ops.clear();
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_int(CFG_S2K_ITER, 1);
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_eddsa_ecdh");
+ assert_true(ask_expert_details(&rnp, ops, "22\n"));
+ assert_true(check_cfg_props(ops, "EDDSA", "ECDH", NULL, "Curve25519", 0, 0));
+ assert_true(check_key_props(
+ &rnp, "expert_eddsa_ecdh", "EDDSA", "ECDH", "Ed25519", "Curve25519", 0, 0, "SHA256"));
+ rnp.end();
+
+ /* rsa/rsa 1024 key */
+ ops.clear();
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_int(CFG_S2K_ITER, 1);
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_rsa_1024");
+ assert_true(ask_expert_details(&rnp, ops, "1\n1024\n"));
+ assert_true(check_cfg_props(ops, "RSA", "RSA", NULL, NULL, 1024, 1024));
+ assert_true(check_key_props(
+ &rnp, "expert_rsa_1024", "RSA", "RSA", NULL, NULL, 1024, 1024, "SHA256"));
+ rnp.end();
+
+ /* rsa 4096 key, asked twice */
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_rsa_4096");
+ assert_true(ask_expert_details(&rnp, ops, "1\n1023\n4096\n"));
+ assert_true(check_cfg_props(ops, "RSA", "RSA", NULL, NULL, 4096, 4096));
+ assert_true(check_key_props(
+ &rnp, "expert_rsa_4096", "RSA", "RSA", NULL, NULL, 4096, 4096, "SHA256"));
+ rnp.end();
+
+ /* sm2 key */
+ ops.clear();
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_int(CFG_S2K_ITER, 1);
+ ops.unset(CFG_USERID);
+ ops.add_str(CFG_USERID, "expert_sm2");
+ if (!sm2_enabled()) {
+ assert_false(ask_expert_details(&rnp, ops, "99\n"));
+ } else {
+ assert_true(ask_expert_details(&rnp, ops, "99\n"));
+ assert_true(check_cfg_props(ops, "SM2", "SM2", NULL, NULL, 0, 0));
+ assert_true(check_key_props(
+ &rnp, "expert_sm2", "SM2", "SM2", "SM2 P-256", "SM2 P-256", 0, 0, "SM3"));
+ }
+ rnp.end();
+}
+
+TEST_F(rnp_tests, generatekeyECDSA_explicitlySetSmallOutputDigest_DigestAlgAdjusted)
+{
+ cli_rnp_t rnp;
+ rnp_cfg ops;
+
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_str(CFG_HASH, "SHA1");
+ ops.set_bool(CFG_WEAK_HASH, true);
+ ops.set_int(CFG_S2K_ITER, 1);
+ ops.add_str(CFG_USERID, "expert_small_digest");
+ assert_true(ask_expert_details(&rnp, ops, "19\n2\n"));
+ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0));
+ assert_true(check_key_props(&rnp,
+ "expert_small_digest",
+ "ECDSA",
+ "ECDH",
+ "NIST P-384",
+ "NIST P-384",
+ 0,
+ 0,
+ "SHA384"));
+ rnp.end();
+}
+
+TEST_F(rnp_tests, generatekey_multipleUserIds_ShouldFail)
+{
+ cli_rnp_t rnp;
+ rnp_cfg ops;
+
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_int(CFG_S2K_ITER, 1);
+ ops.add_str(CFG_USERID, "multi_userid_1");
+ ops.add_str(CFG_USERID, "multi_userid_2");
+ assert_false(ask_expert_details(&rnp, ops, "1\n1024\n"));
+ rnp.end();
+}
+
+TEST_F(rnp_tests, generatekeyECDSA_explicitlySetBiggerThanNeededDigest_ShouldSuceed)
+{
+ cli_rnp_t rnp;
+ rnp_cfg ops;
+
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_str(CFG_HASH, "SHA512");
+ ops.set_int(CFG_S2K_ITER, 1);
+ ops.add_str(CFG_USERID, "expert_large_digest");
+ assert_true(ask_expert_details(&rnp, ops, "19\n2\n"));
+ assert_true(check_cfg_props(ops, "ECDSA", "ECDH", "NIST P-384", "NIST P-384", 0, 0));
+ assert_true(check_key_props(&rnp,
+ "expert_large_digest",
+ "ECDSA",
+ "ECDH",
+ "NIST P-384",
+ "NIST P-384",
+ 0,
+ 0,
+ "SHA512"));
+ rnp.end();
+}
+
+TEST_F(rnp_tests, generatekeyECDSA_explicitlySetUnknownDigest_ShouldFail)
+{
+ cli_rnp_t rnp;
+ rnp_cfg ops;
+
+ ops.set_bool(CFG_EXPERT, true);
+ ops.set_str(CFG_HASH, "WRONG_DIGEST_ALGORITHM");
+ ops.set_int(CFG_S2K_ITER, 1);
+
+ // Finds out that hash doesn't exist and returns an error
+ assert_false(ask_expert_details(&rnp, ops, "19\n2\n"));
+ rnp.end();
+}
+
+/* This tests some of the mid-level key generation functions and their
+ * generated sigs in the keyring.
+ */
+TEST_F(rnp_tests, test_generated_key_sigs)
+{
+ rnp_key_store_t *pubring = new rnp_key_store_t(global_ctx);
+ rnp_key_store_t *secring = new rnp_key_store_t(global_ctx);
+ pgp_key_t * primary_pub = NULL, *primary_sec = NULL;
+ pgp_key_t * sub_pub = NULL, *sub_sec = NULL;
+
+ // primary
+ {
+ pgp_key_t pub;
+ pgp_key_t sec;
+ rnp_keygen_primary_desc_t desc;
+ pgp_sig_subpkt_t * subpkt = NULL;
+ pgp_signature_t * psig = NULL;
+ pgp_signature_t * ssig = NULL;
+ pgp_signature_info_t psiginfo = {};
+ pgp_signature_info_t ssiginfo = {};
+
+ desc.crypto.key_alg = PGP_PKA_RSA;
+ desc.crypto.rsa.modulus_bit_len = 1024;
+ desc.crypto.ctx = &global_ctx;
+ desc.cert.userid = "test";
+
+ // generate
+ assert_true(pgp_generate_primary_key(desc, true, sec, pub, PGP_KEY_STORE_GPG));
+
+ // add to our rings
+ assert_true(rnp_key_store_add_key(pubring, &pub));
+ assert_true(rnp_key_store_add_key(secring, &sec));
+ // retrieve back from our rings (for later)
+ primary_pub = rnp_tests_get_key_by_grip(pubring, pub.grip());
+ primary_sec = rnp_tests_get_key_by_grip(secring, pub.grip());
+ assert_non_null(primary_pub);
+ assert_non_null(primary_sec);
+ assert_true(primary_pub->valid());
+ assert_true(primary_pub->validated());
+ assert_false(primary_pub->expired());
+ assert_true(primary_sec->valid());
+ assert_true(primary_sec->validated());
+ assert_false(primary_sec->expired());
+
+ // check packet and subsig counts
+ assert_int_equal(3, pub.rawpkt_count());
+ assert_int_equal(3, sec.rawpkt_count());
+ assert_int_equal(1, pub.sig_count());
+ assert_int_equal(1, sec.sig_count());
+ psig = &pub.get_sig(0).sig;
+ ssig = &sec.get_sig(0).sig;
+ // make sure our sig MPI is not NULL
+ assert_int_not_equal(psig->material_len, 0);
+ assert_int_not_equal(ssig->material_len, 0);
+ // make sure we're targeting the right packet
+ assert_int_equal(PGP_PKT_SIGNATURE, pub.get_sig(0).rawpkt.tag);
+ assert_int_equal(PGP_PKT_SIGNATURE, sec.get_sig(0).rawpkt.tag);
+
+ // validate the userid self-sig
+
+ psiginfo.sig = psig;
+ pub.validate_cert(psiginfo, pub.pkt(), pub.get_uid(0).pkt, global_ctx);
+ assert_true(psiginfo.valid);
+ assert_true(psig->keyfp() == pub.fp());
+ // check subpackets and their contents
+ subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR);
+ assert_non_null(subpkt);
+ assert_true(subpkt->hashed);
+ subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false);
+ assert_non_null(subpkt);
+ assert_false(subpkt->hashed);
+ assert_int_equal(0,
+ memcmp(subpkt->fields.issuer, pub.keyid().data(), PGP_KEY_ID_SIZE));
+ subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME);
+ assert_non_null(subpkt);
+ assert_true(subpkt->hashed);
+ assert_true(subpkt->fields.create <= time(NULL));
+
+ ssiginfo.sig = ssig;
+ sec.validate_cert(ssiginfo, sec.pkt(), sec.get_uid(0).pkt, global_ctx);
+ assert_true(ssiginfo.valid);
+ assert_true(ssig->keyfp() == sec.fp());
+
+ // modify a hashed portion of the sig packets
+ psig->hashed_data[32] ^= 0xff;
+ ssig->hashed_data[32] ^= 0xff;
+ // ensure validation fails
+ pub.validate_cert(psiginfo, pub.pkt(), pub.get_uid(0).pkt, global_ctx);
+ assert_false(psiginfo.valid);
+ sec.validate_cert(ssiginfo, sec.pkt(), sec.get_uid(0).pkt, global_ctx);
+ assert_false(ssiginfo.valid);
+ // restore the original data
+ psig->hashed_data[32] ^= 0xff;
+ ssig->hashed_data[32] ^= 0xff;
+ // ensure validation fails with incorrect uid
+ pgp_userid_pkt_t uid;
+ uid.tag = PGP_PKT_USER_ID;
+ uid.uid = (uint8_t *) malloc(4);
+ assert_non_null(uid.uid);
+ uid.uid_len = 4;
+ memcpy(uid.uid, "fake", 4);
+
+ pub.validate_cert(psiginfo, pub.pkt(), uid, global_ctx);
+ assert_false(psiginfo.valid);
+ sec.validate_cert(ssiginfo, sec.pkt(), uid, global_ctx);
+ assert_false(ssiginfo.valid);
+
+ // validate via an alternative method
+ // primary_pub + pubring
+ primary_pub->validate(*pubring);
+ assert_true(primary_pub->valid());
+ assert_true(primary_pub->validated());
+ assert_false(primary_pub->expired());
+ // primary_sec + pubring
+ primary_sec->validate(*pubring);
+ assert_true(primary_sec->valid());
+ assert_true(primary_sec->validated());
+ assert_false(primary_sec->expired());
+ // primary_pub + secring
+ primary_pub->validate(*secring);
+ assert_true(primary_pub->valid());
+ assert_true(primary_pub->validated());
+ assert_false(primary_pub->expired());
+ // primary_sec + secring
+ primary_sec->validate(*secring);
+ assert_true(primary_sec->valid());
+ assert_true(primary_sec->validated());
+ assert_false(primary_sec->expired());
+ // modify a hashed portion of the sig packet, offset may change in future
+ pgp_subsig_t &sig = primary_pub->get_sig(0);
+ sig.sig.hashed_data[10] ^= 0xff;
+ sig.validity.validated = false;
+ // ensure validation fails
+ primary_pub->validate(*pubring);
+ assert_false(primary_pub->valid());
+ assert_true(primary_pub->validated());
+ assert_false(primary_pub->expired());
+ // restore the original data
+ sig.sig.hashed_data[10] ^= 0xff;
+ sig.validity.validated = false;
+ primary_pub->validate(*pubring);
+ assert_true(primary_pub->valid());
+ assert_true(primary_pub->validated());
+ assert_false(primary_pub->expired());
+ }
+
+ // sub
+ {
+ pgp_key_t pub;
+ pgp_key_t sec;
+ rnp_keygen_subkey_desc_t desc;
+ pgp_sig_subpkt_t * subpkt = NULL;
+ pgp_signature_t * psig = NULL;
+ pgp_signature_t * ssig = NULL;
+ pgp_signature_info_t psiginfo = {};
+ pgp_signature_info_t ssiginfo = {};
+
+ memset(&desc, 0, sizeof(desc));
+ desc.crypto.key_alg = PGP_PKA_RSA;
+ desc.crypto.rsa.modulus_bit_len = 1024;
+ desc.crypto.ctx = &global_ctx;
+
+ // generate
+ pgp_password_provider_t prov = {};
+ assert_true(pgp_generate_subkey(
+ desc, true, *primary_sec, *primary_pub, sec, pub, prov, PGP_KEY_STORE_GPG));
+ assert_true(pub.valid());
+ assert_true(pub.validated());
+ assert_false(pub.expired());
+ assert_true(sec.valid());
+ assert_true(sec.validated());
+ assert_false(sec.expired());
+
+ // check packet and subsig counts
+ assert_int_equal(2, pub.rawpkt_count());
+ assert_int_equal(2, sec.rawpkt_count());
+ assert_int_equal(1, pub.sig_count());
+ assert_int_equal(1, sec.sig_count());
+ psig = &pub.get_sig(0).sig;
+ ssig = &sec.get_sig(0).sig;
+ // make sure our sig MPI is not NULL
+ assert_int_not_equal(psig->material_len, 0);
+ assert_int_not_equal(ssig->material_len, 0);
+ // make sure we're targeting the right packet
+ assert_int_equal(PGP_PKT_SIGNATURE, pub.get_sig(0).rawpkt.tag);
+ assert_int_equal(PGP_PKT_SIGNATURE, sec.get_sig(0).rawpkt.tag);
+ // validate the binding sig
+ psiginfo.sig = psig;
+ primary_pub->validate_binding(psiginfo, pub, global_ctx);
+ assert_true(psiginfo.valid);
+ assert_true(psig->keyfp() == primary_pub->fp());
+ // check subpackets and their contents
+ subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR);
+ assert_non_null(subpkt);
+ assert_true(subpkt->hashed);
+ subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_ISSUER_KEY_ID, false);
+ assert_non_null(subpkt);
+ assert_false(subpkt->hashed);
+ assert_int_equal(
+ 0, memcmp(subpkt->fields.issuer, primary_pub->keyid().data(), PGP_KEY_ID_SIZE));
+ subpkt = psig->get_subpkt(PGP_SIG_SUBPKT_CREATION_TIME);
+ assert_non_null(subpkt);
+ assert_true(subpkt->hashed);
+ assert_true(subpkt->fields.create <= time(NULL));
+
+ ssiginfo.sig = ssig;
+ primary_pub->validate_binding(ssiginfo, sec, global_ctx);
+ assert_true(ssiginfo.valid);
+ assert_true(ssig->keyfp() == primary_sec->fp());
+
+ // modify a hashed portion of the sig packets
+ psig->hashed_data[10] ^= 0xff;
+ ssig->hashed_data[10] ^= 0xff;
+ // ensure validation fails
+ primary_pub->validate_binding(psiginfo, pub, global_ctx);
+ assert_false(psiginfo.valid);
+ primary_pub->validate_binding(ssiginfo, sec, global_ctx);
+ assert_false(ssiginfo.valid);
+ // restore the original data
+ psig->hashed_data[10] ^= 0xff;
+ ssig->hashed_data[10] ^= 0xff;
+
+ // add to our rings
+ assert_true(rnp_key_store_add_key(pubring, &pub));
+ assert_true(rnp_key_store_add_key(secring, &sec));
+ // retrieve back from our rings
+ sub_pub = rnp_tests_get_key_by_grip(pubring, pub.grip());
+ sub_sec = rnp_tests_get_key_by_grip(secring, pub.grip());
+ assert_non_null(sub_pub);
+ assert_non_null(sub_sec);
+ assert_true(sub_pub->valid());
+ assert_true(sub_pub->validated());
+ assert_false(sub_pub->expired());
+ assert_true(sub_sec->valid());
+ assert_true(sub_sec->validated());
+ assert_false(sub_sec->expired());
+
+ // validate via an alternative method
+ sub_pub->validate(*pubring);
+ assert_true(sub_pub->valid());
+ assert_true(sub_pub->validated());
+ assert_false(sub_pub->expired());
+ sub_sec->validate(*pubring);
+ assert_true(sub_sec->valid());
+ assert_true(sub_sec->validated());
+ assert_false(sub_sec->expired());
+ }
+
+ delete pubring;
+ delete secring;
+}
diff --git a/src/tests/gnupg.py b/src/tests/gnupg.py
new file mode 100644
index 0000000..375cbc9
--- /dev/null
+++ b/src/tests/gnupg.py
@@ -0,0 +1,121 @@
+import copy
+from cli_common import run_proc
+
+class GnuPG(object):
+ def __init__(self, homedir, gpg_path):
+ self.__gpg = gpg_path
+ self.__common_params = ['--homedir', homedir, '--yes']
+ self.__password = None
+ self.__userid = None
+ self.__hash = None
+
+ @property
+ def bin(self):
+ return self.__gpg
+
+ @property
+ def common_params(self):
+ return copy.copy(self.__common_params)
+
+ @property
+ def password(self):
+ return self.__password
+
+ @property
+ def userid(self):
+ return self.__userid
+
+ @userid.setter
+ def userid(self, val):
+ self.__userid = val
+
+ @property
+ def hash(self):
+ return self.__hash
+
+ @hash.setter
+ def hash(self, val):
+ self.__hash = val
+
+ @password.setter
+ def password(self, val):
+ self.__password = val
+
+ def copy(self):
+ return copy.deepcopy(self)
+
+ def _run(self, cmd, params, batch_input = None):
+ retcode, _, _ = run_proc(cmd, params, batch_input)
+ return retcode == 0
+
+ def list_keys(self, secret = False):
+ params = ['--list-secret-keys'] if secret else ['--list-keys']
+ params = params + self.common_params
+ return self._run(self.__gpg, params)
+
+ def generate_key_batch(self, batch_input):
+ params = ['--gen-key', '--expert', '--batch',
+ '--pinentry-mode', 'loopback'] + self.common_params
+ if self.password:
+ params += ['--passphrase', self.password]
+ if self.hash:
+ params += ['--cert-digest-algo', self.hash]
+ return self._run(self.__gpg, params, batch_input)
+
+ def export_key(self, out_filename, secret=False):
+ params = ['--armor']
+ if secret:
+ params += ['--pinentry-mode', 'loopback', '--export-secret-key']
+ params += ['--passphrase', self.password]
+ else:
+ params = ['--export']
+
+ params = self.common_params + \
+ params + ['-o', out_filename, self.userid]
+ return self._run(self.__gpg, params)
+
+ def import_key(self, filename, secret = False):
+ params = self.common_params
+ if secret:
+ params += ['--trust-model', 'always']
+ params += ['--batch']
+ params += ['--passphrase', self.password]
+ params += ['--import', filename]
+ return self._run(self.__gpg, params)
+
+ def sign(self, out, input):
+ params = self.common_params
+ params += ['--passphrase', self.password]
+ params += ['--batch']
+ params += ['--pinentry-mode', 'loopback']
+ params += ['-u', self.userid]
+ params += ['-o', out]
+ params += ['--sign', input]
+ if self.hash:
+ params += ['--digest-algo', self.hash]
+ return self._run(self.__gpg, params)
+
+ def verify(self, input):
+ params = self.common_params
+ params += ['--verify', input]
+ if self.hash:
+ params += ['--digest-algo', self.hash]
+ return self._run(self.__gpg, params)
+
+ def encrypt(self, recipient, out, input):
+ params = self.common_params
+ params += ['--passphrase', self.password]
+ params += ['-r', recipient]
+ params += ['-o', out]
+ # Blindely trust the key without asking
+ params += ['--always-trust']
+ params += ['--encrypt', input]
+ return self._run(self.__gpg, params)
+
+ def decrypt(self, out, input):
+ params = self.common_params
+ params += ['--passphrase', self.password]
+ params += ['--pinentry-mode', 'loopback']
+ params += ['-o', out]
+ params += ['--decrypt', input]
+ return self._run(self.__gpg, params)
diff --git a/src/tests/issues/1030.cpp b/src/tests/issues/1030.cpp
new file mode 100644
index 0000000..692db5a
--- /dev/null
+++ b/src/tests/issues/1030.cpp
@@ -0,0 +1,78 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "../rnp_tests.h"
+#include "../support.h"
+
+static void
+test_issue_1030(const char *keystore)
+{
+ int pipefd[2] = {-1, -1};
+ cli_rnp_t rnp = {};
+ const char *userid = "user";
+ size_t keycount = 0;
+
+ char *home = make_temp_dir();
+ assert_true(setup_cli_rnp_common(&rnp, keystore, home, pipefd));
+ cli_set_default_rsa_key_desc(rnp.cfg(), "SHA256");
+ assert_true(cli_rnp_generate_key(&rnp, userid));
+
+ assert_true(rnp.load_keyrings(true));
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+
+ std::vector<rnp_key_handle_t> keys;
+ assert_true(cli_rnp_keys_matching_string(&rnp, keys, userid, CLI_SEARCH_SUBKEYS_AFTER));
+ assert_int_equal(keys.size(), 2);
+ clear_key_handles(keys);
+
+ assert_true(cli_rnp_keys_matching_string(
+ &rnp, keys, userid, CLI_SEARCH_SECRET | CLI_SEARCH_SUBKEYS_AFTER));
+ assert_int_equal(keys.size(), 2);
+ for (auto key : keys) {
+ bool is_protected = false;
+ assert_rnp_success(rnp_key_is_protected(key, &is_protected));
+ assert_true(is_protected);
+ }
+ clear_key_handles(keys);
+
+ // done
+ if (pipefd[0] != -1) {
+ close(pipefd[0]);
+ }
+ rnp.end();
+ clean_temp_dir(home);
+ free(home);
+}
+
+TEST_F(rnp_tests, issue_1030_rnpkeys_secret_keys_unprotected)
+{
+ for (auto keystore : {RNP_KEYSTORE_GPG, RNP_KEYSTORE_GPG21, RNP_KEYSTORE_KBX}) {
+ test_issue_1030(keystore);
+ }
+}
diff --git a/src/tests/issues/1115.cpp b/src/tests/issues/1115.cpp
new file mode 100644
index 0000000..2f9bb45
--- /dev/null
+++ b/src/tests/issues/1115.cpp
@@ -0,0 +1,47 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "../rnp_tests.h"
+#include "../support.h"
+
+TEST_F(rnp_tests, test_issue_1115)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_key_validity/case9/pubring.gpg"));
+ assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ assert_rnp_success(rnp_input_destroy(input));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(ffi, "userid", "Alice <alice@rnp>", &key));
+ uint32_t expiry = 0;
+ assert_rnp_success(rnp_key_get_expiration(key, &expiry));
+ assert_int_equal(expiry, 0);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
diff --git a/src/tests/issues/1171.cpp b/src/tests/issues/1171.cpp
new file mode 100644
index 0000000..e5e9e19
--- /dev/null
+++ b/src/tests/issues/1171.cpp
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "../rnp_tests.h"
+#include "../support.h"
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+
+TEST_F(rnp_tests, test_issue_1171_key_import_and_remove)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ 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, "grip", "3E3D52A346F0AD47754611B078117C240ED237E9", &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_key_remove(key, RNP_KEY_REMOVE_PUBLIC));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ assert_rnp_success(
+ rnp_locate_key(ffi, "grip", "3E3D52A346F0AD47754611B078117C240ED237E9", &key));
+ assert_null(key);
+
+ assert_rnp_success(
+ rnp_locate_key(ffi, "grip", "128E494F41F107E119AA1EEF7850C375A804341C", &key));
+ assert_non_null(key);
+ uint32_t bits = 0;
+ assert_rnp_success(rnp_key_get_bits(key, &bits));
+ assert_int_equal(bits, 256);
+
+ /* directly use rnp_tests_get_key_by_grip() which caused crash */
+ pgp_key_t *subkey = rnp_tests_get_key_by_grip(ffi->pubring, key->pub->grip());
+ assert_int_equal(subkey->material().bits(), 256);
+ assert_rnp_success(rnp_key_handle_destroy(key));
+
+ assert_true(import_pub_keys(ffi, "data/test_key_validity/alice-sub-pub.pgp"));
+
+ rnp_ffi_destroy(ffi);
+}
diff --git a/src/tests/issues/oss-fuzz-25489.cpp b/src/tests/issues/oss-fuzz-25489.cpp
new file mode 100644
index 0000000..6e22df1
--- /dev/null
+++ b/src/tests/issues/oss-fuzz-25489.cpp
@@ -0,0 +1,68 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "../rnp_tests.h"
+#include "../support.h"
+#include "librekey/g23_sexp.hpp"
+
+TEST_F(rnp_tests, test_sxp_depth)
+{
+ gnupg_sexp_t sxp = {};
+ const char * bytes;
+ size_t len;
+ auto mksxp = [](size_t depth) {
+ std::string data;
+ for (size_t i = 0; i < depth; i++) {
+ data += "(1:a";
+ }
+ for (size_t i = 0; i < depth; i++) {
+ data += ")";
+ }
+ return data;
+ };
+
+ {
+ std::string data(mksxp(1));
+ bytes = &data[0];
+ len = data.size();
+ gnupg_sexp_t sexp;
+ assert_true(sexp.parse(bytes, len, SXP_MAX_DEPTH));
+ }
+ {
+ std::string data(mksxp(SXP_MAX_DEPTH));
+ bytes = &data[0];
+ len = data.size();
+ gnupg_sexp_t sexp;
+ assert_true(sexp.parse(bytes, len, SXP_MAX_DEPTH));
+ }
+ {
+ std::string data(mksxp(SXP_MAX_DEPTH + 1));
+ bytes = &data[0];
+ len = data.size();
+ gnupg_sexp_t sexp;
+ assert_false(sexp.parse(bytes, len, SXP_MAX_DEPTH));
+ }
+}
diff --git a/src/tests/kbx-nsigs-test.cpp b/src/tests/kbx-nsigs-test.cpp
new file mode 100644
index 0000000..7b4b8b8
--- /dev/null
+++ b/src/tests/kbx-nsigs-test.cpp
@@ -0,0 +1,137 @@
+/*
+ * Copyright (c) 2017-2019 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include <librekey/key_store_kbx.h>
+#include <librekey/kbx_blob.hpp>
+#include "rnp_tests.h"
+
+#define BLOB_HEADER_SIZE 0x5
+#define BLOB_FIRST_SIZE 0x20
+
+static uint8_t
+ru8(uint8_t *p)
+{
+ return (uint8_t) p[0];
+}
+
+static uint32_t
+ru32(uint8_t *p)
+{
+ return (uint32_t)(((uint8_t) p[0] << 24) | ((uint8_t) p[1] << 16) | ((uint8_t) p[2] << 8) |
+ (uint8_t) p[3]);
+}
+
+// This is rnp_key_store_kbx_parse_header_blob() adjusted for test
+static void
+test_parse_header_blob(kbx_header_blob_t &first_blob)
+{
+ assert_int_equal(first_blob.length(), BLOB_FIRST_SIZE);
+ assert_true(first_blob.parse());
+}
+
+// This is rnp_key_store_kbx_parse_pgp_blob() adjusted for test
+static void
+test_parse_pgp_blob(kbx_pgp_blob_t &pgp_blob)
+{
+ assert_true(pgp_blob.parse());
+ assert_false(pgp_blob.keyblock_offset() > pgp_blob.length() ||
+ pgp_blob.length() <
+ (pgp_blob.keyblock_offset() + pgp_blob.keyblock_length()));
+}
+
+// This test ensures that NSIGS field of keybox PGP blob contains the total number of
+// signatures, including subkey's
+TEST_F(rnp_tests, test_kbx_nsigs)
+{
+ rnp_ffi_t ffi = NULL;
+ size_t pubring_bufsize = 4096; // buffer size, large enough to hold public keyring
+ // init ffi
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ // 1. Generate key and subkey
+ // generate RSA key
+ rnp_op_generate_t keygen = NULL;
+ assert_rnp_success(rnp_op_generate_create(&keygen, ffi, "RSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1024));
+ // user id
+ assert_rnp_success(rnp_op_generate_set_userid(keygen, "userid"));
+ // now execute keygen operation
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_op_generate_get_key(keygen, &key));
+ assert_non_null(key);
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ keygen = NULL;
+ // generate DSA subkey
+ assert_rnp_success(rnp_op_generate_subkey_create(&keygen, ffi, key, "DSA"));
+ assert_rnp_success(rnp_op_generate_set_bits(keygen, 1536));
+ assert_rnp_success(rnp_op_generate_set_dsa_qbits(keygen, 224));
+ // now generate the subkey
+ assert_rnp_success(rnp_op_generate_execute(keygen));
+ assert_rnp_success(rnp_op_generate_destroy(keygen));
+ keygen = NULL;
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ key = NULL;
+ // 2. Save the public keys to memory
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, pubring_bufsize));
+ assert_rnp_success(
+ rnp_save_keys(ffi, RNP_KEYSTORE_KBX, output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ // 3. Read and test the keybox blobs
+ uint8_t *buf = NULL;
+ size_t has_bytes = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &has_bytes, false));
+ { // header blob
+ assert_true(has_bytes >= BLOB_HEADER_SIZE);
+ uint32_t blob_length = ru32(buf);
+ assert_true(has_bytes >= blob_length);
+ kbx_blob_type_t type = (kbx_blob_type_t) ru8(buf + 4);
+ assert_int_equal(type, KBX_HEADER_BLOB);
+ std::vector<uint8_t> data(buf, buf + blob_length);
+ kbx_header_blob_t header_blob(data);
+ test_parse_header_blob(header_blob);
+ has_bytes -= blob_length;
+ buf += blob_length;
+ }
+ { // PGP blob
+ assert_true(has_bytes >= BLOB_HEADER_SIZE);
+ uint32_t blob_length = ru32(buf);
+ assert_true(has_bytes >= blob_length);
+ kbx_blob_type_t type = (kbx_blob_type_t) ru8(buf + 4);
+ assert_int_equal(type, KBX_PGP_BLOB);
+ std::vector<uint8_t> data(buf, buf + blob_length);
+ kbx_pgp_blob_t pgp_blob(data);
+ test_parse_pgp_blob(pgp_blob);
+ assert_int_equal(pgp_blob.nkeys(), 2); // key and subkey
+ assert_int_equal(pgp_blob.nsigs(), 2); // key and subkey signatures
+ has_bytes -= blob_length;
+ buf += blob_length;
+ }
+ assert_int_equal(has_bytes, 0); // end of keybox
+ // cleanup
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
diff --git a/src/tests/key-add-userid.cpp b/src/tests/key-add-userid.cpp
new file mode 100644
index 0000000..b80dbb6
--- /dev/null
+++ b/src/tests/key-add-userid.cpp
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2017-2019 [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 "../librekey/key_store_pgp.h"
+#include "pgp-key.h"
+#include "rnp_tests.h"
+#include "support.h"
+
+/* This test loads a pgp keyring and adds a few userids to the key.
+ */
+TEST_F(rnp_tests, test_key_add_userid)
+{
+ pgp_key_t * key = NULL;
+ pgp_source_t src = {};
+ pgp_dest_t dst = {};
+ const uint32_t base_expiry = 1234567890;
+ static const char *keyids[] = {"7bc6709b15c23a4a", // primary
+ "1ed63ee56fadc34d",
+ "1d7e8a5393c997a8",
+ "8a05b89fad5aded1",
+ "2fcadf05ffa501bb", // primary
+ "54505a936a4a970e",
+ "326ef111425d14a5"};
+
+ rnp_key_store_t *ks = new rnp_key_store_t(global_ctx);
+
+ assert_rnp_success(init_file_src(&src, "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(ks, &src));
+ src_close(&src);
+
+ // locate our key
+ assert_non_null(key = rnp_tests_get_key_by_id(ks, keyids[0]));
+ assert_non_null(key);
+
+ // unlock the key
+ pgp_password_provider_t pprov(string_copy_password_callback, (void *) "password");
+ assert_true(key->unlock(pprov));
+
+ // save the counts for a few items
+ unsigned uidc = key->uid_count();
+ unsigned subsigc = key->sig_count();
+
+ // add first, non-primary userid
+ rnp_selfsig_cert_info_t selfsig0;
+ selfsig0.userid = "added0";
+ selfsig0.key_flags = 0x2;
+ selfsig0.key_expiration = base_expiry;
+ selfsig0.primary = false;
+ key->add_uid_cert(selfsig0, PGP_HASH_SHA1, global_ctx);
+ // attempt to add sha1-signed uid and make sure it succeeds now and fails after the cutoff
+ // date in 2024
+ assert_int_equal(base_expiry, key->expiration());
+ assert_int_equal(0x2, key->flags());
+ assert_true(key->get_uid(uidc).valid);
+ // delete new uid and add one in the future
+ key->del_uid(uidc);
+ global_ctx.set_time(SHA1_KEY_FROM + 2);
+ key->add_uid_cert(selfsig0, PGP_HASH_SHA1, global_ctx);
+ assert_int_equal(0, key->expiration());
+ assert_int_equal(0x3, key->flags());
+ assert_false(key->get_uid(uidc).valid);
+ global_ctx.set_time(0);
+ key->del_uid(uidc);
+ assert_int_equal(uidc, key->uid_count());
+ assert_int_equal(subsigc, key->sig_count());
+ key->add_uid_cert(selfsig0, PGP_HASH_SHA256, global_ctx);
+ // make sure this userid has not been marked as primary
+ assert_false(key->has_primary_uid());
+ // make sure key expiration and flags are set
+ assert_int_equal(base_expiry, key->expiration());
+ assert_int_equal(0x2, key->flags());
+ assert_true(key->get_uid(uidc).valid);
+
+ // add a primary userid
+ rnp_selfsig_cert_info_t selfsig1;
+ selfsig1.userid = "added1";
+ selfsig1.key_flags = 0xAB;
+ selfsig1.key_expiration = base_expiry + 1;
+ selfsig1.primary = 1;
+ key->add_uid_cert(selfsig1, PGP_HASH_SHA256, global_ctx);
+
+ // make sure this userid has been marked as primary
+ assert_int_equal(key->uid_count() - 1, key->get_primary_uid());
+ // make sure key expiration and flags are set
+ assert_int_equal(base_expiry + 1, key->expiration());
+ assert_int_equal(0xAB, key->flags());
+ assert_true(key->get_uid(uidc + 1).valid);
+
+ // try to add the same userid (should fail)
+ rnp_selfsig_cert_info_t dup_selfsig;
+ dup_selfsig.userid = "added1";
+ assert_throw(key->add_uid_cert(dup_selfsig, PGP_HASH_SHA256, global_ctx));
+
+ // try to add another primary userid (should fail)
+ rnp_selfsig_cert_info_t selfsig2;
+ selfsig2.userid = "added2";
+ selfsig2.primary = 1;
+ assert_throw(key->add_uid_cert(selfsig2, PGP_HASH_SHA256, global_ctx));
+
+ selfsig2.userid = "added2";
+ selfsig2.key_flags = 0xCD;
+ selfsig2.primary = 0;
+
+ // actually add another userid
+ key->add_uid_cert(selfsig2, PGP_HASH_SHA256, global_ctx);
+
+ // confirm that the counts have increased as expected
+ assert_int_equal(key->uid_count(), uidc + 3);
+ assert_int_equal(key->sig_count(), subsigc + 3);
+ assert_true(key->get_uid(uidc + 2).valid);
+
+ // make sure key expiration and flags are not updated as they are picked from the primary
+ assert_int_equal(base_expiry + 1, key->expiration());
+ assert_int_equal(0xAB, key->flags());
+ // check the userids array
+ // added0
+ assert_string_equal(key->get_uid(uidc).str.c_str(), "added0");
+ assert_int_equal(uidc, key->get_sig(subsigc).uid);
+ assert_int_equal(0x2, key->get_sig(subsigc).key_flags);
+ // added1
+ assert_string_equal(key->get_uid(uidc + 1).str.c_str(), "added1");
+ assert_int_equal(uidc + 1, key->get_sig(subsigc + 1).uid);
+ assert_int_equal(0xAB, key->get_sig(subsigc + 1).key_flags);
+ // added2
+ assert_string_equal(key->get_uid(uidc + 2).str.c_str(), "added2");
+ assert_int_equal(uidc + 2, key->get_sig(subsigc + 2).uid);
+ assert_int_equal(0xCD, key->get_sig(subsigc + 2).key_flags);
+
+ // save the raw packets for the key (to reload later)
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ key->write(dst);
+ // cleanup
+ delete ks;
+ key = NULL;
+
+ // start over
+ ks = new rnp_key_store_t(global_ctx);
+ assert_non_null(ks);
+ // read from the saved packets
+ assert_rnp_success(init_mem_src(&src, mem_dest_get_memory(&dst), dst.writeb, false));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(ks, &src));
+ src_close(&src);
+ dst_close(&dst, true);
+ assert_non_null(key = rnp_tests_get_key_by_id(ks, keyids[0]));
+
+ // confirm that the counts have increased as expected
+ assert_int_equal(key->uid_count(), uidc + 3);
+ assert_int_equal(key->sig_count(), subsigc + 3);
+
+ // make sure correct key expiration and flags are set
+ assert_int_equal(base_expiry + 1, key->expiration());
+ assert_int_equal(0xAB, key->flags());
+
+ // check the userids array
+ // added0
+ assert_string_equal(key->get_uid(uidc).str.c_str(), "added0");
+ assert_int_equal(uidc, key->get_sig(subsigc).uid);
+ assert_int_equal(0x2, key->get_sig(subsigc).key_flags);
+ assert_true(key->get_uid(uidc).valid);
+ // added1
+ assert_string_equal(key->get_uid(uidc + 1).str.c_str(), "added1");
+ assert_int_equal(uidc + 1, key->get_sig(subsigc + 1).uid);
+ assert_int_equal(0xAB, key->get_sig(subsigc + 1).key_flags);
+ assert_true(key->get_uid(uidc + 1).valid);
+ // added2
+ assert_string_equal(key->get_uid(uidc + 2).str.c_str(), "added2");
+ assert_int_equal(uidc + 2, key->get_sig(subsigc + 2).uid);
+ assert_int_equal(0xCD, key->get_sig(subsigc + 2).key_flags);
+ assert_true(key->get_uid(uidc + 2).valid);
+ // delete primary userid and see how flags/expiration changes
+ key->del_uid(uidc + 1);
+ key->revalidate(*ks);
+ assert_string_equal(key->get_uid(uidc + 1).str.c_str(), "added2");
+ assert_int_equal(uidc + 1, key->get_sig(subsigc + 1).uid);
+ assert_int_equal(0xCD, key->get_sig(subsigc + 1).key_flags);
+ assert_int_equal(key->expiration(), 0);
+ assert_int_equal(key->flags(), 0xCD);
+ // delete first uid
+ key->del_uid(0);
+ key->revalidate(*ks);
+ assert_string_equal(key->get_uid(uidc).str.c_str(), "added2");
+ assert_int_equal(uidc, key->get_sig(subsigc).uid);
+ assert_int_equal(0xCD, key->get_sig(subsigc).key_flags);
+ assert_int_equal(key->expiration(), 0);
+ assert_int_equal(key->flags(), 0xCD);
+ // delete last uid, leaving added0 only
+ key->del_uid(uidc);
+ key->revalidate(*ks);
+ assert_string_equal(key->get_uid(uidc - 1).str.c_str(), "added0");
+ assert_int_equal(uidc - 1, key->get_sig(subsigc - 1).uid);
+ assert_int_equal(0x2, key->get_sig(subsigc - 1).key_flags);
+ assert_int_equal(key->expiration(), base_expiry);
+ assert_int_equal(key->flags(), 0x2);
+ // delete added0
+ key->del_uid(0);
+ key->revalidate(*ks);
+
+ // cleanup
+ delete ks;
+}
diff --git a/src/tests/key-grip.cpp b/src/tests/key-grip.cpp
new file mode 100644
index 0000000..7e16e9f
--- /dev/null
+++ b/src/tests/key-grip.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (c) 2018-2019 [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 "../librekey/key_store_pgp.h"
+#include "../librepgp/stream-packet.h"
+#include "../librepgp/stream-sig.h"
+#include "pgp-key.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+
+TEST_F(rnp_tests, key_grip)
+{
+ rnp_key_store_t *pub_store = new rnp_key_store_t(
+ PGP_KEY_STORE_KBX, "data/test_stream_key_load/g10/pubring.kbx", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pub_store, NULL));
+
+ rnp_key_store_t *sec_store = new rnp_key_store_t(
+ PGP_KEY_STORE_G10, "data/test_stream_key_load/g10/private-keys-v1.d", global_ctx);
+ pgp_key_provider_t key_provider(rnp_key_provider_store, pub_store);
+ assert_true(rnp_key_store_load_from_path(sec_store, &key_provider));
+
+ const pgp_key_t *key = NULL;
+ // dsa-eg public/secret key
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "552286BEB2999F0A9E26A50385B90D9724001187"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "552286BEB2999F0A9E26A50385B90D9724001187"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "A5E4CD2CBBE44A16E4D6EC05C2E3C3A599DC763C"));
+
+ // rsa/rsa public/secret key
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "D148210FAF36468055B83D0F5A6DEB83FBC8E864"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "D148210FAF36468055B83D0F5A6DEB83FBC8E864"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "CED7034A8EB5F4CE90DF99147EC33D86FCD3296C"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "CED7034A8EB5F4CE90DF99147EC33D86FCD3296C"));
+
+ // ed25519 : public/secret key
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "940D97D75C306D737A59A98EAFF1272832CEDC0B"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "940D97D75C306D737A59A98EAFF1272832CEDC0B"));
+
+ // x25519 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "A77DC8173DA6BEE126F5BD6F5A14E01200B52FCE"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "636C983EDB558527BA82780B52CB5DAE011BE46B"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "636C983EDB558527BA82780B52CB5DAE011BE46B"));
+
+ // nistp256 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "FC81AECE90BCE6E54D0D637D266109783AC8DAC0"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "FC81AECE90BCE6E54D0D637D266109783AC8DAC0"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "A56DC8DB8355747A809037459B4258B8A743EAB5"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "A56DC8DB8355747A809037459B4258B8A743EAB5"));
+
+ // nistp384 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "A1338230AED1C9C125663518470B49056C9D1733"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "A1338230AED1C9C125663518470B49056C9D1733"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "797A83FE041FFE06A7F4B1D32C6F4AE0F6D87ADF"));
+
+ // nistp521 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "D91B789603EC9138AA20342A2B6DC86C81B70F5D"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "D91B789603EC9138AA20342A2B6DC86C81B70F5D"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "FD048B2CA1919CB241DC8A2C7FA3E742EF343DCA"));
+
+ // brainpool256 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "A01BAA22A72F09A0FF0A1D4CBCE70844DD52DDD7"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "C1678B7DE5F144C93B89468D5F9764ACE182ED36"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "C1678B7DE5F144C93B89468D5F9764ACE182ED36"));
+
+ // brainpool384 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "2F25DB025DEBF3EA2715350209B985829B04F50A"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "2F25DB025DEBF3EA2715350209B985829B04F50A"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "B6BD8B81F75AF914163D97DF8DE8F6FC64C283F8"));
+
+ // brainpool512 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "5A484F56AB4B8B6583B6365034999F6543FAE1AE"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "5A484F56AB4B8B6583B6365034999F6543FAE1AE"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "9133E4A7E8FC8515518DF444C3F2F247EEBBADEC"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "9133E4A7E8FC8515518DF444C3F2F247EEBBADEC"));
+
+ // secp256k1 : public/secret key/subkey
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "498B89C485489BA16B40755C0EBA580166393074"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "498B89C485489BA16B40755C0EBA580166393074"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(pub_store, "48FFED40D018747363BDEFFDD404D1F4870F8064"));
+ assert_non_null(
+ key = rnp_tests_get_key_by_grip(sec_store, "48FFED40D018747363BDEFFDD404D1F4870F8064"));
+
+ // cleanup
+ delete pub_store;
+ delete sec_store;
+}
diff --git a/src/tests/key-prefs.cpp b/src/tests/key-prefs.cpp
new file mode 100644
index 0000000..5732dd5
--- /dev/null
+++ b/src/tests/key-prefs.cpp
@@ -0,0 +1,102 @@
+/*
+ * Copyright (c) 2019 [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 "pgp-key.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+
+TEST_F(rnp_tests, test_key_prefs)
+{
+ pgp_user_prefs_t pref1 = {};
+ pgp_user_prefs_t pref2 = {};
+
+ /* symm algs */
+ pref1.add_symm_alg(PGP_SA_AES_256);
+ pref1.add_symm_alg(PGP_SA_AES_256);
+ pref1.add_symm_alg(PGP_SA_AES_192);
+ pref1.add_symm_alg(PGP_SA_AES_192);
+ pref1.add_symm_alg(PGP_SA_AES_128);
+ pref1.add_symm_alg(PGP_SA_AES_128);
+ assert_int_equal(pref1.symm_algs.size(), 3);
+ assert_int_equal(pref1.symm_algs[0], PGP_SA_AES_256);
+ assert_int_equal(pref1.symm_algs[1], PGP_SA_AES_192);
+ assert_int_equal(pref1.symm_algs[2], PGP_SA_AES_128);
+ pref2.add_symm_alg(PGP_SA_CAMELLIA_128);
+ pref2.add_symm_alg(PGP_SA_CAMELLIA_192);
+ pref2.add_symm_alg(PGP_SA_CAMELLIA_256);
+ pref1.set_symm_algs(pref2.symm_algs);
+ assert_int_equal(pref1.symm_algs.size(), 3);
+ assert_int_equal(pref1.symm_algs[0], PGP_SA_CAMELLIA_128);
+ assert_int_equal(pref1.symm_algs[1], PGP_SA_CAMELLIA_192);
+ assert_int_equal(pref1.symm_algs[2], PGP_SA_CAMELLIA_256);
+ /* hash algs */
+ pref1.add_hash_alg(PGP_HASH_SHA512);
+ pref1.add_hash_alg(PGP_HASH_SHA384);
+ pref1.add_hash_alg(PGP_HASH_SHA512);
+ pref1.add_hash_alg(PGP_HASH_SHA384);
+ pref1.add_hash_alg(PGP_HASH_SHA256);
+ pref1.add_hash_alg(PGP_HASH_SHA256);
+ assert_int_equal(pref1.hash_algs.size(), 3);
+ assert_int_equal(pref1.hash_algs[0], PGP_HASH_SHA512);
+ assert_int_equal(pref1.hash_algs[1], PGP_HASH_SHA384);
+ assert_int_equal(pref1.hash_algs[2], PGP_HASH_SHA256);
+ pref2.add_hash_alg(PGP_HASH_SHA3_512);
+ pref2.add_hash_alg(PGP_HASH_SHA3_256);
+ pref2.add_hash_alg(PGP_HASH_SHA1);
+ pref1.set_hash_algs(pref2.hash_algs);
+ assert_int_equal(pref1.hash_algs.size(), 3);
+ assert_int_equal(pref1.hash_algs[0], PGP_HASH_SHA3_512);
+ assert_int_equal(pref1.hash_algs[1], PGP_HASH_SHA3_256);
+ assert_int_equal(pref1.hash_algs[2], PGP_HASH_SHA1);
+ /* z algs */
+ pref1.add_z_alg(PGP_C_ZIP);
+ pref1.add_z_alg(PGP_C_ZLIB);
+ pref1.add_z_alg(PGP_C_BZIP2);
+ pref1.add_z_alg(PGP_C_ZIP);
+ pref1.add_z_alg(PGP_C_ZLIB);
+ pref1.add_z_alg(PGP_C_BZIP2);
+ assert_int_equal(pref1.z_algs.size(), 3);
+ assert_int_equal(pref1.z_algs[0], PGP_C_ZIP);
+ assert_int_equal(pref1.z_algs[1], PGP_C_ZLIB);
+ assert_int_equal(pref1.z_algs[2], PGP_C_BZIP2);
+ pref2.add_z_alg(PGP_C_BZIP2);
+ pref1.set_z_algs(pref2.z_algs);
+ assert_int_equal(pref1.z_algs.size(), 1);
+ assert_int_equal(pref1.z_algs[0], PGP_C_BZIP2);
+ /* ks prefs */
+ pref1.add_ks_pref(PGP_KEY_SERVER_NO_MODIFY);
+ assert_int_equal(pref1.ks_prefs.size(), 1);
+ assert_int_equal(pref1.ks_prefs[0], PGP_KEY_SERVER_NO_MODIFY);
+ pref2.add_ks_pref((pgp_key_server_prefs_t) 0x20);
+ pref2.add_ks_pref((pgp_key_server_prefs_t) 0x40);
+ pref1.set_ks_prefs(pref2.ks_prefs);
+ assert_int_equal(pref1.ks_prefs.size(), 2);
+ assert_int_equal(pref1.ks_prefs[0], 0x20);
+ assert_int_equal(pref1.ks_prefs[1], 0x40);
+ /* ks url */
+ pref1.key_server = "hkp://something/";
+}
diff --git a/src/tests/key-protect.cpp b/src/tests/key-protect.cpp
new file mode 100644
index 0000000..c9acf38
--- /dev/null
+++ b/src/tests/key-protect.cpp
@@ -0,0 +1,358 @@
+/*
+ * Copyright (c) 2017-2019 [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 "../librekey/key_store_pgp.h"
+#include "pgp-key.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+#include "crypto.h"
+
+/* This test loads a .gpg keyring and tests protect/unprotect functionality.
+ * There is also some lock/unlock testing in here, since the two are
+ * somewhat related.
+ */
+TEST_F(rnp_tests, test_key_protect_load_pgp)
+{
+ pgp_key_t * key = NULL;
+ static const char *keyids[] = {"7bc6709b15c23a4a", // primary
+ "1ed63ee56fadc34d",
+ "1d7e8a5393c997a8",
+ "8a05b89fad5aded1",
+ "2fcadf05ffa501bb", // primary
+ "54505a936a4a970e",
+ "326ef111425d14a5"};
+
+ // load our keyring and do some quick checks
+ {
+ pgp_source_t src = {};
+ rnp_key_store_t *ks = new rnp_key_store_t(global_ctx);
+
+ assert_rnp_success(init_file_src(&src, "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(ks, &src));
+ src_close(&src);
+
+ for (size_t i = 0; i < ARRAY_SIZE(keyids); i++) {
+ pgp_key_t *key = NULL;
+ assert_non_null(key = rnp_tests_get_key_by_id(ks, keyids[i]));
+ assert_non_null(key);
+ // all keys in this keyring are encrypted and thus should be both protected and
+ // locked initially
+ assert_true(key->is_protected());
+ assert_true(key->is_locked());
+ }
+
+ pgp_key_t *tmp = NULL;
+ assert_non_null(tmp = rnp_tests_get_key_by_id(ks, keyids[0]));
+
+ // steal this key from the store
+ key = new pgp_key_t(*tmp);
+ assert_non_null(key);
+ delete ks;
+ }
+
+ // confirm that this key is indeed RSA
+ assert_int_equal(key->alg(), PGP_PKA_RSA);
+
+ // confirm key material is currently all NULL (in other words, the key is locked)
+ assert_true(mpi_empty(key->material().rsa.d));
+ assert_true(mpi_empty(key->material().rsa.p));
+ assert_true(mpi_empty(key->material().rsa.q));
+ assert_true(mpi_empty(key->material().rsa.u));
+
+ // try to unprotect with a failing password provider
+ pgp_password_provider_t pprov(failing_password_callback);
+ assert_false(key->unprotect(pprov, global_ctx));
+
+ // try to unprotect with an incorrect password
+ pprov = {string_copy_password_callback, (void *) "badpass"};
+ assert_false(key->unprotect(pprov, global_ctx));
+
+ // unprotect with the correct password
+ pprov = {string_copy_password_callback, (void *) "password"};
+ assert_true(key->unprotect(pprov, global_ctx));
+ assert_false(key->is_protected());
+
+ // should still be locked
+ assert_true(key->is_locked());
+
+ // confirm secret key material is still NULL
+ assert_true(mpi_empty(key->material().rsa.d));
+ assert_true(mpi_empty(key->material().rsa.p));
+ assert_true(mpi_empty(key->material().rsa.q));
+ assert_true(mpi_empty(key->material().rsa.u));
+
+ // unlock (no password required since the key is not protected)
+ pprov = {asserting_password_callback};
+ assert_true(key->unlock(pprov));
+ assert_false(key->is_locked());
+
+ // secret key material should be available
+ assert_false(mpi_empty(key->material().rsa.d));
+ assert_false(mpi_empty(key->material().rsa.p));
+ assert_false(mpi_empty(key->material().rsa.q));
+ assert_false(mpi_empty(key->material().rsa.u));
+
+ // save the secret MPIs for some later comparisons
+ pgp_mpi_t d = key->material().rsa.d;
+ pgp_mpi_t p = key->material().rsa.p;
+ pgp_mpi_t q = key->material().rsa.q;
+ pgp_mpi_t u = key->material().rsa.u;
+
+ // confirm that packets[0] is no longer encrypted
+ {
+ pgp_source_t memsrc = {};
+ rnp_key_store_t *ks = new rnp_key_store_t(global_ctx);
+ pgp_rawpacket_t &pkt = key->rawpkt();
+
+ assert_rnp_success(init_mem_src(&memsrc, pkt.raw.data(), pkt.raw.size(), false));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(ks, &memsrc));
+ src_close(&memsrc);
+
+ // grab the first key
+ pgp_key_t *reloaded_key = NULL;
+ assert_non_null(reloaded_key = rnp_tests_get_key_by_id(ks, keyids[0]));
+ assert_non_null(reloaded_key);
+
+ // should not be locked, nor protected
+ assert_false(reloaded_key->is_locked());
+ assert_false(reloaded_key->is_protected());
+ // secret key material should not be NULL
+ assert_false(mpi_empty(reloaded_key->material().rsa.d));
+ assert_false(mpi_empty(reloaded_key->material().rsa.p));
+ assert_false(mpi_empty(reloaded_key->material().rsa.q));
+ assert_false(mpi_empty(reloaded_key->material().rsa.u));
+
+ // compare MPIs of the reloaded key, with the unlocked key from earlier
+ assert_true(mpi_equal(&key->material().rsa.d, &reloaded_key->material().rsa.d));
+ assert_true(mpi_equal(&key->material().rsa.p, &reloaded_key->material().rsa.p));
+ assert_true(mpi_equal(&key->material().rsa.q, &reloaded_key->material().rsa.q));
+ assert_true(mpi_equal(&key->material().rsa.u, &reloaded_key->material().rsa.u));
+ // negative test to try to ensure the above is a valid test
+ assert_false(mpi_equal(&key->material().rsa.d, &reloaded_key->material().rsa.p));
+
+ // lock it
+ assert_true(reloaded_key->lock());
+ assert_true(reloaded_key->is_locked());
+ // confirm that secret MPIs are NULL again
+ assert_true(mpi_empty(reloaded_key->material().rsa.d));
+ assert_true(mpi_empty(reloaded_key->material().rsa.p));
+ assert_true(mpi_empty(reloaded_key->material().rsa.q));
+ assert_true(mpi_empty(reloaded_key->material().rsa.u));
+ // unlock it (no password, since it's not protected)
+ pgp_password_provider_t pprov(asserting_password_callback);
+ assert_true(reloaded_key->unlock(pprov));
+ assert_false(reloaded_key->is_locked());
+ // compare MPIs of the reloaded key, with the unlocked key from earlier
+ assert_true(mpi_equal(&key->material().rsa.d, &reloaded_key->material().rsa.d));
+ assert_true(mpi_equal(&key->material().rsa.p, &reloaded_key->material().rsa.p));
+ assert_true(mpi_equal(&key->material().rsa.q, &reloaded_key->material().rsa.q));
+ assert_true(mpi_equal(&key->material().rsa.u, &reloaded_key->material().rsa.u));
+
+ delete ks;
+ }
+
+ // lock
+ assert_true(key->lock());
+
+ // try to protect (will fail when key is locked)
+ pprov = {string_copy_password_callback, (void *) "newpass"};
+ assert_false(key->protect({}, pprov, global_ctx));
+ assert_false(key->is_protected());
+
+ // unlock
+ pprov = {asserting_password_callback};
+ assert_true(key->unlock(pprov));
+ assert_false(key->is_locked());
+
+ // try to protect with a failing password provider
+ pprov = {failing_password_callback};
+ assert_false(key->protect({}, pprov, global_ctx));
+ assert_false(key->is_protected());
+
+ // (re)protect with a new password
+ pprov = {string_copy_password_callback, (void *) "newpass"};
+ assert_true(key->protect({}, pprov, global_ctx));
+ assert_true(key->is_protected());
+
+ // lock
+ assert_true(key->lock());
+ assert_true(key->is_locked());
+
+ // try to unlock with old password
+ pprov = {string_copy_password_callback, (void *) "password"};
+ assert_false(key->unlock(pprov));
+ assert_true(key->is_locked());
+
+ // unlock with new password
+ pprov = {string_copy_password_callback, (void *) "newpass"};
+ assert_true(key->unlock(pprov));
+ assert_false(key->is_locked());
+
+ // compare secret MPIs with those from earlier
+ assert_true(mpi_equal(&key->material().rsa.d, &d));
+ assert_true(mpi_equal(&key->material().rsa.p, &p));
+ assert_true(mpi_equal(&key->material().rsa.q, &q));
+ assert_true(mpi_equal(&key->material().rsa.u, &u));
+
+ // cleanup
+ delete key;
+}
+
+TEST_F(rnp_tests, test_key_protect_sec_data)
+{
+ rnp_keygen_primary_desc_t pri_desc = {};
+ pri_desc.crypto.key_alg = PGP_PKA_RSA;
+ pri_desc.crypto.rsa.modulus_bit_len = 1024;
+ pri_desc.crypto.ctx = &global_ctx;
+ pri_desc.cert.userid = "test";
+
+ rnp_keygen_subkey_desc_t sub_desc = {};
+ sub_desc.crypto.key_alg = PGP_PKA_RSA;
+ sub_desc.crypto.rsa.modulus_bit_len = 1024;
+ sub_desc.crypto.ctx = &global_ctx;
+
+ /* generate raw unprotected keypair */
+ pgp_key_t skey, pkey, ssub, psub;
+ pgp_password_provider_t prov = {};
+ assert_true(pgp_generate_primary_key(pri_desc, true, skey, pkey, PGP_KEY_STORE_GPG));
+ assert_true(
+ pgp_generate_subkey(sub_desc, true, skey, pkey, ssub, psub, prov, PGP_KEY_STORE_GPG));
+ assert_non_null(skey.pkt().sec_data);
+ assert_non_null(ssub.pkt().sec_data);
+ assert_null(pkey.pkt().sec_data);
+ assert_null(psub.pkt().sec_data);
+ /* copy part of the cleartext secret key and save pointers for later checks */
+ assert_true(skey.pkt().sec_len >= 32);
+ assert_true(ssub.pkt().sec_len >= 32);
+ uint8_t raw_skey[32];
+ uint8_t raw_ssub[32];
+ memcpy(raw_skey, skey.pkt().sec_data, 32);
+ memcpy(raw_ssub, ssub.pkt().sec_data, 32);
+ pgp_key_pkt_t *skeypkt;
+ pgp_key_pkt_t *ssubpkt;
+#if defined(__has_feature)
+#if !__has_feature(address_sanitizer)
+ /* copy keys and delete, making sure secret data is wiped*/
+ pgp_key_t *skeycp = new pgp_key_t(skey);
+ pgp_key_t *ssubcp = new pgp_key_t(ssub);
+ uint8_t * raw_skey_ptr = skeycp->pkt().sec_data;
+ uint8_t * raw_ssub_ptr = ssubcp->pkt().sec_data;
+ assert_int_equal(memcmp(raw_skey, raw_skey_ptr, 32), 0);
+ assert_int_equal(memcmp(raw_ssub, raw_ssub_ptr, 32), 0);
+ delete skeycp;
+ delete ssubcp;
+ assert_int_not_equal(memcmp(raw_skey, raw_skey_ptr, 32), 0);
+ assert_int_not_equal(memcmp(raw_ssub, raw_ssub_ptr, 32), 0);
+ /* do the same with key packet */
+ skeypkt = new pgp_key_pkt_t(skey.pkt());
+ ssubpkt = new pgp_key_pkt_t(ssub.pkt());
+ raw_skey_ptr = skeypkt->sec_data;
+ raw_ssub_ptr = ssubpkt->sec_data;
+ assert_int_equal(memcmp(raw_skey, raw_skey_ptr, 32), 0);
+ assert_int_equal(memcmp(raw_ssub, raw_ssub_ptr, 32), 0);
+ delete skeypkt;
+ delete ssubpkt;
+ assert_int_not_equal(memcmp(raw_skey, raw_skey_ptr, 32), 0);
+ assert_int_not_equal(memcmp(raw_ssub, raw_ssub_ptr, 32), 0);
+ /* save original pointers */
+ raw_skey_ptr = skey.pkt().sec_data;
+ raw_ssub_ptr = ssub.pkt().sec_data;
+#endif
+#endif
+
+ /* protect key and subkey */
+ pgp_password_provider_t pprov(string_copy_password_callback, (void *) "password");
+ rnp_key_protection_params_t prot = {};
+ assert_true(skey.protect(prot, pprov, global_ctx));
+ assert_true(ssub.protect(prot, pprov, global_ctx));
+ assert_int_not_equal(memcmp(raw_skey, skey.pkt().sec_data, 32), 0);
+ assert_int_not_equal(memcmp(raw_ssub, ssub.pkt().sec_data, 32), 0);
+#if defined(__has_feature)
+#if !__has_feature(address_sanitizer)
+ assert_int_not_equal(memcmp(raw_skey, raw_skey_ptr, 32), 0);
+ assert_int_not_equal(memcmp(raw_ssub, raw_ssub_ptr, 32), 0);
+#endif
+#endif
+ /* make sure rawpkt is also protected */
+ skeypkt = new pgp_key_pkt_t();
+ pgp_source_t memsrc = {};
+ assert_rnp_success(
+ init_mem_src(&memsrc, skey.rawpkt().raw.data(), skey.rawpkt().raw.size(), false));
+ assert_rnp_success(skeypkt->parse(memsrc));
+ src_close(&memsrc);
+ assert_int_not_equal(memcmp(raw_skey, skeypkt->sec_data, 32), 0);
+ assert_int_equal(skeypkt->sec_protection.s2k.specifier, PGP_S2KS_ITERATED_AND_SALTED);
+ delete skeypkt;
+ ssubpkt = new pgp_key_pkt_t();
+ assert_rnp_success(
+ init_mem_src(&memsrc, ssub.rawpkt().raw.data(), ssub.rawpkt().raw.size(), false));
+ assert_rnp_success(ssubpkt->parse(memsrc));
+ src_close(&memsrc);
+ assert_int_not_equal(memcmp(raw_ssub, ssubpkt->sec_data, 32), 0);
+ assert_int_equal(ssubpkt->sec_protection.s2k.specifier, PGP_S2KS_ITERATED_AND_SALTED);
+ delete ssubpkt;
+
+ /* unlock and make sure sec_data is not decrypted */
+ assert_true(skey.unlock(pprov));
+ assert_true(ssub.unlock(pprov));
+ assert_int_not_equal(memcmp(raw_skey, skey.pkt().sec_data, 32), 0);
+ assert_int_not_equal(memcmp(raw_ssub, ssub.pkt().sec_data, 32), 0);
+ /* unprotect key */
+ assert_true(skey.unprotect(pprov, global_ctx));
+ assert_true(ssub.unprotect(pprov, global_ctx));
+ assert_int_equal(memcmp(raw_skey, skey.pkt().sec_data, 32), 0);
+ assert_int_equal(memcmp(raw_ssub, ssub.pkt().sec_data, 32), 0);
+ /* protect it back with another password */
+ pgp_password_provider_t pprov2(string_copy_password_callback, (void *) "password2");
+ assert_true(skey.protect(prot, pprov2, global_ctx));
+ assert_true(ssub.protect(prot, pprov2, global_ctx));
+ assert_int_not_equal(memcmp(raw_skey, skey.pkt().sec_data, 32), 0);
+ assert_int_not_equal(memcmp(raw_ssub, ssub.pkt().sec_data, 32), 0);
+ assert_false(skey.unlock(pprov));
+ assert_false(ssub.unlock(pprov));
+ assert_true(skey.unlock(pprov2));
+ assert_true(ssub.unlock(pprov2));
+ assert_true(skey.lock());
+ assert_true(ssub.lock());
+ /* make sure rawpkt is also protected */
+ skeypkt = new pgp_key_pkt_t();
+ assert_rnp_success(
+ init_mem_src(&memsrc, skey.rawpkt().raw.data(), skey.rawpkt().raw.size(), false));
+ assert_rnp_success(skeypkt->parse(memsrc));
+ src_close(&memsrc);
+ assert_int_not_equal(memcmp(raw_skey, skeypkt->sec_data, 32), 0);
+ assert_int_equal(skeypkt->sec_protection.s2k.specifier, PGP_S2KS_ITERATED_AND_SALTED);
+ delete skeypkt;
+ ssubpkt = new pgp_key_pkt_t();
+ assert_rnp_success(
+ init_mem_src(&memsrc, ssub.rawpkt().raw.data(), ssub.rawpkt().raw.size(), false));
+ assert_rnp_success(ssubpkt->parse(memsrc));
+ src_close(&memsrc);
+ assert_int_not_equal(memcmp(raw_ssub, ssubpkt->sec_data, 32), 0);
+ assert_int_equal(ssubpkt->sec_protection.s2k.specifier, PGP_S2KS_ITERATED_AND_SALTED);
+ delete ssubpkt;
+}
diff --git a/src/tests/key-store-search.cpp b/src/tests/key-store-search.cpp
new file mode 100644
index 0000000..4070b0d
--- /dev/null
+++ b/src/tests/key-store-search.cpp
@@ -0,0 +1,237 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <algorithm>
+#include <set>
+#include "../librekey/key_store_pgp.h"
+#include "pgp-key.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+
+/* This test adds some fake keys to a key store and tests some of
+ * the search functions.
+ */
+TEST_F(rnp_tests, test_key_store_search)
+{
+ // create our store
+ rnp_key_store_t *store = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
+ store->disable_validation = true;
+
+ // some fake key data
+ static const struct {
+ const char *keyid;
+ size_t count; // number of keys like this to add to the store
+ const char *userids[5]; // NULL terminator required on array and strings
+ } testdata[] = {{"000000000000AAAA", 1, {"user1-1", NULL}},
+ {"000000000000BBBB", 2, {"user2", "user1-2", NULL}},
+ {"000000000000CCCC", 1, {"user3", NULL}},
+ {"FFFFFFFFFFFFFFFF", 0, {NULL}}};
+ // add our fake test keys
+ for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
+ for (size_t n = 0; n < testdata[i].count; n++) {
+ pgp_key_t key;
+
+ key.pkt().tag = PGP_PKT_PUBLIC_KEY;
+ key.pkt().version = PGP_V4;
+ key.pkt().alg = PGP_PKA_RSA;
+
+ // set the keyid
+ assert_true(rnp::hex_decode(
+ testdata[i].keyid, (uint8_t *) key.keyid().data(), key.keyid().size()));
+ // keys should have different grips otherwise rnp_key_store_add_key will fail here
+ pgp_key_grip_t &grip = (pgp_key_grip_t &) key.grip();
+ assert_true(rnp::hex_decode(testdata[i].keyid, grip.data(), grip.size()));
+ grip[0] = (uint8_t) n;
+ // and fingerprint
+ pgp_fingerprint_t &fp = (pgp_fingerprint_t &) key.fp();
+ assert_true(
+ rnp::hex_decode(testdata[i].keyid, fp.fingerprint, PGP_FINGERPRINT_SIZE));
+ fp.fingerprint[0] = (uint8_t) n;
+ fp.length = PGP_FINGERPRINT_SIZE;
+ // set the userids
+ for (size_t uidn = 0; testdata[i].userids[uidn]; uidn++) {
+ pgp_transferable_userid_t tuid;
+ tuid.uid.tag = PGP_PKT_USER_ID;
+ tuid.uid.uid_len = strlen(testdata[i].userids[uidn]);
+ tuid.uid.uid = (uint8_t *) malloc(tuid.uid.uid_len);
+ assert_non_null(tuid.uid.uid);
+ memcpy(tuid.uid.uid, testdata[i].userids[uidn], tuid.uid.uid_len);
+ key.add_uid(tuid);
+ }
+ // add to the store
+ assert_true(rnp_key_store_add_key(store, &key));
+ }
+ }
+
+ // keyid search
+ for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
+ std::string keyid = testdata[i].keyid;
+ std::set<pgp_key_t *> seen_keys;
+ for (pgp_key_t *key = rnp_tests_get_key_by_id(store, keyid); key;
+ key = rnp_tests_get_key_by_id(store, keyid, key)) {
+ // check that the keyid actually matches
+ assert_true(cmp_keyid(key->keyid(), keyid));
+ // check that we have not already encountered this key pointer
+ assert_int_equal(seen_keys.count(key), 0);
+ // keep track of what key pointers we have seen
+ seen_keys.insert(key);
+ }
+ assert_int_equal(seen_keys.size(), testdata[i].count);
+ }
+ // keyid search (by_name)
+ for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
+ std::set<pgp_key_t *> seen_keys;
+ pgp_key_t * key = NULL;
+ key = rnp_tests_get_key_by_id(store, testdata[i].keyid);
+ while (key) {
+ // check that the keyid actually matches
+ assert_true(cmp_keyid(key->keyid(), testdata[i].keyid));
+ // check that we have not already encountered this key pointer
+ assert_int_equal(seen_keys.count(key), 0);
+ // keep track of what key pointers we have seen
+ seen_keys.insert(key);
+ // this only returns false on error, regardless of whether it found a match
+ key = rnp_tests_get_key_by_id(store, testdata[i].keyid, key);
+ }
+ // check the count
+ assert_int_equal(seen_keys.size(), testdata[i].count);
+ }
+
+ // userid search (literal)
+ for (auto &key : store->keys) {
+ for (size_t i = 0; i < key.uid_count(); i++) {
+ key.get_uid(i).valid = true;
+ }
+ }
+ for (size_t i = 0; i < ARRAY_SIZE(testdata); i++) {
+ for (size_t uidn = 0; testdata[i].userids[uidn]; uidn++) {
+ std::set<pgp_key_t *> seen_keys;
+ const std::string userid = testdata[i].userids[uidn];
+ pgp_key_t * key = rnp_tests_key_search(store, userid);
+ while (key) {
+ // check that the userid actually matches
+ bool found = false;
+ for (unsigned j = 0; j < key->uid_count(); j++) {
+ if (key->get_uid(j).str == userid) {
+ found = true;
+ }
+ }
+ assert_true(found);
+ // check that we have not already encountered this key pointer
+ assert_int_equal(seen_keys.count(key), 0);
+ // keep track of what key pointers we have seen
+ seen_keys.insert(key);
+ key = rnp_tests_get_key_by_id(store, testdata[i].keyid, key);
+ }
+ // check the count
+ assert_int_equal(seen_keys.size(), testdata[i].count);
+ }
+ }
+
+ // cleanup
+ delete store;
+}
+
+TEST_F(rnp_tests, test_key_store_search_by_name)
+{
+ const pgp_key_t *key;
+ pgp_key_t * primsec;
+ pgp_key_t * subsec;
+ pgp_key_t * primpub;
+ pgp_key_t * subpub;
+
+ // load pubring
+ rnp_key_store_t *pub_store =
+ new rnp_key_store_t(PGP_KEY_STORE_KBX, "data/keyrings/3/pubring.kbx", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pub_store, NULL));
+ // load secring
+ rnp_key_store_t *sec_store =
+ new rnp_key_store_t(PGP_KEY_STORE_G10, "data/keyrings/3/private-keys-v1.d", global_ctx);
+ pgp_key_provider_t key_provider(rnp_key_provider_store, pub_store);
+ assert_true(rnp_key_store_load_from_path(sec_store, &key_provider));
+
+ /* Main key fingerprint and id:
+ 4F2E62B74E6A4CD333BC19004BE147BB22DF1E60, 4BE147BB22DF1E60
+ Subkey fingerprint and id:
+ 10793E367EE867C32E358F2AA49BAE05C16E8BC8, A49BAE05C16E8BC8
+ */
+
+ /* Find keys and subkeys by fingerprint, id and userid */
+ primsec = rnp_tests_get_key_by_fpr(sec_store, "4F2E62B74E6A4CD333BC19004BE147BB22DF1E60");
+ assert_non_null(primsec);
+ key = rnp_tests_get_key_by_id(sec_store, "4BE147BB22DF1E60");
+ assert_true(key == primsec);
+ subsec = rnp_tests_get_key_by_fpr(sec_store, "10793E367EE867C32E358F2AA49BAE05C16E8BC8");
+ assert_non_null(subsec);
+ assert_true(primsec != subsec);
+ key = rnp_tests_get_key_by_id(sec_store, "A49BAE05C16E8BC8");
+ assert_true(key == subsec);
+
+ primpub = rnp_tests_get_key_by_fpr(pub_store, "4F2E62B74E6A4CD333BC19004BE147BB22DF1E60");
+ assert_non_null(primpub);
+ assert_true(primsec != primpub);
+ subpub = rnp_tests_get_key_by_fpr(pub_store, "10793E367EE867C32E358F2AA49BAE05C16E8BC8");
+ assert_true(primpub != subpub);
+ assert_true(subpub != subsec);
+ key = rnp_tests_key_search(pub_store, "test1");
+ assert_true(key == primpub);
+
+ /* Try other searches */
+ key = rnp_tests_get_key_by_fpr(sec_store, "4f2e62b74e6a4cd333bc19004be147bb22df1e60");
+ assert_true(key == primsec);
+ key = rnp_tests_get_key_by_fpr(sec_store, "0x4f2e62b74e6a4cd333bc19004be147bb22df1e60");
+ assert_true(key == primsec);
+ key = rnp_tests_get_key_by_id(pub_store, "4BE147BB22DF1E60");
+ assert_true(key == primpub);
+ key = rnp_tests_get_key_by_id(pub_store, "4be147bb22df1e60");
+ assert_true(key == primpub);
+ key = rnp_tests_get_key_by_id(pub_store, "0x4be147bb22df1e60");
+ assert_true(key == primpub);
+ key = rnp_tests_get_key_by_id(pub_store, "22df1e60");
+ assert_null(key);
+ key = rnp_tests_get_key_by_id(pub_store, "0x22df1e60");
+ assert_null(key);
+ key = rnp_tests_get_key_by_id(pub_store, "4be1 47bb 22df 1e60");
+ assert_true(key == primpub);
+ key = rnp_tests_get_key_by_id(pub_store, "4be147bb 22df1e60");
+ assert_true(key == primpub);
+ key = rnp_tests_get_key_by_id(pub_store, " 4be147bb\t22df1e60 ");
+ assert_true(key == primpub);
+ key = rnp_tests_get_key_by_id(pub_store, "test1");
+ assert_null(key);
+ /* Try negative searches */
+ assert_null(rnp_tests_get_key_by_fpr(sec_store, "4f2e62b74e6a4cd333bc19004be147bb22df1e"));
+ assert_null(rnp_tests_get_key_by_fpr(sec_store, "2e62b74e6a4cd333bc19004be147bb22df1e60"));
+ assert_null(rnp_tests_get_key_by_id(sec_store, "4be147bb22dfle60"));
+ assert_null(rnp_tests_get_key_by_id(sec_store, ""));
+ assert_null(rnp_tests_get_key_by_id(sec_store, "test11"));
+ assert_null(rnp_tests_get_key_by_id(sec_store, "atest1"));
+
+ // cleanup
+ delete pub_store;
+ delete sec_store;
+}
diff --git a/src/tests/key-unlock.cpp b/src/tests/key-unlock.cpp
new file mode 100644
index 0000000..445c704
--- /dev/null
+++ b/src/tests/key-unlock.cpp
@@ -0,0 +1,221 @@
+/*
+ * Copyright (c) 2017-2019 [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 "../librekey/key_store_pgp.h"
+#include "../librepgp/stream-ctx.h"
+#include "pgp-key.h"
+#include "ffi-priv-types.h"
+#include "rnp_tests.h"
+#include "support.h"
+#include <fstream>
+
+TEST_F(rnp_tests, test_key_unlock_pgp)
+{
+ cli_rnp_t rnp = {};
+ const char * data = "my test data";
+ pgp_password_provider_t provider = {0};
+ static const char * keyids[] = {"7bc6709b15c23a4a", // primary
+ "1ed63ee56fadc34d",
+ "1d7e8a5393c997a8",
+ "8a05b89fad5aded1",
+ "2fcadf05ffa501bb", // primary
+ "54505a936a4a970e",
+ "326ef111425d14a5"};
+
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, "data/keyrings/1/", NULL));
+ assert_true(rnp.load_keyrings(true));
+
+ for (size_t i = 0; i < ARRAY_SIZE(keyids); i++) {
+ rnp_key_handle_t handle = NULL;
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "keyid", keyids[i], &handle));
+ assert_non_null(handle);
+ bool locked = false;
+ assert_rnp_success(rnp_key_is_locked(handle, &locked));
+ // all keys in this keyring are encrypted and thus should be locked initially
+ assert_true(locked);
+ rnp_key_handle_destroy(handle);
+ }
+
+ std::ofstream out("dummyfile.dat");
+ out << data;
+ out.close();
+
+ // try signing with a failing password provider (should fail)
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(rnp.ffi, ffi_failing_password_provider, NULL));
+ rnp_cfg &cfg = rnp.cfg();
+ cfg.load_defaults();
+ cfg.set_bool(CFG_SIGN_NEEDED, true);
+ cfg.set_str(CFG_HASH, "SHA256");
+ cfg.set_int(CFG_ZLEVEL, 0);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
+ cfg.add_str(CFG_SIGNERS, keyids[0]);
+ assert_false(cli_rnp_protect_file(&rnp));
+
+ // grab the signing key to unlock
+ rnp_key_handle_t key = NULL;
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "keyid", keyids[0], &key));
+ assert_non_null(key);
+ char *alg = NULL;
+ // confirm that this key is indeed RSA first
+ assert_rnp_success(rnp_key_get_alg(key, &alg));
+ assert_int_equal(strcmp(alg, "RSA"), 0);
+ rnp_buffer_destroy(alg);
+
+ // confirm the secret MPIs are NULL
+ assert_true(mpi_empty(key->sec->material().rsa.d));
+ assert_true(mpi_empty(key->sec->material().rsa.p));
+ assert_true(mpi_empty(key->sec->material().rsa.q));
+ assert_true(mpi_empty(key->sec->material().rsa.u));
+
+ // try to unlock with a failing password provider
+ provider.callback = failing_password_callback;
+ provider.userdata = NULL;
+ assert_false(key->sec->unlock(provider));
+ bool locked = false;
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+
+ // try to unlock with an incorrect password
+ provider.callback = string_copy_password_callback;
+ provider.userdata = (void *) "badpass";
+ assert_false(key->sec->unlock(provider));
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+
+ // unlock the signing key
+ provider.callback = string_copy_password_callback;
+ provider.userdata = (void *) "password";
+ assert_true(key->sec->unlock(provider));
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_false(locked);
+
+ // confirm the secret MPIs are now filled in
+ assert_false(mpi_empty(key->sec->material().rsa.d));
+ assert_false(mpi_empty(key->sec->material().rsa.p));
+ assert_false(mpi_empty(key->sec->material().rsa.q));
+ assert_false(mpi_empty(key->sec->material().rsa.u));
+
+ // now the signing key is unlocked, confirm that no password is required for signing
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(rnp.ffi, ffi_asserting_password_provider, NULL));
+ cfg.clear();
+ cfg.load_defaults();
+ cfg.set_bool(CFG_SIGN_NEEDED, true);
+ cfg.set_str(CFG_HASH, "SHA256");
+ cfg.set_int(CFG_ZLEVEL, 0);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
+ cfg.add_str(CFG_SIGNERS, keyids[0]);
+ assert_true(cli_rnp_protect_file(&rnp));
+ cfg.clear();
+
+ // verify
+ cfg.load_defaults();
+ cfg.set_bool(CFG_OVERWRITE, true);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat.pgp");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.verify");
+ assert_true(cli_rnp_process_file(&rnp));
+
+ // verify (negative)
+ std::fstream verf("dummyfile.dat.pgp",
+ std::ios_base::binary | std::ios_base::out | std::ios_base::in);
+ verf.seekg(-3, std::ios::end);
+ char bt = verf.peek() ^ 0xff;
+ verf.seekp(-3, std::ios::end);
+ verf.write(&bt, 1);
+ verf.close();
+ assert_false(cli_rnp_process_file(&rnp));
+
+ // lock the signing key
+ assert_rnp_success(rnp_key_lock(key));
+ assert_rnp_success(rnp_key_is_locked(key, &locked));
+ assert_true(locked);
+
+ // sign, with no password (should now fail)
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(rnp.ffi, ffi_failing_password_provider, NULL));
+ cfg.clear();
+ cfg.load_defaults();
+ cfg.set_bool(CFG_SIGN_NEEDED, true);
+ cfg.set_bool(CFG_OVERWRITE, true);
+ cfg.set_str(CFG_HASH, "SHA1");
+ cfg.set_int(CFG_ZLEVEL, 0);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
+ cfg.add_str(CFG_SIGNERS, keyids[0]);
+ assert_false(cli_rnp_protect_file(&rnp));
+ cfg.clear();
+
+ // encrypt
+ cfg.load_defaults();
+ cfg.set_bool(CFG_ENCRYPT_PK, true);
+ cfg.set_int(CFG_ZLEVEL, 0);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.dat.pgp");
+ cfg.set_bool(CFG_OVERWRITE, true);
+ cfg.set_str(CFG_CIPHER, "AES256");
+ cfg.add_str(CFG_RECIPIENTS, keyids[1]);
+ assert_true(cli_rnp_protect_file(&rnp));
+ cfg.clear();
+
+ // try decrypting with a failing password provider (should fail)
+ cfg.load_defaults();
+ cfg.set_bool(CFG_OVERWRITE, true);
+ cfg.set_str(CFG_INFILE, "dummyfile.dat.pgp");
+ cfg.set_str(CFG_OUTFILE, "dummyfile.decrypt");
+ assert_false(cli_rnp_process_file(&rnp));
+
+ // grab the encrypting key to unlock
+ rnp_key_handle_t subkey = NULL;
+ assert_rnp_success(rnp_locate_key(rnp.ffi, "keyid", keyids[1], &subkey));
+ assert_non_null(subkey);
+
+ // unlock the encrypting key
+ assert_rnp_success(rnp_key_unlock(subkey, "password"));
+ assert_rnp_success(rnp_key_is_locked(subkey, &locked));
+ assert_false(locked);
+
+ // decrypt, with no password
+ assert_true(cli_rnp_process_file(&rnp));
+
+ std::string decrypt = file_to_str("dummyfile.decrypt");
+ assert_true(decrypt == data);
+
+ // lock the encrypting key
+ assert_rnp_success(rnp_key_lock(subkey));
+ assert_rnp_success(rnp_key_is_locked(subkey, &locked));
+ assert_true(locked);
+
+ // decrypt, with no password (should now fail)
+ assert_false(cli_rnp_process_file(&rnp));
+ // cleanup
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ assert_rnp_success(rnp_key_handle_destroy(subkey));
+ rnp.end();
+ assert_int_equal(rnp_unlink("dummyfile.dat"), 0);
+}
diff --git a/src/tests/key-validate.cpp b/src/tests/key-validate.cpp
new file mode 100644
index 0000000..928415b
--- /dev/null
+++ b/src/tests/key-validate.cpp
@@ -0,0 +1,765 @@
+/*
+ * Copyright (c) 2018-2019 [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 "../librekey/key_store_pgp.h"
+#include "pgp-key.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+#include "../librepgp/stream-packet.h"
+#include "../librepgp/stream-armor.h"
+
+static bool
+all_keys_valid(const rnp_key_store_t *keyring, pgp_key_t *except = NULL)
+{
+ char keyid[PGP_KEY_ID_SIZE * 2 + 3] = {0};
+
+ for (auto &key : keyring->keys) {
+ if ((!key.valid() || key.expired()) && (&key != except)) {
+ if (!rnp::hex_encode(key.keyid().data(),
+ key.keyid().size(),
+ keyid,
+ sizeof(keyid),
+ rnp::HEX_LOWERCASE)) {
+ throw std::exception();
+ }
+ RNP_LOG("key %s is not valid", keyid);
+ return false;
+ }
+ }
+ return true;
+}
+
+TEST_F(rnp_tests, test_key_validate)
+{
+ rnp_key_store_t *pubring;
+ rnp_key_store_t *secring;
+ pgp_key_t * key = NULL;
+
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ /* this keyring has one expired subkey */
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "1d7e8a5393c997a8"));
+ assert_false(key->valid());
+ assert_true(key->expired());
+ assert_true(all_keys_valid(pubring, key));
+ delete pubring;
+
+ /* secret key is marked is expired as well */
+ secring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/secring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(secring, "1d7e8a5393c997a8"));
+ assert_false(key->valid());
+ assert_true(key->expired());
+ assert_true(all_keys_valid(secring, key));
+ delete secring;
+
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/2/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_true(all_keys_valid(pubring));
+
+ /* secret keyring doesn't have signatures - so keys are marked as invalid */
+ secring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/2/secring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secring, NULL));
+ assert_false(all_keys_valid(secring));
+ /* but after adding signatures from public it is marked as valid */
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "dc70c124a50283f1"));
+ assert_non_null(rnp_key_store_import_key(secring, key, true, NULL));
+ assert_true(all_keys_valid(secring));
+ delete pubring;
+ delete secring;
+
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_KBX, "data/keyrings/3/pubring.kbx", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_true(all_keys_valid(pubring));
+
+ secring =
+ new rnp_key_store_t(PGP_KEY_STORE_G10, "data/keyrings/3/private-keys-v1.d", global_ctx);
+ pgp_key_provider_t key_provider(rnp_key_provider_store, pubring);
+ assert_true(rnp_key_store_load_from_path(secring, &key_provider));
+ assert_true(all_keys_valid(secring));
+ delete pubring;
+ delete secring;
+
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/pubring.pgp", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ /* certification has signature with MD5 hash algorithm */
+ assert_false(all_keys_valid(pubring));
+
+ rnp_key_store_clear(pubring);
+ /* add rule which allows MD5 */
+ rnp::SecurityRule allow_md5(
+ rnp::FeatureType::Hash, PGP_HASH_MD5, rnp::SecurityLevel::Default);
+ allow_md5.override = true;
+ global_ctx.profile.add_rule(allow_md5);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_true(all_keys_valid(pubring));
+ rnp_key_store_clear(pubring);
+ /* remove rule */
+ assert_true(global_ctx.profile.del_rule(allow_md5));
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_false(all_keys_valid(pubring));
+ delete pubring;
+
+ /* secret keyring doesn't have certifications - so marked as invalid */
+ secring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/secring.pgp", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secring, NULL));
+ assert_false(all_keys_valid(secring));
+ delete secring;
+
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/5/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_true(all_keys_valid(pubring));
+ delete pubring;
+
+ secring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/5/secring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secring, NULL));
+ assert_true(all_keys_valid(secring));
+ delete secring;
+}
+
+#define DATA_PATH "data/test_forged_keys/"
+
+static void
+key_store_add(rnp_key_store_t *keyring, const char *keypath)
+{
+ pgp_source_t keysrc = {};
+ pgp_transferable_key_t tkey = {};
+
+ assert_rnp_success(init_file_src(&keysrc, keypath));
+ assert_rnp_success(process_pgp_key(keysrc, tkey, false));
+ assert_true(rnp_key_store_add_transferable_key(keyring, &tkey));
+ src_close(&keysrc);
+}
+
+static bool
+key_check(rnp_key_store_t *keyring, const std::string &keyid, bool valid, bool expired = false)
+{
+ pgp_key_t *key = rnp_tests_get_key_by_id(keyring, keyid);
+ return key && (key->validated()) && (key->valid() == valid) && (key->expired() == expired);
+}
+
+TEST_F(rnp_tests, test_forged_key_validate)
+{
+ rnp_key_store_t *pubring;
+ pgp_key_t * key = NULL;
+
+ pubring = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
+
+ /* load valid dsa-eg key */
+ key_store_add(pubring, DATA_PATH "dsa-eg-pub.pgp");
+ assert_true(key_check(pubring, "C8A10A7D78273E10", true));
+ rnp_key_store_clear(pubring);
+
+ /* load dsa-eg key with forged self-signature and binding. Subkey will not be valid as
+ * well. */
+ key_store_add(pubring, DATA_PATH "dsa-eg-pub-forged-key.pgp");
+ assert_true(key_check(pubring, "C8A10A7D78273E10", false));
+ assert_true(key_check(pubring, "02A5715C3537717E", false));
+ rnp_key_store_clear(pubring);
+
+ /* load dsa-eg key with forged key material */
+ key_store_add(pubring, DATA_PATH "dsa-eg-pub-forged-material.pgp");
+ key = rnp_tests_get_key_by_id(pubring, "C8A10A7D78273E10");
+ assert_null(key);
+ /* malformed key material causes keyid change */
+ key = rnp_tests_get_key_by_id(pubring, "C258AB3B54097B9B");
+ assert_non_null(key);
+ assert_false(key->valid());
+ assert_false(key->expired());
+ rnp_key_store_clear(pubring);
+
+ /* load dsa-eg keypair with forged subkey binding signature */
+ key_store_add(pubring, DATA_PATH "dsa-eg-pub-forged-subkey.pgp");
+ assert_true(key_check(pubring, "02A5715C3537717E", false));
+ assert_true(key_check(pubring, "C8A10A7D78273E10", true));
+ rnp_key_store_clear(pubring);
+
+ /* load valid eddsa key */
+ key_store_add(pubring, DATA_PATH "ecc-25519-pub.pgp");
+ assert_true(key_check(pubring, "CC786278981B0728", true));
+ rnp_key_store_clear(pubring);
+
+ /* load eddsa key with forged self-signature */
+ key_store_add(pubring, DATA_PATH "ecc-25519-pub-forged-key.pgp");
+ assert_true(key_check(pubring, "CC786278981B0728", false));
+ rnp_key_store_clear(pubring);
+
+ /* load eddsa key with forged key material */
+ key_store_add(pubring, DATA_PATH "ecc-25519-pub-forged-material.pgp");
+ key = rnp_tests_get_key_by_id(pubring, "1BEF78DF765B79A2");
+ assert_non_null(key);
+ assert_false(key->valid());
+ assert_false(key->expired());
+ rnp_key_store_clear(pubring);
+
+ /* load valid ecdsa/ecdh p-256 keypair */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub.pgp");
+ assert_true(key_check(pubring, "23674F21B2441527", true));
+ assert_true(key_check(pubring, "37E285E9E9851491", true));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/ecdh key with forged self-signature. Both valid since there is valid binding.
+ */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-forged-key.pgp");
+ assert_true(key_check(pubring, "23674F21B2441527", true));
+ assert_true(key_check(pubring, "37E285E9E9851491", true));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/ecdh key with forged key material. Subkey is not valid as well. */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-forged-material.pgp");
+ key = rnp_tests_get_key_by_id(pubring, "23674F21B2441527");
+ assert_null(key);
+ key = rnp_tests_get_key_by_id(pubring, "41DEA786D18E5184");
+ assert_non_null(key);
+ assert_false(key->valid());
+ assert_false(key->expired());
+ assert_true(key_check(pubring, "37E285E9E9851491", false));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/ecdh keypair with forged subkey binding signature */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-forged-subkey.pgp");
+ assert_true(key_check(pubring, "37E285E9E9851491", false));
+ assert_true(key_check(pubring, "23674F21B2441527", true));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/ecdh keypair without certification: valid since have binding */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-no-certification.pgp");
+ assert_true(key_check(pubring, "23674F21B2441527", true));
+ assert_true(key_check(pubring, "37E285E9E9851491", true));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/ecdh keypair without certification and invalid binding */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-no-cert-malf-binding.pgp");
+ assert_true(key_check(pubring, "23674F21B2441527", false));
+ assert_true(key_check(pubring, "37E285E9E9851491", false));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/ecdh keypair without subkey binding */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-no-binding.pgp");
+ assert_true(key_check(pubring, "23674F21B2441527", true));
+ assert_true(key_check(pubring, "37E285E9E9851491", false));
+ rnp_key_store_clear(pubring);
+
+ /* load valid rsa/rsa keypair */
+ key_store_add(pubring, DATA_PATH "rsa-rsa-pub.pgp");
+ /* it is valid only till year 2024 since SHA1 hash is used for signatures */
+ assert_true(key_check(pubring, "2FB9179118898E8B", true));
+ assert_true(key_check(pubring, "6E2F73008F8B8D6E", true));
+ rnp_key_store_clear(pubring);
+ /* load eddsa key which uses SHA1 signature and is created after the cutoff date */
+ global_ctx.set_time(SHA1_KEY_FROM + 10);
+ key_store_add(pubring, DATA_PATH "eddsa-2024-pub.pgp");
+ assert_false(key_check(pubring, "980E3741F632212C", true));
+ assert_false(key_check(pubring, "6DA00BF7F8B59B53", true));
+ global_ctx.set_time(0);
+ rnp_key_store_clear(pubring);
+
+ /* load rsa/rsa key with forged self-signature. Valid because of valid binding. */
+ key_store_add(pubring, DATA_PATH "rsa-rsa-pub-forged-key.pgp");
+ assert_true(key_check(pubring, "2FB9179118898E8B", true));
+ assert_true(key_check(pubring, "6E2F73008F8B8D6E", true));
+ rnp_key_store_clear(pubring);
+
+ /* load rsa/rsa key with forged key material. Subkey is not valid as well. */
+ key_store_add(pubring, DATA_PATH "rsa-rsa-pub-forged-material.pgp");
+ key = rnp_tests_get_key_by_id(pubring, "2FB9179118898E8B");
+ assert_null(key);
+ key = rnp_tests_get_key_by_id(pubring, "791B14952D8F906C");
+ assert_non_null(key);
+ assert_false(key->valid());
+ assert_false(key->expired());
+ assert_true(key_check(pubring, "6E2F73008F8B8D6E", false));
+ rnp_key_store_clear(pubring);
+
+ /* load rsa/rsa keypair with forged subkey binding signature */
+ key_store_add(pubring, DATA_PATH "rsa-rsa-pub-forged-subkey.pgp");
+ assert_true(key_check(pubring, "2FB9179118898E8B", true));
+ assert_true(key_check(pubring, "6E2F73008F8B8D6E", false));
+ rnp_key_store_clear(pubring);
+
+ /* load rsa/rsa keypair with future creation date */
+ key_store_add(pubring, DATA_PATH "rsa-rsa-pub-future-key.pgp");
+ assert_true(key_check(pubring, "3D032D00EE1EC3F5", false));
+ assert_true(key_check(pubring, "021085B640CE8DCE", false));
+ rnp_key_store_clear(pubring);
+
+ /* load eddsa/rsa keypair with certification with future creation date - valid because of
+ * binding. */
+ key_store_add(pubring, DATA_PATH "ecc-25519-pub-future-cert.pgp");
+ assert_true(key_check(pubring, "D3B746FA852C2BE8", true));
+ assert_true(key_check(pubring, "EB8C21ACDC15CA14", true));
+ rnp_key_store_clear(pubring);
+
+ /* load eddsa/rsa keypair with certification with future creation date - invalid because of
+ * invalid binding. */
+ key_store_add(pubring, DATA_PATH "ecc-25519-pub-future-cert-malf-bind.pgp");
+ assert_true(key_check(pubring, "D3B746FA852C2BE8", false));
+ assert_true(key_check(pubring, "EB8C21ACDC15CA14", false));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/rsa keypair with expired subkey */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-expired-subkey.pgp");
+ assert_true(key_check(pubring, "23674F21B2441527", true));
+ assert_true(key_check(pubring, "37E285E9E9851491", false, true));
+ rnp_key_store_clear(pubring);
+
+ /* load ecdsa/ecdh keypair with expired key */
+ key_store_add(pubring, DATA_PATH "ecc-p256-pub-expired-key.pgp");
+ assert_true(key_check(pubring, "23674F21B2441527", false, true));
+ assert_true(key_check(pubring, "37E285E9E9851491", false));
+ rnp_key_store_clear(pubring);
+
+ delete pubring;
+}
+
+#define KEYSIG_PATH "data/test_key_validity/"
+
+TEST_F(rnp_tests, test_key_validity)
+{
+ rnp_key_store_t *pubring;
+ pgp_key_t * key = NULL;
+
+ /* Case1:
+ * Keys: Alice [pub]
+ * Alice is signed by Basil, but without the Basil's key.
+ * Result: Alice [valid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case1/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ delete pubring;
+
+ /* Case2:
+ * Keys: Alice [pub], Basil [pub]
+ * Alice is signed by Basil, Basil is signed by Alice, but Alice's self-signature is
+ * corrupted.
+ * Result: Alice [invalid], Basil [valid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case2/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_false(key->valid());
+ assert_false(key->expired());
+ assert_non_null(key = rnp_tests_key_search(pubring, "Basil <basil@rnp>"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ delete pubring;
+
+ /* Case3:
+ * Keys: Alice [pub], Basil [pub]
+ * Alice is signed by Basil, but doesn't have self-signature
+ * Result: Alice [invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case3/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_false(key->valid());
+ assert_false(key->expired());
+ assert_non_null(key = rnp_tests_key_search(pubring, "Basil <basil@rnp>"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ delete pubring;
+
+ /* Case4:
+ * Keys Alice [pub, sub]
+ * Alice subkey has invalid binding signature
+ * Result: Alice [valid], Alice sub [invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case4/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ assert_int_equal(key->subkey_count(), 1);
+ pgp_key_t *subkey = NULL;
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case5:
+ * Keys Alice [pub, sub], Basil [pub]
+ * Alice subkey has valid binding signature, but from the key Basil
+ * Result: Alice [valid], Alice sub [invalid]
+ *
+ * Note: to re-generate keyring file, use generate.cpp from case5 folder.
+ * To build it, feed -DBUILD_TESTING_GENERATORS=On to the cmake.
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case5/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case6:
+ * Keys Alice [pub, sub]
+ * Key Alice has revocation signature by Alice, and subkey doesn't
+ * Result: Alice [invalid], Alice sub [invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case6/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
+ assert_false(key->valid());
+ assert_false(key->expired());
+ assert_true(key->revoked());
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ assert_false(subkey->revoked());
+ delete pubring;
+
+ /* Case7:
+ * Keys Alice [pub, sub]
+ * Alice subkey has revocation signature by Alice
+ * Result: Alice [valid], Alice sub [invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case7/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ assert_false(key->revoked());
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ assert_true(subkey->revoked());
+ delete pubring;
+
+ /* Case8:
+ * Keys Alice [pub, sub]
+ * Userid is stripped from the key, but it still has valid subkey binding
+ * Result: Alice [valid], Alice sub[valid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case8/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_true(key->valid());
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_true(subkey->valid());
+ delete pubring;
+
+ /* Case9:
+ * Keys Alice [pub, sub]
+ * Alice key has two self-signatures, one which expires key and second without key
+ * expiration.
+ * Result: Alice [valid], Alice sub[valid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case9/pubring.gpg", global_ctx);
+ assert_non_null(pubring);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_true(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case10:
+ * Keys Alice [pub, sub]
+ * Alice key has expiring direct-key signature and non-expiring self-certification.
+ * Result: Alice [invalid], Alice sub[invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case10/pubring.gpg", global_ctx);
+ assert_non_null(pubring);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_false(key->valid());
+ assert_true(key->expired());
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case11:
+ * Keys Alice [pub, sub]
+ * Alice key has expiring direct-key signature, non-expiring self-certification and
+ * expiring primary userid certification. Result: Alice [invalid], Alice sub[invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case11/pubring.gpg", global_ctx);
+ assert_non_null(pubring);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_false(key->valid());
+ assert_true(key->expired());
+ assert_int_equal(key->expiration(), 100);
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case12:
+ * Keys Alice [pub, sub]
+ * Alice key has non-expiring direct-key signature, non-expiring self-certification and
+ * expiring primary userid certification. Result: Alice [invalid], Alice sub[invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case12/pubring.gpg", global_ctx);
+ assert_non_null(pubring);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_false(key->valid());
+ assert_true(key->expired());
+ assert_int_equal(key->expiration(), 2000);
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case13:
+ * Keys Alice [pub, sub]
+ * Alice key has expiring direct-key signature, non-expiring self-certification and
+ * non-expiring primary userid certification. Result: Alice [invalid], Alice sub[invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case13/pubring.gpg", global_ctx);
+ assert_non_null(pubring);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_false(key->valid());
+ assert_true(key->expired());
+ assert_int_equal(key->expiration(), 6);
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case14:
+ * Keys Alice [pub, sub]
+ * Alice key has expiring direct-key signature, non-expiring self-certification and
+ * non-expiring primary userid certification (with 0 key expiration subpacket). Result:
+ * Alice [invalid], Alice sub[invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case14/pubring.gpg", global_ctx);
+ assert_non_null(pubring);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "0451409669FFDE3C"));
+ assert_false(key->valid());
+ assert_true(key->expired());
+ assert_int_equal(key->expiration(), 6);
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+
+ /* Case15:
+ * Keys [pub, sub]
+ * Signing subkey has expired primary-key signature embedded into the subkey binding.
+ * Result: primary [valid], sub[invalid]
+ */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "case15/pubring.gpg", global_ctx);
+ assert_non_null(pubring);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubring, "E863072D3E9042EE"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ assert_int_equal(key->expiration(), 0);
+ assert_int_equal(key->subkey_count(), 1);
+ assert_non_null(subkey = pgp_key_get_subkey(key, pubring, 0));
+ assert_false(subkey->valid());
+ assert_false(subkey->expired());
+ delete pubring;
+}
+
+TEST_F(rnp_tests, test_key_expiry_direct_sig)
+{
+ /* this test was mainly used to generate test data for cases 10-12 in test_key_validity */
+ rnp_key_store_t *secring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-sec.pgp", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secring, NULL));
+ pgp_key_t *key = NULL;
+ assert_non_null(key = rnp_tests_key_search(secring, "Alice <alice@rnp>"));
+ assert_true(key->valid());
+ assert_false(key->expired());
+ /* create direct-key signature */
+ pgp_signature_t sig;
+
+ sig.version = PGP_V4;
+ sig.halg = PGP_HASH_SHA256;
+ sig.palg = key->alg();
+ sig.set_type(PGP_SIG_DIRECT);
+ sig.set_creation(key->creation());
+ sig.set_key_expiration(1000);
+ sig.set_keyfp(key->fp());
+ sig.set_keyid(key->keyid());
+
+ pgp_password_provider_t pprov(string_copy_password_callback, (void *) "password");
+ key->unlock(pprov);
+ key->sign_direct(key->pkt(), sig, global_ctx);
+ key->add_sig(sig, PGP_UID_NONE);
+ key->revalidate(*secring);
+
+ /* key becomsed invalid even since it is secret */
+ assert_int_equal(key->expiration(), 1000);
+ assert_false(key->valid());
+ assert_true(key->expired());
+
+ rnp_key_store_t *pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-pub.pgp", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ pgp_key_t *pubkey = NULL;
+ assert_non_null(pubkey = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
+ assert_int_equal(pubkey->expiration(), 0);
+ assert_true(pubkey->valid());
+ assert_false(pubkey->expired());
+ pgp_key_t *subpub = NULL;
+ assert_non_null(subpub = rnp_tests_get_key_by_id(pubring, "dd23ceb7febeff17"));
+ assert_int_equal(subpub->expiration(), 0);
+ assert_true(subpub->valid());
+ assert_false(subpub->expired());
+
+ pubkey->add_sig(sig, PGP_UID_NONE);
+ pubkey->revalidate(*pubring);
+ assert_int_equal(pubkey->expiration(), 1000);
+ assert_false(pubkey->valid());
+ assert_true(pubkey->expired());
+ assert_int_equal(subpub->expiration(), 0);
+ assert_false(subpub->valid());
+ assert_false(subpub->expired());
+
+ /* add primary userid with smaller expiration date */
+ rnp_selfsig_cert_info_t selfsig1 = {};
+ const char * boris = "Boris <boris@rnp>";
+ selfsig1.userid = boris;
+ selfsig1.key_expiration = 100;
+ selfsig1.primary = true;
+ key->add_uid_cert(selfsig1, PGP_HASH_SHA256, global_ctx);
+ key->revalidate(*secring);
+ /* key becomes invalid even it is secret */
+ assert_int_equal(key->expiration(), 100);
+ assert_false(key->valid());
+ assert_true(key->expired());
+
+ delete secring;
+ delete pubring;
+
+ secring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-sec.pgp", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secring, NULL));
+ assert_non_null(key = rnp_tests_key_search(secring, "Alice <alice@rnp>"));
+ /* create direct-key signature */
+ sig = {};
+ sig.version = PGP_V4;
+ sig.halg = PGP_HASH_SHA256;
+ sig.palg = key->alg();
+ sig.set_type(PGP_SIG_DIRECT);
+ sig.set_creation(key->creation());
+ sig.set_key_expiration(6);
+ sig.set_keyfp(key->fp());
+ sig.set_keyid(key->keyid());
+
+ key->unlock(pprov);
+ key->sign_direct(key->pkt(), sig, global_ctx);
+ key->add_sig(sig, PGP_UID_NONE);
+ key->revalidate(*secring);
+ assert_int_equal(key->expiration(), 6);
+ /* add primary userid with 0 expiration */
+ selfsig1 = {};
+ selfsig1.userid = boris;
+ selfsig1.key_expiration = 0;
+ selfsig1.primary = true;
+ key->add_uid_cert(selfsig1, PGP_HASH_SHA256, global_ctx);
+ key->revalidate(*secring);
+ assert_int_equal(key->expiration(), 6);
+
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, KEYSIG_PATH "alice-sub-pub.pgp", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_non_null(pubkey = rnp_tests_key_search(pubring, "Alice <alice@rnp>"));
+ assert_non_null(subpub = rnp_tests_get_key_by_id(pubring, "dd23ceb7febeff17"));
+ assert_int_equal(subpub->expiration(), 0);
+ assert_true(subpub->valid());
+ assert_false(subpub->expired());
+
+ pubkey->add_sig(sig, PGP_UID_NONE);
+ pubkey->revalidate(*pubring);
+ assert_int_equal(pubkey->expiration(), 6);
+ assert_false(pubkey->valid());
+ assert_true(pubkey->expired());
+
+ pgp_transferable_userid_t truid = {};
+ truid.uid = key->get_uid(1).pkt;
+ truid.signatures.push_back(key->get_sig(key->get_uid(1).get_sig(0)).sig);
+ pubkey->add_uid(truid);
+ pubkey->revalidate(*pubring);
+
+ assert_int_equal(pubkey->expiration(), 6);
+ assert_false(pubkey->valid());
+ assert_true(pubkey->expired());
+
+ /* code below may be used to print out generated key to save it somewhere */
+ /*
+ pgp_dest_t out = {};
+ pgp_dest_t armored = {};
+ assert_rnp_success(init_stdout_dest(&out));
+ assert_rnp_success(init_armored_dst(&armored, &out, PGP_ARMORED_PUBLIC_KEY));
+ pubkey->write_xfer(armored, pubring);
+ dst_close(&armored, false);
+ dst_close(&out, false);
+ */
+
+ delete secring;
+ delete pubring;
+}
diff --git a/src/tests/large-mpi.cpp b/src/tests/large-mpi.cpp
new file mode 100644
index 0000000..78f8734
--- /dev/null
+++ b/src/tests/large-mpi.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include <librepgp/stream-key.h>
+
+TEST_F(rnp_tests, test_large_mpi_rsa_pub)
+{
+ pgp_source_t keysrc = {0};
+ pgp_key_sequence_t keyseq;
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_input_t signature = NULL;
+ rnp_op_verify_t verify;
+
+ /* Load RSA pubkey packet with 65535 bit modulus MPI. Must fail. */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_large_MPIs/rsa-pub-65535bits.pgp"));
+ assert_rnp_failure(process_pgp_keys(keysrc, keyseq, false));
+ assert_true(keyseq.keys.empty());
+ src_close(&keysrc);
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/rsa-pub-65535bits.pgp"));
+ assert_rnp_failure(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ rnp_input_destroy(input);
+
+ /* Load RSA pubkey of PGP_MPINT_BITS + 1 size (16385). Must fail. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/rsa-pub-16385bits.pgp"));
+ assert_rnp_failure(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ rnp_input_destroy(input);
+
+ /* Load RSA pubkey of PGP_MPINT_BITS size (16384). Must succeed. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/rsa-pub-16384bits.pgp"));
+ assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ rnp_input_destroy(input);
+
+ /* Load RSA signature. rsa-pub-65535bits.pgp file signed by previously loaded 16384 bit RSA
+ * key. Must succeed. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/rsa-pub-65535bits.pgp"));
+ assert_rnp_success(
+ rnp_input_from_path(&signature, "data/test_large_MPIs/rsa-pub-65535bits.pgp.sig"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ rnp_input_destroy(input);
+ rnp_input_destroy(signature);
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+
+ /* Load RSA signature with PGP_MPINT_BITS + 1 size (16385) MPI. Must fail. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/rsa-pub-65535bits.pgp"));
+ assert_rnp_success(rnp_input_from_path(
+ &signature, "data/test_large_MPIs/rsa-pub-65535bits.pgp.16385sig.sig"));
+ assert_rnp_success(rnp_op_verify_detached_create(&verify, ffi, input, signature));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ rnp_input_destroy(input);
+ rnp_input_destroy(signature);
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+
+ rnp_ffi_destroy(ffi);
+}
+
+TEST_F(rnp_tests, test_large_mpi_rsa_priv)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+
+ /* Load RSA private key of PGP_MPINT_BITS + 1 size (16385). Must fail. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/rsa-priv-16385bits.pgp"));
+ assert_rnp_failure(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ rnp_input_destroy(input);
+
+ /* Load RSA private key of PGP_MPINT_BITS size (16384). Must succeed. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/rsa-priv-16384bits.pgp"));
+ assert_rnp_success(rnp_import_keys(ffi, input, RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ rnp_input_destroy(input);
+
+ /* Load PKESK encrypted by PGP_MPINT_BITS sized key. Must succeed. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/message.enc.rsa16384.pgp"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ /* Load PKESK having 16385 bit MPI. Must fail. */
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_large_MPIs/message.enc.rsa16385.pgp"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+
+ rnp_ffi_destroy(ffi);
+}
diff --git a/src/tests/large-packet.cpp b/src/tests/large-packet.cpp
new file mode 100644
index 0000000..a25e20a
--- /dev/null
+++ b/src/tests/large-packet.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (c) 2017-2019 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+
+TEST_F(rnp_tests, test_large_packet)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_verify_t verify;
+
+ /* init ffi and inputs */
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_success(rnp_input_destroy(input));
+
+ // Verify part
+ assert_rnp_success(rnp_input_from_path(&input, "data/test_large_packet/4g.bzip2.gpg"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ /* call verify */
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ /* cleanup */
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
diff --git a/src/tests/load-g10.cpp b/src/tests/load-g10.cpp
new file mode 100644
index 0000000..4f0e25b
--- /dev/null
+++ b/src/tests/load-g10.cpp
@@ -0,0 +1,111 @@
+/*
+ * Copyright (c) 2017-2019 [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 "../librekey/key_store_pgp.h"
+#include "pgp-key.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+
+/* This test loads G10 keyrings and verifies certain properties
+ * of the keys are correct.
+ */
+TEST_F(rnp_tests, test_load_g10)
+{
+ rnp_key_store_t * pub_store = NULL;
+ rnp_key_store_t * sec_store = NULL;
+ pgp_key_provider_t key_provider(rnp_key_provider_store);
+
+ // load pubring
+ pub_store =
+ new rnp_key_store_t(PGP_KEY_STORE_KBX, "data/keyrings/3/pubring.kbx", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pub_store, NULL));
+ // load secring
+ sec_store =
+ new rnp_key_store_t(PGP_KEY_STORE_G10, "data/keyrings/3/private-keys-v1.d", global_ctx);
+ key_provider.userdata = pub_store;
+ assert_true(rnp_key_store_load_from_path(sec_store, &key_provider));
+
+ /* check primary key and subkey */
+ test_load_gpg_check_key(pub_store, sec_store, "4BE147BB22DF1E60");
+ test_load_gpg_check_key(pub_store, sec_store, "A49BAE05C16E8BC8");
+
+ // cleanup
+ delete pub_store;
+ delete sec_store;
+
+ /* another store */
+ pub_store = new rnp_key_store_t(
+ PGP_KEY_STORE_KBX, "data/test_stream_key_load/g10/pubring.kbx", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pub_store, NULL));
+ sec_store = new rnp_key_store_t(
+ PGP_KEY_STORE_G10, "data/test_stream_key_load/g10/private-keys-v1.d", global_ctx);
+ key_provider.userdata = pub_store;
+ assert_true(rnp_key_store_load_from_path(sec_store, &key_provider));
+
+ /* dsa/eg key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "C8A10A7D78273E10"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "02A5715C3537717E"));
+
+ /* rsa/rsa key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "2FB9179118898E8B"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "6E2F73008F8B8D6E"));
+
+#ifdef CRYPTO_BACKEND_BOTAN
+ /* GnuPG extended key format requires AEAD support that is available for BOTAN backend
+ only https://github.com/rnpgp/rnp/issues/1642 (???)
+ */
+ /* rsa/rsa new key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "BD860A52D1899C0F"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "8E08D46A37414996"));
+#endif
+
+ /* ed25519 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "CC786278981B0728"));
+
+ /* ed25519/x25519 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "941822A0FC1B30A5"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "C711187E594376AF"));
+
+ /* p-256/p-256 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "23674F21B2441527"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "37E285E9E9851491"));
+
+ /* p-384/p-384 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "242A3AA5EA85F44A"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "E210E3D554A4FAD9"));
+
+ /* p-521/p-521 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "2092CA8324263B6A"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "9853DF2F6D297442"));
+
+ /* p256k1/p156k1 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "3EA5BB6F9692C1A0"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "7635401F90D3E533"));
+
+ delete pub_store;
+ delete sec_store;
+}
diff --git a/src/tests/load-g23.cpp b/src/tests/load-g23.cpp
new file mode 100644
index 0000000..f987344
--- /dev/null
+++ b/src/tests/load-g23.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (c) 2017-2019 [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 "../librekey/key_store_pgp.h"
+#include "pgp-key.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+
+/* This test loads G23 keyring and verifies that certain properties
+ * of the keys are correct.
+ */
+TEST_F(rnp_tests, test_load_g23)
+{
+ rnp_key_store_t * pub_store = NULL;
+ rnp_key_store_t * sec_store = NULL;
+ pgp_key_provider_t key_provider(rnp_key_provider_store);
+
+ /* another store */
+ pub_store = new rnp_key_store_t(
+ PGP_KEY_STORE_KBX, "data/test_stream_key_load/g23/pubring.kbx", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pub_store, NULL));
+ sec_store = new rnp_key_store_t(
+ PGP_KEY_STORE_G10, "data/test_stream_key_load/g23/private-keys-v1.d", global_ctx);
+ key_provider.userdata = pub_store;
+ assert_true(rnp_key_store_load_from_path(sec_store, &key_provider));
+
+#ifdef CRYPTO_BACKEND_BOTAN
+ /* GnuPG extended key format requires AEAD support that is available for BOTAN backend
+ only https://github.com/rnpgp/rnp/issues/1642 (???)
+ */
+ /* dsa/elg key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "2651229E2D4DADF5"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "740AB758FAF0D5B7"));
+
+ /* rsa/rsa key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "D1EF5C27C1F76F88"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "1F4E4EBC86A6E667"));
+
+ /* ed25519 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "4F92A7A7B285CA0F"));
+
+ /* ed25519/cv25519 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "0C96377D972E906C"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "8270B09D57420327"));
+
+ /* p-256/p-256 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "AA1DEBEA6C10FCC6"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "8F511690EADC47F8"));
+
+ /* p-384/p-384 key */
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "62774CDB7B085FB6"));
+ assert_true(test_load_gpg_check_key(pub_store, sec_store, "F0D076E2A3876399"));
+#else
+ assert_false(test_load_gpg_check_key(pub_store, sec_store, "2651229E2D4DADF5"));
+#endif // CRYPTO_BACKEND_BOTAN
+
+ /* Wrong id -- no public key*/
+ assert_false(test_load_gpg_check_key(pub_store, sec_store, "2651229E2D4DADF6"));
+
+ /* Correct id but no private key*/
+ assert_false(test_load_gpg_check_key(pub_store, sec_store, "25810145A8D4699A"));
+
+ delete pub_store;
+ delete sec_store;
+}
diff --git a/src/tests/load-pgp.cpp b/src/tests/load-pgp.cpp
new file mode 100644
index 0000000..560ed3d
--- /dev/null
+++ b/src/tests/load-pgp.cpp
@@ -0,0 +1,985 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "../librekey/key_store_pgp.h"
+#include "../librepgp/stream-packet.h"
+#include "../librepgp/stream-sig.h"
+#include "pgp-key.h"
+#include "utils.h"
+
+#include "rnp_tests.h"
+#include "support.h"
+
+/* This test loads a .gpg pubring with a single V3 key,
+ * and confirms that appropriate key flags are set.
+ */
+TEST_F(rnp_tests, test_load_v3_keyring_pgp)
+{
+ pgp_source_t src = {};
+
+ rnp_key_store_t *key_store = new rnp_key_store_t(global_ctx);
+
+ // load pubring in to the key store
+ assert_rnp_success(init_file_src(&src, "data/keyrings/2/pubring.gpg"));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(key_store, &src));
+ src_close(&src);
+ assert_int_equal(1, rnp_key_store_get_key_count(key_store));
+
+ // find the key by keyid
+ const pgp_key_t *key = rnp_tests_get_key_by_id(key_store, "DC70C124A50283F1");
+ assert_non_null(key);
+
+ // confirm the key flags are correct
+ assert_int_equal(key->flags(),
+ PGP_KF_ENCRYPT | PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH);
+
+ // confirm that key expiration is correct
+ assert_int_equal(key->expiration(), 0);
+
+ // cleanup
+ delete key_store;
+
+ // load secret keyring and decrypt the key
+ key_store = new rnp_key_store_t(global_ctx);
+
+ assert_rnp_success(init_file_src(&src, "data/keyrings/4/secring.pgp"));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(key_store, &src));
+ src_close(&src);
+ assert_int_equal(1, rnp_key_store_get_key_count(key_store));
+
+ key = rnp_tests_get_key_by_id(key_store, "7D0BC10E933404C9");
+ assert_non_null(key);
+
+ // confirm the key flags are correct
+ assert_int_equal(key->flags(),
+ PGP_KF_ENCRYPT | PGP_KF_SIGN | PGP_KF_CERTIFY | PGP_KF_AUTH);
+
+ // check if the key is secret and is locked
+ assert_true(key->is_secret());
+ assert_true(key->is_locked());
+
+ // decrypt the key
+ pgp_key_pkt_t *seckey = pgp_decrypt_seckey_pgp(key->rawpkt(), key->pkt(), "password");
+#if defined(ENABLE_IDEA)
+ assert_non_null(seckey);
+#else
+ assert_null(seckey);
+#endif
+
+ // cleanup
+ delete seckey;
+ delete key_store;
+}
+
+/* This test loads a .gpg pubring with multiple V4 keys,
+ * finds a particular key of interest, and confirms that
+ * the appropriate key flags are set.
+ */
+TEST_F(rnp_tests, test_load_v4_keyring_pgp)
+{
+ pgp_source_t src = {};
+
+ rnp_key_store_t *key_store = new rnp_key_store_t(global_ctx);
+
+ // load it in to the key store
+ assert_rnp_success(init_file_src(&src, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(key_store, &src));
+ src_close(&src);
+ assert_int_equal(7, rnp_key_store_get_key_count(key_store));
+
+ // find the key by keyid
+ static const std::string keyid = "8a05b89fad5aded1";
+ const pgp_key_t * key = rnp_tests_get_key_by_id(key_store, keyid);
+ assert_non_null(key);
+
+ // confirm the key flags are correct
+ assert_int_equal(key->flags(), PGP_KF_ENCRYPT);
+
+ // cleanup
+ delete key_store;
+}
+
+/* Just a helper for the below test */
+static void
+check_pgp_keyring_counts(const char * path,
+ unsigned primary_count,
+ const unsigned subkey_counts[])
+{
+ pgp_source_t src = {};
+ rnp_key_store_t *key_store = new rnp_key_store_t(global_ctx);
+
+ // load it in to the key store
+ assert_rnp_success(init_file_src(&src, path));
+ assert_rnp_success(rnp_key_store_pgp_read_from_src(key_store, &src));
+ src_close(&src);
+
+ // count primary keys first
+ unsigned total_primary_count = 0;
+ for (auto &key : key_store->keys) {
+ if (key.is_primary()) {
+ total_primary_count++;
+ }
+ }
+ assert_int_equal(primary_count, total_primary_count);
+
+ // now count subkeys in each primary key
+ unsigned total_subkey_count = 0;
+ unsigned primary = 0;
+ for (auto &key : key_store->keys) {
+ if (key.is_primary()) {
+ // check the subkey count for this primary key
+ assert_int_equal(key.subkey_count(), subkey_counts[primary++]);
+ } else if (key.is_subkey()) {
+ total_subkey_count++;
+ }
+ }
+
+ // check the total (not really needed)
+ assert_int_equal(rnp_key_store_get_key_count(key_store),
+ total_primary_count + total_subkey_count);
+
+ // cleanup
+ delete key_store;
+}
+
+/* This test loads a pubring.gpg and secring.gpg and confirms
+ * that it contains the expected number of primary keys
+ * and the expected number of subkeys for each primary key.
+ */
+TEST_F(rnp_tests, test_load_keyring_and_count_pgp)
+{
+ unsigned int primary_count = 2;
+ unsigned int subkey_counts[2] = {3, 2};
+
+ // check pubring
+ check_pgp_keyring_counts("data/keyrings/1/pubring.gpg", primary_count, subkey_counts);
+
+ // check secring
+ check_pgp_keyring_counts("data/keyrings/1/secring.gpg", primary_count, subkey_counts);
+}
+
+/* This test loads a V4 keyring and confirms that certain
+ * bitfields and time fields are set correctly.
+ */
+TEST_F(rnp_tests, test_load_check_bitfields_and_times)
+{
+ const pgp_key_t * key;
+ const pgp_signature_t *sig = NULL;
+
+ // load keyring
+ rnp_key_store_t *key_store =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(key_store, NULL));
+
+ // find
+ key = NULL;
+ key = rnp_tests_get_key_by_id(key_store, "7BC6709B15C23A4A");
+ assert_non_null(key);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 3);
+ // check subsig properties
+ for (size_t i = 0; i < key->sig_count(); i++) {
+ sig = &key->get_sig(i).sig;
+ static const time_t expected_creation_times[] = {1500569820, 1500569836, 1500569846};
+ // check SS_ISSUER_KEY_ID
+ assert_true(cmp_keyid(sig->keyid(), "7BC6709B15C23A4A"));
+ // check SS_CREATION_TIME
+ assert_int_equal(sig->creation(), expected_creation_times[i]);
+ // check SS_EXPIRATION_TIME
+ assert_int_equal(sig->expiration(), 0);
+ }
+ // check SS_KEY_EXPIRY
+ assert_int_equal(key->expiration(), 0);
+
+ // find
+ key = NULL;
+ key = rnp_tests_get_key_by_id(key_store, "1ED63EE56FADC34D");
+ assert_non_null(key);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 1);
+ sig = &key->get_sig(0).sig;
+ // check SS_ISSUER_KEY_ID
+ assert_true(cmp_keyid(sig->keyid(), "7BC6709B15C23A4A"));
+ // check SS_CREATION_TIME [0]
+ assert_int_equal(sig->creation(), 1500569820);
+ assert_int_equal(sig->creation(), key->creation());
+ // check SS_EXPIRATION_TIME [0]
+ assert_int_equal(sig->expiration(), 0);
+ // check SS_KEY_EXPIRY
+ assert_int_equal(key->expiration(), 0);
+
+ // find
+ key = NULL;
+ key = rnp_tests_get_key_by_id(key_store, "1D7E8A5393C997A8");
+ assert_non_null(key);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 1);
+ sig = &key->get_sig(0).sig;
+ // check SS_ISSUER_KEY_ID
+ assert_true(cmp_keyid(sig->keyid(), "7BC6709B15C23A4A"));
+ // check SS_CREATION_TIME [0]
+ assert_int_equal(sig->creation(), 1500569851);
+ assert_int_equal(sig->creation(), key->creation());
+ // check SS_EXPIRATION_TIME [0]
+ assert_int_equal(sig->expiration(), 0);
+ // check SS_KEY_EXPIRY
+ assert_int_equal(key->expiration(), 123 * 24 * 60 * 60 /* 123 days */);
+
+ // find
+ key = NULL;
+ key = rnp_tests_get_key_by_id(key_store, "8A05B89FAD5ADED1");
+ assert_non_null(key);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 1);
+ sig = &key->get_sig(0).sig;
+ // check SS_ISSUER_KEY_ID
+ assert_true(cmp_keyid(sig->keyid(), "7BC6709B15C23A4A"));
+ // check SS_CREATION_TIME [0]
+ assert_int_equal(sig->creation(), 1500569896);
+ assert_int_equal(sig->creation(), key->creation());
+ // check SS_EXPIRATION_TIME [0]
+ assert_int_equal(sig->expiration(), 0);
+ // check SS_KEY_EXPIRY
+ assert_int_equal(key->expiration(), 0);
+
+ // find
+ key = NULL;
+ key = rnp_tests_get_key_by_id(key_store, "2FCADF05FFA501BB");
+ assert_non_null(key);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 3);
+ // check subsig properties
+ for (size_t i = 0; i < key->sig_count(); i++) {
+ sig = &key->get_sig(i).sig;
+ static const time_t expected_creation_times[] = {1501372449, 1500570153, 1500570147};
+
+ // check SS_ISSUER_KEY_ID
+ assert_true(cmp_keyid(sig->keyid(), "2FCADF05FFA501BB"));
+ // check SS_CREATION_TIME
+ assert_int_equal(sig->creation(), expected_creation_times[i]);
+ // check SS_EXPIRATION_TIME
+ assert_int_equal(sig->expiration(), 0);
+ }
+ // check SS_KEY_EXPIRY
+ assert_int_equal(key->expiration(), 2076663808);
+
+ // find
+ key = NULL;
+ key = rnp_tests_get_key_by_id(key_store, "54505A936A4A970E");
+ assert_non_null(key);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 1);
+ sig = &key->get_sig(0).sig;
+ // check SS_ISSUER_KEY_ID
+ assert_true(cmp_keyid(sig->keyid(), "2FCADF05FFA501BB"));
+ // check SS_CREATION_TIME [0]
+ assert_int_equal(sig->creation(), 1500569946);
+ assert_int_equal(sig->creation(), key->creation());
+ // check SS_EXPIRATION_TIME [0]
+ assert_int_equal(sig->expiration(), 0);
+ // check SS_KEY_EXPIRY
+ assert_int_equal(key->expiration(), 2076663808);
+
+ // find
+ key = NULL;
+ key = rnp_tests_get_key_by_id(key_store, "326EF111425D14A5");
+ assert_non_null(key);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 1);
+ sig = &key->get_sig(0).sig;
+ // check SS_ISSUER_KEY_ID
+ assert_true(cmp_keyid(sig->keyid(), "2FCADF05FFA501BB"));
+ // check SS_CREATION_TIME [0]
+ assert_int_equal(sig->creation(), 1500570165);
+ assert_int_equal(sig->creation(), key->creation());
+ // check SS_EXPIRATION_TIME [0]
+ assert_int_equal(sig->expiration(), 0);
+ // check SS_KEY_EXPIRY
+ assert_int_equal(key->expiration(), 0);
+
+ // cleanup
+ delete key_store;
+}
+
+/* This test loads a V3 keyring and confirms that certain
+ * bitfields and time fields are set correctly.
+ */
+TEST_F(rnp_tests, test_load_check_bitfields_and_times_v3)
+{
+ pgp_key_id_t keyid = {};
+ const pgp_key_t * key;
+ const pgp_signature_t *sig = NULL;
+
+ // load keyring
+ rnp_key_store_t *key_store =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/2/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(key_store, NULL));
+
+ // find
+ key = NULL;
+ assert_true(rnp::hex_decode("DC70C124A50283F1", keyid.data(), keyid.size()));
+ key = rnp_tests_get_key_by_id(key_store, "DC70C124A50283F1");
+ assert_non_null(key);
+ // check key version
+ assert_int_equal(key->version(), PGP_V3);
+ // check subsig count
+ assert_int_equal(key->sig_count(), 1);
+ sig = &key->get_sig(0).sig;
+ // check signature version
+ assert_int_equal(sig->version, 3);
+ // check issuer
+ assert_true(rnp::hex_decode("DC70C124A50283F1", keyid.data(), keyid.size()));
+ assert_true(keyid == sig->keyid());
+ // check creation time
+ assert_int_equal(sig->creation(), 1005209227);
+ assert_int_equal(sig->creation(), key->creation());
+ // check signature expiration time (V3 sigs have none)
+ assert_int_equal(sig->expiration(), 0);
+ // check key expiration
+ assert_int_equal(key->expiration(), 0); // only for V4 keys
+ assert_int_equal(key->pkt().v3_days, 0);
+
+ // cleanup
+ delete key_store;
+}
+
+#define MERGE_PATH "data/test_stream_key_merge/"
+
+TEST_F(rnp_tests, test_load_armored_pub_sec)
+{
+ rnp_key_store_t *key_store =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, MERGE_PATH "key-both.asc", global_ctx);
+ assert_true(rnp_key_store_load_from_path(key_store, NULL));
+
+ /* we must have 1 main key and 2 subkeys */
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 3);
+
+ pgp_key_t *key = NULL;
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, "9747D2A6B3A63124"));
+ assert_true(key->valid());
+ assert_true(key->is_primary());
+ assert_true(key->is_secret());
+ assert_int_equal(key->rawpkt_count(), 5);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_SECRET_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(key->get_uid(1).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(1).rawpkt.tag, PGP_PKT_SIGNATURE);
+
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, "AF1114A47F5F5B28"));
+ assert_true(key->valid());
+ assert_true(key->is_subkey());
+ assert_true(key->is_secret());
+ assert_int_equal(key->rawpkt_count(), 2);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_SECRET_SUBKEY);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, "16CD16F267CCDD4F"));
+ assert_true(key->valid());
+ assert_true(key->is_subkey());
+ assert_true(key->is_secret());
+ assert_int_equal(key->rawpkt_count(), 2);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_SECRET_SUBKEY);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+
+ /* make sure half of keyid doesn't work */
+ assert_null(key = rnp_tests_get_key_by_id(key_store, "0000000016CD16F2"));
+ assert_null(key = rnp_tests_get_key_by_id(key_store, "67CCDD4F00000000"));
+ assert_null(key = rnp_tests_get_key_by_id(key_store, "0000000067CCDD4F"));
+
+ /* both user ids should be present */
+ assert_non_null(rnp_tests_key_search(key_store, "key-merge-uid-1"));
+ assert_non_null(rnp_tests_key_search(key_store, "key-merge-uid-2"));
+
+ delete key_store;
+}
+
+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);
+ src_close(&src);
+ return res;
+}
+
+static bool
+load_transferable_subkey(pgp_transferable_subkey_t *key, const char *fname)
+{
+ pgp_source_t src = {};
+ bool res = !init_file_src(&src, fname) && !process_pgp_subkey(src, *key, false);
+ src_close(&src);
+ return res;
+}
+
+static bool
+load_keystore(rnp_key_store_t *keystore, const char *fname)
+{
+ pgp_source_t src = {};
+ bool res = !init_file_src(&src, fname) && !rnp_key_store_pgp_read_from_src(keystore, &src);
+ src_close(&src);
+ return res;
+}
+
+static bool
+check_subkey_fp(pgp_key_t *key, pgp_key_t *subkey, size_t index)
+{
+ if (key->get_subkey_fp(index) != subkey->fp()) {
+ return false;
+ }
+ if (!subkey->has_primary_fp()) {
+ return false;
+ }
+ return key->fp() == subkey->primary_fp();
+}
+
+TEST_F(rnp_tests, test_load_merge)
+{
+ pgp_key_t * key, *skey1, *skey2;
+ pgp_transferable_key_t tkey = {};
+ pgp_transferable_subkey_t tskey = {};
+ pgp_password_provider_t provider = {};
+ provider.callback = string_copy_password_callback;
+ provider.userdata = (void *) "password";
+
+ rnp_key_store_t *key_store = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
+ std::string keyid = "9747D2A6B3A63124";
+ std::string sub1id = "AF1114A47F5F5B28";
+ std::string sub2id = "16CD16F267CCDD4F";
+
+ /* load just key packet */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-pub-just-key.pgp"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 1);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_false(key->valid());
+ assert_int_equal(key->rawpkt_count(), 1);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+
+ /* load key + user id 1 without sigs */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-pub-uid-1-no-sigs.pgp"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 1);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_false(key->valid());
+ assert_int_equal(key->uid_count(), 1);
+ assert_int_equal(key->rawpkt_count(), 2);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_null(rnp_tests_key_search(key_store, "key-merge-uid-1"));
+ assert_true(key == rnp_tests_get_key_by_id(key_store, "9747D2A6B3A63124"));
+
+ /* load key + user id 1 with sigs */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-pub-uid-1.pgp"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 1);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_true(key->valid());
+ assert_int_equal(key->uid_count(), 1);
+ assert_int_equal(key->rawpkt_count(), 3);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_true(key == rnp_tests_key_search(key_store, "key-merge-uid-1"));
+
+ /* load key + user id 2 with sigs */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-pub-uid-2.pgp"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ /* try to add it twice */
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 1);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_true(key->valid());
+ assert_int_equal(key->uid_count(), 2);
+ assert_int_equal(key->rawpkt_count(), 5);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(key->get_uid(1).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(1).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_true(key == rnp_tests_key_search(key_store, "key-merge-uid-1"));
+ assert_true(key == rnp_tests_key_search(key_store, "key-merge-uid-2"));
+
+ /* load key + subkey 1 without sigs */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-pub-subkey-1-no-sigs.pgp"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 2);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_true(key->valid());
+ assert_false(skey1->valid());
+ assert_int_equal(key->uid_count(), 2);
+ assert_int_equal(key->subkey_count(), 1);
+ assert_true(check_subkey_fp(key, skey1, 0));
+ assert_int_equal(key->rawpkt_count(), 5);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(key->get_uid(1).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(1).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey1->uid_count(), 0);
+ assert_int_equal(skey1->rawpkt_count(), 1);
+ assert_int_equal(skey1->rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+
+ /* load just subkey 1 but with signature */
+ assert_true(load_transferable_subkey(&tskey, MERGE_PATH "key-pub-no-key-subkey-1.pgp"));
+ assert_true(rnp_key_store_add_transferable_subkey(key_store, &tskey, key));
+ /* try to add it twice */
+ assert_true(rnp_key_store_add_transferable_subkey(key_store, &tskey, key));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 2);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_true(key->valid());
+ assert_true(skey1->valid());
+ assert_int_equal(key->uid_count(), 2);
+ assert_int_equal(key->subkey_count(), 1);
+ assert_true(check_subkey_fp(key, skey1, 0));
+ assert_int_equal(key->rawpkt_count(), 5);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(key->get_uid(1).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(1).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey1->uid_count(), 0);
+ assert_int_equal(skey1->rawpkt_count(), 2);
+ assert_int_equal(skey1->rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_int_equal(skey1->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+
+ /* load key + subkey 2 with signature */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-pub-subkey-2.pgp"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ /* try to add it twice */
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 3);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_non_null(skey2 = rnp_tests_get_key_by_id(key_store, sub2id));
+ assert_true(key->valid());
+ assert_true(skey1->valid());
+ assert_true(skey2->valid());
+ assert_int_equal(key->uid_count(), 2);
+ assert_int_equal(key->subkey_count(), 2);
+ assert_true(check_subkey_fp(key, skey1, 0));
+ assert_true(check_subkey_fp(key, skey2, 1));
+ assert_int_equal(key->rawpkt_count(), 5);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(key->get_uid(1).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(1).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey1->uid_count(), 0);
+ assert_int_equal(skey1->rawpkt_count(), 2);
+ assert_int_equal(skey1->rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_int_equal(skey1->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey2->uid_count(), 0);
+ assert_int_equal(skey2->rawpkt_count(), 2);
+ assert_int_equal(skey2->rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_int_equal(skey2->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+
+ /* load secret key & subkeys */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-sec-no-uid-no-sigs.pgp"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ /* try to add it twice */
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 3);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_non_null(skey2 = rnp_tests_get_key_by_id(key_store, sub2id));
+ assert_true(key->valid());
+ assert_true(skey1->valid());
+ assert_true(skey2->valid());
+ assert_int_equal(key->uid_count(), 2);
+ assert_int_equal(key->subkey_count(), 2);
+ assert_true(check_subkey_fp(key, skey1, 0));
+ assert_true(check_subkey_fp(key, skey2, 1));
+ assert_int_equal(key->rawpkt_count(), 5);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_SECRET_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(key->get_uid(1).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(1).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey1->uid_count(), 0);
+ assert_int_equal(skey1->rawpkt_count(), 2);
+ assert_int_equal(skey1->rawpkt().tag, PGP_PKT_SECRET_SUBKEY);
+ assert_int_equal(skey1->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey2->uid_count(), 0);
+ assert_int_equal(skey2->rawpkt_count(), 2);
+ assert_int_equal(skey2->rawpkt().tag, PGP_PKT_SECRET_SUBKEY);
+ assert_int_equal(skey2->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+
+ assert_true(key->unlock(provider));
+ assert_true(skey1->unlock(provider));
+ assert_true(skey2->unlock(provider));
+
+ /* load the whole public + secret key */
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-pub.asc"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_true(load_transferable_key(&tkey, MERGE_PATH "key-sec.asc"));
+ assert_true(rnp_key_store_add_transferable_key(key_store, &tkey));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 3);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_non_null(skey2 = rnp_tests_get_key_by_id(key_store, sub2id));
+ assert_true(key->valid());
+ assert_true(skey1->valid());
+ assert_true(skey2->valid());
+ assert_int_equal(key->uid_count(), 2);
+ assert_int_equal(key->subkey_count(), 2);
+ assert_true(check_subkey_fp(key, skey1, 0));
+ assert_true(check_subkey_fp(key, skey2, 1));
+ assert_int_equal(key->rawpkt_count(), 5);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_SECRET_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(key->get_uid(1).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(1).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey1->uid_count(), 0);
+ assert_int_equal(skey1->rawpkt_count(), 2);
+ assert_int_equal(skey1->rawpkt().tag, PGP_PKT_SECRET_SUBKEY);
+ assert_int_equal(skey1->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_int_equal(skey2->uid_count(), 0);
+ assert_int_equal(skey2->rawpkt_count(), 2);
+ assert_int_equal(skey2->rawpkt().tag, PGP_PKT_SECRET_SUBKEY);
+ assert_int_equal(skey2->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_true(key == rnp_tests_key_search(key_store, "key-merge-uid-1"));
+ assert_true(key == rnp_tests_key_search(key_store, "key-merge-uid-2"));
+
+ delete key_store;
+}
+
+TEST_F(rnp_tests, test_load_public_from_secret)
+{
+ rnp_key_store_t *secstore =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, MERGE_PATH "key-sec.asc", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secstore, NULL));
+ rnp_key_store_t *pubstore =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "pubring.gpg", global_ctx);
+
+ std::string keyid = "9747D2A6B3A63124";
+ std::string sub1id = "AF1114A47F5F5B28";
+ std::string sub2id = "16CD16F267CCDD4F";
+
+ pgp_key_t *key = NULL, *skey1 = NULL, *skey2 = NULL;
+ assert_non_null(key = rnp_tests_get_key_by_id(secstore, keyid));
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(secstore, sub1id));
+ assert_non_null(skey2 = rnp_tests_get_key_by_id(secstore, sub2id));
+
+ /* copy the secret key */
+ pgp_key_t keycp = pgp_key_t(*key, false);
+ assert_true(keycp.is_secret());
+ assert_int_equal(keycp.subkey_count(), 2);
+ assert_true(keycp.get_subkey_fp(0) == skey1->fp());
+ assert_true(keycp.get_subkey_fp(1) == skey2->fp());
+ assert_true(keycp.grip() == key->grip());
+ assert_int_equal(keycp.rawpkt().tag, PGP_PKT_SECRET_KEY);
+
+ /* copy the public part */
+ keycp = pgp_key_t(*key, true);
+ assert_false(keycp.is_secret());
+ assert_int_equal(keycp.subkey_count(), 2);
+ assert_true(check_subkey_fp(&keycp, skey1, 0));
+ assert_true(check_subkey_fp(&keycp, skey2, 1));
+ assert_true(keycp.grip() == key->grip());
+ assert_int_equal(keycp.rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_null(keycp.pkt().sec_data);
+ assert_int_equal(keycp.pkt().sec_len, 0);
+ assert_false(keycp.pkt().material.secret);
+ rnp_key_store_add_key(pubstore, &keycp);
+ /* subkey 1 */
+ keycp = pgp_key_t(*skey1, true);
+ assert_false(keycp.is_secret());
+ assert_int_equal(keycp.subkey_count(), 0);
+ assert_true(check_subkey_fp(key, &keycp, 0));
+ assert_true(keycp.grip() == skey1->grip());
+ assert_true(cmp_keyid(keycp.keyid(), sub1id));
+ assert_int_equal(keycp.rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_null(keycp.pkt().sec_data);
+ assert_int_equal(keycp.pkt().sec_len, 0);
+ assert_false(keycp.pkt().material.secret);
+ rnp_key_store_add_key(pubstore, &keycp);
+ /* subkey 2 */
+ keycp = pgp_key_t(*skey2, true);
+ assert_false(keycp.is_secret());
+ assert_int_equal(keycp.subkey_count(), 0);
+ assert_true(check_subkey_fp(key, &keycp, 1));
+ assert_true(keycp.grip() == skey2->grip());
+ assert_true(cmp_keyid(keycp.keyid(), sub2id));
+ assert_int_equal(keycp.rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_null(keycp.pkt().sec_data);
+ assert_int_equal(keycp.pkt().sec_len, 0);
+ assert_false(keycp.pkt().material.secret);
+ rnp_key_store_add_key(pubstore, &keycp);
+ /* save pubring */
+ assert_true(rnp_key_store_write_to_path(pubstore));
+ delete pubstore;
+ /* reload */
+ pubstore = new rnp_key_store_t(PGP_KEY_STORE_GPG, "pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubstore, NULL));
+ assert_non_null(key = rnp_tests_get_key_by_id(pubstore, keyid));
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(pubstore, sub1id));
+ assert_non_null(skey2 = rnp_tests_get_key_by_id(pubstore, sub2id));
+
+ delete pubstore;
+ delete secstore;
+}
+
+TEST_F(rnp_tests, test_key_import)
+{
+ cli_rnp_t rnp = {};
+ pgp_transferable_key_t tkey = {};
+ pgp_transferable_subkey_t *tskey = NULL;
+ pgp_transferable_userid_t *tuid = NULL;
+
+ assert_int_equal(RNP_MKDIR(".rnp", S_IRWXU), 0);
+ assert_true(setup_cli_rnp_common(&rnp, RNP_KEYSTORE_GPG, ".rnp", NULL));
+
+ /* import just the public key */
+ rnp_cfg &cfg = rnp.cfg();
+ cfg.set_str(CFG_KEYFILE, MERGE_PATH "key-pub-just-key.pgp");
+ assert_true(cli_rnp_add_key(&rnp));
+ assert_true(cli_rnp_save_keyrings(&rnp));
+ size_t keycount = 0;
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 1);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 0);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/pubring.gpg"));
+ assert_true(tkey.subkeys.empty());
+ assert_true(tkey.signatures.empty());
+ assert_true(tkey.userids.empty());
+ assert_int_equal(tkey.key.tag, PGP_PKT_PUBLIC_KEY);
+
+ /* import public key + 1 userid */
+ cfg.set_str(CFG_KEYFILE, MERGE_PATH "key-pub-uid-1-no-sigs.pgp");
+ assert_true(cli_rnp_add_key(&rnp));
+ assert_true(cli_rnp_save_keyrings(&rnp));
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 1);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 0);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/pubring.gpg"));
+ assert_true(tkey.subkeys.empty());
+ assert_true(tkey.signatures.empty());
+ assert_int_equal(tkey.userids.size(), 1);
+ assert_non_null(tuid = &tkey.userids.front());
+ assert_true(tuid->signatures.empty());
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-1", 15));
+ assert_int_equal(tkey.key.tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+
+ /* import public key + 1 userid + signature */
+ cfg.set_str(CFG_KEYFILE, MERGE_PATH "key-pub-uid-1.pgp");
+ assert_true(cli_rnp_add_key(&rnp));
+ assert_true(cli_rnp_save_keyrings(&rnp));
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 1);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 0);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/pubring.gpg"));
+ assert_true(tkey.subkeys.empty());
+ assert_true(tkey.signatures.empty());
+ assert_int_equal(tkey.userids.size(), 1);
+ assert_int_equal(tkey.key.tag, PGP_PKT_PUBLIC_KEY);
+ assert_non_null(tuid = &tkey.userids.front());
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-1", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+
+ /* import public key + 1 subkey */
+ cfg.set_str(CFG_KEYFILE, MERGE_PATH "key-pub-subkey-1.pgp");
+ assert_true(cli_rnp_add_key(&rnp));
+ assert_true(cli_rnp_save_keyrings(&rnp));
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 0);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/pubring.gpg"));
+ assert_int_equal(tkey.subkeys.size(), 1);
+ assert_true(tkey.signatures.empty());
+ assert_int_equal(tkey.userids.size(), 1);
+ assert_int_equal(tkey.key.tag, PGP_PKT_PUBLIC_KEY);
+ assert_non_null(tuid = &tkey.userids.front());
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-1", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+ assert_non_null(tskey = &tkey.subkeys.front());
+ assert_int_equal(tskey->signatures.size(), 1);
+ assert_int_equal(tskey->subkey.tag, PGP_PKT_PUBLIC_SUBKEY);
+
+ /* import secret key with 1 uid and 1 subkey */
+ cfg.set_str(CFG_KEYFILE, MERGE_PATH "key-sec-uid-1-subkey-1.pgp");
+ assert_true(cli_rnp_add_key(&rnp));
+ assert_true(cli_rnp_save_keyrings(&rnp));
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 2);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/pubring.gpg"));
+ assert_int_equal(tkey.subkeys.size(), 1);
+ assert_true(tkey.signatures.empty());
+ assert_int_equal(tkey.userids.size(), 1);
+ assert_int_equal(tkey.key.tag, PGP_PKT_PUBLIC_KEY);
+ assert_non_null(tuid = &tkey.userids.front());
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-1", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+ assert_non_null(tskey = &tkey.subkeys.front());
+ assert_int_equal(tskey->signatures.size(), 1);
+ assert_int_equal(tskey->subkey.tag, PGP_PKT_PUBLIC_SUBKEY);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/secring.gpg"));
+ assert_int_equal(tkey.subkeys.size(), 1);
+ assert_true(tkey.signatures.empty());
+ assert_int_equal(tkey.userids.size(), 1);
+ assert_int_equal(tkey.key.tag, PGP_PKT_SECRET_KEY);
+ assert_rnp_success(decrypt_secret_key(&tkey.key, "password"));
+ assert_non_null(tuid = &tkey.userids.front());
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-1", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+ assert_non_null(tskey = &tkey.subkeys.front());
+ assert_int_equal(tskey->signatures.size(), 1);
+ assert_int_equal(tskey->subkey.tag, PGP_PKT_SECRET_SUBKEY);
+ assert_rnp_success(decrypt_secret_key(&tskey->subkey, "password"));
+
+ /* import secret key with 2 uids and 2 subkeys */
+ cfg.set_str(CFG_KEYFILE, MERGE_PATH "key-sec.pgp");
+ assert_true(cli_rnp_add_key(&rnp));
+ assert_true(cli_rnp_save_keyrings(&rnp));
+ assert_rnp_success(rnp_get_public_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 3);
+ assert_rnp_success(rnp_get_secret_key_count(rnp.ffi, &keycount));
+ assert_int_equal(keycount, 3);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/pubring.gpg"));
+ assert_int_equal(tkey.subkeys.size(), 2);
+ assert_true(tkey.signatures.empty());
+ assert_int_equal(tkey.userids.size(), 2);
+ assert_int_equal(tkey.key.tag, PGP_PKT_PUBLIC_KEY);
+ assert_non_null(tuid = &tkey.userids.front());
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-1", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+ assert_non_null(tuid = &tkey.userids[1]);
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-2", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+ assert_non_null(tskey = &tkey.subkeys.front());
+ assert_int_equal(tskey->signatures.size(), 1);
+ assert_int_equal(tskey->subkey.tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_non_null(tskey = &tkey.subkeys[1]);
+ assert_int_equal(tskey->signatures.size(), 1);
+ assert_int_equal(tskey->subkey.tag, PGP_PKT_PUBLIC_SUBKEY);
+
+ assert_true(load_transferable_key(&tkey, ".rnp/secring.gpg"));
+ assert_int_equal(tkey.subkeys.size(), 2);
+ assert_true(tkey.signatures.empty());
+ assert_int_equal(tkey.userids.size(), 2);
+ assert_int_equal(tkey.key.tag, PGP_PKT_SECRET_KEY);
+ assert_rnp_success(decrypt_secret_key(&tkey.key, "password"));
+ assert_non_null(tuid = &tkey.userids.front());
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-1", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+ assert_non_null(tuid = &tkey.userids[1]);
+ assert_int_equal(tuid->signatures.size(), 1);
+ assert_false(memcmp(tuid->uid.uid, "key-merge-uid-2", 15));
+ assert_int_equal(tuid->uid.tag, PGP_PKT_USER_ID);
+ assert_non_null(tskey = &tkey.subkeys.front());
+ assert_int_equal(tskey->signatures.size(), 1);
+ assert_int_equal(tskey->subkey.tag, PGP_PKT_SECRET_SUBKEY);
+ assert_rnp_success(decrypt_secret_key(&tskey->subkey, "password"));
+ assert_non_null(tskey = &tkey.subkeys[1]);
+ assert_int_equal(tskey->signatures.size(), 1);
+ assert_int_equal(tskey->subkey.tag, PGP_PKT_SECRET_SUBKEY);
+ assert_rnp_success(decrypt_secret_key(&tskey->subkey, "password"));
+
+ rnp.end();
+}
+
+TEST_F(rnp_tests, test_load_subkey)
+{
+ rnp_key_store_t *key_store = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
+ std::string keyid = "9747D2A6B3A63124";
+ std::string sub1id = "AF1114A47F5F5B28";
+ std::string sub2id = "16CD16F267CCDD4F";
+
+ /* load first subkey with signature */
+ pgp_key_t *key = NULL, *skey1 = NULL, *skey2 = NULL;
+ assert_true(load_keystore(key_store, MERGE_PATH "key-pub-just-subkey-1.pgp"));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 1);
+ assert_non_null(skey1 = rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_false(skey1->valid());
+ assert_int_equal(skey1->rawpkt_count(), 2);
+ assert_int_equal(skey1->rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_int_equal(skey1->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_false(skey1->has_primary_fp());
+
+ /* load second subkey, without signature */
+ assert_true(load_keystore(key_store, MERGE_PATH "key-pub-just-subkey-2-no-sigs.pgp"));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 2);
+ assert_non_null(skey2 = rnp_tests_get_key_by_id(key_store, sub2id));
+ assert_false(skey2->valid());
+ assert_int_equal(skey2->rawpkt_count(), 1);
+ assert_int_equal(skey2->rawpkt().tag, PGP_PKT_PUBLIC_SUBKEY);
+ assert_false(skey2->has_primary_fp());
+ assert_false(skey1 == skey2);
+
+ /* load primary key without subkey signatures */
+ assert_true(load_keystore(key_store, MERGE_PATH "key-pub-uid-1.pgp"));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 3);
+ assert_non_null(key = rnp_tests_get_key_by_id(key_store, keyid));
+ assert_true(key->valid());
+ assert_int_equal(key->rawpkt_count(), 3);
+ assert_int_equal(key->rawpkt().tag, PGP_PKT_PUBLIC_KEY);
+ assert_int_equal(key->get_uid(0).rawpkt.tag, PGP_PKT_USER_ID);
+ assert_int_equal(key->get_sig(0).rawpkt.tag, PGP_PKT_SIGNATURE);
+ assert_true(skey1 == rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_true(skey2 == rnp_tests_get_key_by_id(key_store, sub2id));
+ assert_true(skey1->has_primary_fp());
+ assert_true(check_subkey_fp(key, skey1, 0));
+ assert_int_equal(key->subkey_count(), 1);
+ assert_true(skey1->valid());
+ assert_false(skey2->valid());
+
+ /* load second subkey with signature */
+ assert_true(load_keystore(key_store, MERGE_PATH "key-pub-just-subkey-2.pgp"));
+ assert_int_equal(rnp_key_store_get_key_count(key_store), 3);
+ assert_true(key == rnp_tests_get_key_by_id(key_store, keyid));
+ assert_true(skey1 == rnp_tests_get_key_by_id(key_store, sub1id));
+ assert_true(skey2 == rnp_tests_get_key_by_id(key_store, sub2id));
+ assert_true(skey2->has_primary_fp());
+ assert_true(check_subkey_fp(key, skey2, 1));
+ assert_int_equal(key->subkey_count(), 2);
+ assert_true(skey2->valid());
+
+ delete key_store;
+}
diff --git a/src/tests/log-switch.cpp b/src/tests/log-switch.cpp
new file mode 100644
index 0000000..1a83b83
--- /dev/null
+++ b/src/tests/log-switch.cpp
@@ -0,0 +1,146 @@
+/*
+ * Copyright (c) 2018-2019 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp_tests.h"
+#include "support.h"
+
+static const char LOGTEST_FILENAME[] = "logtest.log";
+
+TEST_F(rnp_tests, test_log_switch)
+{
+ FILE *stream = fopen(LOGTEST_FILENAME, "w");
+ assert_non_null(stream);
+ bool saved_rnp_log_switch = rnp_log_switch();
+
+ // reset _rnp_log_switch manually
+ set_rnp_log_switch(0);
+ RNP_LOG_FD(stream, "x");
+ fflush(stream);
+ assert_int_equal(0, ftell(stream)); // nothing was written
+
+ // enable _rnp_log_switch manually
+ set_rnp_log_switch(1);
+ RNP_LOG_FD(stream, "x");
+ fflush(stream);
+ assert_int_not_equal(0, ftell(stream)); // something was written
+
+ fclose(stream);
+ assert_int_equal(0, rnp_unlink(LOGTEST_FILENAME));
+
+ stream = fopen(LOGTEST_FILENAME, "w");
+ assert_non_null(stream);
+
+ const char *saved_env = getenv(RNP_LOG_CONSOLE);
+
+ // let _rnp_log_switch initialize to 0 from unset environment variable
+ assert_int_equal(0, unsetenv(RNP_LOG_CONSOLE));
+ set_rnp_log_switch(-1);
+ RNP_LOG_FD(stream, "x");
+ fflush(stream);
+ assert_int_equal(0, ftell(stream)); // nothing was written
+
+ // let _rnp_log_switch initialize to 0 from environment variable "0"
+ setenv(RNP_LOG_CONSOLE, "0", 1);
+ set_rnp_log_switch(-1);
+ RNP_LOG_FD(stream, "x");
+ fflush(stream);
+ assert_int_equal(0, ftell(stream)); // nothing was written
+
+ // let _rnp_log_switch initialize to 1 from environment variable "1"
+ setenv(RNP_LOG_CONSOLE, "1", 1);
+ set_rnp_log_switch(-1);
+ RNP_LOG_FD(stream, "x");
+ fflush(stream);
+ assert_int_not_equal(0, ftell(stream)); // something was written
+
+ // restore environment variable
+ if (saved_env) {
+ assert_int_equal(0, setenv(RNP_LOG_CONSOLE, saved_env, 1));
+ } else {
+ unsetenv(RNP_LOG_CONSOLE);
+ }
+
+ // check temporary stopping of logging
+ fclose(stream);
+ assert_int_equal(0, rnp_unlink(LOGTEST_FILENAME));
+ stream = fopen(LOGTEST_FILENAME, "w");
+
+ set_rnp_log_switch(0);
+ // make sure it will not allow logging
+ rnp_log_continue();
+ RNP_LOG_FD(stream, "y");
+ fflush(stream);
+ assert_int_equal(0, ftell(stream));
+ // make sure logging was temporary stopped
+ set_rnp_log_switch(1);
+ rnp_log_stop();
+ RNP_LOG_FD(stream, "y");
+ fflush(stream);
+ assert_int_equal(0, ftell(stream));
+ // make sure logging continued
+ rnp_log_continue();
+ RNP_LOG_FD(stream, "y");
+ fflush(stream);
+ auto sz = ftell(stream);
+ assert_int_not_equal(0, sz);
+ {
+ // check C++ object helper
+ rnp::LogStop log_stop;
+ RNP_LOG_FD(stream, "y");
+ fflush(stream);
+ assert_int_equal(sz, ftell(stream));
+ }
+ // make sure this continues logging
+ {
+ // check C++ object helper
+ rnp::LogStop log_stop(false);
+ RNP_LOG_FD(stream, "y");
+ fflush(stream);
+ assert_true(sz < ftell(stream));
+ sz = ftell(stream);
+ }
+ // combine multiple log_stop calls
+ rnp_log_stop();
+ {
+ rnp::LogStop log_stop;
+ RNP_LOG_FD(stream, "y");
+ fflush(stream);
+ assert_int_equal(sz, ftell(stream));
+ }
+ // this should continue logging
+ rnp_log_continue();
+ RNP_LOG_FD(stream, "y");
+ fflush(stream);
+ auto sz2 = ftell(stream);
+ assert_int_not_equal(sz2, sz);
+ assert_true(sz2 > sz);
+
+ // restore _rnp_log_switch
+ set_rnp_log_switch(saved_rnp_log_switch ? 1 : 0);
+
+ fclose(stream);
+ assert_int_equal(0, rnp_unlink(LOGTEST_FILENAME));
+}
diff --git a/src/tests/partial-length.cpp b/src/tests/partial-length.cpp
new file mode 100644
index 0000000..1e0b3cb
--- /dev/null
+++ b/src/tests/partial-length.cpp
@@ -0,0 +1,228 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <fstream>
+#include <vector>
+#include <string>
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include "librepgp/stream-common.h"
+#include "librepgp/stream-packet.h"
+#include "utils.h"
+#include <json.h>
+#include <vector>
+#include <string>
+
+// structure for filling input
+typedef struct {
+ uint32_t remaining;
+ uint8_t dummy;
+} dummy_reader_ctx_st;
+
+// reader of sequence of dummy bytes
+static bool
+dummy_reader(void *app_ctx, void *buf, size_t len, size_t *read)
+{
+ size_t filled = 0;
+ dummy_reader_ctx_st *ctx = NULL;
+ ctx = (dummy_reader_ctx_st *) app_ctx;
+ filled = (len > ctx->remaining) ? ctx->remaining : len;
+ if (filled > 0) {
+ memset(buf, ctx->dummy, filled);
+ ctx->remaining -= filled;
+ }
+ *read = filled;
+ return true;
+}
+
+static void
+test_partial_length_init(rnp_ffi_t *ffi, uint32_t key_flags)
+{
+ rnp_input_t input = NULL;
+ // init ffi
+ assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(*ffi, ffi_string_password_provider, (void *) "password"));
+ if (key_flags & RNP_LOAD_SAVE_SECRET_KEYS) {
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(rnp_load_keys(*ffi, "GPG", input, key_flags));
+ assert_rnp_success(rnp_input_destroy(input));
+ }
+ if (key_flags & RNP_LOAD_SAVE_PUBLIC_KEYS) {
+ assert_rnp_success(rnp_input_from_path(&input, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(rnp_load_keys(*ffi, "GPG", input, key_flags));
+ assert_rnp_success(rnp_input_destroy(input));
+ }
+}
+
+TEST_F(rnp_tests, test_partial_length_public_key)
+{
+ rnp_input_t input = NULL;
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_partial_length/pubring.gpg.partial"));
+ assert_int_equal(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS),
+ RNP_ERROR_BAD_FORMAT);
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_partial_length_signature)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ // init ffi
+ test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
+ // message having partial length signature packet
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-signed"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_failure(rnp_op_verify_execute(verify));
+ // cleanup
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_partial_length_first_packet_256)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ // init ffi
+ test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
+ // message having first partial length packet of 256 bytes
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-256"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // cleanup
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_partial_length_zero_last_chunk)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ // init ffi
+ test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
+ // message in partial packets having 0-size last chunk
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-zero-last"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // cleanup
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_partial_length_largest)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ // init ffi
+ test_partial_length_init(&ffi, RNP_LOAD_SAVE_PUBLIC_KEYS);
+ // message having largest possible partial packet
+ assert_rnp_success(
+ rnp_input_from_path(&input, "data/test_partial_length/message.txt.partial-1g"));
+ assert_rnp_success(rnp_output_to_null(&output));
+ rnp_op_verify_t verify = NULL;
+ assert_rnp_success(rnp_op_verify_create(&verify, ffi, input, output));
+ assert_rnp_success(rnp_op_verify_execute(verify));
+ // cleanup
+ assert_rnp_success(rnp_op_verify_destroy(verify));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
+
+TEST_F(rnp_tests, test_partial_length_first_packet_length)
+{
+ rnp_ffi_t ffi = NULL;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_sign_t sign = NULL;
+ rnp_key_handle_t key = NULL;
+ // uncacheable size will emulate unknown length from callback source
+ size_t uncacheable_size = PGP_INPUT_CACHE_SIZE + 1;
+ // init ffi
+ test_partial_length_init(&ffi, RNP_LOAD_SAVE_SECRET_KEYS);
+ // generate a sequence of octets with appropriate length using callback
+ dummy_reader_ctx_st reader_ctx;
+ reader_ctx.dummy = 'X';
+ reader_ctx.remaining = uncacheable_size;
+ assert_rnp_success(rnp_input_from_callback(&input, dummy_reader, NULL, &reader_ctx));
+ assert_rnp_success(rnp_output_to_memory(&output, uncacheable_size + 1024));
+ assert_rnp_success(rnp_op_sign_create(&sign, ffi, input, output));
+ assert_rnp_success(rnp_locate_key(ffi, "keyid", "7BC6709B15C23A4A", &key));
+ assert_rnp_success(rnp_op_sign_add_signature(sign, key, NULL));
+ assert_rnp_success(rnp_key_handle_destroy(key));
+ key = NULL;
+ // signing
+ assert_rnp_success(rnp_op_sign_execute(sign));
+ // read from the saved packets
+ pgp_source_t src;
+ uint8_t * mem = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &mem, &len, false));
+ assert_rnp_success(init_mem_src(&src, mem, len, false));
+ // skip first packet (one-pass signature)
+ pgp_packet_body_t body(PGP_PKT_ONE_PASS_SIG);
+ assert_rnp_success(body.read(src));
+ // checking next packet header (should be partial length literal data)
+ uint8_t flags = 0;
+ assert_true(src_read_eq(&src, &flags, 1));
+ assert_int_equal(flags, PGP_PTAG_ALWAYS_SET | PGP_PTAG_NEW_FORMAT | PGP_PKT_LITDATA);
+ // checking length
+ bool last = true; // should be reset by stream_read_partial_chunk_len()
+ assert_true(stream_read_partial_chunk_len(&src, &len, &last));
+ assert_true(len >= PGP_PARTIAL_PKT_FIRST_PART_MIN_SIZE);
+ assert_false(last);
+ // cleanup
+ src_close(&src);
+ assert_rnp_success(rnp_op_sign_destroy(sign));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
diff --git a/src/tests/pipe.cpp b/src/tests/pipe.cpp
new file mode 100644
index 0000000..6f68087
--- /dev/null
+++ b/src/tests/pipe.cpp
@@ -0,0 +1,105 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <fstream>
+#include <vector>
+#include <string>
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include "utils.h"
+#include <json.h>
+#include <vector>
+#include <string>
+
+// this reader produces errors
+static bool
+error_reader(void *app_ctx, void *buf, size_t len, size_t *read)
+{
+ return false;
+}
+
+// this writer produces errors
+static bool
+error_writer(void *app_ctx, const void *buf, size_t len)
+{
+ return false;
+}
+
+TEST_F(rnp_tests, test_pipe)
+{
+ uint8_t * buf = NULL;
+ size_t buf_size = 0;
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ const std::string msg("this is a test");
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) msg.data(), msg.size(), true));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+
+ assert_rnp_failure(rnp_output_pipe(input, NULL));
+ assert_rnp_failure(rnp_output_pipe(NULL, output));
+ assert_rnp_success(rnp_output_pipe(input, output));
+ assert_rnp_success(rnp_output_finish(output));
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &buf_size, false));
+ std::string data = std::string(buf, buf + buf_size);
+ assert_string_equal(data.c_str(), msg.c_str());
+
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+}
+
+TEST_F(rnp_tests, test_pipe_source_error)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+
+ assert_rnp_success(rnp_input_from_callback(&input, error_reader, NULL, NULL));
+ assert_rnp_success(rnp_output_to_null(&output));
+
+ assert_rnp_failure(rnp_output_pipe(input, output));
+
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+}
+
+TEST_F(rnp_tests, test_pipe_dest_error)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ const std::string msg("this is a test");
+
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (const uint8_t *) msg.data(), msg.size(), true));
+ assert_rnp_success(rnp_output_to_callback(&output, error_writer, NULL, NULL));
+
+ assert_rnp_failure(rnp_output_pipe(input, output));
+
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(output));
+}
diff --git a/src/tests/rng-randomness.cpp b/src/tests/rng-randomness.cpp
new file mode 100644
index 0000000..e3ba94f
--- /dev/null
+++ b/src/tests/rng-randomness.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include <string.h>
+#include "support.h"
+
+TEST_F(rnp_tests, test_rng_randomness)
+{
+ uint8_t samples[4096];
+
+ memset(samples, 0, sizeof(samples));
+
+ // Repetition Count Test, see NIST SP 800-90B
+ const size_t C = 6; // cutoff value
+ size_t B = 1;
+ uint8_t A;
+ global_ctx.rng.get(samples, sizeof(samples));
+ A = samples[0];
+ for (size_t i = 1; i < sizeof(samples); i++) {
+ if (samples[i] == A) {
+ B++;
+ assert_int_not_equal(B, C);
+ } else {
+ A = samples[i];
+ B = 1;
+ }
+ }
+}
diff --git a/src/tests/rnp.py b/src/tests/rnp.py
new file mode 100644
index 0000000..e34a5ae
--- /dev/null
+++ b/src/tests/rnp.py
@@ -0,0 +1,141 @@
+from cli_common import (
+ pswd_pipe,
+ run_proc
+)
+import os
+import copy
+
+class Rnp(object):
+ def __init__(self, homedir, rnp_path, rnpkey_path):
+ self.__gpg = rnp_path
+ self.__key_mgm_bin = rnpkey_path
+ self.__common_params = ['--homedir', homedir]
+ self.__password = None
+ self.__userid = None
+ self.__hash = None
+
+ @property
+ def key_mgm_bin(self):
+ return self.__key_mgm_bin
+
+ @property
+ def rnp_bin(self):
+ return self.__gpg
+
+ @property
+ def common_params(self):
+ return copy.copy(self.__common_params)
+
+ @property
+ def password(self):
+ return self.__password
+
+ @password.setter
+ def password(self, val):
+ self.__password = val
+
+ @property
+ def userid(self):
+ return self.__userid
+
+ @userid.setter
+ def userid(self, val):
+ self.__userid = val
+
+ @property
+ def hash(self):
+ return self.__hash
+
+ @hash.setter
+ def hash(self, val):
+ self.__hash = val
+
+ def copy(self):
+ return copy.deepcopy(self)
+
+ def _run(self, cmd, params, batch_input = None):
+ retcode, _, _ = run_proc(cmd, params, batch_input)
+ return retcode == 0
+
+ def list_keys(self, secret = False):
+ params = ['--list-keys', '--secret'] if secret else ['--list-keys']
+ params = params + self.common_params
+ return self._run(self.key_mgm_bin, params)
+
+ def generate_key_batch(self, batch_input):
+ pipe = pswd_pipe(self.__password)
+ params = self.common_params
+ params += ['--generate-key', '--expert']
+ params += ['--pass-fd', str(pipe)]
+ params += ['--userid', self.userid]
+ if self.hash:
+ params += ['--hash', self.hash]
+ try:
+ ret = self._run(self.__key_mgm_bin, params, batch_input)
+ finally:
+ os.close(pipe)
+ return ret
+
+ def export_key(self, output, secure = False):
+ params = self.common_params
+ params += ["--output", output]
+ params += ["--userid", self.userid]
+ params += ["--overwrite"]
+ params += ["--export-key"]
+ if secure:
+ params += ["--secret"]
+ params += [self.userid]
+ return self._run(self.key_mgm_bin, params)
+
+ def import_key(self, filename, secure = False):
+ params = self.common_params
+ params += ['--import-key', filename]
+ return self._run(self.key_mgm_bin, params)
+
+ def sign(self, output, input):
+ pipe = pswd_pipe(self.password)
+ params = self.common_params
+ params += ['--pass-fd', str(pipe)]
+ params += ['--userid', self.userid]
+ params += ['--sign', input]
+ params += ['--output', output]
+ if self.hash:
+ params += ['--hash', self.hash]
+ try:
+ ret = self._run(self.rnp_bin, params)
+ finally:
+ os.close(pipe)
+ return ret
+
+ def verify(self, input):
+ params = self.common_params
+ params += ['--verify', input]
+ if self.hash:
+ params += ['--hash', self.hash]
+ return self._run(self.rnp_bin, params)
+
+ def encrypt(self, recipient, output, input):
+ pipe = pswd_pipe(self.password)
+ params = self.common_params
+ params += ['--pass-fd', str(pipe)]
+ params += ['--recipient', recipient]
+ params += ['--encrypt', input]
+ params += ['--output', output]
+ try:
+ ret = self._run(self.rnp_bin, params)
+ finally:
+ os.close(pipe)
+ return ret
+
+ def decrypt(self, output, input):
+ pipe = pswd_pipe(self.password)
+ params = self.common_params
+ params += ['--pass-fd', str(pipe)]
+ params += ['--userid', self.userid]
+ params += ['--decrypt', input]
+ params += ['--output', output]
+ try:
+ ret = self._run(self.rnp_bin, params)
+ finally:
+ os.close(pipe)
+ return ret
diff --git a/src/tests/rnp_tests.cpp b/src/tests/rnp_tests.cpp
new file mode 100644
index 0000000..910d2d8
--- /dev/null
+++ b/src/tests/rnp_tests.cpp
@@ -0,0 +1,93 @@
+/*
+ * Copyright (c) 2017-2019 [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 "gtest/gtest.h"
+#include <crypto/rng.h>
+#include "rnp_tests.h"
+#include "support.h"
+#ifdef _WIN32
+#include <cstdlib>
+#include <crtdbg.h>
+#endif
+
+static char original_dir[PATH_MAX];
+
+/*
+ * Handler used to access DRBG.
+ */
+rnp::SecurityContext global_ctx;
+
+#ifdef _WIN32
+void
+rnpInvalidParameterHandler(const wchar_t *expression,
+ const wchar_t *function,
+ const wchar_t *file,
+ unsigned int line,
+ uintptr_t pReserved)
+{
+ wprintf(L"%s:%d %s: invalid param: %s.\n", file, line, function, expression);
+}
+#endif
+
+rnp_tests::rnp_tests() : m_dir(make_temp_dir())
+{
+#ifdef _WIN32
+ _invalid_parameter_handler handler = rnpInvalidParameterHandler;
+ _set_invalid_parameter_handler(handler);
+ _CrtSetReportMode(_CRT_ASSERT, 0);
+#endif
+ /* We use LOGNAME in a few places within the tests
+ * and it isn't always set in every environment.
+ */
+ if (!getenv("LOGNAME")) {
+ setenv("LOGNAME", "test-user", 1);
+ }
+ EXPECT_EQ(0, setenv("HOME", m_dir, 1));
+ EXPECT_EQ(0, chdir(m_dir));
+ /* fully specified path works correctly here with cp and xcopy */
+ std::string data_str = std::string(m_dir) + "/data";
+ copy_recursively(getenv("RNP_TEST_DATA"), data_str.c_str());
+}
+
+rnp_tests::~rnp_tests()
+{
+ clean_temp_dir(m_dir);
+ free(m_dir);
+}
+
+const char *
+rnp_tests::original_dir() const
+{
+ return ::original_dir;
+}
+
+int
+main(int argc, char *argv[])
+{
+ EXPECT_NE(nullptr, getcwd(original_dir, sizeof(original_dir)));
+ ::testing::InitGoogleTest(&argc, argv);
+ return RUN_ALL_TESTS();
+}
diff --git a/src/tests/rnp_tests.h b/src/tests/rnp_tests.h
new file mode 100644
index 0000000..2dd43e9
--- /dev/null
+++ b/src/tests/rnp_tests.h
@@ -0,0 +1,81 @@
+/*
+ * Copyright (c) 2017-2019 [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_TESTS_H
+#define RNP_TESTS_H
+
+#include <gtest/gtest.h>
+#include "support.h"
+
+class rnp_tests : public ::testing::Test {
+ public:
+ rnp_tests();
+ virtual ~rnp_tests();
+
+ const char *original_dir() const;
+
+ protected:
+ char *m_dir;
+};
+
+typedef struct {
+ char *original_dir;
+ char *home;
+ char *data_dir;
+ int not_fatal;
+} rnp_test_state_t;
+
+#if defined(RNP_TESTS_EXPECT)
+#define assert_true(a) EXPECT_TRUE((a))
+#define assert_false(a) EXPECT_FALSE((a))
+#define assert_string_equal(a, b) EXPECT_STREQ((a), (b))
+#define assert_int_equal(a, b) EXPECT_EQ((a), (b))
+#define assert_int_not_equal(a, b) EXPECT_NE((a), (b))
+#define assert_greater_than(a, b) EXPECT_GT((a), (b))
+#define assert_non_null(a) EXPECT_NE((a), nullptr)
+#define assert_null(a) EXPECT_EQ((a), nullptr)
+#define assert_rnp_success(a) EXPECT_EQ((a), RNP_SUCCESS)
+#define assert_rnp_failure(a) EXPECT_NE((a), RNP_SUCCESS)
+#define assert_memory_equal(a, b, sz) EXPECT_EQ(0, memcmp((a), (b), (sz)))
+#define assert_memory_not_equal(a, b, sz) EXPECT_NE(0, memcmp((a), (b), (sz)))
+#define assert_throw(a) EXPECT_ANY_THROW(a)
+#else
+#define assert_true(a) ASSERT_TRUE((a))
+#define assert_false(a) ASSERT_FALSE((a))
+#define assert_string_equal(a, b) ASSERT_STREQ((a), (b))
+#define assert_int_equal(a, b) ASSERT_EQ((a), (b))
+#define assert_int_not_equal(a, b) ASSERT_NE((a), (b))
+#define assert_greater_than(a, b) ASSERT_GT((a), (b))
+#define assert_non_null(a) ASSERT_NE((a), nullptr)
+#define assert_null(a) ASSERT_EQ((a), nullptr)
+#define assert_rnp_success(a) ASSERT_EQ((a), RNP_SUCCESS)
+#define assert_rnp_failure(a) ASSERT_NE((a), RNP_SUCCESS)
+#define assert_memory_equal(a, b, sz) ASSERT_EQ(0, memcmp((a), (b), (sz)))
+#define assert_memory_not_equal(a, b, sz) ASSERT_NE(0, memcmp((a), (b), (sz)))
+#define assert_throw(a) ASSERT_ANY_THROW(a)
+#endif
+
+#endif // RNP_TESTS_H
diff --git a/src/tests/s2k-iterations.cpp b/src/tests/s2k-iterations.cpp
new file mode 100644
index 0000000..594fe0f
--- /dev/null
+++ b/src/tests/s2k-iterations.cpp
@@ -0,0 +1,104 @@
+/*
+ * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include <rnp/rnp.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include "utils.h"
+#include "librepgp/stream-common.h"
+#include "librepgp/stream-packet.h"
+
+static void
+test_s2k_iterations_value(rnp_ffi_t ffi,
+ size_t input_iterations,
+ size_t expected_iterations,
+ bool exact_match)
+{
+ rnp_input_t input = NULL;
+ rnp_output_t output = NULL;
+ rnp_op_encrypt_t op = NULL;
+ const char * message = "RNP encryption sample message";
+ assert_rnp_success(
+ rnp_input_from_memory(&input, (uint8_t *) message, strlen(message), false));
+ assert_rnp_success(rnp_output_to_memory(&output, 1024));
+ // create encrypt operation
+ assert_rnp_success(rnp_op_encrypt_create(&op, ffi, input, output));
+ // add password and specify iterations for key derivation here
+ assert_rnp_success(rnp_op_encrypt_add_password(op, "pass1", NULL, input_iterations, NULL));
+ assert_rnp_success(rnp_op_encrypt_execute(op));
+ // testing the saved packets
+ {
+ rnp_input_t input_dump = NULL;
+ char * json = NULL;
+ uint8_t * mem = NULL;
+ size_t len = 0;
+ // get the output and dump it to JSON
+ assert_rnp_success(rnp_output_memory_get_buf(output, &mem, &len, false));
+ assert_rnp_success(rnp_input_from_memory(&input_dump, mem, len, false));
+ assert_rnp_success(rnp_dump_packets_to_json(input_dump, 0, &json));
+ assert_non_null(json);
+ json_object *jso = json_tokener_parse(json);
+ rnp_buffer_destroy(json);
+ assert_non_null(jso);
+ assert_true(json_object_is_type(jso, json_type_array));
+ // check the symmetric-key encrypted session key packet
+ json_object *pkt = json_object_array_get_idx(jso, 0);
+ assert_true(check_json_pkt_type(pkt, PGP_PKT_SK_SESSION_KEY));
+ json_object *s2k = NULL;
+ assert_true(json_object_object_get_ex(pkt, "s2k", &s2k));
+ json_object *fld = NULL;
+ assert_true(json_object_object_get_ex(s2k, "iterations", &fld));
+ assert_true(json_object_is_type(fld, json_type_int));
+ // there was already decoded value in JSON
+ size_t extracted_value = (size_t) json_object_get_int(fld);
+ if (exact_match) {
+ assert_int_equal(extracted_value, expected_iterations);
+ } else {
+ assert_true(extracted_value >= expected_iterations);
+ }
+ // cleanup
+ json_object_put(jso); // release the JSON object
+ assert_rnp_success(rnp_input_destroy(input_dump));
+ }
+ assert_rnp_success(rnp_op_encrypt_destroy(op));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_input_destroy(input));
+}
+
+TEST_F(rnp_tests, test_s2k_iterations)
+{
+ rnp_ffi_t ffi = NULL;
+ assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG"));
+ test_s2k_iterations_value(ffi, 0, 1024, false);
+ test_s2k_iterations_value(ffi, 1024, 1024, true);
+ test_s2k_iterations_value(ffi, 1088, 1088, true);
+ const size_t MAX_ITER = 0x3e00000; // 0x1F << (0xF + 6);
+ test_s2k_iterations_value(ffi, MAX_ITER - 1, MAX_ITER, true);
+ test_s2k_iterations_value(ffi, MAX_ITER, MAX_ITER, true);
+ test_s2k_iterations_value(ffi, MAX_ITER + 1, MAX_ITER, true);
+ test_s2k_iterations_value(ffi, SIZE_MAX, MAX_ITER, true);
+ assert_rnp_success(rnp_ffi_destroy(ffi));
+}
diff --git a/src/tests/streams.cpp b/src/tests/streams.cpp
new file mode 100644
index 0000000..c0bf698
--- /dev/null
+++ b/src/tests/streams.cpp
@@ -0,0 +1,1782 @@
+/*
+ * 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:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp_tests.h"
+#include "support.h"
+#include "rnp.h"
+#include "crypto/hash.hpp"
+#include "crypto/signatures.h"
+#include "pgp-key.h"
+#include <time.h>
+#include "rnp.h"
+#include <librepgp/stream-ctx.h>
+#include <librepgp/stream-packet.h>
+#include <librepgp/stream-sig.h>
+#include <librepgp/stream-key.h>
+#include <librepgp/stream-dump.h>
+#include <librepgp/stream-armor.h>
+#include <librepgp/stream-write.h>
+#include <algorithm>
+#include "time-utils.h"
+
+static bool
+stream_hash_file(rnp::Hash &hash, const char *path)
+{
+ pgp_source_t src;
+ if (init_file_src(&src, path)) {
+ return false;
+ }
+
+ bool res = false;
+ do {
+ uint8_t readbuf[1024];
+ size_t read = 0;
+ if (!src_read(&src, readbuf, sizeof(readbuf), &read)) {
+ goto finish;
+ } else if (read == 0) {
+ break;
+ }
+ hash.add(readbuf, read);
+ } while (1);
+
+ res = true;
+finish:
+ src_close(&src);
+ return res;
+}
+
+TEST_F(rnp_tests, test_stream_memory)
+{
+ const char *data = "Sample data to test memory streams";
+ size_t datalen;
+ pgp_dest_t memdst;
+ void * mown;
+ void * mcpy;
+
+ datalen = strlen(data) + 1;
+
+ /* populate memory dst and own inner data */
+ assert_rnp_success(init_mem_dest(&memdst, NULL, 0));
+ assert_rnp_success(memdst.werr);
+ dst_write(&memdst, data, datalen);
+ assert_rnp_success(memdst.werr);
+ assert_int_equal(memdst.writeb, datalen);
+
+ assert_non_null(mcpy = mem_dest_get_memory(&memdst));
+ assert_false(memcmp(mcpy, data, datalen));
+ assert_non_null(mown = mem_dest_own_memory(&memdst));
+ assert_false(memcmp(mown, data, datalen));
+ dst_close(&memdst, true);
+ /* make sure we own data after close */
+ assert_false(memcmp(mown, data, datalen));
+ free(mown);
+ /* make sure init_mem_src fails with NULL parameter */
+ pgp_source_t memsrc;
+ assert_rnp_failure(init_mem_src(&memsrc, NULL, 12, false));
+ assert_rnp_failure(init_mem_src(&memsrc, NULL, 12, true));
+}
+
+TEST_F(rnp_tests, test_stream_memory_discard)
+{
+ pgp_dest_t memdst = {};
+ char mem[32];
+ const char * hexes = "123456789ABCDEF";
+ const size_t hexes_len = 15;
+
+ /* init mem dst and write some data */
+ assert_rnp_success(init_mem_dest(&memdst, mem, sizeof(mem)));
+ dst_write(&memdst, "Hello", 5);
+ assert_int_equal(memdst.writeb, 5);
+ dst_write(&memdst, ", ", 2);
+ assert_int_equal(memdst.writeb, 7);
+ dst_write(&memdst, "world!\n", 7);
+ assert_int_equal(memdst.writeb, 14);
+ /* Now discard overflowing data and attempt to overflow it */
+ mem_dest_discard_overflow(&memdst, true);
+ dst_write(&memdst, "Hello, world!\n", 14);
+ assert_int_equal(memdst.writeb, 28);
+ assert_int_equal(memdst.werr, RNP_SUCCESS);
+ dst_write(&memdst, hexes, hexes_len);
+ /* While extra data is discarded, writeb is still incremented by hexes_len */
+ assert_int_equal(memdst.writeb, 43);
+ assert_int_equal(memdst.werr, RNP_SUCCESS);
+ assert_int_equal(memcmp(mem, "Hello, world!\nHello, world!\n1234", 32), 0);
+ dst_write(&memdst, hexes, hexes_len);
+ assert_int_equal(memdst.writeb, 58);
+ assert_int_equal(memdst.werr, RNP_SUCCESS);
+ assert_int_equal(memcmp(mem, "Hello, world!\nHello, world!\n1234", 32), 0);
+ /* Now make sure that error is generated */
+ mem_dest_discard_overflow(&memdst, false);
+ dst_write(&memdst, hexes, hexes_len);
+ assert_int_equal(memdst.writeb, 58);
+ assert_int_not_equal(memdst.werr, RNP_SUCCESS);
+
+ dst_close(&memdst, true);
+ assert_int_equal(memcmp(mem, "Hello, world!\nHello, world!\n1234", 32), 0);
+
+ /* Now do tests with dynamic memory allocation */
+ const size_t bytes = 12345;
+ assert_rnp_success(init_mem_dest(&memdst, NULL, bytes));
+ for (size_t i = 0; i < bytes / hexes_len; i++) {
+ dst_write(&memdst, hexes, hexes_len);
+ assert_int_equal(memdst.writeb, (i + 1) * hexes_len);
+ assert_int_equal(memdst.werr, RNP_SUCCESS);
+ }
+
+ mem_dest_discard_overflow(&memdst, true);
+ dst_write(&memdst, hexes, hexes_len);
+ assert_int_equal(memdst.writeb, bytes - bytes % hexes_len + hexes_len);
+ assert_int_equal(memdst.werr, RNP_SUCCESS);
+ mem_dest_discard_overflow(&memdst, false);
+ dst_write(&memdst, hexes, hexes_len);
+ assert_int_equal(memdst.writeb, bytes - bytes % hexes_len + hexes_len);
+ assert_int_not_equal(memdst.werr, RNP_SUCCESS);
+ dst_write(&memdst, hexes, hexes_len);
+ assert_int_equal(memdst.writeb, bytes - bytes % hexes_len + hexes_len);
+ assert_int_not_equal(memdst.werr, RNP_SUCCESS);
+ dst_close(&memdst, true);
+}
+
+static void
+copy_tmp_path(char *buf, size_t buflen, pgp_dest_t *dst)
+{
+ typedef struct pgp_dest_file_param_t {
+ int fd;
+ int errcode;
+ bool overwrite;
+ std::string path;
+ } pgp_dest_file_param_t;
+
+ pgp_dest_file_param_t *param = (pgp_dest_file_param_t *) dst->param;
+ strncpy(buf, param->path.c_str(), buflen);
+}
+
+TEST_F(rnp_tests, test_stream_file)
+{
+ const char * filename = "dummyfile.dat";
+ const char * dirname = "dummydir";
+ const char * file2name = "dummydir/dummyfile.dat";
+ const char * filedata = "dummy message to be stored in the file";
+ const int iterations = 10000;
+ const int filedatalen = strlen(filedata);
+ char tmpname[128] = {0};
+ uint8_t tmpbuf[1024] = {0};
+ pgp_dest_t dst = {};
+ pgp_source_t src = {};
+
+ /* try to read non-existing file */
+ assert_rnp_failure(init_file_src(&src, filename));
+ assert_rnp_failure(init_file_src(&src, dirname));
+ /* create dir */
+ assert_int_equal(RNP_MKDIR(dirname, S_IRWXU), 0);
+ /* attempt to read or create file in place of directory */
+ assert_rnp_failure(init_file_src(&src, dirname));
+ assert_rnp_failure(init_file_dest(&dst, dirname, false));
+ /* with overwrite flag it must succeed, then delete it */
+ assert_rnp_success(init_file_dest(&dst, dirname, true));
+ assert_int_equal(file_size(dirname), 0);
+ dst_close(&dst, true);
+ /* create dir back */
+ assert_int_equal(RNP_MKDIR(dirname, S_IRWXU), 0);
+
+ /* write some data to the file and the discard it */
+ assert_rnp_success(init_file_dest(&dst, filename, false));
+ dst_write(&dst, filedata, filedatalen);
+ assert_int_not_equal(file_size(filename), -1);
+ dst_close(&dst, true);
+ assert_int_equal(file_size(filename), -1);
+
+ /* write some data to the file and make sure it is written */
+ assert_rnp_success(init_file_dest(&dst, filename, false));
+ dst_write(&dst, filedata, filedatalen);
+ assert_int_not_equal(file_size(filename), -1);
+ dst_close(&dst, false);
+ assert_int_equal(file_size(filename), filedatalen);
+
+ /* attempt to create file over existing without overwrite flag */
+ assert_rnp_failure(init_file_dest(&dst, filename, false));
+ assert_int_equal(file_size(filename), filedatalen);
+
+ /* overwrite file - it should be truncated, then write bunch of bytes */
+ assert_rnp_success(init_file_dest(&dst, filename, true));
+ assert_int_equal(file_size(filename), 0);
+ for (int i = 0; i < iterations; i++) {
+ dst_write(&dst, filedata, filedatalen);
+ }
+ /* and some smaller writes */
+ for (int i = 0; i < 5 * iterations; i++) {
+ dst_write(&dst, "zzz", 3);
+ }
+ dst_close(&dst, false);
+ assert_int_equal(file_size(filename), iterations * (filedatalen + 15));
+
+ /* read file back, checking the contents */
+ assert_rnp_success(init_file_src(&src, filename));
+ for (int i = 0; i < iterations; i++) {
+ size_t read = 0;
+ assert_true(src_read(&src, tmpbuf, filedatalen, &read));
+ assert_int_equal(read, filedatalen);
+ assert_int_equal(memcmp(tmpbuf, filedata, filedatalen), 0);
+ }
+ for (int i = 0; i < 5 * iterations; i++) {
+ size_t read = 0;
+ assert_true(src_read(&src, tmpbuf, 3, &read));
+ assert_int_equal(read, 3);
+ assert_int_equal(memcmp(tmpbuf, "zzz", 3), 0);
+ }
+ src_close(&src);
+
+ /* overwrite and discard - file should be deleted */
+ assert_rnp_success(init_file_dest(&dst, filename, true));
+ assert_int_equal(file_size(filename), 0);
+ for (int i = 0; i < iterations; i++) {
+ dst_write(&dst, "hello", 6);
+ }
+ dst_close(&dst, true);
+ assert_int_equal(file_size(filename), -1);
+
+ /* create and populate file in subfolder */
+ assert_rnp_success(init_file_dest(&dst, file2name, true));
+ assert_int_equal(file_size(file2name), 0);
+ for (int i = 0; i < iterations; i++) {
+ dst_write(&dst, filedata, filedatalen);
+ }
+ dst_close(&dst, false);
+ assert_int_equal(file_size(file2name), filedatalen * iterations);
+ assert_int_equal(rnp_unlink(file2name), 0);
+
+ /* create and populate file stream, using tmp name before closing */
+ assert_rnp_success(init_tmpfile_dest(&dst, filename, false));
+ copy_tmp_path(tmpname, sizeof(tmpname), &dst);
+ assert_int_equal(file_size(tmpname), 0);
+ assert_int_equal(file_size(filename), -1);
+ for (int i = 0; i < iterations; i++) {
+ dst_write(&dst, filedata, filedatalen);
+ }
+ dst_close(&dst, false);
+ assert_int_equal(file_size(tmpname), -1);
+ assert_int_equal(file_size(filename), filedatalen * iterations);
+
+ /* create and then discard file stream, using tmp name before closing */
+ assert_rnp_success(init_tmpfile_dest(&dst, filename, true));
+ copy_tmp_path(tmpname, sizeof(tmpname), &dst);
+ assert_int_equal(file_size(tmpname), 0);
+ dst_write(&dst, filedata, filedatalen);
+ /* make sure file was not overwritten */
+ assert_int_equal(file_size(filename), filedatalen * iterations);
+ dst_close(&dst, true);
+ assert_int_equal(file_size(tmpname), -1);
+ assert_int_equal(file_size(filename), filedatalen * iterations);
+
+ /* create and then close file stream, using tmp name before closing. No overwrite. */
+ assert_rnp_success(init_tmpfile_dest(&dst, filename, false));
+ copy_tmp_path(tmpname, sizeof(tmpname), &dst);
+ assert_int_equal(file_size(tmpname), 0);
+ dst_write(&dst, filedata, filedatalen);
+ /* make sure file was not overwritten */
+ assert_int_equal(file_size(filename), filedatalen * iterations);
+ assert_rnp_failure(dst_finish(&dst));
+ dst_close(&dst, false);
+ assert_int_equal(file_size(tmpname), filedatalen);
+ assert_int_equal(file_size(filename), filedatalen * iterations);
+ assert_int_equal(rnp_unlink(tmpname), 0);
+
+ /* create and then close file stream, using tmp name before closing. Overwrite existing. */
+ assert_rnp_success(init_tmpfile_dest(&dst, filename, true));
+ copy_tmp_path(tmpname, sizeof(tmpname), &dst);
+ assert_int_equal(file_size(tmpname), 0);
+ dst_write(&dst, filedata, filedatalen);
+ /* make sure file was not overwritten yet */
+ assert_int_equal(file_size(filename), filedatalen * iterations);
+ assert_rnp_success(dst_finish(&dst));
+ dst_close(&dst, false);
+ assert_int_equal(file_size(tmpname), -1);
+ assert_int_equal(file_size(filename), filedatalen);
+
+ /* make sure we can overwrite directory */
+ assert_rnp_success(init_tmpfile_dest(&dst, dirname, true));
+ copy_tmp_path(tmpname, sizeof(tmpname), &dst);
+ assert_int_equal(file_size(tmpname), 0);
+ dst_write(&dst, filedata, filedatalen);
+ /* make sure file was not overwritten yet */
+ assert_int_equal(file_size(dirname), -1);
+ assert_rnp_success(dst_finish(&dst));
+ dst_close(&dst, false);
+ assert_int_equal(file_size(tmpname), -1);
+ assert_int_equal(file_size(dirname), filedatalen);
+
+ /* cleanup */
+ assert_int_equal(rnp_unlink(dirname), 0);
+}
+
+TEST_F(rnp_tests, test_stream_signatures)
+{
+ rnp_key_store_t *pubring;
+ rnp_key_store_t *secring;
+ pgp_signature_t sig;
+ pgp_source_t sigsrc;
+ pgp_key_t * key = NULL;
+
+ /* load keys */
+ pubring = new rnp_key_store_t(
+ PGP_KEY_STORE_GPG, "data/test_stream_signatures/pub.asc", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ /* load signature */
+ assert_rnp_success(init_file_src(&sigsrc, "data/test_stream_signatures/source.txt.sig"));
+ assert_rnp_success(sig.parse(sigsrc));
+ src_close(&sigsrc);
+ /* hash signed file */
+ pgp_hash_alg_t halg = sig.halg;
+ auto hash_orig = rnp::Hash::create(halg);
+ assert_true(stream_hash_file(*hash_orig, "data/test_stream_signatures/source.txt"));
+ /* hash forged file */
+ auto hash_forged = rnp::Hash::create(halg);
+ assert_true(
+ stream_hash_file(*hash_forged, "data/test_stream_signatures/source_forged.txt"));
+ /* find signing key */
+ assert_non_null(key = rnp_key_store_get_signer_key(pubring, &sig));
+ /* validate signature and fields */
+ auto hash = hash_orig->clone();
+ assert_int_equal(sig.creation(), 1522241943);
+ assert_rnp_success(signature_validate(sig, key->material(), *hash, global_ctx));
+ /* check forged file */
+ hash = hash_forged->clone();
+ assert_rnp_failure(signature_validate(sig, key->material(), *hash, global_ctx));
+ /* now let's create signature and sign file */
+
+ /* load secret key */
+ secring = new rnp_key_store_t(
+ PGP_KEY_STORE_GPG, "data/test_stream_signatures/sec.asc", global_ctx);
+ assert_true(rnp_key_store_load_from_path(secring, NULL));
+ assert_non_null(key = rnp_key_store_get_signer_key(secring, &sig));
+ assert_true(key->is_secret());
+ /* fill signature */
+ uint32_t create = time(NULL);
+ uint32_t expire = 123456;
+ sig = {};
+ sig.version = PGP_V4;
+ sig.halg = halg;
+ sig.palg = key->alg();
+ sig.set_type(PGP_SIG_BINARY);
+ sig.set_keyfp(key->fp());
+ sig.set_keyid(key->keyid());
+ sig.set_creation(create);
+ sig.set_expiration(expire);
+ /* make use of add_notation() to cover it */
+ try {
+ std::vector<uint8_t> value;
+ value.resize(66000);
+ sig.add_notation("dummy@example.com", value, false, true);
+ assert_true(false);
+ } catch (const rnp::rnp_exception &e) {
+ assert_int_equal(e.code(), RNP_ERROR_BAD_PARAMETERS);
+ }
+ sig.add_notation("dummy@example.com", "make codecov happy!", false);
+ sig.fill_hashed_data();
+ /* try to sign without decrypting of the secret key */
+ hash = hash_orig->clone();
+ assert_throw(signature_calculate(sig, key->material(), *hash, global_ctx));
+ /* now unlock the key and sign */
+ pgp_password_provider_t pswd_prov(rnp_password_provider_string, (void *) "password");
+ assert_true(key->unlock(pswd_prov));
+ hash = hash_orig->clone();
+ signature_calculate(sig, key->material(), *hash, global_ctx);
+ /* now verify signature */
+ hash = hash_orig->clone();
+ /* validate signature and fields */
+ assert_int_equal(sig.creation(), create);
+ assert_int_equal(sig.expiration(), expire);
+ assert_true(sig.has_subpkt(PGP_SIG_SUBPKT_ISSUER_FPR));
+ assert_true(sig.keyfp() == key->fp());
+ assert_rnp_success(signature_validate(sig, key->material(), *hash, global_ctx));
+ /* cleanup */
+ delete pubring;
+ delete secring;
+}
+
+TEST_F(rnp_tests, test_stream_signatures_revoked_key)
+{
+ pgp_signature_t sig = {};
+ pgp_source_t sigsrc = {0};
+
+ /* load signature */
+ assert_rnp_success(
+ init_file_src(&sigsrc, "data/test_stream_signatures/revoked-key-sig.gpg"));
+ assert_rnp_success(sig.parse(sigsrc));
+ src_close(&sigsrc);
+ /* check revocation */
+ assert_int_equal(sig.revocation_code(), PGP_REVOCATION_RETIRED);
+ assert_string_equal(sig.revocation_reason().c_str(), "For testing!");
+}
+
+TEST_F(rnp_tests, test_stream_key_load)
+{
+ pgp_source_t keysrc = {0};
+ pgp_dest_t keydst = {0};
+ pgp_key_sequence_t keyseq;
+ pgp_key_id_t keyid = {};
+ pgp_fingerprint_t keyfp;
+ pgp_transferable_key_t * key = NULL;
+ pgp_transferable_subkey_t *skey = NULL;
+
+ /* public keyring, read-save-read-save armored-read */
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_true(keyseq.keys.size() > 1);
+ src_close(&keysrc);
+
+ assert_rnp_success(init_file_dest(&keydst, "keyout.gpg", true));
+ assert_true(write_transferable_keys(keyseq, &keydst, false));
+ dst_close(&keydst, false);
+
+ assert_rnp_success(init_file_src(&keysrc, "keyout.gpg"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ src_close(&keysrc);
+
+ assert_rnp_success(init_file_dest(&keydst, "keyout.asc", true));
+ assert_true(write_transferable_keys(keyseq, &keydst, true));
+ dst_close(&keydst, false);
+
+ assert_rnp_success(init_file_src(&keysrc, "keyout.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ src_close(&keysrc);
+
+ /* secret keyring */
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_true(keyseq.keys.size() > 1);
+ src_close(&keysrc);
+
+ assert_rnp_success(init_file_dest(&keydst, "keyout-sec.gpg", true));
+ assert_true(write_transferable_keys(keyseq, &keydst, false));
+ dst_close(&keydst, false);
+
+ assert_rnp_success(init_file_src(&keysrc, "keyout-sec.gpg"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ src_close(&keysrc);
+
+ assert_rnp_success(init_file_dest(&keydst, "keyout-sec.asc", true));
+ assert_true(write_transferable_keys(keyseq, &keydst, true));
+ dst_close(&keydst, false);
+
+ assert_rnp_success(init_file_src(&keysrc, "keyout-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ src_close(&keysrc);
+
+ /* armored v3 public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-p.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_keyid(keyid, key->key));
+ assert_true(cmp_keyid(keyid, "7D0BC10E933404C9"));
+ assert_false(cmp_keyid(keyid, "1D0BC10E933404C9"));
+ src_close(&keysrc);
+
+ /* armored v3 secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-s.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_keyid(keyid, key->key));
+ assert_true(cmp_keyid(keyid, "7D0BC10E933404C9"));
+ src_close(&keysrc);
+
+ /* rsa/rsa public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/rsa-rsa-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "6BC04A5A3DDB35766B9A40D82FB9179118898E8B"));
+ assert_rnp_success(pgp_keyid(keyid, key->key));
+ assert_true(cmp_keyid(keyid, "2FB9179118898E8B"));
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* rsa/rsa secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/rsa-rsa-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "6BC04A5A3DDB35766B9A40D82FB9179118898E8B"));
+ assert_rnp_success(pgp_keyid(keyid, key->key));
+ assert_true(cmp_keyid(keyid, "2FB9179118898E8B"));
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* dsa/el-gamal public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/dsa-eg-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "091C44CE9CFBC3FF7EC7A64DC8A10A7D78273E10"));
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "02A5715C3537717E"));
+ src_close(&keysrc);
+
+ /* dsa/el-gamal secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/dsa-eg-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* curve 25519 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-25519-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "21FC68274AAE3B5DE39A4277CC786278981B0728"));
+ src_close(&keysrc);
+
+ /* curve 25519 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-25519-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_true(key->subkeys.empty());
+ src_close(&keysrc);
+
+ /* eddsa/x25519 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-x25519-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "4C9738A6F2BE4E1A796C9B7B941822A0FC1B30A5"));
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "C711187E594376AF"));
+ src_close(&keysrc);
+
+ /* eddsa/x25519 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-x25519-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* p-256 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "B54FDEBBB673423A5D0AA54423674F21B2441527"));
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "37E285E9E9851491"));
+ src_close(&keysrc);
+
+ /* p-256 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* p-384 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p384-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "AB25CBA042DD924C3ACC3ED3242A3AA5EA85F44A"));
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "E210E3D554A4FAD9"));
+ src_close(&keysrc);
+
+ /* p-384 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p384-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* p-521 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p521-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "4FB39FF6FA4857A4BD7EF5B42092CA8324263B6A"));
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "9853DF2F6D297442"));
+ src_close(&keysrc);
+
+ /* p-521 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p521-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* Brainpool P256 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp256-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "0633C5F72A198F51E650E4ABD0C8A3DAF9E0634A"));
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "2EDABB94D3055F76"));
+ src_close(&keysrc);
+
+ /* Brainpool P256 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp256-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* Brainpool P384 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp384-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "5B8A254C823CED98DECD10ED6CF2DCE85599ADA2"));
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "CFF1BB6F16D28191"));
+ src_close(&keysrc);
+
+ /* Brainpool P384 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp384-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* Brainpool P512 ecc public key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp512-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "4C59AB9272AA6A1F60B85BD0AA5C58D14F7B8F48"));
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "20CDAA1482BA79CE"));
+ src_close(&keysrc);
+
+ /* Brainpool P512 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-bp512-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+
+ /* secp256k1 ecc public key, not supported now */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256k1-pub.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(pgp_fingerprint(keyfp, key->key));
+ assert_true(cmp_keyfp(keyfp, "81F772B57D4EBFE7000A66233EA5BB6F9692C1A0"));
+ assert_non_null(skey = &key->subkeys.front());
+ assert_rnp_success(pgp_keyid(keyid, skey->subkey));
+ assert_true(cmp_keyid(keyid, "7635401F90D3E533"));
+ src_close(&keysrc);
+
+ /* secp256k1 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256k1-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_int_equal(keyseq.keys.size(), 1);
+ assert_non_null(key = &keyseq.keys.front());
+ assert_int_equal(key->subkeys.size(), 1);
+ assert_non_null(key->subkeys[0].subkey.hashed_data);
+ src_close(&keysrc);
+}
+
+static void
+buggy_key_load_single(const void *keydata, size_t keylen)
+{
+ pgp_source_t memsrc = {0};
+ pgp_key_sequence_t keyseq;
+ size_t partlen;
+ uint8_t * dataptr;
+
+ /* try truncated load */
+ for (partlen = 1; partlen < keylen; partlen += 15) {
+ assert_rnp_success(init_mem_src(&memsrc, keydata, partlen, false));
+ if (!process_pgp_keys(memsrc, keyseq, false)) {
+ /* it may succeed if we accidentally hit some packet boundary */
+ assert_false(keyseq.keys.empty());
+ } else {
+ assert_true(keyseq.keys.empty());
+ }
+ src_close(&memsrc);
+ }
+
+ /* try modified load */
+ dataptr = (uint8_t *) keydata;
+ for (partlen = 1; partlen < keylen; partlen++) {
+ dataptr[partlen] ^= 0xff;
+ assert_rnp_success(init_mem_src(&memsrc, keydata, keylen, false));
+ if (!process_pgp_keys(memsrc, keyseq, false)) {
+ /* it may succeed if we accidentally hit some packet boundary */
+ assert_false(keyseq.keys.empty());
+ } else {
+ assert_true(keyseq.keys.empty());
+ }
+ src_close(&memsrc);
+ dataptr[partlen] ^= 0xff;
+ }
+}
+
+/* check for memory leaks during buggy key loads */
+TEST_F(rnp_tests, test_stream_key_load_errors)
+{
+ pgp_source_t fsrc = {0};
+ pgp_source_t armorsrc = {0};
+ pgp_source_t memsrc = {0};
+
+ const char *key_files[] = {"data/keyrings/4/rsav3-p.asc",
+ "data/keyrings/4/rsav3-s.asc",
+ "data/keyrings/1/pubring.gpg",
+ "data/keyrings/1/secring.gpg",
+ "data/test_stream_key_load/dsa-eg-pub.asc",
+ "data/test_stream_key_load/dsa-eg-sec.asc",
+ "data/test_stream_key_load/ecc-25519-pub.asc",
+ "data/test_stream_key_load/ecc-25519-sec.asc",
+ "data/test_stream_key_load/ecc-x25519-pub.asc",
+ "data/test_stream_key_load/ecc-x25519-sec.asc",
+ "data/test_stream_key_load/ecc-p256-pub.asc",
+ "data/test_stream_key_load/ecc-p256-sec.asc",
+ "data/test_stream_key_load/ecc-p384-pub.asc",
+ "data/test_stream_key_load/ecc-p384-sec.asc",
+ "data/test_stream_key_load/ecc-p521-pub.asc",
+ "data/test_stream_key_load/ecc-p521-sec.asc",
+ "data/test_stream_key_load/ecc-bp256-pub.asc",
+ "data/test_stream_key_load/ecc-bp256-sec.asc",
+ "data/test_stream_key_load/ecc-bp384-pub.asc",
+ "data/test_stream_key_load/ecc-bp384-sec.asc",
+ "data/test_stream_key_load/ecc-bp512-pub.asc",
+ "data/test_stream_key_load/ecc-bp512-sec.asc",
+ "data/test_stream_key_load/ecc-p256k1-pub.asc",
+ "data/test_stream_key_load/ecc-p256k1-sec.asc"};
+
+ for (size_t i = 0; i < sizeof(key_files) / sizeof(char *); i++) {
+ assert_rnp_success(init_file_src(&fsrc, key_files[i]));
+ if (is_armored_source(&fsrc)) {
+ assert_rnp_success(init_armored_src(&armorsrc, &fsrc));
+ assert_rnp_success(read_mem_src(&memsrc, &armorsrc));
+ src_close(&armorsrc);
+ } else {
+ assert_rnp_success(read_mem_src(&memsrc, &fsrc));
+ }
+ src_close(&fsrc);
+ buggy_key_load_single(mem_src_get_memory(&memsrc), memsrc.size);
+ src_close(&memsrc);
+ }
+}
+
+TEST_F(rnp_tests, test_stream_key_decrypt)
+{
+ pgp_source_t keysrc = {0};
+ pgp_key_sequence_t keyseq;
+ pgp_transferable_key_t * key = NULL;
+ pgp_transferable_subkey_t *subkey = NULL;
+
+ /* load and decrypt secret keyring */
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ for (auto &key : keyseq.keys) {
+ assert_rnp_failure(decrypt_secret_key(&key.key, "passw0rd"));
+ assert_rnp_success(decrypt_secret_key(&key.key, "password"));
+
+ for (auto &subkey : key.subkeys) {
+ assert_rnp_failure(decrypt_secret_key(&subkey.subkey, "passw0rd"));
+ assert_rnp_success(decrypt_secret_key(&subkey.subkey, "password"));
+ }
+ }
+ src_close(&keysrc);
+
+ /* armored v3 secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-s.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_failure(decrypt_secret_key(&key->key, "passw0rd"));
+#if defined(ENABLE_IDEA)
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+#else
+ assert_rnp_failure(decrypt_secret_key(&key->key, "password"));
+#endif
+ src_close(&keysrc);
+
+ /* rsa/rsa secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/rsa-rsa-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+ assert_non_null(subkey = &key->subkeys.front());
+ assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
+ src_close(&keysrc);
+
+ /* dsa/el-gamal secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/dsa-eg-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+ assert_non_null(subkey = &key->subkeys.front());
+ assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
+ src_close(&keysrc);
+
+ /* curve 25519 eddsa ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-25519-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+ src_close(&keysrc);
+
+ /* x25519 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-x25519-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+ assert_non_null(subkey = &key->subkeys.front());
+ assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
+ src_close(&keysrc);
+
+ /* p-256 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p256-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+ assert_non_null(subkey = &key->subkeys.front());
+ assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
+ src_close(&keysrc);
+
+ /* p-384 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p384-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+ assert_non_null(subkey = &key->subkeys.front());
+ assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
+ src_close(&keysrc);
+
+ /* p-521 ecc secret key */
+ assert_rnp_success(init_file_src(&keysrc, "data/test_stream_key_load/ecc-p521-sec.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ assert_non_null(key = &keyseq.keys.front());
+ assert_rnp_success(decrypt_secret_key(&key->key, "password"));
+ assert_non_null(subkey = &key->subkeys.front());
+ assert_rnp_success(decrypt_secret_key(&subkey->subkey, "password"));
+ src_close(&keysrc);
+}
+
+TEST_F(rnp_tests, test_stream_key_encrypt)
+{
+ pgp_source_t keysrc = {0};
+ pgp_dest_t keydst = {0};
+ uint8_t keybuf[16384];
+ size_t keylen;
+ pgp_key_sequence_t keyseq;
+ pgp_key_sequence_t keyseq2;
+
+ /* load and decrypt secret keyring, then re-encrypt and reload keys */
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/secring.gpg"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ src_close(&keysrc);
+ for (auto &key : keyseq.keys) {
+ assert_rnp_success(decrypt_secret_key(&key.key, "password"));
+
+ for (auto &subkey : key.subkeys) {
+ assert_rnp_success(decrypt_secret_key(&subkey.subkey, "password"));
+ }
+
+ /* change password and encryption algorithm */
+ key.key.sec_protection.symm_alg = PGP_SA_CAMELLIA_192;
+ assert_rnp_success(encrypt_secret_key(&key.key, "passw0rd", global_ctx.rng));
+ for (auto &subkey : key.subkeys) {
+ subkey.subkey.sec_protection.symm_alg = PGP_SA_CAMELLIA_256;
+ assert_rnp_success(encrypt_secret_key(&subkey.subkey, "passw0rd", global_ctx.rng));
+ }
+ /* write changed key */
+ assert_rnp_success(init_mem_dest(&keydst, keybuf, sizeof(keybuf)));
+ assert_true(write_transferable_key(key, keydst));
+ keylen = keydst.writeb;
+ dst_close(&keydst, false);
+ /* load and decrypt changed key */
+ assert_rnp_success(init_mem_src(&keysrc, keybuf, keylen, false));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq2, false));
+ src_close(&keysrc);
+ assert_false(keyseq2.keys.empty());
+ auto &key2 = keyseq2.keys.front();
+ assert_int_equal(key2.key.sec_protection.symm_alg, PGP_SA_CAMELLIA_192);
+ assert_rnp_success(decrypt_secret_key(&key2.key, "passw0rd"));
+
+ for (auto &subkey : key2.subkeys) {
+ assert_int_equal(subkey.subkey.sec_protection.symm_alg, PGP_SA_CAMELLIA_256);
+ assert_rnp_success(decrypt_secret_key(&subkey.subkey, "passw0rd"));
+ }
+ /* write key without the password */
+ key2.key.sec_protection.s2k.usage = PGP_S2KU_NONE;
+ assert_rnp_success(encrypt_secret_key(&key2.key, NULL, global_ctx.rng));
+ for (auto &subkey : key2.subkeys) {
+ subkey.subkey.sec_protection.s2k.usage = PGP_S2KU_NONE;
+ assert_rnp_success(encrypt_secret_key(&subkey.subkey, NULL, global_ctx.rng));
+ }
+ /* write changed key */
+ assert_rnp_success(init_mem_dest(&keydst, keybuf, sizeof(keybuf)));
+ assert_true(write_transferable_key(key2, keydst));
+ keylen = keydst.writeb;
+ dst_close(&keydst, false);
+ /* load non-encrypted key */
+ assert_rnp_success(init_mem_src(&keysrc, keybuf, keylen, false));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq2, false));
+ src_close(&keysrc);
+ assert_false(keyseq2.keys.empty());
+ auto &key3 = keyseq2.keys.front();
+ assert_int_equal(key3.key.sec_protection.s2k.usage, PGP_S2KU_NONE);
+ assert_rnp_success(decrypt_secret_key(&key3.key, NULL));
+
+ for (auto &subkey : key3.subkeys) {
+ assert_int_equal(subkey.subkey.sec_protection.s2k.usage, PGP_S2KU_NONE);
+ assert_rnp_success(decrypt_secret_key(&subkey.subkey, NULL));
+ }
+ }
+}
+
+TEST_F(rnp_tests, test_stream_key_signatures)
+{
+ pgp_source_t keysrc = {0};
+ pgp_key_sequence_t keyseq;
+ pgp_key_t * pkey = NULL;
+ pgp_signature_info_t sinfo = {};
+
+ /* v3 public key */
+ auto pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/rsav3-p.asc", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/4/rsav3-p.asc"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ src_close(&keysrc);
+ assert_int_equal(keyseq.keys.size(), 1);
+ auto &key = keyseq.keys.front();
+ auto &uid = key.userids.front();
+ auto &sig = uid.signatures.front();
+ assert_non_null(pkey = rnp_key_store_get_signer_key(pubring, &sig));
+ /* check certification signature */
+ auto hash = signature_hash_certification(sig, key.key, uid.uid);
+ /* this signature uses MD5 hash after the allowed date */
+ assert_int_equal(signature_validate(sig, pkey->material(), *hash, global_ctx),
+ RNP_ERROR_SIGNATURE_INVALID);
+ /* add rule which allows MD5 */
+ rnp::SecurityRule allow_md5(
+ rnp::FeatureType::Hash, PGP_HASH_MD5, rnp::SecurityLevel::Default);
+ allow_md5.override = true;
+ global_ctx.profile.add_rule(allow_md5);
+ assert_rnp_success(signature_validate(sig, pkey->material(), *hash, global_ctx));
+ /* modify userid and check signature */
+ uid.uid.uid[2] = '?';
+ hash = signature_hash_certification(sig, key.key, uid.uid);
+ assert_rnp_failure(signature_validate(sig, pkey->material(), *hash, global_ctx));
+ /* remove MD5 rule */
+ assert_true(global_ctx.profile.del_rule(allow_md5));
+ delete pubring;
+
+ /* keyring */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_rnp_success(init_file_src(&keysrc, "data/keyrings/1/pubring.gpg"));
+ assert_rnp_success(process_pgp_keys(keysrc, keyseq, false));
+ src_close(&keysrc);
+
+ /* check key signatures */
+ for (auto &keyref : keyseq.keys) {
+ for (auto &uid : keyref.userids) {
+ /* userid certifications */
+ for (auto &sig : uid.signatures) {
+ assert_non_null(pkey = rnp_key_store_get_signer_key(pubring, &sig));
+ /* high level interface */
+ sinfo.sig = &sig;
+ pkey->validate_cert(sinfo, keyref.key, uid.uid, global_ctx);
+ assert_true(sinfo.valid);
+ /* low level check */
+ auto hash = signature_hash_certification(sig, keyref.key, uid.uid);
+ assert_rnp_success(
+ signature_validate(sig, pkey->material(), *hash, global_ctx));
+ /* modify userid and check signature */
+ uid.uid.uid[2] = '?';
+ pkey->validate_cert(sinfo, keyref.key, uid.uid, global_ctx);
+ assert_false(sinfo.valid);
+ hash = signature_hash_certification(sig, keyref.key, uid.uid);
+ assert_rnp_failure(
+ signature_validate(sig, pkey->material(), *hash, global_ctx));
+ }
+ }
+
+ /* subkey binding signatures */
+ for (auto &subkey : keyref.subkeys) {
+ auto &sig = subkey.signatures.front();
+ assert_non_null(pkey = rnp_key_store_get_signer_key(pubring, &sig));
+ /* high level interface */
+ sinfo.sig = &sig;
+ pgp_key_id_t subid;
+ assert_rnp_success(pgp_keyid(subid, subkey.subkey));
+ char ssubid[PGP_KEY_ID_SIZE * 2 + 1];
+ assert_true(rnp::hex_encode(subid.data(), subid.size(), ssubid, sizeof(ssubid)));
+ pgp_key_t *psub = rnp_tests_get_key_by_id(pubring, ssubid);
+ assert_non_null(psub);
+ pkey->validate_binding(sinfo, *psub, global_ctx);
+ assert_true(sinfo.valid);
+ /* low level check */
+ hash = signature_hash_binding(sig, keyref.key, subkey.subkey);
+ pkey->validate_sig(sinfo, *hash, global_ctx);
+ assert_true(sinfo.valid);
+ }
+ }
+
+ delete pubring;
+}
+
+static bool
+validate_key_sigs(const char *path)
+{
+ rnp_key_store_t *pubring = new rnp_key_store_t(PGP_KEY_STORE_GPG, path, global_ctx);
+ bool valid = rnp_key_store_load_from_path(pubring, NULL);
+ for (auto &key : pubring->keys) {
+ key.validate(*pubring);
+ valid = valid && key.valid();
+ }
+ delete pubring;
+ return valid;
+}
+
+TEST_F(rnp_tests, test_stream_key_signature_validate)
+{
+ /* v3 public key */
+ rnp_key_store_t *pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/4/rsav3-p.asc", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_int_equal(rnp_key_store_get_key_count(pubring), 1);
+ pgp_key_t &pkey = pubring->keys.front();
+ pkey.validate(*pubring);
+ /* MD5 signature is marked as invalid by default */
+ assert_false(pkey.valid());
+ rnp::SecurityRule allow_md5(
+ rnp::FeatureType::Hash, PGP_HASH_MD5, rnp::SecurityLevel::Default);
+ allow_md5.override = true;
+ /* Allow MD5 */
+ global_ctx.profile.add_rule(allow_md5);
+ /* we need to manually reset signature validity */
+ pkey.get_sig(0).validity.reset();
+ pkey.revalidate(*pubring);
+ assert_true(pkey.valid());
+ /* Remove MD5 and revalidate */
+ assert_true(global_ctx.profile.del_rule(allow_md5));
+ pkey.get_sig(0).validity.reset();
+ pkey.revalidate(*pubring);
+ assert_false(pkey.valid());
+ delete pubring;
+
+ /* keyring */
+ pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_true(rnp_key_store_get_key_count(pubring) > 0);
+ int i = 0;
+ for (auto &key : pubring->keys) {
+ key.validate(*pubring);
+ // subkey #2 is expired
+ if (i == 2) {
+ assert_false(key.valid());
+ } else {
+ assert_true(key.valid());
+ }
+ i++;
+ }
+ delete pubring;
+
+ /* misc key files */
+ assert_true(validate_key_sigs("data/test_stream_key_load/dsa-eg-pub.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/dsa-eg-sec.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-25519-pub.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-25519-sec.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-x25519-pub.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-x25519-sec.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p256-pub.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p256-sec.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p384-pub.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p384-sec.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p521-pub.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p521-sec.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-bp256-pub.asc") ==
+ brainpool_enabled());
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-bp256-sec.asc") ==
+ brainpool_enabled());
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-bp384-pub.asc") ==
+ brainpool_enabled());
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-bp384-sec.asc") ==
+ brainpool_enabled());
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-bp512-pub.asc") ==
+ brainpool_enabled());
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-bp512-sec.asc") ==
+ brainpool_enabled());
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p256k1-pub.asc"));
+ assert_true(validate_key_sigs("data/test_stream_key_load/ecc-p256k1-sec.asc"));
+}
+
+TEST_F(rnp_tests, test_stream_verify_no_key)
+{
+ cli_rnp_t rnp;
+ rnp_cfg cfg;
+
+ /* setup rnp structure and params */
+ cfg.set_str(CFG_KR_PUB_PATH, "");
+ cfg.set_str(CFG_KR_SEC_PATH, "");
+ cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_GPG);
+ cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_GPG);
+ assert_true(rnp.init(cfg));
+
+ rnp_cfg &rnpcfg = rnp.cfg();
+ /* setup cfg for verification */
+ rnpcfg.set_str(CFG_INFILE, "data/test_stream_verification/verify_encrypted_no_key.pgp");
+ rnpcfg.set_str(CFG_OUTFILE, "output.dat");
+ rnpcfg.set_bool(CFG_OVERWRITE, true);
+ /* setup operation context */
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(rnp.ffi, ffi_string_password_provider, (void *) "pass1"));
+ rnpcfg.set_bool(CFG_NO_OUTPUT, false);
+ if (sm2_enabled()) {
+ /* operation should success if output is not discarded, i.e. operation = decrypt */
+ assert_true(cli_rnp_process_file(&rnp));
+ assert_int_equal(file_size("output.dat"), 4);
+ } else {
+ /* operation should fail */
+ assert_false(cli_rnp_process_file(&rnp));
+ assert_int_equal(file_size("output.dat"), -1);
+ }
+ /* try second password */
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(rnp.ffi, ffi_string_password_provider, (void *) "pass2"));
+ assert_true(cli_rnp_process_file(&rnp));
+ assert_int_equal(file_size("output.dat"), 4);
+ /* decryption/verification fails without password */
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(rnp.ffi, ffi_failing_password_provider, NULL));
+ assert_false(cli_rnp_process_file(&rnp));
+ assert_int_equal(file_size("output.dat"), -1);
+ /* decryption/verification fails with wrong password */
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(rnp.ffi, ffi_string_password_provider, (void *) "pass_wrong"));
+ assert_false(cli_rnp_process_file(&rnp));
+ assert_int_equal(file_size("output.dat"), -1);
+ /* verification fails if output is discarded, i.e. operation = verify */
+ rnpcfg.set_bool(CFG_NO_OUTPUT, true);
+ assert_false(cli_rnp_process_file(&rnp));
+ assert_int_equal(file_size("output.dat"), -1);
+
+ /* cleanup */
+ rnp.end();
+}
+
+static bool
+check_dump_file_dst(const char *file, bool mpi, bool grip)
+{
+ pgp_source_t src;
+ pgp_dest_t dst;
+ rnp_dump_ctx_t ctx = {0};
+
+ ctx.dump_mpi = mpi;
+ ctx.dump_grips = grip;
+
+ if (init_file_src(&src, file)) {
+ return false;
+ }
+ if (init_mem_dest(&dst, NULL, 0)) {
+ return false;
+ }
+ if (stream_dump_packets(&ctx, &src, &dst)) {
+ return false;
+ }
+ src_close(&src);
+ dst_close(&dst, false);
+ return true;
+}
+
+static bool
+check_dump_file_json(const char *file, bool mpi, bool grip)
+{
+ pgp_source_t src;
+ rnp_dump_ctx_t ctx = {0};
+ json_object * jso = NULL;
+
+ ctx.dump_mpi = mpi;
+ ctx.dump_grips = grip;
+
+ if (init_file_src(&src, file)) {
+ return false;
+ }
+ if (stream_dump_packets_json(&ctx, &src, &jso)) {
+ return false;
+ }
+ if (!json_object_is_type(jso, json_type_array)) {
+ return false;
+ }
+ src_close(&src);
+ json_object_put(jso);
+ return true;
+}
+
+static bool
+check_dump_file(const char *file, bool mpi, bool grip)
+{
+ return check_dump_file_dst(file, mpi, grip) && check_dump_file_json(file, mpi, grip);
+}
+
+TEST_F(rnp_tests, test_y2k38)
+{
+ cli_rnp_t rnp;
+ rnp_cfg cfg;
+
+ /* setup rnp structure and params */
+ cfg.set_str(CFG_KR_PUB_PATH, "data/keyrings/6");
+ cfg.set_str(CFG_KR_SEC_PATH, "data/keyrings/6");
+ cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_GPG);
+ cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_GPG);
+ cfg.set_str(CFG_IO_RESS, "stderr.dat");
+ assert_true(rnp.init(cfg));
+
+ rnp_cfg &rnpcfg = rnp.cfg();
+ /* verify */
+ rnpcfg.set_str(CFG_INFILE, "data/test_messages/future.pgp");
+ rnpcfg.set_bool(CFG_OVERWRITE, true);
+ assert_false(cli_rnp_process_file(&rnp));
+
+ /* clean up and flush the file */
+ rnp.end();
+
+ /* check the file for presence of correct dates */
+ auto output = file_to_str("stderr.dat");
+ time_t crtime = 0xC0000000;
+ std::string correctMade = "signature made ";
+ if (rnp_y2k38_warning(crtime)) {
+ correctMade += ">=";
+ }
+ correctMade += rnp_ctime(crtime);
+ assert_true(
+ std::search(output.begin(), output.end(), correctMade.begin(), correctMade.end()) !=
+ output.end());
+ time_t validtime = rnp_timeadd(crtime, 0xD0000000);
+ std::string correctValid = "Valid until ";
+ if (rnp_y2k38_warning(validtime)) {
+ correctValid += ">=";
+ }
+ correctValid += rnp_ctime(validtime);
+ assert_true(
+ std::search(output.begin(), output.end(), correctValid.begin(), correctValid.end()) !=
+ output.end());
+ unlink("stderr.dat");
+}
+
+TEST_F(rnp_tests, test_stream_dumper_y2k38)
+{
+ pgp_source_t src;
+ pgp_dest_t dst;
+ rnp_dump_ctx_t ctx = {0};
+
+ assert_rnp_success(init_file_src(&src, "data/keyrings/6/pubring.gpg"));
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ auto written = (const uint8_t *) mem_dest_get_memory(&dst);
+ auto last = written + dst.writeb;
+ time_t timestamp = 2958774690;
+ // regenerate time for the current timezone
+ std::string correct = "creation time: 2958774690 (";
+ if (rnp_y2k38_warning(timestamp)) {
+ correct += ">=";
+ }
+ correct += rnp_ctime(timestamp).substr(0, 24);
+ correct += ')';
+ assert_true(std::search(written, last, correct.begin(), correct.end()) != last);
+ dst_close(&dst, false);
+}
+
+TEST_F(rnp_tests, test_stream_dumper)
+{
+ pgp_source_t src;
+ pgp_dest_t dst;
+ rnp_dump_ctx_t ctx = {0};
+
+ assert_true(check_dump_file("data/keyrings/1/pubring.gpg", false, false));
+ assert_true(check_dump_file("data/keyrings/1/secring.gpg", false, false));
+ assert_true(check_dump_file("data/keyrings/4/rsav3-p.asc", false, false));
+ assert_true(check_dump_file("data/keyrings/4/rsav3-p.asc", true, true));
+ assert_true(check_dump_file("data/keyrings/4/rsav3-s.asc", true, false));
+ assert_true(check_dump_file("data/test_repgp/encrypted_text.gpg", true, false));
+ assert_true(check_dump_file("data/test_repgp/signed.gpg", true, false));
+ assert_true(check_dump_file("data/test_repgp/encrypted_key.gpg", true, false));
+ assert_true(check_dump_file("data/test_stream_key_load/dsa-eg-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/dsa-eg-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-25519-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-25519-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-x25519-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-x25519-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p256-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p256-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p384-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p384-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p521-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p521-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-bp256-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-bp256-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-bp384-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-bp384-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-bp512-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-bp512-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p256k1-pub.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_key_load/ecc-p256k1-sec.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_signatures/source.txt.asc", true, true));
+ assert_true(check_dump_file("data/test_stream_signatures/source.txt.asc.asc", true, true));
+ assert_true(check_dump_file(
+ "data/test_stream_verification/verify_encrypted_no_key.pgp", true, true));
+
+ assert_rnp_success(init_file_src(&src, "data/test_stream_signatures/source.txt"));
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_failure(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, false);
+
+ assert_rnp_success(init_file_src(&src, "data/test_messages/message.txt.enc-no-mdc"));
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, false);
+
+ assert_rnp_success(init_file_src(&src, "data/test_messages/message.txt.enc-mdc"));
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, false);
+
+ assert_rnp_success(init_file_src(&src, "data/test_messages/message-32k-crlf.txt.gpg"));
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, false);
+}
+
+TEST_F(rnp_tests, test_stream_z)
+{
+ pgp_source_t src;
+ pgp_dest_t dst;
+ rnp_dump_ctx_t ctx = {0};
+
+ /* packet dumper will decompress source stream, making less code lines here */
+ ctx.dump_mpi = true;
+ ctx.dump_packets = true;
+
+ assert_rnp_success(init_file_src(&src, "data/test_stream_z/4gb.bzip2"));
+ assert_rnp_success(init_null_dest(&dst));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, true);
+
+ assert_rnp_success(init_file_src(&src, "data/test_stream_z/4gb.bzip2.cut"));
+ assert_rnp_success(init_null_dest(&dst));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, true);
+
+ assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zlib"));
+ assert_rnp_success(init_null_dest(&dst));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, true);
+
+ assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zlib.cut"));
+ assert_rnp_success(init_null_dest(&dst));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, true);
+
+ assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zip"));
+ assert_rnp_success(init_null_dest(&dst));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, true);
+
+ assert_rnp_success(init_file_src(&src, "data/test_stream_z/128mb.zip.cut"));
+ assert_rnp_success(init_null_dest(&dst));
+ assert_rnp_success(stream_dump_packets(&ctx, &src, &dst));
+ src_close(&src);
+ dst_close(&dst, true);
+}
+
+/* This test checks for GitHub issue #814.
+ */
+TEST_F(rnp_tests, test_stream_814_dearmor_double_free)
+{
+ pgp_source_t src;
+ pgp_dest_t dst;
+ const char * buf = "-----BEGIN PGP BAD HEADER-----";
+
+ assert_rnp_success(init_mem_src(&src, buf, strlen(buf), false));
+ assert_rnp_success(init_null_dest(&dst));
+ assert_rnp_failure(rnp_dearmor_source(&src, &dst));
+ src_close(&src);
+ dst_close(&dst, true);
+}
+
+TEST_F(rnp_tests, test_stream_825_dearmor_blank_line)
+{
+ rnp_key_store_t *keystore = NULL;
+ pgp_source_t src = {};
+
+ keystore = new rnp_key_store_t(PGP_KEY_STORE_GPG, "", global_ctx);
+ assert_rnp_success(
+ init_file_src(&src, "data/test_stream_armor/extra_line_before_trailer.asc"));
+ assert_true(rnp_key_store_load_from_src(keystore, &src, NULL));
+ assert_int_equal(rnp_key_store_get_key_count(keystore), 2);
+ src_close(&src);
+ delete keystore;
+}
+
+static bool
+try_dearmor(const char *str, int len)
+{
+ pgp_source_t src = {};
+ pgp_dest_t dst = {};
+ bool res = false;
+
+ if (len < 0) {
+ return false;
+ }
+ if (init_mem_src(&src, str, len, false) != RNP_SUCCESS) {
+ goto done;
+ }
+ if (init_null_dest(&dst) != RNP_SUCCESS) {
+ goto done;
+ }
+ res = rnp_dearmor_source(&src, &dst) == RNP_SUCCESS;
+done:
+ src_close(&src);
+ dst_close(&dst, true);
+ return res;
+}
+
+TEST_F(rnp_tests, test_stream_dearmor_edge_cases)
+{
+ const char *HDR = "-----BEGIN PGP PUBLIC KEY BLOCK-----";
+ const char *B1 = "mDMEWsN6MBYJKwYBBAHaRw8BAQdAAS+nkv9BdVi0JX7g6d+O201bdKhdowbielOo";
+ const char *B2 = "ugCpCfi0CWVjYy0yNTUxOYiUBBMWCAA8AhsDBQsJCAcCAyICAQYVCgkICwIEFgID";
+ const char *B3 = "AQIeAwIXgBYhBCH8aCdKrjtd45pCd8x4YniYGwcoBQJcVa/NAAoJEMx4YniYGwco";
+ const char *B4 = "lFAA/jMt3RUUb5xt63JW6HFcrYq0RrDAcYMsXAY73iZpPsEcAQDmKbH21LkwoClU";
+ const char *B5 = "9RrUJSYZnMla/pQdgOxd7/PjRCpbCg==";
+ const char *CRC = "=miZp";
+ const char *FTR = "-----END PGP PUBLIC KEY BLOCK-----";
+ const char *FTR2 = "-----END PGP WEIRD KEY BLOCK-----";
+ char b64[1024];
+ char msg[1024];
+ int b64len = 0;
+ int len = 0;
+
+ /* fill the body with normal \n line endings */
+ b64len = snprintf(b64, sizeof(b64), "%s\n%s\n%s\n%s\n%s", B1, B2, B3, B4, B5);
+ assert_true((b64len > 0) && (b64len < (int) sizeof(b64)));
+
+ /* try normal message */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+
+ /* no empty line after the headers, now accepted, see #1289 */
+ len = snprintf(msg, sizeof(msg), "%s\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+
+ /* \r\n line ending */
+ len = snprintf(msg, sizeof(msg), "%s\r\n\r\n%s\r\n%s\r\n%s\r\n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+
+ /* mixed line ending */
+ len = snprintf(msg, sizeof(msg), "%s\r\n\n%s\r\n%s\n%s\r\n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+
+ /* extra line before the footer */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n\n%s\n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+
+ /* extra spaces after the header: allowed by RFC */
+ len = snprintf(msg, sizeof(msg), "%s \t \n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+
+ /* extra spaces after the footer: allowed by RFC as well */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s \n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\t\n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\t\t \t\t \n", HDR, b64, CRC, FTR);
+ assert_true(try_dearmor(msg, len));
+
+ /* invalid footer */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR2);
+ assert_false(try_dearmor(msg, len));
+
+ /* extra spaces or chars before the footer - FAIL */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n %s\n", HDR, b64, CRC, FTR);
+ assert_false(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n\t\t %s\n", HDR, b64, CRC, FTR);
+ assert_false(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n11111%s\n", HDR, b64, CRC, FTR);
+ assert_false(try_dearmor(msg, len));
+
+ /* cut out or extended b64 padding */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%.*s\n%s\n%s\n", HDR, b64len - 1, b64, CRC, FTR);
+ assert_false(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%.*s\n%s\n%s\n", HDR, b64len - 2, b64, CRC, FTR);
+ assert_false(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s==\n%s\n%s\n", HDR, b64, CRC, FTR);
+ assert_false(try_dearmor(msg, len));
+
+ /* invalid chars in b64 data */
+ char old = b64[30];
+ b64[30] = '?';
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n%s\n", HDR, b64, CRC, FTR);
+ assert_false(try_dearmor(msg, len));
+ b64[30] = old;
+
+ /* modified/malformed crc (should be accepted now, see #1401) */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n=miZq\n%s\n", HDR, b64, FTR);
+ assert_true(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\nmiZp\n%s\n", HDR, b64, FTR);
+ assert_false(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n==miZp\n%s\n", HDR, b64, FTR);
+ assert_false(try_dearmor(msg, len));
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n=miZpp\n%s\n", HDR, b64, FTR);
+ assert_false(try_dearmor(msg, len));
+ /* missing crc */
+ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n\n%s\n", HDR, b64, FTR);
+ assert_true(try_dearmor(msg, len));
+}
+
+static void
+add_openpgp_layers(const char *msg, pgp_dest_t &pgpdst, int compr, int encr)
+{
+ pgp_source_t src = {};
+ pgp_dest_t dst = {};
+
+ assert_rnp_success(init_mem_src(&src, msg, strlen(msg), false));
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_success(rnp_wrap_src(src, dst, "message.txt", time(NULL)));
+ src_close(&src);
+ assert_rnp_success(init_mem_src(&src, mem_dest_own_memory(&dst), dst.writeb, true));
+ dst_close(&dst, false);
+
+ /* add compression layers */
+ for (int i = 0; i < compr; i++) {
+ pgp_compression_type_t alg = (pgp_compression_type_t)((i % 3) + 1);
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_success(rnp_compress_src(src, dst, alg, 9));
+ src_close(&src);
+ assert_rnp_success(init_mem_src(&src, mem_dest_own_memory(&dst), dst.writeb, true));
+ dst_close(&dst, false);
+ }
+
+ /* add encryption layers */
+ for (int i = 0; i < encr; i++) {
+ assert_rnp_success(init_mem_dest(&dst, NULL, 0));
+ assert_rnp_success(rnp_raw_encrypt_src(src, dst, "password", global_ctx));
+ src_close(&src);
+ assert_rnp_success(init_mem_src(&src, mem_dest_own_memory(&dst), dst.writeb, true));
+ dst_close(&dst, false);
+ }
+
+ assert_rnp_success(init_mem_dest(&pgpdst, NULL, 0));
+ assert_rnp_success(dst_write_src(&src, &pgpdst));
+ src_close(&src);
+}
+
+TEST_F(rnp_tests, test_stream_deep_packet_nesting)
+{
+ const char *message = "message";
+ pgp_dest_t dst = {};
+
+ /* add 30 compression layers and 2 encryption - must fail */
+ add_openpgp_layers(message, dst, 30, 2);
+#ifdef DUMP_TEST_CASE
+ /* remove ifdef if you want to write it to stdout */
+ pgp_source_t src = {};
+ assert_rnp_success(init_mem_src(&src, mem_dest_get_memory(&dst), dst.writeb, false));
+ pgp_dest_t outdst = {};
+ assert_rnp_success(init_stdout_dest(&outdst));
+ assert_rnp_success(rnp_armor_source(&src, &outdst, PGP_ARMORED_MESSAGE));
+ dst_close(&outdst, false);
+ src_close(&src);
+#endif
+ /* decrypt it via FFI for less code */
+ rnp_ffi_t ffi = NULL;
+ rnp_ffi_create(&ffi, "GPG", "GPG");
+ assert_rnp_success(
+ rnp_ffi_set_pass_provider(ffi, ffi_string_password_provider, (void *) "password"));
+
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_memory(
+ &input, (const uint8_t *) mem_dest_get_memory(&dst), dst.writeb, false));
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_failure(rnp_decrypt(ffi, input, output));
+ rnp_input_destroy(input);
+ rnp_output_destroy(output);
+ dst_close(&dst, false);
+
+ /* add 27 compression & 4 encryption layers - must succeed */
+ add_openpgp_layers("message", dst, 27, 4);
+#ifdef DUMP_TEST_CASE
+ /* remove ifdef if you want to write it to stdout */
+ assert_rnp_success(init_mem_src(&src, mem_dest_get_memory(&dst), dst.writeb, false));
+ assert_rnp_success(init_stdout_dest(&outdst));
+ assert_rnp_success(rnp_armor_source(&src, &outdst, PGP_ARMORED_MESSAGE));
+ dst_close(&outdst, false);
+ src_close(&src);
+#endif
+ /* decrypt it via FFI for less code */
+ assert_rnp_success(rnp_input_from_memory(
+ &input, (const uint8_t *) mem_dest_get_memory(&dst), dst.writeb, false));
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_decrypt(ffi, input, output));
+ rnp_input_destroy(input);
+ /* check output */
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ assert_int_equal(strlen(message), len);
+ assert_int_equal(memcmp(buf, message, len), 0);
+
+ rnp_output_destroy(output);
+ dst_close(&dst, false);
+
+ rnp_ffi_destroy(ffi);
+}
+
+static bool
+src_reader_generator(pgp_source_t *, void *buf, size_t len, size_t *read)
+{
+ *read = len;
+ for (; len; buf = ((uint8_t *) buf) + 1, len--) {
+ *(uint8_t *) buf = len & 0x7F;
+ }
+ return true;
+}
+
+TEST_F(rnp_tests, test_stream_cache)
+{
+ pgp_source_t src = {0};
+ uint8_t sample[sizeof(src.cache->buf)];
+ size_t samplesize = sizeof(sample);
+ assert_true(src_reader_generator(NULL, sample, samplesize, &samplesize));
+ assert_int_equal(sizeof(sample), samplesize);
+
+ init_src_common(&src, 0);
+ int8_t *buf = (int8_t *) src.cache->buf;
+ src.read = src_reader_generator;
+ size_t len = sizeof(src.cache->buf);
+
+ // empty cache, pos=0
+ memset(src.cache->buf, 0xFF, len);
+ src.cache->pos = 0;
+ src.cache->len = 0;
+ assert_true(src_peek_eq(&src, NULL, len));
+ assert_false(memcmp(buf, sample, samplesize));
+
+ // empty cache, pos is somewhere in the middle
+ memset(src.cache->buf, 0xFF, len);
+ src.cache->pos = 100;
+ src.cache->len = 100;
+ assert_true(src_peek_eq(&src, NULL, len));
+ assert_false(memcmp(buf, sample, samplesize));
+
+ // empty cache, pos=max
+ memset(src.cache->buf, 0xFF, len);
+ src.cache->pos = len;
+ src.cache->len = len;
+ assert_true(src_peek_eq(&src, NULL, len));
+ assert_false(memcmp(buf, sample, samplesize));
+
+ // cache has some data in the middle
+ src.cache->pos = 128; // sample boundary
+ src.cache->len = 300;
+ memset(src.cache->buf, 0xFF, src.cache->pos);
+ memset(src.cache->buf + src.cache->len, 0xFF, len - src.cache->len);
+ assert_true(src_peek_eq(&src, NULL, len));
+ assert_false(memcmp(buf, sample, samplesize));
+
+ // cache has some data starting from pos until the end
+ src.cache->pos = 128; // sample boundary
+ src.cache->len = len;
+ memset(src.cache->buf, 0xFF, src.cache->pos);
+ assert_true(src_peek_eq(&src, NULL, len));
+ assert_false(memcmp(buf, sample, samplesize));
+
+ // cache is almost full
+ src.cache->pos = 0;
+ src.cache->len = len - 1;
+ src.cache->buf[len - 1] = 0xFF;
+ assert_true(src_peek_eq(&src, NULL, len));
+ assert_false(memcmp(buf, sample, samplesize));
+
+ // cache is full
+ src.cache->pos = 0;
+ src.cache->len = len;
+ memset(src.cache->buf, 0xFF, src.cache->pos);
+ memset(src.cache->buf + src.cache->len, 0xFF, len - src.cache->len);
+ assert_true(src_peek_eq(&src, NULL, len));
+ assert_false(memcmp(buf, sample, samplesize));
+
+ src_close(&src);
+}
diff --git a/src/tests/support.cpp b/src/tests/support.cpp
new file mode 100644
index 0000000..c94a901
--- /dev/null
+++ b/src/tests/support.cpp
@@ -0,0 +1,1216 @@
+/*
+ * Copyright (c) 2017-2019 [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 "support.h"
+#include "rnp_tests.h"
+#include "str-utils.h"
+#include <librepgp/stream-ctx.h>
+#include "pgp-key.h"
+#include "librepgp/stream-armor.h"
+#include "ffi-priv-types.h"
+
+#ifdef _MSC_VER
+#include "uniwin.h"
+#include <shlwapi.h>
+#else
+#include <sys/types.h>
+#include <sys/param.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <stdio.h>
+
+#include <crypto.h>
+#include <pgp-key.h>
+#include <fstream>
+#include <vector>
+#include <algorithm>
+
+#ifndef WINSHELLAPI
+#include <ftw.h>
+#endif
+
+#ifdef _WIN32
+int
+setenv(const char *name, const char *value, int overwrite)
+{
+ if (getenv(name) && !overwrite) {
+ return 0;
+ }
+ char varbuf[512] = {0};
+ snprintf(varbuf, sizeof(varbuf) - 1, "%s=%s", name, value);
+ return _putenv(varbuf);
+}
+
+int
+unsetenv(const char *name)
+{
+ char varbuf[512] = {0};
+ snprintf(varbuf, sizeof(varbuf) - 1, "%s=", name);
+ return _putenv(varbuf);
+}
+#endif
+
+std::string
+file_to_str(const std::string &path)
+{
+ // TODO: wstring path _WIN32
+ std::ifstream infile(path);
+ return std::string(std::istreambuf_iterator<char>(infile),
+ std::istreambuf_iterator<char>());
+}
+
+std::vector<uint8_t>
+file_to_vec(const std::string &path)
+{
+ // TODO: wstring path _WIN32
+ std::ifstream stream(path, std::ios::in | std::ios::binary);
+ return std::vector<uint8_t>((std::istreambuf_iterator<char>(stream)),
+ std::istreambuf_iterator<char>());
+}
+
+void
+str_to_file(const std::string &path, const char *str)
+{
+ std::ofstream stream(path, std::ios::out | std::ios::binary);
+ stream.write(str, strlen(str));
+}
+
+off_t
+file_size(const char *path)
+{
+ struct stat path_stat;
+ if (rnp_stat(path, &path_stat) != -1) {
+ if (S_ISDIR(path_stat.st_mode)) {
+ return -1;
+ }
+ return path_stat.st_size;
+ }
+ return -1;
+}
+
+/* Concatenate multiple strings into a full path.
+ * A directory separator is added between components.
+ * Must be called in between va_start and va_end.
+ * Final argument of calling function must be NULL.
+ */
+void
+vpaths_concat(char *buffer, size_t buffer_size, const char *first, va_list ap)
+{
+ size_t length = strlen(first);
+ const char *s;
+
+ assert_true(length < buffer_size);
+
+ memset(buffer, 0, buffer_size);
+
+ strncpy(buffer, first, buffer_size - 1);
+ while ((s = va_arg(ap, const char *))) {
+ length += strlen(s) + 1;
+ assert_true(length < buffer_size);
+ strncat(buffer, "/", buffer_size - 1);
+ strncat(buffer, s, buffer_size - 1);
+ }
+}
+
+/* Concatenate multiple strings into a full path.
+ * Final argument must be NULL.
+ */
+char *
+paths_concat(char *buffer, size_t buffer_length, const char *first, ...)
+{
+ va_list ap;
+
+ va_start(ap, first);
+ vpaths_concat(buffer, buffer_length, first, ap);
+ va_end(ap);
+ return buffer;
+}
+
+/* Concatenate multiple strings into a full path and
+ * check that the file exists.
+ * Final argument must be NULL.
+ */
+int
+path_rnp_file_exists(const char *first, ...)
+{
+ va_list ap;
+ char buffer[512] = {0};
+
+ va_start(ap, first);
+ vpaths_concat(buffer, sizeof(buffer), first, ap);
+ va_end(ap);
+ return rnp_file_exists(buffer);
+}
+
+/* Concatenate multiple strings into a full path and
+ * create the directory.
+ * Final argument must be NULL.
+ */
+void
+path_mkdir(mode_t mode, const char *first, ...)
+{
+ va_list ap;
+ char buffer[512];
+
+ va_start(ap, first);
+ vpaths_concat(buffer, sizeof(buffer), first, ap);
+ va_end(ap);
+
+ assert_int_equal(0, RNP_MKDIR(buffer, mode));
+}
+
+static int
+remove_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf)
+{
+ int ret = remove(fpath);
+ if (ret)
+ perror(fpath);
+
+ return ret;
+}
+
+static const char *
+get_tmp()
+{
+ const char *tmp = getenv("TEMP");
+ return tmp ? tmp : "/tmp";
+}
+
+static bool
+is_tmp_path(const char *path)
+{
+ char *rlpath = realpath(path, NULL);
+ if (!rlpath) {
+ rlpath = strdup(path);
+ }
+ const char *tmp = get_tmp();
+ char * rltmp = realpath(tmp, NULL);
+ if (!rltmp) {
+ rltmp = strdup(tmp);
+ }
+ bool res = rlpath && rltmp && !strncmp(rlpath, rltmp, strlen(rltmp));
+ free(rlpath);
+ free(rltmp);
+ return res;
+}
+
+/* Recursively remove a directory.
+ * The path must be located in /tmp, for safety.
+ */
+void
+delete_recursively(const char *path)
+{
+ bool relative =
+#ifdef _MSC_VER
+ PathIsRelativeA(path);
+#else
+ *path != '/';
+#endif
+ std::string fullpath = path;
+ if (relative) {
+ char *cwd = getcwd(NULL, 0);
+ fullpath = rnp::path::append(cwd, fullpath);
+ free(cwd);
+ }
+ /* sanity check, we should only be purging things from /tmp/ */
+ assert_true(is_tmp_path(fullpath.c_str()));
+
+#ifdef WINSHELLAPI
+ SHFILEOPSTRUCTA fileOp = {};
+ fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI;
+ assert_true(fullpath.size() < MAX_PATH);
+ char newFrom[MAX_PATH + 1];
+ strcpy_s(newFrom, fullpath.c_str());
+ newFrom[fullpath.size() + 1] = NULL; // two NULLs are required
+ fileOp.pFrom = newFrom;
+ fileOp.pTo = NULL;
+ fileOp.wFunc = FO_DELETE;
+ fileOp.hNameMappings = NULL;
+ fileOp.hwnd = NULL;
+ fileOp.lpszProgressTitle = NULL;
+ SHFileOperationA(&fileOp);
+#else
+ nftw(path, remove_cb, 64, FTW_DEPTH | FTW_PHYS);
+#endif
+}
+
+void
+copy_recursively(const char *src, const char *dst)
+{
+ assert_true(src != nullptr);
+ /* sanity check, we should only be copying things to /tmp/ */
+ assert_true(is_tmp_path(dst));
+
+#ifdef WINSHELLAPI
+ SHFILEOPSTRUCTA fileOp = {};
+ fileOp.fFlags = FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR;
+ fileOp.pFrom = src;
+ fileOp.pTo = dst;
+ assert_true(strlen(src) < MAX_PATH);
+ char newFrom[MAX_PATH + 1];
+ strcpy_s(newFrom, src);
+ newFrom[strlen(src) + 1] = NULL; // two NULLs are required
+ fileOp.pFrom = newFrom;
+ assert_true(strlen(dst) < MAX_PATH);
+ char newTo[MAX_PATH + 1];
+ strcpy_s(newTo, dst);
+ newTo[strlen(dst) + 1] = NULL; // two NULLs are required
+ fileOp.wFunc = FO_COPY;
+ fileOp.hNameMappings = NULL;
+ fileOp.hwnd = NULL;
+ fileOp.lpszProgressTitle = NULL;
+ assert_int_equal(0, SHFileOperationA(&fileOp));
+#else
+ // TODO: maybe use fts or something less hacky
+ char buf[2048];
+#ifndef _WIN32
+ snprintf(buf, sizeof(buf), "cp -a '%s' '%s'", src, dst);
+#else
+ snprintf(buf, sizeof(buf), "xcopy \"%s\" \"%s\" /I /Q /E /Y", src, dst);
+#endif // _WIN32
+ assert_int_equal(0, system(buf));
+#endif // WINSHELLAPI
+}
+
+/* Creates and returns a temporary directory path.
+ * Caller must free the string.
+ */
+#if defined(HAVE_MKDTEMP)
+char *
+make_temp_dir()
+{
+ char rltmp[PATH_MAX] = {0};
+ if (!realpath(get_tmp(), rltmp)) {
+ printf("Fatal: realpath on tmp folder failed. Error %d.\n", errno);
+ return NULL;
+ }
+
+ const char *tmplate = "/rnp-gtest-XXXXXX";
+ char * buffer = (char *) calloc(1, strlen(rltmp) + strlen(tmplate) + 1);
+ if (buffer == NULL) {
+ return NULL;
+ }
+ memcpy(buffer, rltmp, strlen(rltmp));
+ memcpy(buffer + strlen(rltmp), tmplate, strlen(tmplate));
+ buffer[strlen(rltmp) + strlen(tmplate)] = '\0';
+ char *res = mkdtemp(buffer);
+ if (!res) {
+ free(buffer);
+ }
+ return res;
+}
+#elif defined(HAVE__TEMPNAM)
+char *
+make_temp_dir()
+{
+ const int MAX_ATTEMPTS = 10;
+ for (int i = 0; i < MAX_ATTEMPTS; i++) {
+ char *dir = _tempnam(NULL, "rnp-gtest-");
+ if (!dir) {
+ fprintf(stderr, "_tempnam failed to generate temporary path");
+ continue;
+ }
+ if (RNP_MKDIR(dir, S_IRWXU)) {
+ fprintf(stderr, "Failed to create temporary directory");
+ free(dir);
+ continue;
+ }
+ return dir;
+ }
+ fprintf(stderr, "Failed to make temporary directory, aborting");
+ return NULL;
+}
+#else
+#error Unsupported platform
+#endif
+
+void
+clean_temp_dir(const char *path)
+{
+ if (!getenv("RNP_KEEP_TEMP")) {
+ delete_recursively(path);
+ }
+}
+
+bool
+bin_eq_hex(const uint8_t *data, size_t len, const char *val)
+{
+ size_t stlen = strlen(val);
+ if (stlen != len * 2) {
+ return false;
+ }
+
+ std::vector<uint8_t> dec(len);
+ rnp::hex_decode(val, dec.data(), len);
+ return !memcmp(data, dec.data(), len);
+}
+
+bool
+hex2mpi(pgp_mpi_t *val, const char *hex)
+{
+ const size_t hex_len = strlen(hex);
+ size_t buf_len = hex_len / 2;
+ bool ok;
+
+ uint8_t *buf = NULL;
+
+ buf = (uint8_t *) malloc(buf_len);
+
+ if (buf == NULL) {
+ return false;
+ }
+
+ rnp::hex_decode(hex, buf, buf_len);
+
+ ok = mem2mpi(val, buf, buf_len);
+ free(buf);
+ return ok;
+}
+
+bool
+cmp_keyid(const pgp_key_id_t &id, const std::string &val)
+{
+ return bin_eq_hex(id.data(), id.size(), val.c_str());
+}
+
+bool
+cmp_keyfp(const pgp_fingerprint_t &fp, const std::string &val)
+{
+ return bin_eq_hex(fp.fingerprint, fp.length, val.c_str());
+}
+
+void
+test_ffi_init(rnp_ffi_t *ffi)
+{
+ // 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", "data/keyrings/1/secring.gpg"));
+}
+
+bool
+mpi_empty(const pgp_mpi_t &val)
+{
+ pgp_mpi_t zero{};
+ return (val.len == 0) && !memcmp(val.mpi, zero.mpi, PGP_MPINT_SIZE);
+}
+
+bool
+write_pass_to_pipe(int fd, size_t count)
+{
+ const char *const password = "passwordforkeygeneration\n";
+ for (size_t i = 0; i < count; i++) {
+ const char *p = password;
+ ssize_t remaining = strlen(p);
+
+ do {
+ ssize_t written = write(fd, p, remaining);
+ if (written <= 0) {
+ perror("write");
+ return false;
+ }
+ p += written;
+ remaining -= written;
+ } while (remaining);
+ }
+ return true;
+}
+
+bool
+setupPasswordfd(int *pipefd)
+{
+ bool ok = false;
+
+ if (pipe(pipefd) == -1) {
+ perror("pipe");
+ goto end;
+ }
+ // write it twice for normal keygen (primary+sub)
+ if (!write_pass_to_pipe(pipefd[1], 2)) {
+ close(pipefd[1]);
+ goto end;
+ }
+ ok = true;
+
+end:
+ close(pipefd[1]);
+ return ok;
+}
+
+static bool
+setup_rnp_cfg(rnp_cfg &cfg, const char *ks_format, const char *homedir, int *pipefd)
+{
+ bool res;
+ char pubpath[MAXPATHLEN];
+ char secpath[MAXPATHLEN];
+ char homepath[MAXPATHLEN];
+
+ /* set password fd if any */
+ if (pipefd) {
+ if (!(res = setupPasswordfd(pipefd))) {
+ return res;
+ }
+ cfg.set_int(CFG_PASSFD, pipefd[0]);
+ // pipefd[0] will be closed via passfp
+ pipefd[0] = -1;
+ }
+ /* setup keyring paths */
+ if (homedir == NULL) {
+ /* if we use default homedir then we append '.rnp' and create directory as well */
+ homedir = getenv("HOME");
+ paths_concat(homepath, sizeof(homepath), homedir, ".rnp", NULL);
+ if (!rnp_dir_exists(homepath)) {
+ path_mkdir(0700, homepath, NULL);
+ }
+ homedir = homepath;
+ }
+
+ if (homedir == NULL) {
+ return false;
+ }
+
+ cfg.set_str(CFG_KR_PUB_FORMAT, ks_format);
+ cfg.set_str(CFG_KR_SEC_FORMAT, ks_format);
+
+ if (strcmp(ks_format, RNP_KEYSTORE_GPG) == 0) {
+ paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_GPG, NULL);
+ paths_concat(secpath, MAXPATHLEN, homedir, SECRING_GPG, NULL);
+ } else if (strcmp(ks_format, RNP_KEYSTORE_KBX) == 0) {
+ paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_KBX, NULL);
+ paths_concat(secpath, MAXPATHLEN, homedir, SECRING_KBX, NULL);
+ } else if (strcmp(ks_format, RNP_KEYSTORE_G10) == 0) {
+ paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_G10, NULL);
+ paths_concat(secpath, MAXPATHLEN, homedir, SECRING_G10, NULL);
+ } else if (strcmp(ks_format, RNP_KEYSTORE_GPG21) == 0) {
+ paths_concat(pubpath, MAXPATHLEN, homedir, PUBRING_KBX, NULL);
+ paths_concat(secpath, MAXPATHLEN, homedir, SECRING_G10, NULL);
+ cfg.set_str(CFG_KR_PUB_FORMAT, RNP_KEYSTORE_KBX);
+ cfg.set_str(CFG_KR_SEC_FORMAT, RNP_KEYSTORE_G10);
+ } else {
+ return false;
+ }
+
+ cfg.set_str(CFG_KR_PUB_PATH, (char *) pubpath);
+ cfg.set_str(CFG_KR_SEC_PATH, (char *) secpath);
+ return true;
+}
+
+bool
+setup_cli_rnp_common(cli_rnp_t *rnp, const char *ks_format, const char *homedir, int *pipefd)
+{
+ rnp_cfg cfg;
+ if (!setup_rnp_cfg(cfg, ks_format, homedir, pipefd)) {
+ return false;
+ }
+
+ /*initialize the basic RNP structure. */
+ return rnp->init(cfg);
+}
+
+void
+cli_set_default_rsa_key_desc(rnp_cfg &cfg, const char *hashalg)
+{
+ cfg.set_int(CFG_NUMBITS, 1024);
+ cfg.set_str(CFG_HASH, hashalg);
+ cfg.set_int(CFG_S2K_ITER, 1);
+ cli_rnp_set_generate_params(cfg);
+}
+
+// this is a password callback that will always fail
+bool
+failing_password_callback(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata)
+{
+ return false;
+}
+
+bool
+ffi_failing_password_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ return false;
+}
+
+bool
+ffi_asserting_password_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ EXPECT_TRUE(false);
+ return false;
+}
+
+bool
+ffi_string_password_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ size_t pass_len = strlen((const char *) app_ctx);
+ if (pass_len >= buf_len) {
+ return false;
+ }
+ memcpy(buf, app_ctx, pass_len + 1);
+ return true;
+}
+
+// this is a password callback that should never be called
+bool
+asserting_password_callback(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata)
+{
+ EXPECT_TRUE(false);
+ return false;
+}
+
+// this is a password callback that just copies the string in userdata to
+// the password buffer
+bool
+string_copy_password_callback(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata)
+{
+ const char *str = (const char *) userdata;
+ strncpy(password, str, password_size - 1);
+ return true;
+}
+
+void
+unused_getkeycb(rnp_ffi_t ffi,
+ void * app_ctx,
+ const char *identifier_type,
+ const char *identifier,
+ bool secret)
+{
+ EXPECT_TRUE(false);
+}
+
+bool
+unused_getpasscb(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len)
+{
+ EXPECT_TRUE(false);
+ return false;
+}
+
+bool
+starts_with(const std::string &data, const std::string &match)
+{
+ return data.find(match) == 0;
+}
+
+bool
+ends_with(const std::string &data, const std::string &match)
+{
+ return data.size() >= match.size() &&
+ data.substr(data.size() - match.size(), match.size()) == match;
+}
+
+std::string
+fmt(const char *format, ...)
+{
+ int size;
+ va_list ap;
+
+ va_start(ap, format);
+ size = vsnprintf(NULL, 0, format, ap);
+ va_end(ap);
+
+ // +1 for terminating null
+ std::string buf(size + 1, '\0');
+
+ va_start(ap, format);
+ size = vsnprintf(&buf[0], buf.size(), format, ap);
+ va_end(ap);
+
+ // drop terminating null
+ buf.resize(size);
+ return buf;
+}
+
+std::string
+strip_eol(const std::string &str)
+{
+ size_t endpos = str.find_last_not_of("\r\n");
+ if (endpos != std::string::npos) {
+ return str.substr(0, endpos + 1);
+ }
+ return str;
+}
+
+std::string
+lowercase(const std::string &str)
+{
+ std::string res = str;
+ std::transform(
+ res.begin(), res.end(), res.begin(), [](unsigned char ch) { return std::tolower(ch); });
+ return res;
+}
+
+static bool
+jso_get_field(json_object *obj, json_object **fld, const std::string &name)
+{
+ if (!obj || !json_object_is_type(obj, json_type_object)) {
+ return false;
+ }
+ return json_object_object_get_ex(obj, name.c_str(), fld);
+}
+
+bool
+check_json_field_str(json_object *obj, const std::string &field, const std::string &value)
+{
+ json_object *fld = NULL;
+ if (!jso_get_field(obj, &fld, field)) {
+ return false;
+ }
+ if (!json_object_is_type(fld, json_type_string)) {
+ return false;
+ }
+ const char *jsoval = json_object_get_string(fld);
+ return jsoval && (value == jsoval);
+}
+
+bool
+check_json_field_int(json_object *obj, const std::string &field, int value)
+{
+ json_object *fld = NULL;
+ if (!jso_get_field(obj, &fld, field)) {
+ return false;
+ }
+ if (!json_object_is_type(fld, json_type_int)) {
+ return false;
+ }
+ return json_object_get_int(fld) == value;
+}
+
+bool
+check_json_field_bool(json_object *obj, const std::string &field, bool value)
+{
+ json_object *fld = NULL;
+ if (!jso_get_field(obj, &fld, field)) {
+ return false;
+ }
+ if (!json_object_is_type(fld, json_type_boolean)) {
+ return false;
+ }
+ return json_object_get_boolean(fld) == value;
+}
+
+bool
+check_json_pkt_type(json_object *pkt, int tag)
+{
+ if (!pkt || !json_object_is_type(pkt, json_type_object)) {
+ return false;
+ }
+ json_object *hdr = NULL;
+ if (!json_object_object_get_ex(pkt, "header", &hdr)) {
+ return false;
+ }
+ if (!json_object_is_type(hdr, json_type_object)) {
+ return false;
+ }
+ return check_json_field_int(hdr, "tag", tag);
+}
+
+pgp_key_t *
+rnp_tests_get_key_by_id(rnp_key_store_t *keyring, const std::string &keyid, pgp_key_t *after)
+{
+ if (!keyring || keyid.empty() || !rnp::is_hex(keyid)) {
+ return NULL;
+ }
+ pgp_key_id_t keyid_bin = {};
+ size_t binlen = rnp::hex_decode(keyid.c_str(), keyid_bin.data(), keyid_bin.size());
+ if (binlen > PGP_KEY_ID_SIZE) {
+ return NULL;
+ }
+ pgp_key_search_t search(PGP_KEY_SEARCH_KEYID);
+ search.by.keyid = keyid_bin;
+ return rnp_key_store_search(keyring, &search, after);
+}
+
+pgp_key_t *
+rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const std::string &grip)
+{
+ if (!keyring || grip.empty() || !rnp::is_hex(grip)) {
+ return NULL;
+ }
+ pgp_key_grip_t grip_bin = {};
+ size_t binlen = rnp::hex_decode(grip.c_str(), grip_bin.data(), grip_bin.size());
+ if (binlen > PGP_KEY_GRIP_SIZE) {
+ return NULL;
+ }
+ return rnp_tests_get_key_by_grip(keyring, grip_bin);
+}
+
+pgp_key_t *
+rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const pgp_key_grip_t &grip)
+{
+ if (!keyring) {
+ return NULL;
+ }
+ pgp_key_search_t search(PGP_KEY_SEARCH_GRIP);
+ search.by.grip = grip;
+ return rnp_key_store_search(keyring, &search, NULL);
+}
+
+pgp_key_t *
+rnp_tests_get_key_by_fpr(rnp_key_store_t *keyring, const std::string &keyid)
+{
+ if (!keyring || keyid.empty() || !rnp::is_hex(keyid)) {
+ return NULL;
+ }
+ std::vector<uint8_t> keyid_bin(PGP_FINGERPRINT_SIZE, 0);
+ size_t binlen = rnp::hex_decode(keyid.c_str(), keyid_bin.data(), keyid_bin.size());
+ if (binlen > PGP_FINGERPRINT_SIZE) {
+ return NULL;
+ }
+ pgp_fingerprint_t fp = {{}, static_cast<unsigned>(binlen)};
+ memcpy(fp.fingerprint, keyid_bin.data(), binlen);
+ return rnp_key_store_get_key_by_fpr(keyring, fp);
+}
+
+pgp_key_t *
+rnp_tests_key_search(rnp_key_store_t *keyring, const std::string &uid)
+{
+ if (!keyring || uid.empty()) {
+ return NULL;
+ }
+
+ pgp_key_search_t srch_userid(PGP_KEY_SEARCH_USERID);
+ strncpy(srch_userid.by.userid, uid.c_str(), sizeof(srch_userid.by.userid));
+ srch_userid.by.userid[sizeof(srch_userid.by.userid) - 1] = '\0';
+ return rnp_key_store_search(keyring, &srch_userid, NULL);
+}
+
+void
+reload_pubring(rnp_ffi_t *ffi)
+{
+ rnp_output_t output = NULL;
+ assert_rnp_success(rnp_output_to_memory(&output, 0));
+ assert_rnp_success(rnp_save_keys(*ffi, "GPG", output, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ assert_rnp_success(rnp_ffi_destroy(*ffi));
+
+ /* get output */
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(output, &buf, &len, false));
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));
+
+ /* re-init ffi and load keys */
+ assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG"));
+ assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ assert_rnp_success(rnp_output_destroy(output));
+ assert_rnp_success(rnp_input_destroy(input));
+}
+
+void
+reload_keyrings(rnp_ffi_t *ffi)
+{
+ rnp_output_t outpub = NULL;
+ assert_rnp_success(rnp_output_to_memory(&outpub, 0));
+ assert_rnp_success(rnp_save_keys(*ffi, "GPG", outpub, RNP_LOAD_SAVE_PUBLIC_KEYS));
+ rnp_output_t outsec = NULL;
+ assert_rnp_success(rnp_output_to_memory(&outsec, 0));
+ assert_rnp_success(rnp_save_keys(*ffi, "GPG", outsec, RNP_LOAD_SAVE_SECRET_KEYS));
+ assert_rnp_success(rnp_ffi_destroy(*ffi));
+ /* re-init ffi and load keys */
+ assert_rnp_success(rnp_ffi_create(ffi, "GPG", "GPG"));
+
+ uint8_t *buf = NULL;
+ size_t len = 0;
+ assert_rnp_success(rnp_output_memory_get_buf(outpub, &buf, &len, false));
+ rnp_input_t input = NULL;
+ assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));
+ assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_PUBLIC_KEYS, NULL));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(outpub));
+
+ assert_rnp_success(rnp_output_memory_get_buf(outsec, &buf, &len, false));
+ assert_rnp_success(rnp_input_from_memory(&input, buf, len, false));
+ assert_rnp_success(rnp_import_keys(*ffi, input, RNP_LOAD_SAVE_SECRET_KEYS, NULL));
+ assert_rnp_success(rnp_input_destroy(input));
+ assert_rnp_success(rnp_output_destroy(outsec));
+}
+
+static bool
+load_keys_internal(rnp_ffi_t ffi,
+ const std::string &format,
+ const std::string &path,
+ bool secret)
+{
+ if (path.empty()) {
+ return true;
+ }
+ rnp_input_t input = NULL;
+ if (rnp_input_from_path(&input, path.c_str())) {
+ return false;
+ }
+ bool res = !rnp_load_keys(ffi,
+ format.c_str(),
+ input,
+ secret ? RNP_LOAD_SAVE_SECRET_KEYS : RNP_LOAD_SAVE_PUBLIC_KEYS);
+ rnp_input_destroy(input);
+ return res;
+}
+
+bool
+load_keys_gpg(rnp_ffi_t ffi, const std::string &pub, const std::string &sec)
+{
+ return load_keys_internal(ffi, "GPG", pub, false) &&
+ load_keys_internal(ffi, "GPG", sec, true);
+}
+
+bool
+load_keys_kbx_g10(rnp_ffi_t ffi, const std::string &pub, const std::string &sec)
+{
+ return load_keys_internal(ffi, "KBX", pub, false) &&
+ load_keys_internal(ffi, "G10", sec, true);
+}
+
+static bool
+import_keys(rnp_ffi_t ffi, const std::string &path, uint32_t flags)
+{
+ rnp_input_t input = NULL;
+ if (rnp_input_from_path(&input, path.c_str())) {
+ return false;
+ }
+ bool res = !rnp_import_keys(ffi, input, flags, NULL);
+ rnp_input_destroy(input);
+ return res;
+}
+
+bool
+import_all_keys(rnp_ffi_t ffi, const std::string &path)
+{
+ return import_keys(ffi, path, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS);
+}
+
+bool
+import_pub_keys(rnp_ffi_t ffi, const std::string &path)
+{
+ return import_keys(ffi, path, RNP_LOAD_SAVE_PUBLIC_KEYS);
+}
+
+bool
+import_sec_keys(rnp_ffi_t ffi, const std::string &path)
+{
+ return import_keys(ffi, path, RNP_LOAD_SAVE_SECRET_KEYS);
+}
+
+static bool
+import_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len, uint32_t flags)
+{
+ rnp_input_t input = NULL;
+ if (rnp_input_from_memory(&input, data, len, false)) {
+ return false;
+ }
+ bool res = !rnp_import_keys(ffi, input, flags, NULL);
+ rnp_input_destroy(input);
+ return res;
+}
+
+bool
+import_all_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len, uint32_t flags)
+{
+ return import_keys(
+ ffi, data, len, RNP_LOAD_SAVE_PUBLIC_KEYS | RNP_LOAD_SAVE_SECRET_KEYS | flags);
+}
+
+bool
+import_pub_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len)
+{
+ return import_keys(ffi, data, len, RNP_LOAD_SAVE_PUBLIC_KEYS);
+}
+
+bool
+import_sec_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len)
+{
+ return import_keys(ffi, data, len, RNP_LOAD_SAVE_SECRET_KEYS);
+}
+
+std::vector<uint8_t>
+export_key(rnp_key_handle_t key, bool armored, bool secret)
+{
+ uint32_t flags = RNP_KEY_EXPORT_SUBKEYS;
+ if (armored) {
+ flags = flags | RNP_KEY_EXPORT_ARMORED;
+ }
+ if (secret) {
+ flags = flags | RNP_KEY_EXPORT_SECRET;
+ } else {
+ flags = flags | RNP_KEY_EXPORT_PUBLIC;
+ }
+ rnp_output_t output = NULL;
+ rnp_output_to_memory(&output, 0);
+ rnp_key_export(key, output, flags);
+ size_t len = 0;
+ uint8_t *buf = NULL;
+ rnp_output_memory_get_buf(output, &buf, &len, false);
+ std::vector<uint8_t> res(buf, buf + len);
+ rnp_output_destroy(output);
+ return res;
+}
+
+#if 0
+void
+dump_key_stdout(rnp_key_handle_t key, bool secret)
+{
+ auto pub = export_key(key, true, false);
+ printf("%.*s", (int) pub.size(), (char *) pub.data());
+ if (!secret) {
+ return;
+ }
+ auto sec = export_key(key, true, true);
+ printf("%.*s", (int) sec.size(), (char *) sec.data());
+}
+#endif
+
+bool
+write_transferable_key(pgp_transferable_key_t &key, pgp_dest_t &dst, bool armor)
+{
+ pgp_key_sequence_t keys;
+ keys.keys.push_back(key);
+ return write_transferable_keys(keys, &dst, armor);
+}
+
+bool
+write_transferable_keys(pgp_key_sequence_t &keys, pgp_dest_t *dst, bool armor)
+{
+ std::unique_ptr<rnp::ArmoredDest> armdst;
+ 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;
+ }
+ armdst = std::unique_ptr<rnp::ArmoredDest>(new rnp::ArmoredDest(*dst, msgtype));
+ dst = &armdst->dst();
+ }
+
+ 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);
+ }
+ }
+ }
+ return !dst->werr;
+}
+
+bool
+check_uid_valid(rnp_key_handle_t key, size_t idx, bool valid)
+{
+ rnp_uid_handle_t uid = NULL;
+ if (rnp_key_get_uid_handle_at(key, idx, &uid)) {
+ return false;
+ }
+ bool val = !valid;
+ rnp_uid_is_valid(uid, &val);
+ rnp_uid_handle_destroy(uid);
+ return val == valid;
+}
+
+bool
+check_uid_primary(rnp_key_handle_t key, size_t idx, bool primary)
+{
+ rnp_uid_handle_t uid = NULL;
+ if (rnp_key_get_uid_handle_at(key, idx, &uid)) {
+ return false;
+ }
+ bool prim = !primary;
+ rnp_uid_is_primary(uid, &prim);
+ rnp_uid_handle_destroy(uid);
+ return prim == primary;
+}
+
+bool
+check_key_valid(rnp_key_handle_t key, bool validity)
+{
+ bool valid = !validity;
+ if (rnp_key_is_valid(key, &valid)) {
+ return false;
+ }
+ return valid == validity;
+}
+
+uint32_t
+get_key_expiry(rnp_key_handle_t key)
+{
+ uint32_t expiry = (uint32_t) -1;
+ rnp_key_get_expiration(key, &expiry);
+ return expiry;
+}
+
+size_t
+get_key_uids(rnp_key_handle_t key)
+{
+ size_t count = (size_t) -1;
+ rnp_key_get_uid_count(key, &count);
+ return count;
+}
+
+bool
+check_sub_valid(rnp_key_handle_t key, size_t idx, bool validity)
+{
+ rnp_key_handle_t sub = NULL;
+ if (rnp_key_get_subkey_at(key, idx, &sub)) {
+ return false;
+ }
+ bool valid = !validity;
+ rnp_key_is_valid(sub, &valid);
+ rnp_key_handle_destroy(sub);
+ return valid == validity;
+}
+
+rnp_key_handle_t
+bogus_key_handle(rnp_ffi_t ffi)
+{
+ rnp_key_handle_t handle = (rnp_key_handle_t) calloc(1, sizeof(*handle));
+ handle->ffi = ffi;
+ handle->pub = NULL;
+ handle->sec = NULL;
+ handle->locator.type = PGP_KEY_SEARCH_KEYID;
+ return handle;
+}
+
+bool
+sm2_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_PK_ALG, "SM2", &enabled) && enabled;
+}
+
+bool
+aead_eax_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "EAX", &enabled) && enabled;
+}
+
+bool
+aead_ocb_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_AEAD_ALG, "OCB", &enabled) && enabled;
+}
+
+bool
+aead_ocb_aes_only()
+{
+ return aead_ocb_enabled() && !strcmp(rnp_backend_string(), "OpenSSL");
+}
+
+bool
+twofish_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "Twofish", &enabled) && enabled;
+}
+
+bool
+idea_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "IDEA", &enabled) && enabled;
+}
+
+bool
+brainpool_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_CURVE, "brainpoolP256r1", &enabled) && enabled;
+}
+
+bool
+blowfish_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "BLOWFISH", &enabled) && enabled;
+}
+
+bool
+cast5_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_SYMM_ALG, "CAST5", &enabled) && enabled;
+}
+
+bool
+ripemd160_enabled()
+{
+ bool enabled = false;
+ return !rnp_supports_feature(RNP_FEATURE_HASH_ALG, "RIPEMD160", &enabled) && enabled;
+}
+
+bool
+test_load_gpg_check_key(rnp_key_store_t *pub, rnp_key_store_t *sec, const char *id)
+{
+ pgp_key_t *key = rnp_tests_get_key_by_id(pub, id);
+ if (!key) {
+ return false;
+ }
+ if (!(key = rnp_tests_get_key_by_id(sec, id))) {
+ return false;
+ }
+ pgp_password_provider_t pswd_prov(string_copy_password_callback, (void *) "password");
+ return key->is_protected() && key->unlock(pswd_prov) && key->lock();
+}
diff --git a/src/tests/support.h b/src/tests/support.h
new file mode 100644
index 0000000..6206924
--- /dev/null
+++ b/src/tests/support.h
@@ -0,0 +1,298 @@
+/*
+ * Copyright (c) 2017-2019 [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 SUPPORT_H_
+#define SUPPORT_H_
+
+#include "config.h"
+#include <string>
+#include <stdarg.h>
+#include <stddef.h>
+#include <setjmp.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>
+#else
+#include "uniwin.h"
+#endif
+#ifdef HAVE_LIMITS_H
+#include <limits.h>
+#endif
+#ifdef HAVE_SYS_STAT_H
+#include <sys/stat.h>
+#endif
+#ifdef HAVE_FCNTL_H
+#include <fcntl.h>
+#endif
+
+#include "rnp.h"
+#include "rekey/rnp_key_store.h"
+#include "../rnp/fficli.h"
+#include "file-utils.h"
+#include "crypto/mem.h"
+
+#ifdef _WIN32
+#define pipe(fds) _pipe(fds, 256, O_BINARY)
+int setenv(const char *name, const char *value, int overwrite);
+int unsetenv(const char *name);
+#endif
+#ifndef HAVE_MKDTEMP
+char *mkdtemp(char *templ);
+#endif
+#ifndef HAVE_REALPATH
+#define realpath(N, R) _fullpath((R), (N), _MAX_PATH)
+#endif
+
+extern rnp::SecurityContext global_ctx;
+
+off_t file_size(const char *path);
+
+/* Read file contents into the std::string */
+std::string file_to_str(const std::string &path);
+
+/* Read binary file contents into the vector */
+std::vector<uint8_t> file_to_vec(const std::string &path);
+
+/* Write string contents to the file */
+void str_to_file(const std::string &path, const char *str);
+
+/* Concatenate multiple strings into a full path.
+ * A directory separator is added between components.
+ * Must be called in between va_start and va_end.
+ * Final argument of calling function must be NULL.
+ */
+void vpaths_concat(char *buffer, size_t buffer_size, const char *first, va_list ap);
+
+/* Concatenate multiple strings into a full path.
+ * Final argument must be NULL.
+ */
+char *paths_concat(char *buffer, size_t buffer_length, const char *first, ...);
+
+/* Concatenate multiple strings into a full path and
+ * check that the file exists.
+ * Final argument must be NULL.
+ */
+int path_rnp_file_exists(const char *first, ...);
+
+/* Concatenate multiple strings into a full path and
+ * create the directory.
+ * Final argument must be NULL.
+ */
+void path_mkdir(mode_t mode, const char *first, ...);
+
+/* Recursively remove a directory.
+ * The path must be a full path and must be located in /tmp, for safety.
+ */
+void delete_recursively(const char *path);
+
+void copy_recursively(const char *src, const char *dst);
+
+/* Creates and returns a temporary directory path.
+ * Caller must free the string.
+ */
+char *make_temp_dir(void);
+
+void clean_temp_dir(const char *path);
+
+/* check whether bin value is equals hex string */
+bool bin_eq_hex(const uint8_t *data, size_t len, const char *val);
+
+bool hex2mpi(pgp_mpi_t *val, const char *hex);
+
+/* check whether key id is equal to hex string */
+bool cmp_keyid(const pgp_key_id_t &id, const std::string &val);
+
+/* check whether key fp is equal to hex string */
+bool cmp_keyfp(const pgp_fingerprint_t &fp, const std::string &val);
+
+void test_ffi_init(rnp_ffi_t *ffi);
+
+bool mpi_empty(const pgp_mpi_t &val);
+
+bool write_pass_to_pipe(int fd, size_t count);
+/* Setup readable pipe with default password inside */
+bool setupPasswordfd(int *pipefd);
+
+/* Common initialization of rnp structure : home path, keystore format and pointer to store
+ * password fd */
+bool setup_cli_rnp_common(cli_rnp_t * rnp,
+ const char *ks_format,
+ const char *homedir,
+ int * pipefd);
+
+/* Initialize key generation params with default values and specified hash algorithm */
+void cli_set_default_rsa_key_desc(rnp_cfg &cfg, const char *hash);
+
+// this is a password callback that will always fail
+bool failing_password_callback(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata);
+
+bool ffi_failing_password_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len);
+
+// this is a password callback that should never be called
+bool asserting_password_callback(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata);
+
+bool ffi_asserting_password_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len);
+
+// this is a password callback that just copies the string in userdata to
+// the password buffer
+bool string_copy_password_callback(const pgp_password_ctx_t *ctx,
+ char * password,
+ size_t password_size,
+ void * userdata);
+
+bool ffi_string_password_provider(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len);
+
+void unused_getkeycb(rnp_ffi_t ffi,
+ void * app_ctx,
+ const char *identifier_type,
+ const char *identifier,
+ bool secret);
+
+bool unused_getpasscb(rnp_ffi_t ffi,
+ void * app_ctx,
+ rnp_key_handle_t key,
+ const char * pgp_context,
+ char * buf,
+ size_t buf_len);
+
+bool starts_with(const std::string &data, const std::string &match);
+bool ends_with(const std::string &data, const std::string &match);
+
+std::string fmt(const char *format, ...);
+std::string strip_eol(const std::string &str);
+std::string lowercase(const std::string &str);
+
+bool check_json_field_str(json_object * obj,
+ const std::string &field,
+ const std::string &value);
+bool check_json_field_int(json_object *obj, const std::string &field, int value);
+bool check_json_field_bool(json_object *obj, const std::string &field, bool value);
+bool check_json_pkt_type(json_object *pkt, int tag);
+
+pgp_key_t *rnp_tests_get_key_by_id(rnp_key_store_t * keyring,
+ const std::string &keyid,
+ pgp_key_t * after = NULL);
+pgp_key_t *rnp_tests_get_key_by_fpr(rnp_key_store_t *keyring, const std::string &keyid);
+pgp_key_t *rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const std::string &grip);
+pgp_key_t *rnp_tests_get_key_by_grip(rnp_key_store_t *keyring, const pgp_key_grip_t &grip);
+pgp_key_t *rnp_tests_key_search(rnp_key_store_t *keyring, const std::string &uid);
+
+/* key load/reload shortcuts */
+void reload_pubring(rnp_ffi_t *ffi);
+void reload_keyrings(rnp_ffi_t *ffi);
+bool load_keys_gpg(rnp_ffi_t ffi, const std::string &pub, const std::string &sec = "");
+bool load_keys_kbx_g10(rnp_ffi_t ffi, const std::string &pub, const std::string &sec = "");
+
+/* key import shortcuts */
+bool import_all_keys(rnp_ffi_t ffi, const std::string &path);
+bool import_pub_keys(rnp_ffi_t ffi, const std::string &path);
+bool import_sec_keys(rnp_ffi_t ffi, const std::string &path);
+bool import_all_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len, uint32_t flags = 0);
+bool import_pub_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len);
+bool import_sec_keys(rnp_ffi_t ffi, const uint8_t *data, size_t len);
+/* key export shortcut */
+std::vector<uint8_t> export_key(rnp_key_handle_t key,
+ bool armored = false,
+ bool secret = false);
+/* write transferable key(s) to stream */
+bool write_transferable_key(pgp_transferable_key_t &key, pgp_dest_t &dst, bool armor = false);
+bool write_transferable_keys(pgp_key_sequence_t &keys, pgp_dest_t *dst, bool armor = false);
+
+/* Dump key to the stdout. Not used in real tests, but useful for artefact generation */
+void dump_key_stdout(rnp_key_handle_t key, bool secret = false);
+
+/* some shortcuts for less code */
+bool check_key_valid(rnp_key_handle_t key, bool validity);
+uint32_t get_key_expiry(rnp_key_handle_t key);
+size_t get_key_uids(rnp_key_handle_t key);
+bool check_sub_valid(rnp_key_handle_t key, size_t idx, bool validity);
+bool check_uid_valid(rnp_key_handle_t key, size_t idx, bool valid);
+bool check_uid_primary(rnp_key_handle_t key, size_t idx, bool primary);
+void check_loaded_keys(const char * format,
+ bool armored,
+ uint8_t * buf,
+ size_t buf_len,
+ const char * id_type,
+ const std::vector<std::string> &expected_ids,
+ bool secret);
+
+/* create bogus key handle with NULL pub/sec keys */
+rnp_key_handle_t bogus_key_handle(rnp_ffi_t ffi);
+
+bool sm2_enabled();
+bool aead_eax_enabled();
+bool aead_ocb_enabled();
+bool aead_ocb_aes_only();
+bool twofish_enabled();
+bool idea_enabled();
+bool brainpool_enabled();
+bool blowfish_enabled();
+bool cast5_enabled();
+bool ripemd160_enabled();
+
+inline size_t
+rnp_round_up(size_t n, size_t align_to)
+{
+ if (n % align_to || n == 0) {
+ n += align_to - (n % align_to);
+ }
+ return n;
+}
+
+/* load g10/g23 gpg key and verify that it can be
+ unprotected/protected
+*/
+bool test_load_gpg_check_key(rnp_key_store_t *pub, rnp_key_store_t *sec, const char *id);
+
+#define MD5_FROM 1325376000
+#define SHA1_DATA_FROM 1547856000
+#define SHA1_KEY_FROM 1705629600
+
+#endif /* SUPPORT_H_ */
diff --git a/src/tests/user-prefs.cpp b/src/tests/user-prefs.cpp
new file mode 100644
index 0000000..9681307
--- /dev/null
+++ b/src/tests/user-prefs.cpp
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2017-2019 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp.h"
+#include <rekey/rnp_key_store.h>
+#include "rnp_tests.h"
+#include "support.h"
+#include "pgp-key.h"
+
+static const pgp_subsig_t *
+find_subsig(const pgp_key_t *key, const char *userid)
+{
+ // find the userid index
+ int uididx = -1;
+ for (unsigned i = 0; i < key->uid_count(); i++) {
+ if (key->get_uid(i).str == userid) {
+ uididx = i;
+ break;
+ }
+ }
+ if (uididx == -1) {
+ return NULL;
+ }
+ // find the subsig index
+ for (size_t i = 0; i < key->sig_count(); i++) {
+ const pgp_subsig_t &subsig = key->get_sig(i);
+ if ((int) subsig.uid == uididx) {
+ return &subsig;
+ }
+ }
+ return NULL;
+}
+
+TEST_F(rnp_tests, test_load_user_prefs)
+{
+ rnp_key_store_t *pubring =
+ new rnp_key_store_t(PGP_KEY_STORE_GPG, "data/keyrings/1/pubring.gpg", global_ctx);
+ assert_true(rnp_key_store_load_from_path(pubring, NULL));
+ assert_int_equal(rnp_key_store_get_key_count(pubring), 7);
+
+ {
+ const char *userid = "key1-uid0";
+
+ // find the key
+ pgp_key_t *key = NULL;
+ assert_non_null(key = rnp_tests_key_search(pubring, userid));
+
+ const pgp_subsig_t *subsig = find_subsig(key, userid);
+ assert_non_null(subsig);
+
+ const pgp_user_prefs_t &prefs = subsig->prefs;
+
+ // symm algs
+ std::vector<uint8_t> expected = {PGP_SA_AES_192, PGP_SA_CAST5};
+ assert_true(prefs.symm_algs == expected);
+ // hash algs
+ expected = {PGP_HASH_SHA1, PGP_HASH_SHA224};
+ assert_true(prefs.hash_algs == expected);
+ // compression algs
+ expected = {PGP_C_ZIP, PGP_C_NONE};
+ assert_true(prefs.z_algs == expected);
+ // key server prefs
+ expected = {PGP_KEY_SERVER_NO_MODIFY};
+ assert_true(prefs.ks_prefs == expected);
+ // preferred key server
+ assert_string_equal(prefs.key_server.c_str(), "hkp://pgp.mit.edu");
+ }
+
+ {
+ const char *userid = "key0-uid0";
+
+ // find the key
+ pgp_key_t *key = NULL;
+ assert_non_null(key = rnp_tests_key_search(pubring, userid));
+
+ const pgp_subsig_t *subsig = find_subsig(key, userid);
+ assert_non_null(subsig);
+
+ const pgp_user_prefs_t &prefs = subsig->prefs;
+ // symm algs
+ std::vector<uint8_t> expected = {PGP_SA_AES_256,
+ PGP_SA_AES_192,
+ PGP_SA_AES_128,
+ PGP_SA_CAST5,
+ PGP_SA_TRIPLEDES,
+ PGP_SA_IDEA};
+ assert_true(prefs.symm_algs == expected);
+ // hash algs
+ expected = {
+ PGP_HASH_SHA256, PGP_HASH_SHA1, PGP_HASH_SHA384, PGP_HASH_SHA512, PGP_HASH_SHA224};
+ assert_true(prefs.hash_algs == expected);
+ // compression algs
+ expected = {PGP_C_ZLIB, PGP_C_BZIP2, PGP_C_ZIP};
+ assert_true(prefs.z_algs == expected);
+ // key server prefs
+ expected = {PGP_KEY_SERVER_NO_MODIFY};
+ assert_true(prefs.ks_prefs == expected);
+ // preferred key server
+ assert_true(prefs.key_server.empty());
+ }
+
+ /* Cleanup */
+ delete pubring;
+}
diff --git a/src/tests/utils-hex2bin.cpp b/src/tests/utils-hex2bin.cpp
new file mode 100644
index 0000000..8daf509
--- /dev/null
+++ b/src/tests/utils-hex2bin.cpp
@@ -0,0 +1,84 @@
+/*
+ * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp_tests.h"
+
+TEST_F(rnp_tests, test_utils_hex2bin)
+{
+ // with 0x prefix
+ {
+ uint8_t buf[4];
+ assert_int_equal(rnp::hex_decode("0xfeedbeef", buf, sizeof(buf)), 4);
+ assert_int_equal(0, memcmp(buf, "\xfe\xed\xbe\xef", 4));
+ }
+ // with 0X prefix, capital
+ {
+ uint8_t buf[4];
+ assert_int_equal(rnp::hex_decode("0XFEEDBEEF", buf, sizeof(buf)), 4);
+ assert_int_equal(0, memcmp(buf, "\xfe\xed\xbe\xef", 4));
+ }
+ // without 0x prefix
+ {
+ uint8_t buf[4];
+ assert_int_equal(rnp::hex_decode("feedbeef", buf, sizeof(buf)), 4);
+ assert_int_equal(0, memcmp(buf, "\xfe\xed\xbe\xef", 4));
+ }
+ // keyid with spaces
+ {
+ uint8_t buf[PGP_KEY_ID_SIZE];
+ assert_int_equal(rnp::hex_decode("4be1 47bb 22df 1e60", buf, sizeof(buf)),
+ PGP_KEY_ID_SIZE);
+ assert_int_equal(0, memcmp(buf, "\x4b\xe1\x47\xbb\x22\xdf\x1e\x60", PGP_KEY_ID_SIZE));
+ }
+ // keyid with spaces and tab
+ {
+ uint8_t buf[PGP_KEY_ID_SIZE];
+ assert_int_equal(rnp::hex_decode(" 4be147bb\t22df1e60 ", buf, sizeof(buf)),
+ PGP_KEY_ID_SIZE);
+ assert_int_equal(0, memcmp(buf, "\x4b\xe1\x47\xbb\x22\xdf\x1e\x60", PGP_KEY_ID_SIZE));
+ }
+ // buffer is too small
+ {
+ uint8_t buf[4];
+ assert_int_equal(rnp::hex_decode("4be147bb22df1e60 ", buf, sizeof(buf)), 0);
+ }
+ // wrong hex length
+ {
+ uint8_t buf[2];
+ assert_int_equal(rnp::hex_decode("0xA", buf, sizeof(buf)), 0);
+ }
+ // wrong hex chars
+ {
+ uint8_t buf[2];
+ assert_int_equal(rnp::hex_decode("0xYY", buf, sizeof(buf)), 0);
+ }
+ // too small buffer for encoding
+ {
+ uint8_t buf[2] = {0xAB, 0xCD};
+ char hex[4];
+ assert_false(rnp::hex_encode(buf, sizeof(buf), hex, sizeof(hex), rnp::HEX_LOWERCASE));
+ }
+}
diff --git a/src/tests/utils-rnpcfg.cpp b/src/tests/utils-rnpcfg.cpp
new file mode 100644
index 0000000..040f3b8
--- /dev/null
+++ b/src/tests/utils-rnpcfg.cpp
@@ -0,0 +1,167 @@
+/*
+ * Copyright (c) 2017, [Ribose Inc](https://www.ribose.com).
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without modification,
+ * are permitted provided that the following conditions are met:
+ *
+ * 1. Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ *
+ * 2. Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+ * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
+ * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "rnp_tests.h"
+#include "support.h"
+#include <rnp/rnpcfg.h>
+#include "time-utils.h"
+
+TEST_F(rnp_tests, test_rnpcfg)
+{
+ rnp_cfg cfg1, cfg2;
+ rnp_cfg *cfgs[2] = {&cfg1, &cfg2};
+ rnp_cfg *cfg = NULL;
+ char buf[32];
+
+ assert_null(cfg1.get_cstr("key"));
+
+ /* set the values */
+ cfg1.set_str("key_str", "val");
+ cfg1.set_str("key_true", "true");
+ cfg1.set_str("key_True", "True");
+ cfg1.set_int("key_int", 999);
+ cfg1.set_str("key_100", "100");
+ cfg1.set_bool("key_bool", true);
+
+ for (int i = 0; i < 10; i++) {
+ snprintf(buf, sizeof(buf), "val%d", i);
+ cfg1.add_str("key_list", buf);
+ }
+
+ /* copy empty cfg2 to cfg1 to make sure values are not deleted */
+ cfg1.copy(cfg2);
+
+ /* copy to the cfg2 */
+ cfg2.copy(cfg1);
+
+ /* copy second time to make sure there are no leaks */
+ cfg2.copy(cfg1);
+
+ /* get values back, including transformations */
+ for (int i = 0; i < 2; i++) {
+ cfg = cfgs[i];
+
+ assert_int_equal(cfg->get_int("key_int"), 999);
+ assert_int_equal(cfg->get_int("key_100"), 100);
+ assert_true(cfg->get_bool("key_int"));
+ assert_true(cfg->get_bool("key_bool"));
+ assert_true(cfg->get_bool("key_true"));
+ assert_true(cfg->get_bool("key_True"));
+ assert_false(cfg->get_bool("key_notfound"));
+
+ assert_string_equal(cfg->get_cstr("key_str"), "val");
+ assert_null(cfg->get_cstr("key_str1"));
+ assert_null(cfg->get_cstr("key_st"));
+
+ const auto &lst = cfg->get_list("key_list");
+ assert_int_equal(lst.size(), 10);
+
+ for (int j = 0; j < 10; j++) {
+ const auto &li = lst[j];
+ snprintf(buf, sizeof(buf), "val%d", j);
+ assert_string_equal(buf, li.c_str());
+ }
+ }
+
+ /* get values back as C++ strings */
+ assert_true(cfg1.get_str("key_str") == "val");
+ assert_true(cfg1.get_str("key_unknown") == "");
+ assert_true(cfg1.get_str("") == "");
+ assert_true(cfg1.get_str("key_true") == "true");
+ assert_true(cfg1.get_str("key_True") == "True");
+
+ /* get C++ string list */
+ auto keylist = cfg1.get_list("key_list11");
+ assert_int_equal(keylist.size(), 0);
+ keylist = cfg1.get_list("key_list");
+ assert_int_equal(keylist.size(), 10);
+ keylist = {"1", "2", "3"};
+ keylist = cfg1.get_list("key_list");
+ assert_int_equal(keylist.size(), 10);
+
+ /* override value */
+ cfg1.set_int("key_int", 222);
+ assert_int_equal(cfg1.get_int("key_int"), 222);
+ assert_int_equal(cfg2.get_int("key_int"), 999);
+ cfg1.set_str("key_int", "333");
+ assert_int_equal(cfg1.get_int("key_int"), 333);
+
+ /* unset value */
+ cfg1.unset("key_int");
+}
+
+TEST_F(rnp_tests, test_rnpcfg_get_expiration)
+{
+ time_t basetime = time(NULL);
+ time_t rawtime = basetime + 604800;
+ struct tm timeinfo;
+ rnp_localtime(rawtime, timeinfo);
+ // clear hours, minutes and seconds
+ timeinfo.tm_hour = 0;
+ timeinfo.tm_min = 0;
+ timeinfo.tm_sec = 0;
+ rawtime = mktime(&timeinfo);
+ auto year = timeinfo.tm_year + 1900;
+ auto mon = timeinfo.tm_mon + 1;
+ auto day = timeinfo.tm_mday;
+ rnp_cfg cfg;
+ cfg.set_str("expiry-", fmt("%d-%02d-%02d", year, mon, day));
+ cfg.set_str("expiry/", fmt("%d/%02d/%02d", year, mon, day));
+ cfg.set_str("expiry.", fmt("%d.%02d.%02d", year, mon, day));
+
+ uint32_t raw_expiry = 0;
+ assert_true(cfg.get_expiration("expiry-", raw_expiry));
+ assert_int_equal(raw_expiry, rawtime - basetime);
+ assert_true(cfg.get_expiration("expiry/", raw_expiry));
+ assert_int_equal(raw_expiry, rawtime - basetime);
+ assert_true(cfg.get_expiration("expiry.", raw_expiry));
+ assert_int_equal(raw_expiry, rawtime - basetime);
+ cfg.set_str("expiry", "2100-01-01");
+ assert_true(cfg.get_expiration("expiry", raw_expiry));
+ assert_int_not_equal(raw_expiry, rawtime - basetime);
+ cfg.set_str("expiry", "2124-02-29");
+ assert_true(cfg.get_expiration("expiry", raw_expiry));
+ /* date in a past */
+ cfg.set_str("expiry", "2000-02-29");
+ assert_false(cfg.get_expiration("expiry", raw_expiry));
+ cfg.set_str("expiry", "2400-02-29");
+ if ((sizeof(time_t) > 4)) {
+ /* date is correct, but overflows 32 bits */
+ assert_false(cfg.get_expiration("expiry", raw_expiry));
+ } else {
+ /* for 32-bit time_t we return INT32_MAX for all dates beyond the y2038 */
+ assert_true(cfg.get_expiration("expiry", raw_expiry));
+ assert_int_equal(raw_expiry, INT32_MAX - basetime);
+ }
+ cfg.set_str("expiry", "2100-02-29");
+ assert_false(cfg.get_expiration("expiry", raw_expiry));
+ cfg.set_str("expiry", "4294967296");
+ assert_false(cfg.get_expiration("expiry", raw_expiry));
+ cfg.set_str("expiry", "2037-02-29");
+ assert_false(cfg.get_expiration("expiry", raw_expiry));
+ cfg.set_str("expiry", "2037-13-01");
+ assert_false(cfg.get_expiration("expiry", raw_expiry));
+}
diff --git a/version.txt b/version.txt
new file mode 100644
index 0000000..c5523bd
--- /dev/null
+++ b/version.txt
@@ -0,0 +1 @@
+0.17.0