#!/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 ' 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 " 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 ') 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())