summaryrefslogtreecommitdiffstats
path: root/src/tests
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 /src/tests
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>
Diffstat (limited to '')
-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
632 files changed, 45997 insertions, 0 deletions
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
+
+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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/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-----
+
+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/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-----
+
+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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_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));
+}