From 4bff573631394aa2b56da2e2b305925b6498b285 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 13 Oct 2025 12:53:35 -0400 Subject: [PATCH 01/14] tests: clean error when cipher is None When cipher is None, if encrypting fails, throwing an exception itself fails because you can't append None to a string with the + operator. Handle this error case cleanly. --- src/tests/cli_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 9ffdef585..0621161c4 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -543,7 +543,8 @@ def gpg_encrypt_file(src, dst, cipher=None, z=None, armor=False): ret, _, err = run_proc(GPG, params) if ret != 0: - raise_err('gpg encryption failed for cipher ' + cipher, err) + raise_err('gpg encryption failed for cipher ' + + (cipher or 'unspecified'), err) def gpg_symencrypt_file(src, dst, cipher=None, z=None, armor=False, aead=None): src = path_for_gpg(src) From 9af437037b136e983594a8e92cdd97a469730f5e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 13 Oct 2025 11:04:53 -0400 Subject: [PATCH 02/14] tests: simplify decode_string_escape In GnuPG's DETAILS, it says about field 10 (the User ID field of GnuPG colon-delimited output: The value is quoted like a C string to avoid control characters The previous decode_string_escape() function was more complex than it needed to be, and it also didn't cover certain kinds of legitimate C escape sequences. This should be simpler and more robust. --- src/tests/cli_common.py | 24 +++--------------------- 1 file changed, 3 insertions(+), 21 deletions(-) diff --git a/src/tests/cli_common.py b/src/tests/cli_common.py index f8d7001fc..1d3d81f92 100644 --- a/src/tests/cli_common.py +++ b/src/tests/cli_common.py @@ -5,6 +5,7 @@ import os import re import shutil +import codecs from subprocess import Popen, PIPE RNP_ROOT = None @@ -185,27 +186,8 @@ def run_proc_windows(proc, params, stdin=None): 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_string_escape(s: str) -> str: + return codecs.escape_decode(bytes(s, 'utf-8'))[0].decode() def _decode(s): return s else: # Python 2 From 55535a9661631c43f1cf9cf0eb17371e2359c253 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 10 Oct 2025 18:00:05 -0400 Subject: [PATCH 03/14] tests: Extract fingerprint from gpg --quick-generate-key status Programmatically parsing the output of gpg --list-keys is explicitly discouraged by gpg upstream. The status file output is designed to be parsed, however (see DETAILS.gz in the GnuPG documentation), and it is automatically produced during --generate-key. This drops one unnecessary invocation of gpg in the test suite. --- src/tests/cli_tests.py | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 0621161c4..f0ff64048 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -169,11 +169,7 @@ def escape_regex(str): 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_GENERATED_KEY_FPR = r'^\[GNUPG:\] KEY_CREATED P ([0-9A-F]{40})\s*' RE_GPG_GOOD_SIGNATURE = r'(?s)^.*' \ r'gpg: Signature made .*' \ @@ -861,7 +857,7 @@ def gpg_check_features(): 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) + 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))] @@ -1158,13 +1154,15 @@ def test_generate_key_with_gpg_import_to_rnp(self): Generate key with GnuPG and import it to rnp ''' # Generate key in GnuPG + statusfile = os.path.join(WORKDIR, "gpg-status") ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--passphrase', - '', '--quick-generate-key', 'rsakey@gpg', 'rsa']) + '', '--status-file', statusfile, + '--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') + with open(statusfile, 'r') as status: + match = re.search(RE_GPG_GENERATED_KEY_FPR, status.read(), re.MULTILINE) + self.assertTrue(match, 'wrong gpg status output') keyfp = match.group(1) # Exporting generated public key ret, out, err = run_proc( From e7b2ced66dab2d6fb6ab74d5c6c5d180ac1a8e4a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Mon, 13 Oct 2025 12:47:25 -0400 Subject: [PATCH 04/14] tests: use gpg --compress-algo with names gpg(1) says: ``` --compress-algo name Use compression algorithm name. "zlib" is RFC-1950 ZLIB compression. "zip" is RFC-1951 ZIP compression which is used by PGP. "bzip2" is a more modern compression scheme that can compress some things better than zip or zlib, but at the cost of more memory used during compression and decompression. "uncompressed" or "none" disables compression. If this option is not used, the default behavior is to examine the recipient key preferences to see which algorithms the recipient supports. If all else fails, ZIP is used for maximum compatibility. ``` The fact that gpg accepts numeric values as an argument for this option is undocumented/accidental. There is no need to supply anything other than the name. --- src/tests/cli_tests.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index f0ff64048..f0bdf4173 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -220,7 +220,6 @@ def escape_regex(str): 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', @@ -523,7 +522,7 @@ def gpg_export_secret_key(userid, password, keyfile): 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]]] + params[pos:pos] = ['--compress-algo', z[0]] if len(z) > 1 and z[1] != None: params[pos:pos] = ['-z', str(z[1])] From 4c3b05f92523b7246b170af33a06ad4b970251be Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 15 Oct 2025 12:43:07 -0400 Subject: [PATCH 05/14] tests: Use "aes" instead of "aes128" for gpg gpg documents symmetric ciphers as aes, aes192, and aes256. While some versions of gpg offer an alias of "aes128" to mean the same thing as "aes", not all of them do. All of them implement "aes" itself. --- src/tests/cli_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index f0bdf4173..556bf45e2 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -221,7 +221,7 @@ def escape_regex(str): RE_KEYSTORE_INFO = r'(?s)^.*fatal: cannot set keystore info' # These are mostly identical -RNP_TO_GPG_CIPHERS = {'AES' : 'aes128', 'AES192' : 'aes192', 'AES256' : 'aes256', +RNP_TO_GPG_CIPHERS = {'AES' : 'aes', 'AES192' : 'aes192', 'AES256' : 'aes256', 'TWOFISH' : 'twofish', 'CAMELLIA128' : 'camellia128', 'CAMELLIA192' : 'camellia192', 'CAMELLIA256' : 'camellia256', 'IDEA' : 'idea', '3DES' : '3des', 'CAST5' : 'cast5', From ce04b2d7120a7eb47e9bca94f900844f73f23ef2 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 14 Oct 2025 19:28:12 -0400 Subject: [PATCH 06/14] tests: include --pinentry-mode=loopback in a few more places --- src/tests/cli_tests.py | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 556bf45e2..d330a5ba7 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -545,6 +545,7 @@ 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', + GPG_LOOPBACK, '--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]] @@ -1074,7 +1075,9 @@ def _rnpkey_generate_rsa(self, bits= None): 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', + ret, _, _ = run_proc(GPG, ['--batch', '--passphrase', PASSWORD, + GPG_LOOPBACK, + '--homedir', GPGHOME, '--import', path_for_gpg(os.path.join(RNPDIR, PUBRING)), path_for_gpg(os.path.join(RNPDIR, SECRING))]) @@ -1154,7 +1157,9 @@ def test_generate_key_with_gpg_import_to_rnp(self): ''' # Generate key in GnuPG statusfile = os.path.join(WORKDIR, "gpg-status") - ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--passphrase', + ret, _, _ = run_proc(GPG, ['--batch', + GPG_LOOPBACK, + '--homedir', GPGHOME, '--passphrase', '', '--status-file', statusfile, '--quick-generate-key', 'rsakey@gpg', 'rsa']) self.assertEqual(ret, 0, 'gpg key generation failed') @@ -2162,7 +2167,9 @@ def test_encryption_no_mdc(self): random_text(src, 64000) # Encrypt cleartext file with GPG params = ['--homedir', GPGHOME, '-c', '-z', '0', '--disable-mdc', '--s2k-count', - '65536', '--batch', '--passphrase', PASSWORD, '--output', + '65536', '--batch', + GPG_LOOPBACK, + '--passphrase', PASSWORD, '--output', path_for_gpg(dst), path_for_gpg(src)] ret, _, _ = run_proc(GPG, params) self.assertEqual(ret, 0, 'gpg symmetric encryption failed') @@ -2183,7 +2190,9 @@ def test_encryption_s2k(self): 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, + '--s2k-digest-algo', hash_alg, '--batch', + GPG_LOOPBACK, + '--passphrase', PASSWORD, '--output', dst, src] if s2k is not None: From 4d59cf282adb215c3f9a5480c9ea9007bc7138d4 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 10 Oct 2025 18:40:23 -0400 Subject: [PATCH 07/14] tests: Test RSA keypairs at size 3072 at least It is strongly deprecated to operate (sign, encrypt, verify, decrypt) with 1024-bit RSA at this point: https://www.rfc-editor.org/rfc/rfc9580.html#section-12.4-3 This way we expect success from gpg only from more modern RSA. --- src/tests/cli_tests.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index d330a5ba7..598276839 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -318,7 +318,7 @@ def clear_workfiles(): remove_files(*TEST_WORKFILES) TEST_WORKFILES = [] -def rnp_genkey_rsa(userid, bits=2048, pswd=PASSWORD): +def rnp_genkey_rsa(userid, bits=3072, 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']) @@ -1636,8 +1636,8 @@ def test_userid_escape(self): 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) + rnp_genkey_rsa(tracker_1) + rnp_genkey_rsa(tracker_2) #Read with rnpkeys ret, out_rnp, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys']) self.assertEqual(ret, 0, 'rnpkeys : failed to read keystore') @@ -1736,7 +1736,7 @@ def _test_userid_genkey(self, userid_beginning, weird_part, userid_end, weird_pa USERS.append(userid_beginning + weird_part2 + userid_end) # Run key generation for userid in USERS: - rnp_genkey_rsa(userid, 1024) + rnp_genkey_rsa(userid) # Read with GPG ret, out, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys', '--charset', CONSOLE_ENCODING]) self.assertEqual(ret, 0, 'gpg : failed to read keystore') @@ -3769,7 +3769,7 @@ def test_interactive_password(self): def test_set_current_time(self): # Too old date is64bit = sys.maxsize > 2 ** 32 - gparam = ['--homedir', RNPDIR2, '--notty', '--password', PASSWORD, '--generate-key', '--numbits', '1024', '--current-time'] + gparam = ['--homedir', RNPDIR2, '--notty', '--password', PASSWORD, '--generate-key', '--numbits', '2048', '--current-time'] rparam = ['--homedir', RNPDIR2, '--notty', '--remove-key'] ret, out, err = run_proc(RNPK, gparam + ['1950-01-02', '--userid', 'key-1950']) self.assertEqual(ret, 0) @@ -4341,8 +4341,8 @@ 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) + rnp_genkey_rsa('dummy1@rnp') + rnp_genkey_rsa('dummy2@rnp') gpg_import_pubring() gpg_import_secring() Encryption.CIPHERS += rnp_supported_ciphers(False) @@ -4568,7 +4568,7 @@ def test_encryption_multiple_recipients(self): PASSWORDS = ['password1', 'password2', 'password3'] # Generate multiple keys and import to GnuPG for uid, pswd in zip(USERIDS, KEYPASS): - rnp_genkey_rsa(uid, 1024, pswd) + rnp_genkey_rsa(uid, 3072, pswd) gpg_import_pubring() gpg_import_secring() @@ -4629,7 +4629,7 @@ def test_encryption_and_signing(self): 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) + rnp_genkey_rsa(uid, 3072, pswd) gpg_import_pubring() gpg_import_secring() @@ -4761,7 +4761,7 @@ def test_encryption_and_signing_pqc(self): def test_encryption_weird_userids_special_1(self): uid = WEIRD_USERID_SPECIAL_CHARS pswd = 'encSpecial1Pass' - rnp_genkey_rsa(uid, 1024, pswd) + rnp_genkey_rsa(uid, 3072, pswd) # Encrypt src = data_path(MSG_TXT) dst, dec = reg_workfiles('weird_userids_special_1', '.rnp', '.dec') @@ -4776,7 +4776,7 @@ def test_encryption_weird_userids_special_2(self): KEYPASS = ['encSpecial2Pass1', 'encSpecial2Pass2', 'encSpecial2Pass3', 'encSpecial2Pass4'] # Generate multiple keys for uid, pswd in zip(USERIDS, KEYPASS): - rnp_genkey_rsa(uid, 1024, pswd) + rnp_genkey_rsa(uid, 2048, pswd) # Encrypt to all recipients src = data_path(MSG_TXT) dst, dec = reg_workfiles('weird_userids_special_2', '.rnp', '.dec') @@ -4802,7 +4802,7 @@ def test_encryption_weird_userids_unicode(self): KEYPASS = ['encUnicodePass1', 'encUnicodePass2'] # Generate multiple keys for uid, pswd in zip(USERIDS_1, KEYPASS): - rnp_genkey_rsa(uid, 1024, pswd) + rnp_genkey_rsa(uid, 3072, pswd) # Encrypt to all recipients src = data_path('test_messages') + '/message.txt' dst, dec = reg_workfiles('weird_unicode', '.rnp', '.dec') @@ -5111,7 +5111,7 @@ def test_rnp_multiple_signers(self): # Generate multiple keys and import to GnuPG for uid, pswd in zip(USERIDS, KEYPASS): - rnp_genkey_rsa(uid, 1024, pswd) + rnp_genkey_rsa(uid, 3072, pswd) gpg_import_pubring() gpg_import_secring() @@ -5151,7 +5151,7 @@ def test_sign_weird_userids(self): # Generate multiple keys for uid, pswd in zip(USERIDS, KEYPASS): - rnp_genkey_rsa(uid, 1024, pswd) + rnp_genkey_rsa(uid, 3072, pswd) gpg_import_pubring() gpg_import_secring() @@ -5558,12 +5558,12 @@ def do_rnp_decrypt_sign(self, key_size): 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_3072(self): self.do_encrypt_verify(3072) 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_3072(self): self.do_rnp_decrypt_sign(3072) def test_rnp_decrypt_sign_4096(self): self.do_rnp_decrypt_sign(4096) def setUp(self): From 962252d7b68580fb7274646e6b123e589c56b6db Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Tue, 14 Oct 2025 18:11:25 -0400 Subject: [PATCH 08/14] tests: compression test: rely on keyring 5 (ECC) instead of RSA-1024 RSA 1024 is strongly deprecated, and tests in general shouldn't depend on those keys. --- src/tests/cli_tests.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 598276839..492bf056c 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -105,6 +105,7 @@ def escape_regex(str): KEYRING_DIR_1 = 'keyrings/1' KEYRING_DIR_2 = 'keyrings/2' KEYRING_DIR_3 = 'keyrings/3' +KEYRING_DIR_5 = 'keyrings/5' PUBRING_7 = 'keyrings/7/pubring.gpg' SECRING_G10 = 'test_stream_key_load/g10' KEY_ALICE_PUB = 'test_key_validity/alice-pub.asc' @@ -5037,7 +5038,7 @@ def test_rnp_compression(self): def test_rnp_compression_corner_cases(self): shutil.rmtree(RNPDIR) - kring = shutil.copytree(data_path(KEYRING_DIR_1), RNPDIR) + kring = shutil.copytree(data_path(KEYRING_DIR_5), RNPDIR) gpg_import_pubring() gpg_import_secring() From 025a792bd90889ba5d8e0b678e9477a3978d0629 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Thu, 16 Oct 2025 18:31:06 -0400 Subject: [PATCH 09/14] tests: replace inlinesigned messages with ECDSA sigs The old signatures were made from 1024-bit RSA. It would not be unreasonable for gpg to decline to verify signatures from 1024-bit RSA. Make sure the messages are shaped the same way, but the signatures come from a more modern ECDSA signing key. --- src/tests/cli_tests.py | 9 +++++---- src/tests/data/test_large_packet/4g.bzip2.gpg | Bin 3349 -> 5482 bytes .../message.txt.partial-1g | Bin 1139 -> 1007 bytes .../message.txt.partial-256 | Bin 724 -> 399 bytes .../message.txt.partial-signed | Bin 237 -> 169 bytes .../message.txt.partial-zero-last | Bin 717 -> 655 bytes src/tests/large-packet.cpp | 2 +- src/tests/partial-length.cpp | 6 +++--- 8 files changed, 9 insertions(+), 8 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 492bf056c..a509fbc2e 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -102,6 +102,7 @@ def escape_regex(str): SECRING = 'secring.gpg' PUBRING_1 = 'keyrings/1/pubring.gpg' SECRING_1 = 'keyrings/1/secring.gpg' +PUBRING_5 = 'keyrings/5/pubring.gpg' KEYRING_DIR_1 = 'keyrings/1' KEYRING_DIR_2 = 'keyrings/2' KEYRING_DIR_3 = 'keyrings/3' @@ -2425,14 +2426,14 @@ def test_rnpkeys_g10_def_key(self): def test_large_packet(self): # Verifying large packet file with GnuPG - kpath = path_for_gpg(data_path(PUBRING_1)) + kpath = path_for_gpg(data_path(PUBRING_5)) 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)) + kpath = path_for_gpg(data_path(PUBRING_5)) 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') @@ -2445,14 +2446,14 @@ def test_partial_length_public_key(self): 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)) + kpath = path_for_gpg(data_path(PUBRING_5)) 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)) + kpath = path_for_gpg(data_path(PUBRING_5)) 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') diff --git a/src/tests/data/test_large_packet/4g.bzip2.gpg b/src/tests/data/test_large_packet/4g.bzip2.gpg index 81b0ef6d3907148d574ee2360fd96a1f9a44094f..c6291d7e364bd2a61b8caba00d05f3dbe89018c2 100644 GIT binary patch literal 5482 zcmb7|dr(tX9)Qoi2_Xc~BoZIk)esUOh`T_8QbY`J!6eHo0eqloDW!0wrP3Y5(bamP zid@9SkYIrdM2HGhT?MqOR!i}*f}loP+hJBkM-(ft7SJNw>fY5EI+H`sPX6N$?o96e z&i9?)IhVgZ#EM8E-wFs{?UlG%x&3gj1GRF!Y#>t2<^l`=B_9Ab^CvFmnL&=WUQG6Q zh6g3x0HkZ<$=$i5SLU6`{ClhB&*yS{sqQ3Noqbv+EBa2Pp)PH>J31sRyEdFJ(XDy- zyqMP!Sk?9KGl%$&%e(n5K#T*x2jPH~Uq!l^dUytJqE0d-@F_IWp%#ziwT;4box!^+ zVTIasGh>4*2G&{zs8o!322eN`yy4QLv>dE3(IHMMV=+-V*BT*8zG3jHGtpsb1FYGjUMahL*E;^)F#-?K%zjt9v zI*T}f&wOYu5K~xNw}PixN2`ox6CG?(8_v3UB9oe%Z`7g}l8lv_!*ZPG&+yS@K>M)88lmG`@ zG01r{7B8!MS0^1X5&AkA|AxWawB7fMa!E|FPWl?x)%nJwl=HGjFfO6NJ6taOSY`Cw z>=V&+S||N>5|*a4(~I(5GX<<9Iz%RzMbd=AqVD(LHTA2El1W_q-pq{-TwKR|iUX3^Nmvap4Lfnk z8Y)Nod{fvkE)7x5wx};kVs;Pg=QTZ28MTvch1DG40aG{(3+dM7KYdxaiaY!(j0@qG zc3Mw9nAOt&!$P^$9Qb7OZ0wv0*$rf(u})?DEgE6}>I-n7R@lCn zX*Q#ylgh}HBKjgXc?FZgR>uCrpM&@TjEnyL6o%<{mWu^J zNiZyA3QJS=AI4^=B~S_xZe0~*$b#_$XJJ@Kw=%ch3Z_EBNc(WLyxBl<@qTYxTrveB zg%!PiRAAiEGLOurL1V-Wi#4u*wHq1gG{KTy9EEwzSTYFqf6tQQIVava(=1K1*>1GU|hdHW;7>#qE%qJJ1{I{3afeBx}MDN zpJL}iq%c3@_;Y4C*_p!h=d^WGS$JkRcfnDbc!$PJoeCt<%ZObnT9BJX^vNBkunUpG zk-^YLKsVXxR@dh5TvNGKYTLIm^&s5J*%csf6D^zq7Rs%bCBu&{VAfGPaaC4FDwVV3 zA-1@XC$3A@3>u`c>`Y-s>E}c!`&kK$3z0&viJaQh>GyK&bPMR8p2xV&FfPQH(ed8d zMLxLJ0fvQii@cJzxNUJo1MC(O*UtNo&G@wm7#AXiW%VPpnYVWWhJ{SwK;nx`sctQza7SfaBGJk)D0}7VOYo!zVngT*vF~S+llLwV0~xX z%GFcgLZtBSp_@3i^f(L)nL^{5X%eMyXBvzPF=lkHe{qUZjKKRA^2C*nH$EU)S7*Yw z5N=5}w~wcCYLZ}B$Ps=}e~bbh3OjLiWO;2F=k3jhaUoI|f9Q({O2#McbF0~T=}hxr(|PTH?B3XU@uR@HK0Hgn(d`tm9C+QJW+<7ca#xb*Ha4j68J z`SRt4j`DGeOU4U4HM6(7H8e;2mPkXKxkfk&N(w0efC2!QPnCjXPbDBpcQQSlSD?UEyZDpCZmHtd`UJi~n@L__f6?uq`U;na85CV%*nnNv7d+=}#M_i94LY;HHv<{{ouGSnU7+ literal 3349 zcmX?cCWzT7D#OyyF;Xo!vf}3LqpWon`Ac3nF)=U*0HFgAaWHU*0`UTdfMq^D&B`nx zJ#EbySN6)jG}~|5Nww#un&#UD}?4B8(L zI0$esFtRYP32?4B5yG-nF+gZkel&bW)5mE37%d-0%g53BVYGf6Z6A!bk4D>v)NUUu zs~r?hkhFRK|NlFV7pXeuXB}O~A5{@2CA>IdhN}~I(Y4tNCr%OE+v;1!Aa+*L*MNb6 zDS?53!3RtUXV^MiV4Up0zzY;Y0G?G17cOKjxaG_2rq;_NaB;Gmo5Kak70l`@m(5Cc zWWMq_goA}qp@)SrA;Y>Va_Wm1k5vH*&aJQVPc2Z+R&O_3>32M`_TG$TxseBrHx+Dt z8!Qu3oc?h2*;ewdXt$A=YlwiSiV!2ir92Lm2^=a-OeZPoJL>7(+X;2sU1!JR(-osj*-&{h2Z|Mx=Q=2REXCSHav?*Im- z0}TO6ZHflg3{N{;oLHF*_!cm9@oGsiNb$He`L7%(niVBlM* z%Tlo3w8cnsbJEE$*N{sVj#UPEb>3Eikw@i_9q5uE@ literal 1139 zcmX?ccmuOjREDLYW29Pe?z!0#^$ER7D zC1iH6L&`0`71t)7UETfo+|xkDP?sGXN?Hw4GtT829^hhH)w%vmK$z#0YtA1`J}`to zZfH;tU|`~4;873`oUnp3%V~k?sQhU74F2?y7sfk$Bk z`Kxhj_Jw(qgRdwpb2%U^EX*irpsd)yaKTr?)IiP0&25G%j~#7!f}BSV+XUagPMYfpkblI=0cA3_Gi6oo<)9`Eph>bRRMT4r&$F)B zSY1|oAe(c+Nt?-6{A(xm3(n;$Dt~Gt8~4Cs@2mB1R)_l^b4+KH&<(!)$MVB#0p`sG zLIN!u4)*g{6gdPMzRwY8QE*}?(w*vFe%tNITc=t+_Jb+XHmjMRa*DrvSmhD<_^QKa zCN>L;T@fjF#L`r@elc6jl39C~N#9Sv-lq5Oz694ljW0MBe*Ly+rOK5{CYg_XUAfP( X830XXTJ-!+>`9S_PEO~2cgO$$&m9FvyB2vTWP>WctGLP`T3` zNeZxlVr7aw^}m>bi&Fq5qUyx(&p^?^yQr**?Te+)&t3JMw^g@X5PXhj4ue%HYvY3^$ literal 724 zcmX@Y%gn&RSbeNuw&)=%FUHd^k{CcBV#ITb0&BdETynA#h&_K%*qT-EnJ)eF!3!x%zwpH+P-F-mj5c%>)u!@bVjoF%!LO} z^Uv%IXwi+bGo=H%=yE|(u?R=)Xm@BIuTZT>P9HeMB_P1(Hb OQ+`gaU-GYZf++xjfehvV diff --git a/src/tests/data/test_partial_length/message.txt.partial-signed b/src/tests/data/test_partial_length/message.txt.partial-signed index 8f2b270843f5c98029cc2b4320c688747a709f93..5f10a6f174c7e490bde9b18a17b6190543819a4c 100644 GIT binary patch delta 142 zcmaFMxRR0Y2rn}OhcKVan~j+ZCV$xwfG~oZ~iwYCW+^X{NYyd&gyc??CSs)syl=L delta 211 zcmZ3<_?D6H2rn}O2V?cIg4v>nth^@jsnkDbVPNE7P!>~Uc^PH1Kf-DSm(H2sRWKE- zOtB|l1hO(SfB+Y#08Cu_6!ZTK4n`IGg~$41T_!N?J#>WYUdKrj-v5~`@i$wF51q6A ze_+osTjhklsjSRi+nFc5-dJDGHswWK@^szXVL`z$?=CT1J6(6uCU=?T`Q5kYa~xdw z;K1bP{z;7=eC|!%$mK7vt48PP_L%Ftrl_5~)VF1B-L)Ie_g`;IWL$Uoq+XTe#|LM> GD*^xms$V?- 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 index d3341c4cccd8960097465ae184bdf6e81ac2a897..df2c7767cc5d9717d89d092ae79cb8b46bd4c850 100644 GIT binary patch delta 148 zcmX@h+Rw^&gqN9tLzvI_uiJltk8?Kiu`<WctGLP`T3`NeVC} ztW2?|9T%}OGk^dWrvOa6=`|z65sMp|-%_jOYU1ALbxhdQH-+nP)h?Ij&{O}DSH@Tc pI5GT{ZR>qzSIW#dQ+CF)-c8?Tg^Nl5U7~+$3Xgt`|Mq|B+W~NhJ4FBh delta 211 zcmeBYJgqN9tgR%Np!EDh(R$d$VSefc~vM?}mFer;Dvb>D4*&kuGf=lO2@G6)R zR;Jj~j*D2C89;!GQvfFZXA1Lw_k$7wlDii^XUs@T=vSY2C^1QMcbI=j>UF)BS#B#T zC#_i*H%a5O-m*>W{uL$kWqepx^!=5M*i;Fl=0aLG9ZJtuMq@`BAYeD{+Lnr2^hb1nN{;Rs~+R}4A!k@2J F0sy%hT5$jX diff --git a/src/tests/large-packet.cpp b/src/tests/large-packet.cpp index a25e20a48..1e5d7a131 100644 --- a/src/tests/large-packet.cpp +++ b/src/tests/large-packet.cpp @@ -36,7 +36,7 @@ TEST_F(rnp_tests, test_large_packet) /* 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_input_from_path(&input, "data/keyrings/5/pubring.gpg")); assert_rnp_success(rnp_load_keys(ffi, "GPG", input, RNP_LOAD_SAVE_PUBLIC_KEYS)); assert_rnp_success(rnp_input_destroy(input)); diff --git a/src/tests/partial-length.cpp b/src/tests/partial-length.cpp index 3371a9590..af1be716e 100644 --- a/src/tests/partial-length.cpp +++ b/src/tests/partial-length.cpp @@ -69,12 +69,12 @@ test_partial_length_init(rnp_ffi_t *ffi, uint32_t key_flags) 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_input_from_path(&input, "data/keyrings/5/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_input_from_path(&input, "data/keyrings/5/pubring.gpg")); assert_rnp_success(rnp_load_keys(*ffi, "GPG", input, key_flags)); assert_rnp_success(rnp_input_destroy(input)); } @@ -195,7 +195,7 @@ TEST_F(rnp_tests, test_partial_length_first_packet_length) 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_locate_key(ffi, "keyid", "0E33FD46FF10F19C", &key)); assert_rnp_success(rnp_op_sign_add_signature(sign, key, NULL)); assert_rnp_success(rnp_key_handle_destroy(key)); key = NULL; From a51dbb4cf024dd1c5eab0e03c2aa288e13945d1a Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 10 Oct 2025 18:49:21 -0400 Subject: [PATCH 10/14] tests: fix GnuPG User ID escaping and ordering brittleness GnuPG upstream has explicitly rejected programmatic interpretation of --list-keys output as unstable unless using the machine-readable flag --with-colons. Make the test less brittle by including --with-colons. Some versions and implementations of gpg don't use the exact same escaping as g10code's current development branches. What really matters is whether the unescaped strings match, so test the matches in decoded form, not encoded form. Furthermore, it doesn't matter what order the strings show up in for either implementation: compare the user IDs as sets instead of lists. --- src/tests/cli_tests.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index a509fbc2e..825f537e8 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -1644,13 +1644,15 @@ def test_userid_escape(self): 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']) + ret, out_gpg, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--with-colons', '--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') + self.assertEqual(set(map(decode_string_escape, tracker_rnp)), + set(map(decode_string_escape, tracker_gpg)), + 'userids from rnpkeys and gpg don\'t match') clear_keyrings() def test_key_revoke(self): @@ -1740,17 +1742,17 @@ def _test_userid_genkey(self, userid_beginning, weird_part, userid_end, weird_pa for userid in USERS: rnp_genkey_rsa(userid) # Read with GPG - ret, out, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys', '--charset', CONSOLE_ENCODING]) + ret, out, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--with-colons', '--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') + tracker_gpg = set(map(decode_string_escape, tracker_escaped)) + self.assertEqual(tracker_gpg, set(USERS), 'gpg : failed to find expected userids from keystore') # Read with rnpkeys ret, out, _ = 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') + tracker_rnp = set(map(decode_string_escape, tracker_escaped)) + self.assertEqual(tracker_rnp, set(USERS), 'rnpkeys : failed to find expected userids from keystore') clear_keyrings() def test_userid_unicode_genkeys(self): From e16225afd554c235f6b8ca545bed8e9116ebecb1 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 15 Oct 2025 11:50:20 -0400 Subject: [PATCH 11/14] tests: gpg might not compress inlinesigned message Some versions of gpg default to uncompressed messages when producing an inline-signed message. Don't assume that there is a compressed packet. --- src/tests/cli_tests.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 825f537e8..547ee60c8 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -4942,7 +4942,7 @@ def test_encryption_no_wrap(self): 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.*') + r':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) From 4d2e556e754052b3bb8d9d68668f3d9958b42aca Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Fri, 17 Oct 2025 14:47:49 -0400 Subject: [PATCH 12/14] tests: relax gpg handling of partial-length public keys gpg is expected to reject public key packets that are partial length. But at least some implementations skip over malformed pubkeys in the keyring without causing an error. Confirm that the output of `gpg --list-keys` on a pubring.gpg that contains only a partial-length public key is empty, rather than expecting it to return non-zero. See also: https://gitlab.com/sequoia-pgp/sequoia-chameleon-gnupg/-/issues/148 --- src/tests/cli_tests.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 547ee60c8..baa472007 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -2443,8 +2443,8 @@ def test_partial_length_signature(self): 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') + ret, out, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--list-keys']) + self.assertEqual(out, '', 'some listing emitted when reviewing partial-length public key packet') def test_partial_length_zero_last_chunk(self): # Verifying message in partial packets having 0-size last chunk with GnuPG From b865ccbc8c38d195ce2fb52bafd50d263f2e177e Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 15 Oct 2025 11:04:50 -0400 Subject: [PATCH 13/14] tests: Avoid testing El Gamal against gpg if gpg doesn't support it Some implementations of /usr/bin/gpg don't support El Gamal. If that's the case, run the El Gamal tests against rnp itself. --- src/tests/cli_tests.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index baa472007..5309f187f 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -34,6 +34,7 @@ GPG_AEAD_OCB = False GPG_NO_OLD = False GPG_BRAINPOOL = False +GPG_ELG = False TESTS_SUCCEEDED = [] TESTS_FAILED = [] TEST_WORKFILES = [] @@ -853,8 +854,10 @@ def rnp_cleartext_signing_gpg_to_rnp(filesize): clear_workfiles() def gpg_check_features(): - global GPG_AEAD, GPG_AEAD_EAX, GPG_AEAD_OCB, GPG_NO_OLD, GPG_BRAINPOOL + global GPG_ELG, GPG_AEAD, GPG_AEAD_EAX, GPG_AEAD_OCB, GPG_NO_OLD, GPG_BRAINPOOL _, out, _ = run_proc(GPG, ["--version"]) + # El Gamal + GPG_ELG = re.match(r'(?s)^.*ELG.*', out) is not None # 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 @@ -876,6 +879,7 @@ def gpg_check_features(): # 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_ELG: ' + str(GPG_ELG)) print('GPG_AEAD_EAX: ' + str(GPG_AEAD_EAX)) print('GPG_AEAD_OCB: ' + str(GPG_AEAD_OCB)) print('GPG_NO_OLD: ' + str(GPG_NO_OLD)) @@ -5285,6 +5289,13 @@ class EncryptElgamal(Encrypt): RNP_GENERATE_DSA_ELGAMAL_PATTERN = "16\n{0}\n" + @property + def elg_peer(self): + if GPG_ELG: + return self.gpg + else: + return self.rnp + @staticmethod def key_pfx(sign_key_size, enc_key_size): return "GnuPG_dsa_elgamal_%d_%d" % (sign_key_size, enc_key_size) @@ -5296,7 +5307,7 @@ def do_test_encrypt(self, sign_key_size, enc_key_size): # 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) + self._encrypt_decrypt(self.elg_peer, self.rnp) def do_test_decrypt(self, sign_key_size, enc_key_size): pfx = EncryptElgamal.key_pfx(sign_key_size, enc_key_size) @@ -5304,7 +5315,7 @@ def do_test_decrypt(self, sign_key_size, enc_key_size): self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE if sign_key_size == 1024: return - self._encrypt_decrypt(self.rnp, self.gpg) + self._encrypt_decrypt(self.rnp, self.elg_peer) def test_encrypt_P1024_1024(self): self.do_test_encrypt(1024, 1024) def test_encrypt_P1024_2048(self): self.do_test_encrypt(1024, 2048) @@ -5317,6 +5328,8 @@ def test_decrypt_P1234_1234(self): self.do_test_decrypt(1234, 1234) # 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): + if not GPG_ELG: + self.skipTest("gpg does not support El Gamal") 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) @@ -5324,7 +5337,7 @@ def test_generate_elgamal_key1536_in_gpg_and_encrypt(self): 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) + self._encrypt_decrypt(self.rnp, self.elg_peer) class EncryptEcdh(Encrypt): From f934805de6f90b7e9fc8ab1cd67aaa334b389e74 Mon Sep 17 00:00:00 2001 From: Daniel Kahn Gillmor Date: Wed, 15 Oct 2025 12:44:35 -0400 Subject: [PATCH 14/14] tests: skip testing old ciphers that gpg doesn't claim to support --- src/tests/cli_tests.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/src/tests/cli_tests.py b/src/tests/cli_tests.py index 5309f187f..83566dba0 100755 --- a/src/tests/cli_tests.py +++ b/src/tests/cli_tests.py @@ -35,6 +35,9 @@ GPG_NO_OLD = False GPG_BRAINPOOL = False GPG_ELG = False +GPG_3DES = False +GPG_IDEA = False +GPG_CAST5 = False TESTS_SUCCEEDED = [] TESTS_FAILED = [] TEST_WORKFILES = [] @@ -855,9 +858,14 @@ def rnp_cleartext_signing_gpg_to_rnp(filesize): def gpg_check_features(): global GPG_ELG, GPG_AEAD, GPG_AEAD_EAX, GPG_AEAD_OCB, GPG_NO_OLD, GPG_BRAINPOOL + global GPG_3DES, GPG_IDEA, GPG_CAST5 _, out, _ = run_proc(GPG, ["--version"]) # El Gamal GPG_ELG = re.match(r'(?s)^.*ELG.*', out) is not None + # old symmetric ciphers + GPG_3DES = re.match(r'(?s)^.*3DES.*', out) is not None + GPG_IDEA = re.match(r'(?s)^.*IDEA.*', out) is not None + GPG_CAST5 = re.match(r'(?s)^.*CAST5.*', out) is not None # 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 @@ -4354,6 +4362,15 @@ def setUpClass(cls): gpg_import_pubring() gpg_import_secring() Encryption.CIPHERS += rnp_supported_ciphers(False) + cipher_skip = [] + if not GPG_3DES: + cipher_skip += ['3DES'] + if not GPG_IDEA: + cipher_skip += ['IDEA'] + if not GPG_CAST5: + cipher_skip += ['CAST5'] + if cipher_skip: + Encryption.CIPHERS = list(filter(lambda x: x not in cipher_skip, Encryption.CIPHERS)) 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)