summaryrefslogtreecommitdiffstats
path: root/src/tests/cli_tests.py
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/cli_tests.py')
-rwxr-xr-xsrc/tests/cli_tests.py5048
1 files changed, 5048 insertions, 0 deletions
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())