diff options
Diffstat (limited to 'src/tests')
23 files changed, 345 insertions, 98 deletions
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index 7d2a6b0..d1a89d4 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt @@ -66,13 +66,26 @@ else() endif() find_package(JSON-C 0.11 REQUIRED) -if (CRYPTO_BACKEND_LOWERCASE STREQUAL "botan") - find_package(Botan2 2.14.0 REQUIRED) +if (CRYPTO_BACKEND_BOTAN3) + find_package(Botan 3.0.0 REQUIRED) +elseif (CRYPTO_BACKEND_BOTAN) + find_package(Botan 2.14.0 REQUIRED) + if(BOTAN_VERSION VERSION_GREATER_EQUAL 3.0.0) + set(CRYPTO_BACKEND_BOTAN3 1) + endif() endif() if (CRYPTO_BACKEND_LOWERCASE STREQUAL "openssl") find_package(OpenSSL 1.1.1 REQUIRED) endif() +if(NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER "4.8.5") + set(CMAKE_CXX_STANDARD 14) +endif() + +if(CRYPTO_BACKEND_BOTAN3) + set(CMAKE_CXX_STANDARD 20) +endif() + add_executable(rnp_tests ../rnp/rnpcfg.cpp ../rnp/fficli.cpp @@ -170,13 +183,14 @@ target_include_directories(rnp_tests PRIVATE "${PROJECT_SOURCE_DIR}/src" "${PROJECT_SOURCE_DIR}/src/lib" - "${BOTAN2_INCLUDE_DIRS}" + "${BOTAN_INCLUDE_DIRS}" + "${SEXPP_INCLUDE_DIRS}" ) target_link_libraries(rnp_tests PRIVATE librnp-static JSON-C::JSON-C - sexp + sexpp ${GTestMain} ) if (CRYPTO_BACKEND_LOWERCASE STREQUAL "openssl") @@ -220,6 +234,13 @@ function(add_cli_test suite) "RNP_TESTS_GPG_PATH=${GPG_EXECUTABLE}" "RNP_TESTS_GPGCONF_PATH=${GPGCONF_EXECUTABLE}" ) + if (CRYPTO_BACKEND_OPENSSL) + get_filename_component(ossl_root "${OPENSSL_INCLUDE_DIR}" DIRECTORY) + list(APPEND _env + "RNP_TESTS_OPENSSL_ROOT=${ossl_root}" + ) + endif() + set_tests_properties(${_test_name} PROPERTIES TIMEOUT 3000 FIXTURES_REQUIRED testdata diff --git a/src/tests/cipher.cpp b/src/tests/cipher.cpp index 25b98bf..3df5f0b 100644 --- a/src/tests/cipher.cpp +++ b/src/tests/cipher.cpp @@ -216,17 +216,15 @@ TEST_F(rnp_tests, rnp_test_x25519) } static void -elgamal_roundtrip(pgp_eg_key_t *key) +elgamal_roundtrip(pgp_eg_key_t *key, rnp::RNG &rng) { 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(elgamal_encrypt_pkcs1(&rng, &enc, in_b, sizeof(in_b), key), RNP_SUCCESS); + assert_int_equal(elgamal_decrypt_pkcs1(&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")); } @@ -236,7 +234,7 @@ 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); + elgamal_roundtrip(&key, global_ctx.rng); } TEST_F(rnp_tests, ecdsa_signverify_success) @@ -1011,3 +1009,89 @@ TEST_F(rnp_tests, test_brainpool_enabled) assert_false(supported); #endif } + +#if defined(CRYPTO_BACKEND_BOTAN) +TEST_F(rnp_tests, test_windows_botan_crash) +{ + /* Reproducer for https://github.com/randombit/botan/issues/3812 . Related CLI test + * test_sym_encrypted__rnp_aead_botan_crash */ + + auto data = file_to_vec("data/test_messages/message.aead-windows-issue-botan"); + /* First 32 bytes are encrypted key as it was extracted from the OpenPGP stream, so + * skipping. */ + uint8_t *idx = data.data() + 32; + uint8_t bufbin[64] = {0}; + uint8_t outbuf[32768] = {0}; + size_t outsz = sizeof(outbuf); + size_t written = 0; + size_t read = 0; + size_t diff = 0; + + /* Now the data which exposes a possible crash */ + struct botan_cipher_struct *cipher = NULL; + assert_int_equal(botan_cipher_init(&cipher, "AES-128/OCB", BOTAN_CIPHER_INIT_FLAG_DECRYPT), + 0); + + const char *key2 = "417835a476bc5958b18d41fb00cf682d"; + assert_int_equal(rnp::hex_decode(key2, bufbin, 16), 16); + assert_int_equal(botan_cipher_set_key(cipher, bufbin, 16), 0); + + const char *ad2 = "d40107020c0000000000000000"; + assert_int_equal(rnp::hex_decode(ad2, bufbin, 13), 13); + assert_int_equal(botan_cipher_set_associated_data(cipher, bufbin, 13), 0); + + const char *nonce2 = "005dbbbe0088f9d17ca2d8d464920f"; + assert_int_equal(rnp::hex_decode(nonce2, bufbin, 15), 15); + assert_int_equal(botan_cipher_start(cipher, bufbin, 15), 0); + + assert_int_equal( + botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, 32736, &read), 0); + diff = 32736 - read; + idx += read; + + assert_int_equal( + botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, diff + 32736, &read), 0); + idx += read; + diff = diff + 32736 - read; + + assert_int_equal( + botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, diff + 32736, &read), 0); + idx += read; + diff = diff + 32736 - read; + + assert_int_equal( + botan_cipher_update(cipher, 0, outbuf, outsz, &written, idx, diff + 32736, &read), 0); + idx += read; + diff = diff + 32736 - read; + + uint32_t ver_major = botan_version_major(); + uint32_t ver_minor = botan_version_minor(); + uint32_t ver_patch = botan_version_patch(); + uint32_t ver = (ver_major << 16) | (ver_minor << 8) | ver_patch; + uint32_t ver_2_19_3 = (2 << 16) | (19 << 8) | 3; + uint32_t ver_3_2_0 = (3 << 16) | (2 << 8); + bool check = true; + /* Currently AV happens with versions up to 2.19.3 and 3.2.0 */ + if ((ver_major == 2) && (ver <= ver_2_19_3)) { + check = false; + } + if ((ver_major == 3) && (ver <= ver_3_2_0)) { + check = false; + } + + if (check) { + assert_int_equal(botan_cipher_update(cipher, + BOTAN_CIPHER_UPDATE_FLAG_FINAL, + outbuf, + outsz, + &written, + idx, + diff + 25119, + &read), + 0); + } + + assert_int_equal(botan_cipher_reset(cipher), 0); + assert_int_equal(botan_cipher_destroy(cipher), 0); +} +#endif diff --git a/src/tests/cipher_cxx.cpp b/src/tests/cipher_cxx.cpp index b5f7f83..a33e38e 100644 --- a/src/tests/cipher_cxx.cpp +++ b/src/tests/cipher_cxx.cpp @@ -138,7 +138,7 @@ test_cipher(pgp_symm_alg_t alg, 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 +#if defined(CRYPTO_BACKEND_OPENSSL) || defined(CRYPTO_BACKEND_BOTAN3) /* Since ossl backend sets tag explicitly tag bytes cannot be split between two blocks. The issue may easily occur is (for example) @@ -146,6 +146,7 @@ test_cipher(pgp_symm_alg_t alg, ct.size() = 24 tag_size=16 */ + /* Botan 3 also requires to include whole tag in the finish() call. */ if (ct.size() - nonfinal_bytes < tag_size) { nonfinal_bytes = ct.size() - tag_size; } @@ -153,12 +154,16 @@ test_cipher(pgp_symm_alg_t alg, output_written = 0; input_consumed = 0; while (input_consumed != nonfinal_bytes) { + size_t consume = std::min(ud, nonfinal_bytes - input_consumed); + if (consume < ud) { + break; + } 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), + consume, &consumed)); output_written += written; input_consumed += consumed; diff --git a/src/tests/cli_common.py b/src/tests/cli_common.py index 12bf5d8..f8d7001 100644 --- a/src/tests/cli_common.py +++ b/src/tests/cli_common.py @@ -1,10 +1,10 @@ import sys -import distutils.spawn import random import string import logging import os import re +import shutil from subprocess import Popen, PIPE RNP_ROOT = None @@ -78,7 +78,7 @@ def file_text(path, encoding = CONSOLE_ENCODING): return f.read().decode(encoding).replace('\r\r', '\r') def find_utility(name, exitifnone = True): - path = distutils.spawn.find_executable(name) + path = shutil.which(name) if not path and exitifnone: logging.error('Cannot find utility {}. Exiting.'.format(name)) sys.exit(1) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index e6f5ed7..47f6890 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -9,6 +9,7 @@ import sys import tempfile import time import unittest +import random from platform import architecture from cli_common import (file_text, find_utility, is_windows, list_upto, @@ -47,6 +48,8 @@ RNP_IDEA = True RNP_BLOWFISH = True RNP_CAST5 = True RNP_RIPEMD160 = True +# Botan may cause AV during OCB decryption in certain cases, see https://github.com/randombit/botan/issues/3812 +RNP_BOTAN_OCB_AV = False if sys.version_info >= (3,): unichr = chr @@ -861,6 +864,7 @@ def gpg_check_features(): 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 + global RNP_BOTAN_OCB_AV ret, out, _ = run_proc(RNP, ['--version']) if ret != 0: raise_err('Failed to get RNP version.') @@ -869,6 +873,14 @@ def rnp_check_features(): 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 + # Botan OCB crash + if re.match(r'(?s)^.*Backend.*Botan.*', out): + match = re.match(r'(?s)^.*Backend version: ([\d]+)\.([\d]+)\.([\d]+).*$', out) + ver = [int(match.group(1)), int(match.group(2)), int(match.group(3))] + if ver <= [2, 19, 3]: + RNP_BOTAN_OCB_AV = True + if (ver >= [3, 0, 0]) and (ver <= [3, 2, 0]): + RNP_BOTAN_OCB_AV = True # Twofish RNP_TWOFISH = re.match(r'(?s)^.*Encryption:.*TWOFISH.*', out) is not None # Brainpool curves @@ -887,6 +899,7 @@ def rnp_check_features(): 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)) + print('RNP_BOTAN_OCB_AV: ' + str(RNP_BOTAN_OCB_AV)) def setup(loglvl): # Setting up directories. @@ -3037,10 +3050,10 @@ class Misc(unittest.TestCase): def test_backend_version(self): BOTAN_BACKEND_VERSION = r'(?s)^.*.' \ 'Backend: Botan.*' \ - 'Backend version: ([a-zA-z\.0-9]+).*$' + 'Backend version: ([a-zA-Z\\.0-9]+).*$' OPENSSL_BACKEND_VERSION = r'(?s)^.*' \ 'Backend: OpenSSL.*' \ - 'Backend version: ([a-zA-z\.0-9]+).*$' + 'Backend version: ([a-zA-Z\\.0-9]+).*$' # Run without parameters and make sure it matches ret, out, _ = run_proc(RNP, []) self.assertNotEqual(ret, 0) @@ -3054,28 +3067,33 @@ class Misc(unittest.TestCase): if not match: match = re.match(OPENSSL_BACKEND_VERSION, out) backend_prog = 'openssl' - openssl_root = os.getenv('OPENSSL_ROOT_DIR') + openssl_root = os.getenv('RNP_TESTS_OPENSSL_ROOT') 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.*$') + self.assertNotRegex(err, r'(?is)^.*unknown.*$') # 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 is_windows(): + backend_prog += '.exe' + backend_prog_ext = None if openssl_root is not None: - backen_prog_ext = shutil.which(backend_prog, path = openssl_root + '/bin') + backend_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) + backend_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) + if backend_prog_ext is None: + return + ret, out, _ = run_proc(backend_prog_ext, ['version']) + self.assertEqual(ret, 0) + self.assertIn(match.group(1), out) def test_help_message(self): # rnp help message @@ -3971,6 +3989,17 @@ class Misc(unittest.TestCase): clear_workfiles() shutil.rmtree(RNP2, ignore_errors=True) + def test_armored_detection_on_cleartext(self): + ret, out, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--clearsign'], 'Hello\n') + self.assertEqual(ret, 0) + self.assertRegex(out, r'(?s)^.*BEGIN PGP SIGNED MESSAGE.*$') + self.assertRegex(out, r'(?s)^.*BEGIN PGP SIGNATURE.*$') + ret, _, err = run_proc(RNP, ['--keyfile', data_path(PUBRING_1), '--verify', '-'], out) + self.assertEqual(ret, 0) + self.assertRegex(err, r'(?s)^.*Good signature made.*$') + self.assertNotRegex(err, r'(?s)^.*Warning: missing or malformed CRC line.*$') + self.assertNotRegex(err, r'(?s)^.*wrong armor trailer.*$') + class Encryption(unittest.TestCase): ''' Things to try later: @@ -4120,12 +4149,25 @@ class Encryption(unittest.TestCase): 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) + SIZES = Encryption.SIZES_R + random.shuffle(SIZES) # Encrypt and decrypt cleartext using the AEAD - for size, cipher, aead, bits, z in zip(Encryption.SIZES_R, AEAD_C, + for size, cipher, aead, bits, z in zip(SIZES, AEAD_C, AEAD_M, AEAD_B, Encryption.Z_R): + if RNP_BOTAN_OCB_AV and (aead == 'ocb') and (size > 30000): + continue rnp_sym_encryption_rnp_aead(size, cipher, z, [aead, bits], GPG_AEAD) + def test_sym_encrypted__rnp_aead_botan_crash(self): + if RNP_BOTAN_OCB_AV: + return + dst, = reg_workfiles('cleartext', '.txt') + rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue'), dst) + remove_files(dst) + rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue2'), dst) + remove_files(dst) + def test_aead_chunk_edge_cases(self): if not RNP_AEAD: print('AEAD is not available for RNP - skipping.') @@ -4710,12 +4752,16 @@ class EncryptElgamal(Encrypt): 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 + if sign_key_size == 1024: + return 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 + if sign_key_size == 1024: + return self._encrypt_decrypt(self.rnp, self.gpg) def test_encrypt_P1024_1024(self): self.do_test_encrypt(1024, 1024) @@ -4726,11 +4772,7 @@ class EncryptElgamal(Encrypt): 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) + # 1024-bit key generation test was removed since it uses SHA1, which is not allowed for key signatures since Jan 19, 2024. def test_generate_elgamal_key1536_in_gpg_and_encrypt(self): cmd = EncryptElgamal.GPG_GENERATE_DSA_ELGAMAL_PATTERN.format(1536, 1536, self.gpg.userid) diff --git a/src/tests/data/test_messages/message.aead-windows-issue b/src/tests/data/test_messages/message.aead-windows-issue Binary files differnew file mode 100644 index 0000000..4836610 --- /dev/null +++ b/src/tests/data/test_messages/message.aead-windows-issue diff --git a/src/tests/data/test_messages/message.aead-windows-issue-botan b/src/tests/data/test_messages/message.aead-windows-issue-botan Binary files differnew file mode 100644 index 0000000..84e17e8 --- /dev/null +++ b/src/tests/data/test_messages/message.aead-windows-issue-botan diff --git a/src/tests/data/test_messages/message.aead-windows-issue2 b/src/tests/data/test_messages/message.aead-windows-issue2 Binary files differnew file mode 100644 index 0000000..9af3d50 --- /dev/null +++ b/src/tests/data/test_messages/message.aead-windows-issue2 diff --git a/src/tests/data/test_messages/message.txt.signed-mimemode b/src/tests/data/test_messages/message.txt.signed-mimemode Binary files differnew file mode 100644 index 0000000..27f7579 --- /dev/null +++ b/src/tests/data/test_messages/message.txt.signed-mimemode diff --git a/src/tests/data/test_stream_key_load/ecc-25519-pub-extra-line-2.asc b/src/tests/data/test_stream_key_load/ecc-25519-pub-extra-line-2.asc new file mode 100644 index 0000000..dbec69f --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-25519-pub-extra-line-2.asc @@ -0,0 +1,11 @@ +-----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-extra-line.asc b/src/tests/data/test_stream_key_load/ecc-25519-pub-extra-line.asc new file mode 100644 index 0000000..9617fb9 --- /dev/null +++ b/src/tests/data/test_stream_key_load/ecc-25519-pub-extra-line.asc @@ -0,0 +1,11 @@ +-----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/ffi-enc.cpp b/src/tests/ffi-enc.cpp index 55b0d10..40b17cc 100644 --- a/src/tests/ffi-enc.cpp +++ b/src/tests/ffi-enc.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2020-2023 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -178,15 +178,16 @@ TEST_F(rnp_tests, test_ffi_encrypt_pass) 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")); + const char *alg = blowfish_enabled() ? "BLOWFISH" : "AES256"; + assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SHA256", 12345, alg)); } 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")); + const char *alg = blowfish_enabled() ? "BLOWFISH" : "AES256"; + assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, alg)); } else { assert_rnp_success(rnp_op_encrypt_add_password(op, "pass2", "SM3", 12345, "TWOFISH")); } @@ -598,7 +599,7 @@ TEST_F(rnp_tests, test_ffi_encrypt_pk) rnp_ffi_destroy(ffi); } -bool +static bool first_key_password_provider(rnp_ffi_t ffi, void * app_ctx, rnp_key_handle_t key, @@ -1360,3 +1361,29 @@ TEST_F(rnp_tests, test_ffi_encrypt_no_wrap) // final cleanup rnp_ffi_destroy(ffi); } + +TEST_F(rnp_tests, test_ffi_mimemode_signature) +{ + rnp_ffi_t ffi = NULL; + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + assert_true(import_pub_keys(ffi, "data/test_stream_key_load/ecc-25519-pub.asc")); + + rnp_input_t input = NULL; + assert_rnp_success( + rnp_input_from_path(&input, "data/test_messages/message.txt.signed-mimemode")); + 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_success(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_rnp_success(rnp_op_verify_signature_get_status(sig)); + rnp_op_verify_destroy(verify); + rnp_input_destroy(input); + rnp_output_destroy(output); + rnp_ffi_destroy(ffi); +} diff --git a/src/tests/ffi-key-sig.cpp b/src/tests/ffi-key-sig.cpp index 01bfdd2..1176de5 100644 --- a/src/tests/ffi-key-sig.cpp +++ b/src/tests/ffi-key-sig.cpp @@ -902,6 +902,11 @@ TEST_F(rnp_tests, test_ffi_sig_validity) uint32_t expires = 0; assert_rnp_success(rnp_signature_get_expiration(sig, &expires)); assert_int_equal(expires, 86400); + uint32_t features = 0; + assert_rnp_failure(rnp_signature_get_features(NULL, &features)); + assert_rnp_failure(rnp_signature_get_features(sig, NULL)); + assert_rnp_success(rnp_signature_get_features(sig, &features)); + assert_int_equal(features, 0); rnp_signature_handle_destroy(sig); rnp_uid_handle_destroy(uid); rnp_key_handle_destroy(key); diff --git a/src/tests/ffi-key.cpp b/src/tests/ffi-key.cpp index 2933d68..a25e6c4 100644 --- a/src/tests/ffi-key.cpp +++ b/src/tests/ffi-key.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2022-2023 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -4440,3 +4440,28 @@ TEST_F(rnp_tests, test_reprotect_keys) assert_rnp_success(rnp_identifier_iterator_destroy(it)); rnp_ffi_destroy(ffi); } + +TEST_F(rnp_tests, test_armored_keys_extra_line) +{ + rnp_ffi_t ffi = NULL; + assert_rnp_success(rnp_ffi_create(&ffi, "GPG", "GPG")); + /* Key with extra line after the checksum */ + assert_true( + import_pub_keys(ffi, "data/test_stream_key_load/ecc-25519-pub-extra-line.asc")); + rnp_key_handle_t key = NULL; + assert_rnp_success(rnp_locate_key(ffi, "keyid", "cc786278981b0728", &key)); + assert_true(check_key_valid(key, true)); + assert_true(check_uid_valid(key, 0, true)); + rnp_key_handle_destroy(key); + + /* Key with extra lines with spaces after the checksum */ + assert_true( + import_pub_keys(ffi, "data/test_stream_key_load/ecc-25519-pub-extra-line-2.asc")); + key = NULL; + assert_rnp_success(rnp_locate_key(ffi, "keyid", "cc786278981b0728", &key)); + assert_true(check_key_valid(key, true)); + assert_true(check_uid_valid(key, 0, true)); + rnp_key_handle_destroy(key); + + rnp_ffi_destroy(ffi); +} diff --git a/src/tests/ffi.cpp b/src/tests/ffi.cpp index 1e75871..4c9f553 100644 --- a/src/tests/ffi.cpp +++ b/src/tests/ffi.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2020 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2017-2023 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -29,6 +29,7 @@ #include <string> #include <set> #include <utility> +#include <cstdint> #include <rnp/rnp.h> #include "rnp_tests.h" @@ -2659,7 +2660,8 @@ TEST_F(rnp_tests, test_ffi_revocations) 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; + const uintptr_t p_sig = 0xdeadbeef; + rnp_signature_handle_t sig = reinterpret_cast<rnp_signature_handle_t>(p_sig); 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)); @@ -5951,11 +5953,16 @@ TEST_F(rnp_tests, test_ffi_security_profile) 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); + auto now = time(NULL); + bool sha1_cutoff = now > SHA1_KEY_FROM; + /* This would pick default rule closer to the date independent on usage */ + assert_rnp_success( + rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", now, &flags, &from, &level)); + auto expect_from = sha1_cutoff ? SHA1_KEY_FROM : SHA1_DATA_FROM; + auto expect_usage = sha1_cutoff ? RNP_SECURITY_VERIFY_KEY : RNP_SECURITY_VERIFY_DATA; + assert_int_equal(from, expect_from); assert_int_equal(level, RNP_SECURITY_INSECURE); - assert_int_equal(flags, RNP_SECURITY_VERIFY_DATA); + assert_int_equal(flags, expect_usage); flags = 0; assert_rnp_success(rnp_get_security_rule( ffi, RNP_FEATURE_HASH_ALG, "SHA1", SHA1_DATA_FROM - 1, &flags, &from, &level)); @@ -5968,11 +5975,14 @@ TEST_F(rnp_tests, test_ffi_security_profile) 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); + assert_rnp_success( + rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, "SHA1", now, &flags, &from, &level)); + expect_from = sha1_cutoff ? SHA1_KEY_FROM : 0; + auto expect_level = sha1_cutoff ? RNP_SECURITY_INSECURE : RNP_SECURITY_DEFAULT; + expect_usage = sha1_cutoff ? RNP_SECURITY_VERIFY_KEY : 0; + assert_int_equal(from, expect_from); + assert_int_equal(level, expect_level); + assert_int_equal(flags, expect_usage); 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)); diff --git a/src/tests/generatekey.cpp b/src/tests/generatekey.cpp index b846ebb..dd0aaff 100644 --- a/src/tests/generatekey.cpp +++ b/src/tests/generatekey.cpp @@ -96,12 +96,11 @@ hash_supported(const std::string &hash) } static bool -hash_secure(rnp_ffi_t ffi, const std::string &hash, uint32_t action) +hash_secure(rnp_ffi_t ffi, const std::string &hash, uint32_t action, uint64_t time) { 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); + rnp_get_security_rule(ffi, RNP_FEATURE_HASH_ALG, hash.c_str(), time, &flags, NULL, &level); return level == RNP_SECURITY_DEFAULT; } @@ -185,7 +184,8 @@ TEST_F(rnp_tests, rnpkeys_generatekey_testSignature) 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)) { + if (!hash_secure( + rnp.ffi, hashAlg[i], RNP_SECURITY_VERIFY_DATA, global_ctx.time())) { assert_false(cli_rnp_process_file(&rnp)); rnp.end(); assert_int_equal(rnp_unlink("dummyfile.dat.pgp"), 0); @@ -361,7 +361,7 @@ TEST_F(rnp_tests, rnpkeys_generatekey_verifySupportedHashAlg) 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)) { + if (hash_secure(rnp.ffi, hashAlg[i], RNP_SECURITY_VERIFY_KEY, global_ctx.time())) { assert_non_null(handle); bool valid = false; rnp_key_is_valid(handle, &valid); diff --git a/src/tests/key-add-userid.cpp b/src/tests/key-add-userid.cpp index b80dbb6..fba6ec0 100644 --- a/src/tests/key-add-userid.cpp +++ b/src/tests/key-add-userid.cpp @@ -69,6 +69,8 @@ TEST_F(rnp_tests, test_key_add_userid) selfsig0.key_flags = 0x2; selfsig0.key_expiration = base_expiry; selfsig0.primary = false; + auto curtime = global_ctx.time(); + global_ctx.set_time(curtime > SHA1_KEY_FROM ? SHA1_KEY_FROM - 100 : 0); 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 diff --git a/src/tests/load-pgp.cpp b/src/tests/load-pgp.cpp index 560ed3d..6583eee 100644 --- a/src/tests/load-pgp.cpp +++ b/src/tests/load-pgp.cpp @@ -124,9 +124,10 @@ TEST_F(rnp_tests, test_load_v4_keyring_pgp) /* Just a helper for the below test */ static void -check_pgp_keyring_counts(const char * path, - unsigned primary_count, - const unsigned subkey_counts[]) +check_pgp_keyring_counts(const char * path, + unsigned primary_count, + const unsigned subkey_counts[], + rnp::SecurityContext &global_ctx) { pgp_source_t src = {}; rnp_key_store_t *key_store = new rnp_key_store_t(global_ctx); @@ -175,10 +176,12 @@ TEST_F(rnp_tests, test_load_keyring_and_count_pgp) unsigned int subkey_counts[2] = {3, 2}; // check pubring - check_pgp_keyring_counts("data/keyrings/1/pubring.gpg", primary_count, subkey_counts); + check_pgp_keyring_counts( + "data/keyrings/1/pubring.gpg", primary_count, subkey_counts, global_ctx); // check secring - check_pgp_keyring_counts("data/keyrings/1/secring.gpg", primary_count, subkey_counts); + check_pgp_keyring_counts( + "data/keyrings/1/secring.gpg", primary_count, subkey_counts, global_ctx); } /* This test loads a V4 keyring and confirms that certain diff --git a/src/tests/rnp_tests.cpp b/src/tests/rnp_tests.cpp index 910d2d8..6961da4 100644 --- a/src/tests/rnp_tests.cpp +++ b/src/tests/rnp_tests.cpp @@ -35,11 +35,6 @@ static char original_dir[PATH_MAX]; -/* - * Handler used to access DRBG. - */ -rnp::SecurityContext global_ctx; - #ifdef _WIN32 void rnpInvalidParameterHandler(const wchar_t *expression, diff --git a/src/tests/rnp_tests.h b/src/tests/rnp_tests.h index 2dd43e9..58f99c8 100644 --- a/src/tests/rnp_tests.h +++ b/src/tests/rnp_tests.h @@ -38,7 +38,8 @@ class rnp_tests : public ::testing::Test { const char *original_dir() const; protected: - char *m_dir; + char * m_dir; + rnp::SecurityContext global_ctx; }; typedef struct { diff --git a/src/tests/streams.cpp b/src/tests/streams.cpp index c0bf698..3219387 100644 --- a/src/tests/streams.cpp +++ b/src/tests/streams.cpp @@ -1086,7 +1086,7 @@ TEST_F(rnp_tests, test_stream_key_signatures) } static bool -validate_key_sigs(const char *path) +validate_key_sigs(const char *path, rnp::SecurityContext &global_ctx) { 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); @@ -1144,32 +1144,30 @@ TEST_F(rnp_tests, test_stream_key_signature_validate) 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")); + auto validate = [this](const std::string &file) { + auto path = "data/test_stream_key_load/" + file; + return validate_key_sigs(path.c_str(), this->global_ctx); + }; + assert_true(validate("dsa-eg-pub.asc")); + assert_true(validate("dsa-eg-sec.asc")); + assert_true(validate("ecc-25519-pub.asc")); + assert_true(validate("ecc-25519-sec.asc")); + assert_true(validate("ecc-x25519-pub.asc")); + assert_true(validate("ecc-x25519-sec.asc")); + assert_true(validate("ecc-p256-pub.asc")); + assert_true(validate("ecc-p256-sec.asc")); + assert_true(validate("ecc-p384-pub.asc")); + assert_true(validate("ecc-p384-sec.asc")); + assert_true(validate("ecc-p521-pub.asc")); + assert_true(validate("ecc-p521-sec.asc")); + assert_true(validate("ecc-bp256-pub.asc") == brainpool_enabled()); + assert_true(validate("ecc-bp256-sec.asc") == brainpool_enabled()); + assert_true(validate("ecc-bp384-pub.asc") == brainpool_enabled()); + assert_true(validate("ecc-bp384-sec.asc") == brainpool_enabled()); + assert_true(validate("ecc-bp512-pub.asc") == brainpool_enabled()); + assert_true(validate("ecc-bp512-sec.asc") == brainpool_enabled()); + assert_true(validate("ecc-p256k1-pub.asc")); + assert_true(validate("ecc-p256k1-sec.asc")); } TEST_F(rnp_tests, test_stream_verify_no_key) @@ -1568,11 +1566,15 @@ TEST_F(rnp_tests, test_stream_dearmor_edge_cases) 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 */ + /* extra spaces or tabs before the footer - allow it, see issue #2199 */ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n %s\n", HDR, b64, CRC, FTR); - assert_false(try_dearmor(msg, len)); + assert_true(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_true(try_dearmor(msg, len)); + /* no empty line between crc and footer - FAIL */ + len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s%s\n", HDR, b64, CRC, FTR); assert_false(try_dearmor(msg, len)); + /* extra chars before the footer - FAIL */ len = snprintf(msg, sizeof(msg), "%s\n\n%s\n%s\n11111%s\n", HDR, b64, CRC, FTR); assert_false(try_dearmor(msg, len)); @@ -1606,7 +1608,8 @@ TEST_F(rnp_tests, test_stream_dearmor_edge_cases) } static void -add_openpgp_layers(const char *msg, pgp_dest_t &pgpdst, int compr, int encr) +add_openpgp_layers( + const char *msg, pgp_dest_t &pgpdst, int compr, int encr, rnp::SecurityContext &global_ctx) { pgp_source_t src = {}; pgp_dest_t dst = {}; @@ -1648,7 +1651,7 @@ TEST_F(rnp_tests, test_stream_deep_packet_nesting) pgp_dest_t dst = {}; /* add 30 compression layers and 2 encryption - must fail */ - add_openpgp_layers(message, dst, 30, 2); + add_openpgp_layers(message, dst, 30, 2, global_ctx); #ifdef DUMP_TEST_CASE /* remove ifdef if you want to write it to stdout */ pgp_source_t src = {}; @@ -1676,7 +1679,7 @@ TEST_F(rnp_tests, test_stream_deep_packet_nesting) dst_close(&dst, false); /* add 27 compression & 4 encryption layers - must succeed */ - add_openpgp_layers("message", dst, 27, 4); + add_openpgp_layers("message", dst, 27, 4, global_ctx); #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)); diff --git a/src/tests/support.cpp b/src/tests/support.cpp index c94a901..2867304 100644 --- a/src/tests/support.cpp +++ b/src/tests/support.cpp @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017-2019 [Ribose Inc](https://www.ribose.com). + * Copyright (c) 2017-2023 [Ribose Inc](https://www.ribose.com). * All rights reserved. * * Redistribution and use in source and binary forms, with or without modification, @@ -184,6 +184,7 @@ path_mkdir(mode_t mode, const char *first, ...) assert_int_equal(0, RNP_MKDIR(buffer, mode)); } +#ifndef WINSHELLAPI static int remove_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ftwbuf) { @@ -193,6 +194,7 @@ remove_cb(const char *fpath, const struct stat *sb, int typeflag, struct FTW *ft return ret; } +#endif static const char * get_tmp() @@ -737,7 +739,9 @@ check_json_field_bool(json_object *obj, const std::string &field, bool value) if (!json_object_is_type(fld, json_type_boolean)) { return false; } - return json_object_get_boolean(fld) == value; + // 'json_object_get_boolean' returns 'json_bool' which is 'int' on Windows + // but bool on other platforms + return (json_object_get_boolean(fld) ? true : false) == value; } bool diff --git a/src/tests/support.h b/src/tests/support.h index 6206924..543a0d4 100644 --- a/src/tests/support.h +++ b/src/tests/support.h @@ -69,8 +69,6 @@ char *mkdtemp(char *templ); #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 */ |