#!/usr/bin/env python

import logging
import os
import os.path
import re
import shutil
import sys
import tempfile
import time
import unittest
import random
from platform import architecture

from cli_common import (file_text, find_utility, is_windows, list_upto,
                        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
# Botan may cause AV during OCB decryption in certain cases, see https://github.com/randombit/botan/issues/3812
RNP_BOTAN_OCB_AV = False

if sys.version_info >= (3,):
    unichr = chr

def escape_regex(str):
    return '^' + ''.join((c, "[\\x{:02X}]".format(ord(c)))[0 <= ord(c) <= 0x20 \
        or c in ['[',']','(',')','|','"','$','.','*','^','$','\\','+','?','{','}']] for c in str) + '$'

UNICODE_LATIN_CAPITAL_A_GRAVE = unichr(192)
UNICODE_LATIN_SMALL_A_GRAVE = unichr(224)
UNICODE_LATIN_CAPITAL_A_MACRON = unichr(256)
UNICODE_LATIN_SMALL_A_MACRON = unichr(257)
UNICODE_GREEK_CAPITAL_HETA = unichr(880)
UNICODE_GREEK_SMALL_HETA = unichr(881)
UNICODE_GREEK_CAPITAL_OMEGA = unichr(937)
UNICODE_GREEK_SMALL_OMEGA = unichr(969)
UNICODE_CYRILLIC_CAPITAL_A = unichr(0x0410)
UNICODE_CYRILLIC_SMALL_A = unichr(0x0430)
UNICODE_CYRILLIC_CAPITAL_YA = unichr(0x042F)
UNICODE_CYRILLIC_SMALL_YA = unichr(0x044F)
UNICODE_SEQUENCE_1 = UNICODE_LATIN_CAPITAL_A_GRAVE + UNICODE_LATIN_SMALL_A_MACRON \
    + UNICODE_GREEK_CAPITAL_HETA + UNICODE_GREEK_SMALL_OMEGA \
    + UNICODE_CYRILLIC_CAPITAL_A + UNICODE_CYRILLIC_SMALL_YA
UNICODE_SEQUENCE_2 = UNICODE_LATIN_SMALL_A_GRAVE + UNICODE_LATIN_CAPITAL_A_MACRON \
    + UNICODE_GREEK_SMALL_HETA + UNICODE_GREEK_CAPITAL_OMEGA \
    + UNICODE_CYRILLIC_SMALL_A + UNICODE_CYRILLIC_CAPITAL_YA
WEIRD_USERID_UNICODE_1 = unichr(160) + unichr(161) \
    + UNICODE_SEQUENCE_1 + unichr(40960) + u'@rnp'
WEIRD_USERID_UNICODE_2 = unichr(160) + unichr(161) \
    + UNICODE_SEQUENCE_2 + unichr(40960) + u'@rnp'
WEIRD_USERID_SPECIAL_CHARS = '\\}{][)^*.+(\t\n|$@rnp'
WEIRD_USERID_SPACE = ' '
WEIRD_USERID_QUOTE = '"'
WEIRD_USERID_SPACE_AND_QUOTE = ' "'
WEIRD_USERID_QUOTE_AND_SPACE = '" '
WEIRD_USERID_TOO_LONG = 'x' * 125 + '@rnp' # totaling 129 (MAX_USER_ID + 1)

# Key userids
KEY_ENCRYPT = 'encryption@rnp'
KEY_SIGN_RNP = 'signing@rnp'
KEY_SIGN_GPG = 'signing@gpg'
KEY_ENC_RNP = 'enc@rnp'
AT_EXAMPLE = '@example.com'

# Keyrings
PUBRING = 'pubring.gpg'
SECRING = 'secring.gpg'
PUBRING_1 = 'keyrings/1/pubring.gpg'
SECRING_1 = 'keyrings/1/secring.gpg'
KEYRING_DIR_1 = 'keyrings/1'
KEYRING_DIR_3 = 'keyrings/3'
SECRING_G10 = 'test_stream_key_load/g10'
KEY_ALICE_PUB = 'test_key_validity/alice-pub.asc'
KEY_ALICE_SUB_PUB = 'test_key_validity/alice-sub-pub.pgp'
KEY_ALICE_SEC = 'test_key_validity/alice-sec.asc'
KEY_ALICE_SUB_SEC = 'test_key_validity/alice-sub-sec.pgp'
KEY_ALICE = 'Alice <alice@rnp>'
KEY_25519_NOTWEAK_SEC = 'test_key_edge_cases/key-25519-non-tweaked-sec.asc'

# Messages
MSG_TXT = 'test_messages/message.txt'
MSG_ES_25519 = 'test_messages/message.txt.enc-sign-25519'
MSG_SIG_CRCR = 'test_messages/message.text-sig-crcr.sig'

# Extensions
EXT_SIG = '.txt.sig'
EXT_ASC = '.txt.asc'
EXT_PGP = '.txt.pgp'

# Misc
GPG_LOOPBACK = '--pinentry-mode=loopback'

# Regexps
RE_RSA_KEY = r'(?s)^' \
r'# .*' \
r':public key packet:\s+' \
r'version 4, algo 1, created \d+, expires 0\s+' \
r'pkey\[0\]: \[(\d{4}) bits\]\s+' \
r'pkey\[1\]: \[17 bits\]\s+' \
r'keyid: ([0-9A-F]{16})\s+' \
r'# .*' \
r':user ID packet: "(.+)"\s+' \
r'# .*' \
r':signature packet: algo 1, keyid \2\s+' \
r'.*' \
r'# .*' \
r':public sub key packet:' \
r'.*' \
r':signature packet: algo 1, keyid \2\s+' \
r'.*$'

RE_RSA_KEY_LIST = r'^\s*' \
r'2 keys found\s+' \
r'pub\s+(\d{4})/RSA ([0-9a-z]{16}) \d{4}-\d{2}-\d{2} \[.*\]\s+' \
r'([0-9a-z]{40})\s+' \
r'uid\s+(.+)\s+' \
r'sub.+\s+' \
r'[0-9a-z]{40}\s+$'

RE_MULTIPLE_KEY_LIST = r'(?s)^\s*(\d+) (?:key|keys) found.*$'
RE_MULTIPLE_KEY_5 = r'(?s)^\s*' \
r'10 keys found.*' \
r'.+uid\s+0@rnp-multiple' \
r'.+uid\s+1@rnp-multiple' \
r'.+uid\s+2@rnp-multiple' \
r'.+uid\s+3@rnp-multiple' \
r'.+uid\s+4@rnp-multiple.*$'

RE_MULTIPLE_SUBKEY_3 = r'(?s)^\s*' \
r'3 keys found.*$'

RE_MULTIPLE_SUBKEY_8 = r'(?s)^\s*' \
r'8 keys found.*$'

RE_GPG_SINGLE_RSA_KEY = r'(?s)^\s*' \
r'.+-+\s*' \
r'pub\s+rsa.+' \
r'\s+([0-9A-F]{40})\s*' \
r'uid\s+.+rsakey@gpg.*'

RE_GPG_GOOD_SIGNATURE = r'(?s)^.*' \
r'gpg: Signature made .*' \
r'gpg: Good signature from "(.*)".*'

RE_RNP_GOOD_SIGNATURE = r'(?s)^.*' \
r'Good signature made .*' \
r'using .* key .*' \
r'pub .*' \
r'uid\s+(.*)\s*' \
r'Signature\(s\) verified successfully.*$'

RE_RNP_ENCRYPTED_KEY = r'(?s)^.*' \
r'Secret key packet.*' \
r'secret key material:.*' \
r'encrypted secret key data:.*' \
r'UserID packet.*' \
r'id: enc@rnp.*' \
r'Secret subkey packet.*' \
r'secret key material:.*' \
r'encrypted secret key data:.*$'

RE_RNP_REVOCATION_SIG = r'(?s)' \
r':armored input\n' \
r':off 0: packet header .* \(tag 2, len .*' \
r'Signature packet.*' \
r'version: 4.*' \
r'type: 32 \(Key revocation signature\).*' \
r'public key algorithm:.*' \
r'hashed subpackets:.*' \
r':type 33, len 21.*' \
r'issuer fingerprint:.*' \
r':type 2, len 4.*' \
r'signature creation time:.*' \
r':type 29.*' \
r'reason for revocation: (.*)' \
r'message: (.*)' \
r'unhashed subpackets:.*' \
r':type 16, len 8.*' \
r'issuer key ID: .*$'

RE_GPG_REVOCATION_IMPORT = r'(?s)^.*' \
r'key 0451409669FFDE3C: "Alice <alice@rnp>" revocation certificate imported.*' \
r'Total number processed: 1.*' \
r'new key revocations: 1.*$'

RE_SIG_1_IMPORT = r'(?s)^.*Import finished: 1 new signature, 0 unchanged, 0 unknown.*'

RE_KEYSTORE_INFO = r'(?s)^.*fatal: cannot set keystore info'

RNP_TO_GPG_ZALGS = { 'zip' : '1', 'zlib' : '2', 'bzip2' : '3' }
# These are mostly identical
RNP_TO_GPG_CIPHERS = {'AES' : 'aes128', 'AES192' : 'aes192', 'AES256' : 'aes256',
                      'TWOFISH' : 'twofish', 'CAMELLIA128' : 'camellia128',
                      'CAMELLIA192' : 'camellia192', 'CAMELLIA256' : 'camellia256',
                      'IDEA' : 'idea', '3DES' : '3des', 'CAST5' : 'cast5',
                      'BLOWFISH' : 'blowfish'}

# Error messages
RNP_DATA_DIFFERS = 'rnp decrypted data differs'
GPG_DATA_DIFFERS = 'gpg decrypted data differs'
KEY_GEN_FAILED = 'key generation failed'
KEY_LIST_FAILED = 'key list failed'
KEY_LIST_WRONG = 'wrong key list output'
PKT_LIST_FAILED = 'packet listing failed'
ALICE_IMPORT_FAIL = 'Alice key import failed'
ENC_FAILED = 'encryption failed'
DEC_FAILED = 'decryption failed'
DEC_DIFFERS = 'Decrypted data differs'
GPG_IMPORT_FAILED = 'gpg key import failed'

def check_packets(fname, regexp):
    ret, output, err = run_proc(GPG, ['--homedir', '.',
                                      '--list-packets', path_for_gpg(fname)])
    if ret != 0:
        logging.error(err)
        return None
    else:
        result = re.match(regexp, output)
        if not result:
            logging.debug('Wrong packets:')
            logging.debug(output)
        return result

def clear_keyrings():
    shutil.rmtree(RNPDIR, ignore_errors=True)
    os.mkdir(RNPDIR, 0o700)

    run_proc(GPGCONF, ['--homedir', GPGHOME, '--kill', 'gpg-agent'])
    while os.path.isdir(GPGDIR):
        try:
            shutil.rmtree(GPGDIR)
        except Exception:
            time.sleep(0.1)
    os.mkdir(GPGDIR, 0o700)

def allow_y2k38_on_32bit(filename):
    if architecture()[0] == '32bit':
        return [filename, filename + '_y2k38']
    else:
        return [filename]

def compare_files(src, dst, message):
    if file_text(src) != file_text(dst):
        raise_err(message)

def compare_file(src, string, message):
    if file_text(src) != string:
        raise_err(message)

def compare_file_any(srcs, string, message):
    for src in srcs:
        if file_text(src) == string:
            return
    raise_err(message)

def compare_file_ex(src, string, message, symbol='?'):
    ftext = file_text(src)
    if len(ftext) != len(string):
        raise_err(message)
    for i in range(0, len(ftext)):
        if (ftext[i] != symbol[0]) and (ftext[i] != string[i]):
            raise_err(message)

def remove_files(*args):
    for fpath in args:
        try:
            os.remove(fpath)
        except Exception:
            # Ignore if file cannot be removed
            pass

def reg_workfiles(mainname, *exts):
    global TEST_WORKFILES
    res = []
    for ext in exts:
        fpath = os.path.join(WORKDIR, mainname + ext)
        if fpath in TEST_WORKFILES:
            logging.warn('Warning! Path {} is already in TEST_WORKFILES'.format(fpath))
        else:
            TEST_WORKFILES += [fpath]
        res += [fpath]
    return res

def clear_workfiles():
    global TEST_WORKFILES
    remove_files(*TEST_WORKFILES)
    TEST_WORKFILES = []

def rnp_genkey_rsa(userid, bits=2048, pswd=PASSWORD):
    pipe = pswd_pipe(pswd)
    ret, _, err = run_proc(RNPK, ['--numbits', str(bits), '--homedir', RNPDIR, '--pass-fd', str(pipe),
                                  '--notty', '--s2k-iterations', '50000', '--userid', userid, '--generate-key'])
    os.close(pipe)
    if ret != 0:
        raise_err('rsa key generation failed', err)

def rnp_params_insert_z(params, pos, z):
    if z:
        if len(z) > 0 and z[0] != None:
            params[pos:pos] = ['--' + z[0]]
        if len(z) > 1 and z[1] != None:
            params[pos:pos] = ['-z', str(z[1])]

def rnp_params_insert_aead(params, pos, aead):
    if aead != None:
        params[pos:pos] = ['--aead=' + aead[0]] if len(aead) > 0 and aead[0] != None else ['--aead']
        if len(aead) > 1 and aead[1] != None:
            params[pos + 1:pos + 1] = ['--aead-chunk-bits=' + str(aead[1])]

def rnp_encrypt_file_ex(src, dst, recipients=None, passwords=None, aead=None, cipher=None,
                        z=None, armor=False, s2k_iter=False, s2k_msec=False):
    params = ['--homedir', RNPDIR, src, '--output', dst]
    # Recipients. None disables PK encryption, [] to use default key. Otherwise list of ids.
    if recipients != None:
        params[2:2] = ['--encrypt']
        for userid in reversed(recipients):
            params[2:2] = ['-r', escape_regex(userid)]
    # Passwords to encrypt to. None or [] disables password encryption.
    if passwords:
        if recipients is None:
            params[2:2] = ['-c']
        if s2k_iter != False:
            params += ['--s2k-iterations', str(s2k_iter)]
        if s2k_msec != False:
            params += ['--s2k-msec', str(s2k_msec)]
        pipe = pswd_pipe('\n'.join(passwords))
        params[2:2] = ['--pass-fd', str(pipe), '--passwords', str(len(passwords))]

    # Cipher or None for default
    if cipher: params[2:2] = ['--cipher', cipher]
    # Armor
    if armor: params += ['--armor']
    rnp_params_insert_aead(params, 2, aead)
    rnp_params_insert_z(params, 2, z)
    ret, _, err = run_proc(RNP, params)
    if passwords: os.close(pipe)
    if ret != 0:
        raise_err('rnp encryption failed with ' + cipher, err)

def rnp_encrypt_and_sign_file(src, dst, recipients, encrpswd, signers, signpswd,
                              aead=None, cipher=None, z=None, armor=False):
    params = ['--homedir', RNPDIR, '--sign', '--encrypt', src, '--output', dst]
    pipe = pswd_pipe('\n'.join(encrpswd + signpswd))
    params[2:2] = ['--pass-fd', str(pipe)]

    # Encrypting passwords if any
    if encrpswd:
        params[2:2] = ['--passwords', str(len(encrpswd))]
    # Adding recipients. If list is empty then default will be used.
    for userid in reversed(recipients):
        params[2:2] = ['-r', escape_regex(userid)]
    # Adding signers. If list is empty then default will be used.
    for signer in reversed(signers):
        params[2:2] = ['-u', escape_regex(signer)]
    # Cipher or None for default
    if cipher: params[2:2] = ['--cipher', cipher]
    # Armor
    if armor: params += ['--armor']
    rnp_params_insert_aead(params, 2, aead)
    rnp_params_insert_z(params, 2, z)

    ret, _, err = run_proc(RNP, params)
    os.close(pipe)
    if ret != 0:
        raise_err('rnp encrypt-and-sign failed', err)

def rnp_decrypt_file(src, dst, password = PASSWORD):
    pipe = pswd_pipe(password)
    ret, out, err = run_proc(
        RNP, ['--homedir', RNPDIR, '--pass-fd', str(pipe), '--decrypt', src, '--output', dst])
    os.close(pipe)
    if ret != 0:
        raise_err('rnp decryption failed', out + err)

def rnp_sign_file_ex(src, dst, signers, passwords, options = None):
    pipe = pswd_pipe('\n'.join(passwords))
    params = ['--homedir', RNPDIR, '--pass-fd', str(pipe), src]
    if dst: params += ['--output', dst]
    if 'cleartext' in options:
        params[4:4] = ['--clearsign']
    else:
        params[4:4] = ['--sign']
        if 'armor' in options: params += ['--armor']
        if 'detached' in options: params += ['--detach']

    for signer in reversed(signers):
        params[4:4] = ['--userid', escape_regex(signer)]

    ret, _, err = run_proc(RNP, params)
    os.close(pipe)
    if ret != 0:
        raise_err('rnp signing failed', err)


def rnp_sign_file(src, dst, signers, passwords, armor=False):
    options = []
    if armor: options += ['armor']
    rnp_sign_file_ex(src, dst, signers, passwords, options)


def rnp_sign_detached(src, signers, passwords, armor=False):
    options = ['detached']
    if armor: options += ['armor']
    rnp_sign_file_ex(src, None, signers, passwords, options)


def rnp_sign_cleartext(src, dst, signers, passwords):
    rnp_sign_file_ex(src, dst, signers, passwords, ['cleartext'])


def rnp_verify_file(src, dst, signer=None):
    params = ['--homedir', RNPDIR, '--verify-cat', src, '--output', dst]
    ret, out, err = run_proc(RNP, params)
    if ret != 0:
        raise_err('rnp verification failed', err + out)
    # Check RNP output
    match = re.match(RE_RNP_GOOD_SIGNATURE, err)
    if not match:
        raise_err('wrong rnp verification output', err)
    if signer and (match.group(1).strip() != signer.strip()):
        raise_err('rnp verification failed, wrong signer')


def rnp_verify_detached(sig, signer=None):
    ret, out, err = run_proc(RNP, ['--homedir', RNPDIR, '--verify', sig])
    if ret != 0:
        raise_err('rnp detached verification failed', err + out)
    # Check RNP output
    match = re.match(RE_RNP_GOOD_SIGNATURE, err)
    if not match:
        raise_err('wrong rnp detached verification output', err)
    if signer and (match.group(1).strip() != signer.strip()):
        raise_err('rnp detached verification failed, wrong signer'.format())


def rnp_verify_cleartext(src, signer=None):
    params = ['--homedir', RNPDIR, '--verify', src]
    ret, out, err = run_proc(RNP, params)
    if ret != 0:
        raise_err('rnp verification failed', err + out)
    # Check RNP output
    match = re.match(RE_RNP_GOOD_SIGNATURE, err)
    if not match:
        raise_err('wrong rnp verification output', err)
    if signer and (match.group(1).strip() != signer.strip()):
        raise_err('rnp verification failed, wrong signer')


def gpg_import_pubring(kpath=None):
    if not kpath:
        kpath = os.path.join(RNPDIR, PUBRING)
    ret, _, err = run_proc(
        GPG, ['--display-charset', CONSOLE_ENCODING, '--batch', '--homedir', GPGHOME, '--import', kpath])
    if ret != 0:
        raise_err(GPG_IMPORT_FAILED, err)


def gpg_import_secring(kpath=None, password = PASSWORD):
    if not kpath:
        kpath = os.path.join(RNPDIR, SECRING)
    ret, _, err = run_proc(
        GPG, ['--display-charset', CONSOLE_ENCODING, '--batch', '--passphrase', password, '--homedir', GPGHOME, '--import', kpath])
    if ret != 0:
        raise_err('gpg secret key import failed', err)


def gpg_export_secret_key(userid, password, keyfile):
    ret, _, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, GPG_LOOPBACK,
                                 '--yes', '--passphrase', password, '--output',
                                 path_for_gpg(keyfile), '--export-secret-key', userid])

    if ret != 0:
        raise_err('gpg secret key export failed', err)

def gpg_params_insert_z(params, pos, z):
    if z:
        if len(z) > 0 and z[0] != None:
            params[pos:pos] = ['--compress-algo', RNP_TO_GPG_ZALGS[z[0]]]
        if len(z) > 1 and z[1] != None:
            params[pos:pos] = ['-z', str(z[1])]

def gpg_encrypt_file(src, dst, cipher=None, z=None, armor=False):
    src = path_for_gpg(src)
    dst = path_for_gpg(dst)
    params = ['--homedir', GPGHOME, '-e', '-r', KEY_ENCRYPT, '--batch',
              '--trust-model', 'always', '--output', dst, src]
    if z: gpg_params_insert_z(params, 3, z)
    if cipher: params[3:3] = ['--cipher-algo', RNP_TO_GPG_CIPHERS[cipher]]
    if armor: params[2:2] = ['--armor']
    if GPG_NO_OLD: params[2:2] = ['--allow-old-cipher-algos']

    ret, out, err = run_proc(GPG, params)
    if ret != 0:
        raise_err('gpg encryption failed for cipher ' + cipher, err)

def gpg_symencrypt_file(src, dst, cipher=None, z=None, armor=False, aead=None):
    src = path_for_gpg(src)
    dst = path_for_gpg(dst)
    params = ['--homedir', GPGHOME, '-c', '--s2k-count', '65536', '--batch',
              '--passphrase', PASSWORD, '--output', dst, src]
    if z: gpg_params_insert_z(params, 3, z)
    if cipher: params[3:3] = ['--cipher-algo', RNP_TO_GPG_CIPHERS[cipher]]
    if GPG_NO_OLD: params[3:3] = ['--allow-old-cipher-algos']
    if armor: params[2:2] = ['--armor']
    if aead != None:
        if len(aead) > 0 and aead[0] != None:
            params[3:3] = ['--aead-algo', aead[0]]
        if len(aead) > 1 and aead[1] != None:
            params[3:3] = ['--chunk-size', str(aead[1] + 6)]
        params[3:3] = ['--rfc4880bis', '--force-aead']

    ret, out, err = run_proc(GPG, params)
    if ret != 0:
        raise_err('gpg symmetric encryption failed for cipher ' + cipher, err)


def gpg_decrypt_file(src, dst, keypass):
    src = path_for_gpg(src)
    dst = path_for_gpg(dst)
    ret, out, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, GPG_LOOPBACK, '--batch',
                                   '--yes', '--passphrase', keypass, '--trust-model',
                                   'always', '-o', dst, '-d', src])
    if ret != 0:
        raise_err('gpg decryption failed', err)


def gpg_verify_file(src, dst, signer=None):
    src = path_for_gpg(src)
    dst = path_for_gpg(dst)
    ret, out, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch',
                                   '--yes', '--trust-model', 'always', '-o', dst, '--verify', src])
    if ret != 0:
        raise_err('gpg verification failed', err)
    # Check GPG output
    match = re.match(RE_GPG_GOOD_SIGNATURE, err)
    if not match:
        raise_err('wrong gpg verification output', err)
    if signer and (match.group(1) != signer):
        raise_err('gpg verification failed, wrong signer')


def gpg_verify_detached(src, sig, signer=None):
    src = path_for_gpg(src)
    sig = path_for_gpg(sig)
    ret, _, err = run_proc(GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch', '--yes', '--trust-model',
                                 'always', '--verify', sig, src])
    if ret != 0:
        raise_err('gpg detached verification failed', err)
    # Check GPG output
    match = re.match(RE_GPG_GOOD_SIGNATURE, err)
    if not match:
        raise_err('wrong gpg detached verification output', err)
    if signer and (match.group(1) != signer):
        raise_err('gpg detached verification failed, wrong signer')


def gpg_verify_cleartext(src, signer=None):
    src = path_for_gpg(src)
    ret, _, err = run_proc(
        GPG, ['--display-charset', CONSOLE_ENCODING, '--homedir', GPGHOME, '--batch', '--yes', '--trust-model', 'always', '--verify', src])
    if ret != 0:
        raise_err('gpg cleartext verification failed', err)
    # Check GPG output
    match = re.match(RE_GPG_GOOD_SIGNATURE, err)
    if not match:
        raise_err('wrong gpg verification output', err)
    if signer and (match.group(1) != signer):
        raise_err('gpg verification failed, wrong signer')


def gpg_sign_file(src, dst, signer, z=None, armor=False):
    src = path_for_gpg(src)
    dst = path_for_gpg(dst)
    params = ['--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--yes',
              '--passphrase', PASSWORD, '--trust-model', 'always', '-u', signer, '-o',
              dst, '-s', src]
    if z: gpg_params_insert_z(params, 3, z)
    if armor: params.insert(2, '--armor')
    ret, _, err = run_proc(GPG, params)
    if ret != 0:
        raise_err('gpg signing failed', err)


def gpg_sign_detached(src, signer, armor=False, textsig=False):
    src = path_for_gpg(src)
    params = ['--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--yes',
              '--passphrase', PASSWORD, '--trust-model', 'always', '-u', signer,
              '--detach-sign', src]
    if armor: params.insert(2, '--armor')
    if textsig: params.insert(2, '--text')
    ret, _, err = run_proc(GPG, params)
    if ret != 0:
        raise_err('gpg detached signing failed', err)


def gpg_sign_cleartext(src, dst, signer):
    src = path_for_gpg(src)
    dst = path_for_gpg(dst)
    params = ['--homedir', GPGHOME, GPG_LOOPBACK, '--batch', '--yes', '--passphrase',
              PASSWORD, '--trust-model', 'always', '-u', signer, '-o', dst, '--clearsign', src]
    ret, _, err = run_proc(GPG, params)
    if ret != 0:
        raise_err('gpg cleartext signing failed', err)


def gpg_agent_clear_cache():
    run_proc(GPGCONF, ['--homedir', GPGHOME, '--kill', 'gpg-agent'])

'''
    Things to try here later on:
    - different symmetric algorithms
    - different file sizes (block len/packet len tests)
    - different public key algorithms
    - different compression levels/algorithms
'''


def gpg_to_rnp_encryption(filesize, cipher=None, z=None):
    '''
    Encrypts with GPG and decrypts with RNP
    '''
    src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [False, True]:
        # Encrypt cleartext file with GPG
        gpg_encrypt_file(src, dst, cipher, z, armor)
        # Decrypt encrypted file with RNP
        rnp_decrypt_file(dst, dec)
        compare_files(src, dec, RNP_DATA_DIFFERS)
        remove_files(dst, dec)
    clear_workfiles()


def file_encryption_rnp_to_gpg(filesize, z=None):
    '''
    Encrypts with RNP and decrypts with GPG and RNP
    '''
    # TODO: Would be better to do "with reg_workfiles() as src,dst,enc ... and
    # do cleanup at the end"
    src, dst, enc = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [False, True]:
        # Encrypt cleartext file with RNP
        rnp_encrypt_file_ex(src, enc, [KEY_ENCRYPT], None, None, None, z, armor)
        # Decrypt encrypted file with GPG
        gpg_decrypt_file(enc, dst, PASSWORD)
        compare_files(src, dst, GPG_DATA_DIFFERS)
        remove_files(dst)
        # Decrypt encrypted file with RNP
        rnp_decrypt_file(enc, dst)
        compare_files(src, dst, RNP_DATA_DIFFERS)
        remove_files(enc, dst)
    clear_workfiles()

'''
    Things to try later:
    - different public key algorithms
    - decryption with generated by GPG and imported keys
'''


def rnp_sym_encryption_gpg_to_rnp(filesize, cipher = None, z = None):
    src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [False, True]:
        # Encrypt cleartext file with GPG
        gpg_symencrypt_file(src, dst, cipher, z, armor)
        # Decrypt encrypted file with RNP
        rnp_decrypt_file(dst, dec)
        compare_files(src, dec, RNP_DATA_DIFFERS)
        remove_files(dst, dec)
    clear_workfiles()


def rnp_sym_encryption_rnp_to_gpg(filesize, cipher = None, z = None, s2k_iter = False, s2k_msec = False):
    src, dst, enc = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [False, True]:
        # Encrypt cleartext file with RNP
        rnp_encrypt_file_ex(src, enc, None, [PASSWORD], None, cipher, z, armor, s2k_iter, s2k_msec)
        # Decrypt encrypted file with GPG
        gpg_decrypt_file(enc, dst, PASSWORD)
        compare_files(src, dst, GPG_DATA_DIFFERS)
        remove_files(dst)
        # Decrypt encrypted file with RNP
        rnp_decrypt_file(enc, dst)
        compare_files(src, dst, RNP_DATA_DIFFERS)
        remove_files(enc, dst)
    clear_workfiles()

def rnp_sym_encryption_rnp_aead(filesize, cipher = None, z = None, aead = None, usegpg = False):
    src, dst, enc = reg_workfiles('cleartext', '.txt', '.rnp', '.enc')
    # Generate random file of required size
    random_text(src, filesize)
    # Encrypt cleartext file with RNP
    rnp_encrypt_file_ex(src, enc, None, [PASSWORD], aead, cipher, z)
    # Decrypt encrypted file with RNP
    rnp_decrypt_file(enc, dst)
    compare_files(src, dst, RNP_DATA_DIFFERS)
    remove_files(dst)

    if usegpg:
        # Decrypt encrypted file with GPG
        gpg_decrypt_file(enc, dst, PASSWORD)
        compare_files(src, dst, GPG_DATA_DIFFERS)
        remove_files(dst, enc)
        # Encrypt cleartext file with GPG
        gpg_symencrypt_file(src, enc, cipher, z, False, aead)
        # Decrypt encrypted file with RNP
        rnp_decrypt_file(enc, dst)
        compare_files(src, dst, RNP_DATA_DIFFERS)

    clear_workfiles()

def rnp_signing_rnp_to_gpg(filesize):
    src, sig, ver = reg_workfiles('cleartext', '.txt', '.sig', '.ver')
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [False, True]:
        # Sign file with RNP
        rnp_sign_file(src, sig, [KEY_SIGN_RNP], [PASSWORD], armor)
        # Verify signed file with RNP
        rnp_verify_file(sig, ver, KEY_SIGN_RNP)
        compare_files(src, ver, 'rnp verified data differs')
        remove_files(ver)
        # Verify signed message with GPG
        gpg_verify_file(sig, ver, KEY_SIGN_RNP)
        compare_files(src, ver, 'gpg verified data differs')
        remove_files(sig, ver)
    clear_workfiles()


def rnp_detached_signing_rnp_to_gpg(filesize):
    src, sig, asc = reg_workfiles('cleartext', '.txt', EXT_SIG, EXT_ASC)
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [True, False]:
        # Sign file with RNP
        rnp_sign_detached(src, [KEY_SIGN_RNP], [PASSWORD], armor)
        sigpath = asc if armor else sig
        # Verify signature with RNP
        rnp_verify_detached(sigpath, KEY_SIGN_RNP)
        # Verify signed message with GPG
        gpg_verify_detached(src, sigpath, KEY_SIGN_RNP)
        remove_files(sigpath)
    clear_workfiles()


def rnp_cleartext_signing_rnp_to_gpg(filesize):
    src, asc = reg_workfiles('cleartext', '.txt', EXT_ASC)
    # Generate random file of required size
    random_text(src, filesize)
    # Sign file with RNP
    rnp_sign_cleartext(src, asc, [KEY_SIGN_RNP], [PASSWORD])
    # Verify signature with RNP
    rnp_verify_cleartext(asc, KEY_SIGN_RNP)
    # Verify signed message with GPG
    gpg_verify_cleartext(asc, KEY_SIGN_RNP)
    clear_workfiles()


def rnp_signing_gpg_to_rnp(filesize, z=None):
    src, sig, ver = reg_workfiles('cleartext', '.txt', '.sig', '.ver')
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [True, False]:
        # Sign file with GPG
        gpg_sign_file(src, sig, KEY_SIGN_GPG, z, armor)
        # Verify file with RNP
        rnp_verify_file(sig, ver, KEY_SIGN_GPG)
        compare_files(src, ver, 'rnp verified data differs')
        remove_files(sig, ver)
    clear_workfiles()


def rnp_detached_signing_gpg_to_rnp(filesize, textsig=False):
    src, sig, asc = reg_workfiles('cleartext', '.txt', EXT_SIG, EXT_ASC)
    # Generate random file of required size
    random_text(src, filesize)
    for armor in [True, False]:
        # Sign file with GPG
        gpg_sign_detached(src, KEY_SIGN_GPG, armor, textsig)
        sigpath = asc if armor else sig
        # Verify file with RNP
        rnp_verify_detached(sigpath, KEY_SIGN_GPG)
    clear_workfiles()

def rnp_cleartext_signing_gpg_to_rnp(filesize):
    src, asc = reg_workfiles('cleartext', '.txt', EXT_ASC)
    # Generate random file of required size
    random_text(src, filesize)
    # Sign file with GPG
    gpg_sign_cleartext(src, asc, KEY_SIGN_GPG)
    # Verify signature with RNP
    rnp_verify_cleartext(asc, KEY_SIGN_GPG)
    # Verify signed message with GPG
    gpg_verify_cleartext(asc, KEY_SIGN_GPG)
    clear_workfiles()

def gpg_check_features():
    global GPG_AEAD, GPG_AEAD_EAX, GPG_AEAD_OCB, GPG_NO_OLD, GPG_BRAINPOOL
    _, out, _ = run_proc(GPG, ["--version"])
    # AEAD
    GPG_AEAD_EAX = re.match(r'(?s)^.*AEAD:.*EAX.*', out) is not None
    GPG_AEAD_OCB = re.match(r'(?s)^.*AEAD:.*OCB.*', out) is not None
    # Version 2.3.0-beta1598 and up drops support of 64-bit block algos
    match = re.match(r'(?s)^.*gpg \(GnuPG\) (\d+)\.(\d+)\.(\d+)(-beta(\d+))?.*$', out)
    if not match:
        raise_err('Failed to parse GnuPG version.')
    ver = [int(match.group(1)), int(match.group(2)), int(match.group(3))]
    beta = int(match.group(5)) if match.group(5) else 0
    if not beta:
        GPG_NO_OLD = ver >= [2, 3, 0]
    else:
        GPG_NO_OLD = ver == [2, 3, 0] and (beta >= 1598)
    # Version 2.4.0 and up doesn't support EAX and doesn't has AEAD in output
    if ver >= [2, 4, 0]:
        GPG_AEAD_OCB = True
        GPG_AEAD_EAX = False
    GPG_AEAD = GPG_AEAD_OCB or GPG_AEAD_EAX
    # Check whether Brainpool curves are supported
    _, out, _ = run_proc(GPG, ["--with-colons", "--list-config", "curve"])
    GPG_BRAINPOOL = re.match(r'(?s)^.*brainpoolP256r1.*', out) is not None
    print('GPG_AEAD_EAX: ' + str(GPG_AEAD_EAX))
    print('GPG_AEAD_OCB: ' + str(GPG_AEAD_OCB))
    print('GPG_NO_OLD: ' + str(GPG_NO_OLD))
    print('GPG_BRAINPOOL: ' + str(GPG_BRAINPOOL))

def rnp_check_features():
    global RNP_TWOFISH, RNP_BRAINPOOL, RNP_AEAD, RNP_AEAD_EAX, RNP_AEAD_OCB, RNP_AEAD_OCB_AES, RNP_IDEA, RNP_BLOWFISH, RNP_CAST5, RNP_RIPEMD160
    global RNP_BOTAN_OCB_AV
    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
    # Botan OCB crash
    if re.match(r'(?s)^.*Backend.*Botan.*', out):
        match = re.match(r'(?s)^.*Backend version: ([\d]+)\.([\d]+)\.([\d]+).*$', out)
        ver = [int(match.group(1)), int(match.group(2)), int(match.group(3))]
        if ver <= [2, 19, 3]:
            RNP_BOTAN_OCB_AV = True
        if (ver >= [3, 0, 0]) and (ver <= [3, 2, 0]):
            RNP_BOTAN_OCB_AV = True
    # Twofish
    RNP_TWOFISH = re.match(r'(?s)^.*Encryption:.*TWOFISH.*', out) is not None
    # Brainpool curves
    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))
    print('RNP_BOTAN_OCB_AV: ' + str(RNP_BOTAN_OCB_AV))

def setup(loglvl):
    # Setting up directories.
    global RMWORKDIR, WORKDIR, RNPDIR, RNP, RNPK, GPG, GPGDIR, GPGHOME, GPGCONF
    logging.basicConfig(stream=sys.stderr, format="%(message)s")
    logging.getLogger().setLevel(loglvl)
    WORKDIR = tempfile.mkdtemp(prefix='rnpctmp')
    set_workdir(WORKDIR)
    RMWORKDIR = True

    logging.info('Running in ' + WORKDIR)

    RNPDIR = os.path.join(WORKDIR, '.rnp')
    RNP = os.getenv('RNP_TESTS_RNP_PATH') or 'rnp'
    RNPK = os.getenv('RNP_TESTS_RNPKEYS_PATH') or 'rnpkeys'
    shutil.rmtree(RNPDIR, ignore_errors=True)
    os.mkdir(RNPDIR, 0o700)

    os.environ["RNP_LOG_CONSOLE"] = "1"

    GPGDIR = os.path.join(WORKDIR, '.gpg')
    GPGHOME = path_for_gpg(GPGDIR) if is_windows() else GPGDIR
    GPG = os.getenv('RNP_TESTS_GPG_PATH') or find_utility('gpg')
    GPGCONF = os.getenv('RNP_TESTS_GPGCONF_PATH') or find_utility('gpgconf')
    gpg_check_features()
    rnp_check_features()
    shutil.rmtree(GPGDIR, ignore_errors=True)
    os.mkdir(GPGDIR, 0o700)

def data_path(subpath):
    ''' Constructs path to the tests data file/dir'''
    return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', subpath)

def key_path(file_base_name, secret):
    ''' Constructs path to the .gpg file'''
    path=os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data/cli_EncryptSign',
                      file_base_name)
    return ''.join([path, '-sec' if secret else '', '.gpg'])

def rnp_supported_ciphers(aead = False):
    ciphers = ['AES', 'AES192', 'AES256']
    if aead and RNP_AEAD_OCB_AES:
        return ciphers
    ciphers += ['CAMELLIA128', 'CAMELLIA192', 'CAMELLIA256']
    if RNP_TWOFISH:
        ciphers += ['TWOFISH']
    # AEAD supports only 128-bit block ciphers
    if aead:
        return ciphers
    ciphers += ['3DES']
    if RNP_IDEA:
        ciphers += ['IDEA']
    if RNP_BLOWFISH:
        ciphers += ['BLOWFISH']
    if RNP_CAST5:
        ciphers += ['CAST5']
    return ciphers

class TestIdMixin(object):

    @property
    def test_id(self):
        return "".join(self.id().split('.')[1:3])

class KeyLocationChooserMixin(object):
    def __init__(self):
        # If set it will try to import a key from provided location
        # otherwise it will try to generate a key
        self.__op_key_location = None
        self.__op_key_gen_cmd = None

    @property
    def operation_key_location(self):
        return self.__op_key_location

    @operation_key_location.setter
    def operation_key_location(self, key):
        if (type(key) is not tuple): raise RuntimeError("Key must be tuple(pub,sec)")
        self.__op_key_location = key
        self.__op_key_gen_cmd = None

    @property
    def operation_key_gencmd(self):
        return self.__op_key_gen_cmd

    @operation_key_gencmd.setter
    def operation_key_gencmd(self, cmd):
        self.__op_key_gen_cmd = cmd
        self.__op_key_location = None

'''
    Things to try here later on:
    - different public key algorithms
    - different key protection levels/algorithms
    - armored import/export
'''
class Keystore(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        clear_keyrings()

    @classmethod
    def tearDownClass(cls):
        clear_keyrings()

    def tearDown(self):
        clear_workfiles()

    def _rnpkey_generate_rsa(self, bits= None):
        # Setup command line params
        if bits:
            params = ['--numbits', str(bits)]
        else:
            params = []
            bits = 2048

        userid = str(bits) + '@rnptest'
        # Open pipe for password
        pipe = pswd_pipe(PASSWORD)
        params = params + ['--homedir', RNPDIR, '--pass-fd', str(pipe),
                           '--userid', userid, '--s2k-iterations', '50000', '--generate-key']
        # Run key generation
        ret, _, _ = run_proc(RNPK, params)
        os.close(pipe)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Check packets using the gpg
        match = check_packets(os.path.join(RNPDIR, PUBRING), RE_RSA_KEY)
        self.assertTrue(match, 'generated key check failed')
        keybits = int(match.group(1))
        self.assertLessEqual(keybits, bits, 'too much bits')
        self.assertGreater(keybits, bits - 8, 'too few bits')
        keyid = match.group(2)
        self.assertEqual(match.group(3), userid, 'wrong user id')
        # List keys using the rnpkeys
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, KEY_LIST_FAILED)
        match = re.match(RE_RSA_KEY_LIST, out)
        # Compare key ids
        self.assertTrue(match, 'wrong RSA key list output')
        self.assertEqual(match.group(3)[-16:], match.group(2), 'wrong fp')
        self.assertEqual(match.group(2), keyid.lower(), 'wrong keyid')
        self.assertEqual(match.group(1), str(bits), 'wrong key bits in list')
        # Import key to the gnupg
        ret, _, _ = run_proc(GPG, ['--batch', '--passphrase', PASSWORD, '--homedir',
                                       GPGHOME, '--import',
                                       path_for_gpg(os.path.join(RNPDIR, PUBRING)),
                                       path_for_gpg(os.path.join(RNPDIR, SECRING))])
        self.assertEqual(ret, 0, GPG_IMPORT_FAILED)
        # Cleanup and return
        clear_keyrings()

    def test_generate_default_rsa_key(self):
        self._rnpkey_generate_rsa()

    def test_rnpkeys_keygen_invalid_parameters(self):
        # Pass invalid numbits
        ret, _, err = run_proc(RNPK, ['--numbits', 'wrong', '--homedir', RNPDIR, '--password', 'password',
                                      '--userid', 'wrong', '--generate-key'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*wrong bits value: wrong.*')
        # Too small
        ret, _, err = run_proc(RNPK, ['--numbits', '768', '--homedir', RNPDIR, '--password', 'password',
                                      '--userid', '768', '--generate-key'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*wrong bits value: 768.*')
        # Wrong hash algorithm
        ret, _, err = run_proc(RNPK, ['--hash', 'BAD_HASH', '--homedir', RNPDIR, '--password', 'password',
                                      '--userid', 'bad_hash', '--generate-key'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Unsupported hash algorithm: BAD_HASH.*')
        # Wrong S2K iterations
        ret, _, err = run_proc(RNPK, ['--s2k-iterations', 'WRONG_ITER', '--homedir', RNPDIR, '--password', 'password',
                                      '--userid', 'wrong_iter', '--generate-key'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Wrong iterations value: WRONG_ITER.*')
        # Wrong S2K msec
        ret, _, err = run_proc(RNPK, ['--s2k-msec', 'WRONG_MSEC', '--homedir', RNPDIR, '--password', 'password',
                                      '--userid', 'wrong_msec', '--generate-key'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Invalid s2k msec value: WRONG_MSEC.*')
        # Wrong cipher
        ret, _, err = run_proc(RNPK, ['--cipher', 'WRONG_AES', '--homedir', RNPDIR, '--password', 'password',
                                      '--userid', 'wrong_aes', '--generate-key'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: WRONG_AES.*Failed to process argument --cipher.*')

    def test_generate_multiple_rsa_key__check_if_available(self):
        '''
        Generate multiple RSA keys and check if they are all available
        '''
        clear_keyrings()
        # Generate 5 keys with different user ids
        for i in range(0, 5):
            # generate the next key
            pipe = pswd_pipe(PASSWORD)
            userid = str(i) + '@rnp-multiple'
            ret, _, _ = run_proc(RNPK, ['--numbits', '2048', '--homedir', RNPDIR, '--s2k-msec', '100',
                                        '--cipher', 'AES-128', '--pass-fd', str(pipe), '--userid', userid,
                                        '--generate-key'])
            os.close(pipe)
            self.assertEqual(ret, 0, KEY_GEN_FAILED)
            # list keys using the rnpkeys, checking whether it reports correct key
            # number
            ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
            self.assertEqual(ret, 0, KEY_LIST_FAILED)
            match = re.match(RE_MULTIPLE_KEY_LIST, out)
            self.assertTrue(match, KEY_LIST_WRONG)
            self.assertEqual(match.group(1), str((i + 1) * 2), 'wrong key count')

        # Checking the 5 keys output
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, KEY_LIST_FAILED)
        self.assertRegex(out, RE_MULTIPLE_KEY_5, KEY_LIST_WRONG)

        # Cleanup and return
        clear_keyrings()

    def test_generate_key_with_gpg_import_to_rnp(self):
        '''
        Generate key with GnuPG and import it to rnp
        '''
        # Generate key in GnuPG
        ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--passphrase',
                                       '', '--quick-generate-key', 'rsakey@gpg', 'rsa'])
        self.assertEqual(ret, 0, 'gpg key generation failed')
        # Getting fingerprint of the generated key
        ret, out, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--list-keys'])
        match = re.match(RE_GPG_SINGLE_RSA_KEY, out)
        self.assertTrue(match, 'wrong gpg key list output')
        keyfp = match.group(1)
        # Exporting generated public key
        ret, out, err = run_proc(
            GPG, ['--batch', '--homedir', GPGHOME, '--armor', '--export', keyfp])
        self.assertEqual(ret, 0, 'gpg : public key export failed')
        pubpath = os.path.join(RNPDIR, keyfp + '-pub.asc')
        with open(pubpath, 'w+') as f:
            f.write(out)
        # Exporting generated secret key
        ret, out, err = run_proc(
            GPG, ['--batch', '--homedir', GPGHOME, '--armor', '--export-secret-key', keyfp])
        self.assertEqual(ret, 0, 'gpg : secret key export failed')
        secpath = os.path.join(RNPDIR, keyfp + '-sec.asc')
        with open(secpath, 'w+') as f:
            f.write(out)
        # Importing public key to rnp
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', pubpath])
        self.assertEqual(ret, 0, 'rnp : public key import failed')
        # Importing secret key to rnp
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', secpath])
        self.assertEqual(ret, 0, 'rnp : secret key import failed')

    def test_generate_with_rnp_import_to_gpg(self):
        '''
        Generate key with RNP and export it and then import to GnuPG
        '''
        # Open pipe for password
        pipe = pswd_pipe(PASSWORD)
        # Run key generation
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
                                        '--userid', 'rsakey@rnp', '--generate-key'])
        os.close(pipe)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Export key
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'rsakey@rnp'])
        self.assertEqual(ret, 0, 'key export failed')
        pubpath = os.path.join(RNPDIR, 'rnpkey-pub.asc')
        with open(pubpath, 'w+') as f:
            f.write(out)
        # Import key with GPG
        ret, _, _ = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import',
                                       path_for_gpg(pubpath)])
        self.assertEqual(ret, 0, 'gpg : public key import failed')

    def test_generate_to_kbx(self):
        '''
        Generate KBX with RNP and ensurethat the key can be read with GnuPG
        '''
        clear_keyrings()
        pipe = pswd_pipe(PASSWORD)
        kbx_userid_tracker = 'kbx_userid_tracker@rnp'
        # Run key generation
        ret, out, err = run_proc(RNPK, ['--gen-key', '--keystore-format', 'GPG21',
                                        '--userid', kbx_userid_tracker, '--homedir',
                                        RNPDIR, '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Read KBX with GPG
        ret, out, err = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys'])
        self.assertEqual(ret, 0, 'gpg : failed to read KBX')
        self.assertTrue(kbx_userid_tracker in out, 'gpg : failed to read expected key from KBX')
        clear_keyrings()

    def test_generate_protection_pass_fd(self):
        '''
        Generate key with RNP, using the --pass-fd parameter, and make sure key is encrypted
        '''
        clear_keyrings()
        # Open pipe for password
        pipe = pswd_pipe(PASSWORD)
        # Run key generation
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
                                        '--userid', KEY_ENC_RNP, '--generate-key'])
        os.close(pipe)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Check packets using the gpg
        params = ['--homedir', RNPDIR, '--list-packets', os.path.join(RNPDIR, SECRING)]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, RE_RNP_ENCRYPTED_KEY, 'wrong encrypted secret key listing')

    def test_generate_protection_password(self):
        '''
        Generate key with RNP, using the --password parameter, and make sure key is encrypted
        '''
        clear_keyrings()
        params = ['--homedir', RNPDIR, '--password', 'password', '--userid', KEY_ENC_RNP, '--generate-key']
        ret, _, _ = run_proc(RNPK, params)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Check packets using the gpg
        params = ['--homedir', RNPDIR, '--list-packets', os.path.join(RNPDIR, SECRING)]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, RE_RNP_ENCRYPTED_KEY, 'wrong encrypted secret key listing')

    def test_generate_unprotected_key(self):
        '''
        Generate key with RNP, using the --password parameter, and make sure key is encrypted
        '''
        clear_keyrings()
        params = ['--homedir', RNPDIR, '--password=', '--userid', KEY_ENC_RNP, '--generate-key']
        ret, _, _ = run_proc(RNPK, params)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Check packets using the gpg
        params = ['--homedir', RNPDIR, '--list-packets', os.path.join(RNPDIR, SECRING)]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertNotRegex(out, RE_RNP_ENCRYPTED_KEY, 'wrong unprotected secret key listing')

    def test_generate_preferences(self):
        pipe = pswd_pipe(PASSWORD)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe), '--userid',
                                      'eddsa_25519_prefs', '--generate-key', '--expert'], '22\n')
        os.close(pipe)
        self.assertEqual(ret, 0)
        ret, out, _ = run_proc(RNP, ['--list-packets', os.path.join(RNPDIR, PUBRING)])
        self.assertRegex(out, r'.*preferred symmetric algorithms: AES-256, AES-192, AES-128 \(9, 8, 7\).*')
        self.assertRegex(out, r'.*preferred hash algorithms: SHA256, SHA384, SHA512, SHA224 \(8, 9, 10, 11\).*')

    def test_import_signatures(self):
        clear_keyrings()
        RE_SIG_2_UNCHANGED = r'(?s)^.*Import finished: 0 new signatures, 2 unchanged, 0 unknown.*'
        # Import command without the path parameter
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs'])
        self.assertNotEqual(ret, 0, 'Sigs import without file failed')
        self.assertRegex(err, r'(?s)^.*Import path isn\'t specified.*', 'Sigs import without file wrong output')
        # Import command with invalid path parameter
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', data_path('test_key_validity/alice-rev-no-file.pgp')])
        self.assertNotEqual(ret, 0, 'Sigs import with invalid path failed')
        self.assertRegex(err, r'(?s)^.*Failed to create input for .*', 'Sigs import with invalid path wrong output')
        # Try to import signature to empty keyring
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', data_path('test_key_validity/alice-rev.pgp')])
        self.assertEqual(ret, 0, 'Alice key rev import failed')
        self.assertRegex(err, r'(?s)^.*Import finished: 0 new signatures, 0 unchanged, 1 unknown.*', 'Alice key rev import wrong output')
        # Import Basil's key
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/basil-pub.asc')])
        self.assertEqual(ret, 0, 'Basil key import failed')
        # Try to import Alice's signatures with Basil's key only
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sigs.pgp')])
        self.assertEqual(ret, 0, 'Alice sigs import failed')
        self.assertRegex(err, r'(?s)^.*Import finished: 0 new signatures, 0 unchanged, 2 unknown.*', 'Alice sigs import wrong output')
        # Import Alice's key without revocation/direct-key signatures
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_PUB)])
        self.assertEqual(ret, 0, ALICE_IMPORT_FAIL)
        # Import key revocation signature
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', data_path('test_key_validity/alice-rev.pgp')])
        self.assertEqual(ret, 0, 'Alice key rev import failed')
        self.assertRegex(err, RE_SIG_1_IMPORT, 'Alice key rev import wrong output')
        # Import direct-key signature
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-revoker-sig.pgp')])
        self.assertEqual(ret, 0, 'Alice direct-key sig import failed')
        self.assertRegex(err, RE_SIG_1_IMPORT, 'Alice direct-key sig import wrong output')
        # Try to import two signatures again
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sigs.pgp')])
        self.assertEqual(ret, 0, 'Alice sigs reimport failed')
        self.assertRegex(err, RE_SIG_2_UNCHANGED, 'Alice sigs file reimport wrong output')
        # Import two signatures again via stdin
        stext = file_text(data_path('test_key_validity/alice-sigs.asc'))
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', '-'], stext)
        self.assertEqual(ret, 0, 'Alice sigs stdin reimport failed')
        self.assertRegex(err, RE_SIG_2_UNCHANGED, 'Alice sigs stdin reimport wrong output')
        # Import two signatures via env variable
        os.environ["SIG_FILE"] = stext
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', 'env:SIG_FILE'])
        self.assertEqual(ret, 0, 'Alice sigs env reimport failed')
        self.assertRegex(err, RE_SIG_2_UNCHANGED, 'Alice sigs var reimport wrong output')
        # Try to import malformed signatures
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sigs-malf.pgp')])
        self.assertNotEqual(ret, 0, 'Alice malformed sigs import failed')
        self.assertRegex(err, r'(?s)^.*Failed to import signatures from .*', 'Alice malformed sigs wrong output')

    def test_export_revocation(self):
        clear_keyrings()
        OUT_NO_REV = 'no-revocation.pgp'
        OUT_ALICE_REV = 'alice-revocation.pgp'
        # Import Alice's public key and be unable to export revocation
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_PUB)])
        self.assertEqual(ret, 0, ALICE_IMPORT_FAIL)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'alice'])
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*Revoker secret key not found.*', 'Wrong pubkey revocation export output')
        # Import Alice's secret key and subkey
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
        self.assertEqual(ret, 0, 'Alice secret key import failed')
        # Attempt to export revocation without specifying key
        pipe = pswd_pipe(PASSWORD)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*You need to specify key to generate revocation for.*', 'Wrong no key revocation export output')
        # Attempt to export revocation for unknown key
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'basil'])
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*Key matching \'basil\' not found.*', 'Wrong unknown key revocation export output')
        # Attempt to export revocation for subkey
        pipe = pswd_pipe(PASSWORD)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'DD23CEB7FEBEFF17'])
        os.close(pipe)
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*Key matching \'DD23CEB7FEBEFF17\' not found.*', 'Wrong subkey revocation export output')
        # Attempt to export revocation with too broad search
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/basil-sec.asc')])
        self.assertEqual(ret, 0, 'Basil secret key import failed')
        pipe = pswd_pipe(PASSWORD)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'rnp', '--pass-fd', str(pipe),
                                      '--output', OUT_NO_REV, '--force'])
        os.close(pipe)
        self.assertNotEqual(ret, 0, 'Failed to fail to export revocation')
        self.assertFalse(os.path.isfile(OUT_NO_REV), 'Failed to fail to export revocation')
        self.assertRegex(err, r'(?s)^.*Ambiguous input: too many keys found for \'rnp\'.*', 'Wrong revocation export output')
        # Finally successfully export revocation
        pipe = pswd_pipe(PASSWORD)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe),
                                    '--output', OUT_ALICE_REV, '--overwrite'])
        os.close(pipe)
        self.assertEqual(ret, 0)
        self.assertTrue(os.path.isfile(OUT_ALICE_REV))
        with open(OUT_ALICE_REV, "rb") as armored:
            self.assertRegex(armored.read().decode('utf-8'), r'-----END PGP PUBLIC KEY BLOCK-----\r\n$', 'Armor tail not found')
        # Check revocation contents
        ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '--list-packets', OUT_ALICE_REV])
        self.assertEqual(ret, 0)
        self.assertNotEqual(len(out), 0)
        match = re.match(RE_RNP_REVOCATION_SIG, out)
        self.assertTrue(match, 'Wrong revocation signature contents')
        self.assertEqual(match.group(1).strip(), '0 (No reason)', 'Wrong revocation signature reason')
        self.assertEqual(match.group(2).strip(), '', 'Wrong revocation signature message')
        # Make sure it can be imported back
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', OUT_ALICE_REV])
        self.assertEqual(ret, 0, 'Failed to import revocation back')
        self.assertRegex(err, RE_SIG_1_IMPORT, 'Revocation import wrong output')
        # Make sure file will not be overwritten with --force parameter
        with open(OUT_ALICE_REV, 'w+') as f:
            f.truncate(10)
        pipe = pswd_pipe(PASSWORD)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe), '--output', OUT_ALICE_REV, '--force', '--notty'], '\n\n')
        os.close(pipe)
        self.assertNotEqual(ret, 0, 'Revocation was overwritten with --force')
        self.assertEqual(10, os.stat(OUT_ALICE_REV).st_size, 'Revocation was overwritten with --force')
        # Make sure file will not be overwritten without --overwrite parameter
        pipe = pswd_pipe(PASSWORD)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe), '--output', OUT_ALICE_REV, '--notty'], '\n\n')
        os.close(pipe)
        self.assertNotEqual(ret, 0, 'Revocation was overwritten without --overwrite and --force')
        self.assertTrue(os.path.isfile(OUT_ALICE_REV), 'Revocation was overwritten without --overwrite')
        self.assertEqual(10, os.stat(OUT_ALICE_REV).st_size, 'Revocation was overwritten without --overwrite')
        # Make sure file will be overwritten with --overwrite parameter
        pipe = pswd_pipe(PASSWORD)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe), '--output', OUT_ALICE_REV, '--overwrite'])
        os.close(pipe)
        self.assertEqual(ret, 0)
        self.assertGreater(os.stat(OUT_ALICE_REV).st_size, 10)
        # Create revocation with wrong code - 'no longer valid' (which is usable only for userid)
        pipe = pswd_pipe(PASSWORD)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'alice', '--rev-type', 'no longer valid',
                                        '--pass-fd', str(pipe), '--output', OUT_NO_REV, '--force'])
        os.close(pipe)
        self.assertNotEqual(ret, 0, 'Failed to use wrong revocation reason')
        self.assertFalse(os.path.isfile(OUT_NO_REV))
        self.assertRegex(err, r'(?s)^.*Wrong key revocation code: 32.*', 'Wrong revocation export output')
        # Create revocation without rev-code parameter
        pipe = pswd_pipe(PASSWORD)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', 'alice', '--pass-fd', str(pipe),
                                        '--output', OUT_NO_REV, '--force', '--rev-type'])
        os.close(pipe)
        self.assertNotEqual(ret, 0, 'Failed to use rev-type without parameter')
        self.assertFalse(os.path.isfile(OUT_NO_REV), 'Failed to use rev-type without parameter')
        # Create another revocation with custom code/reason
        revcodes = {"0" : "0 (No reason)", "1" : "1 (Superseded)", "2" : "2 (Compromised)",
                    "3" : "3 (Retired)", "no" : "0 (No reason)", "superseded" : "1 (Superseded)",
                    "compromised" : "2 (Compromised)", "retired" : "3 (Retired)"}
        for revcode in revcodes:
            revreason = 'Custom reason: ' + revcode
            pipe = pswd_pipe(PASSWORD)
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-rev', '0451409669FFDE3C', '--pass-fd', str(pipe),
                                            '--output', OUT_ALICE_REV, '--overwrite', '--rev-type', revcode, '--rev-reason', revreason])
            os.close(pipe)
            self.assertEqual(ret, 0, 'Failed to export revocation with code ' + revcode)
            self.assertTrue(os.path.isfile(OUT_ALICE_REV), 'Failed to export revocation with code ' + revcode)
            # Check revocation contents
            with open(OUT_ALICE_REV, "rb") as armored:
                self.assertRegex(armored.read().decode('utf-8'), r'-----END PGP PUBLIC KEY BLOCK-----\r\n$', 'Armor tail not found')
            ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '--list-packets', OUT_ALICE_REV])
            self.assertEqual(ret, 0, 'Failed to list exported revocation packets')
            self.assertNotEqual(len(out), 0, 'Failed to list exported revocation packets')
            match = re.match(RE_RNP_REVOCATION_SIG, out)
            self.assertTrue(match)
            self.assertEqual(match.group(1).strip(), revcodes[revcode], 'Wrong revocation signature revcode')
            self.assertEqual(match.group(2).strip(), revreason, 'Wrong revocation signature reason')
            # Make sure it is also imported back
            ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-sigs', OUT_ALICE_REV])
            self.assertEqual(ret, 0)
            self.assertRegex(err, RE_SIG_1_IMPORT, 'Revocation import wrong output')
            # Now let's import it with GnuPG
            gpg_import_pubring(data_path(KEY_ALICE_PUB))
            ret, _, err = run_proc(GPG, ['--batch', '--homedir', GPGHOME, '--import', OUT_ALICE_REV])
            self.assertEqual(ret, 0, 'gpg signature revocation import failed')
            self.assertRegex(err, RE_GPG_REVOCATION_IMPORT, 'Wrong gpg revocation import output')

        os.remove(OUT_ALICE_REV)
        clear_keyrings()

    def test_import_keys(self):
        clear_keyrings()
        # try to import non-existing file
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('thiskeyfiledoesnotexist')])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)^.*Failed to create input for .*thiskeyfiledoesnotexist.*')
        # try malformed file
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sigs-malf.pgp')])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)^.*failed to import key\(s\) from .*test_key_validity/alice-sigs-malf.pgp, stopping\..*')
        self.assertRegex(err, r'(?s)^.*Import finished: 0 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*')
        # try --import
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 2 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 2 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_stream_key_merge/key-both.asc')])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 3 new public keys, 3 new secret keys, 0 updated, 0 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_stream_key_merge/key-both.asc')])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 6 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 1 new public keys, 0 new secret keys, 1 updated, 0 unchanged\..*')
        clear_keyrings()
        # try --import-key
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 2 new public keys, 0 new secret keys, 0 updated, 0 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path(KEY_ALICE_SUB_PUB)])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 2 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_stream_key_merge/key-both.asc')])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 3 new public keys, 3 new secret keys, 0 updated, 0 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_stream_key_merge/key-both.asc')])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 6 keys processed, 0 new public keys, 0 new secret keys, 0 updated, 6 unchanged\..*')
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-key', data_path('test_key_validity/alice-sign-sub-exp-pub.asc')])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Import finished: 2 keys processed, 1 new public keys, 0 new secret keys, 1 updated, 0 unchanged\..*')
        clear_keyrings()

    def test_export_keys(self):
        PUB_KEY = r'(?s)^.*' \
        r'-----BEGIN PGP PUBLIC KEY BLOCK-----.*' \
        r'-----END PGP PUBLIC KEY BLOCK-----.*$'
        PUB_KEY_PKTS = r'(?s)^.*' \
        r'Public key packet.*' \
        r'keyid: 0x0451409669ffde3c.*' \
        r'Public subkey packet.*' \
        r'keyid: 0xdd23ceb7febeff17.*$'
        SEC_KEY = r'(?s)^.*' \
        r'-----BEGIN PGP PRIVATE KEY BLOCK-----.*' \
        r'-----END PGP PRIVATE KEY BLOCK-----.*$'
        SEC_KEY_PKTS = r'(?s)^.*' \
        r'Secret key packet.*' \
        r'keyid: 0x0451409669ffde3c.*' \
        r'Secret subkey packet.*' \
        r'keyid: 0xdd23ceb7febeff17.*$'
        KEY_OVERWRITE = r'(?s)^.*' \
        r'File \'.*alice-key.pub.asc\' already exists.*' \
        r'Would you like to overwrite it\? \(y/N\).*' \
        r'Please enter the new filename:.*$'

        clear_keyrings()
        # Import Alice's public key
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_PUB)])
        self.assertEqual(ret, 0)
        # Attempt to export no key
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*No key specified\.$')
        # Attempt to export wrong key
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'boris'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Key\(s\) matching \'boris\' not found\.$')
        # Export it to the stdout
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, PUB_KEY)
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', '-'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, PUB_KEY)
        # Export key via --userid parameter
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--userid', 'alice'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, PUB_KEY)
        # Export with empty --userid parameter
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--userid'])
        self.assertNotEqual(ret, 0)
        # Export it to the file
        kpub, ksec, kren = reg_workfiles('alice-key', '.pub.asc', '.sec.asc', '.pub.ren-asc')
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub])
        self.assertEqual(ret, 0)
        self.assertRegex(file_text(kpub), PUB_KEY)
        # Try to export again to the same file without additional parameters
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--notty'], '\n\n')
        self.assertNotEqual(ret, 0)
        self.assertRegex(out, KEY_OVERWRITE)
        self.assertRegex(err, r'(?s)^.*Operation failed: file \'.*alice-key.pub.asc\' already exists.*$')
        # Try to export with --force parameter
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--force', '--notty'], '\n\n')
        self.assertNotEqual(ret, 0)
        self.assertRegex(out, KEY_OVERWRITE)
        self.assertRegex(err, r'(?s)^.*Operation failed: file \'.*alice-key.pub.asc\' already exists.*$')
        # Export with --overwrite parameter
        with open(kpub, 'w+') as f:
            f.truncate(10)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--overwrite'])
        self.assertEqual(ret, 0)
        # Re-import it, making sure file was correctly overwritten
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', kpub])
        self.assertEqual(ret, 0)
        # Enter 'y' in ovewrite prompt
        with open(kpub, 'w+') as f:
            f.truncate(10)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--notty'], 'y\n')
        self.assertEqual(ret, 0)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', kpub])
        self.assertEqual(ret, 0)
        # Enter new filename in overwrite prompt
        with open(kpub, 'w+') as f:
            f.truncate(10)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', kpub, '--notty'], 'n\n' + kren + '\n')
        self.assertEqual(ret, 0)
        self.assertEqual(os.path.getsize(kpub), 10)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', kren])
        self.assertEqual(ret, 0)
        # Attempt to export secret key
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--secret', 'alice'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Key\(s\) matching \'alice\' not found\.$')
        # Import Alice's secret key and subkey
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
        self.assertEqual(ret, 0)
        # Make sure secret key is not exported when public is requested
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', 'alice', '--output', ksec])
        self.assertEqual(ret, 0)
        self.assertRegex(file_text(ksec), PUB_KEY)
        ret, out, _ = run_proc(RNP, ['--list-packets', ksec])
        self.assertEqual(ret, 0)
        self.assertRegex(out, PUB_KEY_PKTS)
        # Make sure secret key is correctly exported
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export-key', '--secret', 'alice', '--output', ksec, '--overwrite'])
        self.assertEqual(ret, 0)
        self.assertRegex(file_text(ksec), SEC_KEY)
        ret, out, _ = run_proc(RNP, ['--list-packets', ksec])
        self.assertEqual(ret, 0)
        self.assertRegex(out, SEC_KEY_PKTS)
        clear_keyrings()

    def test_userid_escape(self):
        clear_keyrings()
        tracker_beginning = 'tracker'
        tracker_end = '@rnp'
        tracker_1 = tracker_beginning + ''.join(map(chr, range(1,0x10))) + tracker_end
        tracker_2 = tracker_beginning + ''.join(map(chr, range(0x10,0x20))) + tracker_end
        #Run key generation
        rnp_genkey_rsa(tracker_1, 1024)
        rnp_genkey_rsa(tracker_2, 1024)
        #Read with rnpkeys
        ret, out_rnp, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, 'rnpkeys : failed to read keystore')
        #Read with GPG
        ret, out_gpg, _ = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys'])
        self.assertEqual(ret, 0, 'gpg : failed to read keystore')
        tracker_rnp = re.findall(r'' + tracker_beginning + '.*' + tracker_end + '', out_rnp)
        tracker_gpg = re.findall(r'' + tracker_beginning + '.*' + tracker_end + '', out_gpg)
        self.assertEqual(len(tracker_rnp), 2, 'failed to find expected rnp userids')
        self.assertEqual(len(tracker_gpg), 2, 'failed to find expected gpg userids')
        self.assertEqual(tracker_rnp, tracker_gpg, 'userids from rnpkeys and gpg don\'t match')
        clear_keyrings()

    def test_key_revoke(self):
        clear_keyrings()
        # Import Alice's public key and be unable to revoke
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_PUB)])
        self.assertEqual(ret, 0, ALICE_IMPORT_FAIL)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke-key', 'alice'])
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*Revoker secret key not found.*Failed to revoke a key.*')
        # Import Alice's secret key and subkey
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
        self.assertEqual(ret, 0)
        # Attempt to revoke without specifying a key
        pipe = pswd_pipe(PASSWORD)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*You need to specify key or subkey to revoke.*')
        # Attempt to revoke unknown key
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'basil'])
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*Key matching \'basil\' not found.*')
        # Attempt to revoke with too broad search
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('test_key_validity/basil-sec.asc')])
        self.assertEqual(ret, 0)
        pipe = pswd_pipe(PASSWORD)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'rnp', '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertRegex(err, r'(?s)^.*Ambiguous input: too many keys found for \'rnp\'.*')
        # Revoke a primary key
        pipe = pswd_pipe(PASSWORD)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '0451409669FFDE3C', '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertEqual(ret, 0)
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*pub.*0451409669ffde3c.*\[REVOKED\].*73edcc9119afc8e2dbbdcde50451409669ffde3c.*')
        # Try again without the '--force' parameter
        pipe = pswd_pipe(PASSWORD)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '0451409669FFDE3C', '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*Error: key \'0451409669FFDE3C\' is revoked already. Use --force to generate another revocation signature.*')
        # Try again with --force parameter
        pipe = pswd_pipe(PASSWORD)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', '0451409669FFDE3C', '--pass-fd', str(pipe), "--force", "--rev-type", "3", "--rev-reason", "Custom"])
        os.close(pipe)
        self.assertEqual(ret, 0)
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*pub.*0451409669ffde3c.*\[REVOKED\].*73edcc9119afc8e2dbbdcde50451409669ffde3c.*')
        # Revoke a subkey
        pipe = pswd_pipe(PASSWORD)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'DD23CEB7FEBEFF17', '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertEqual(ret, 0)
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*sub.*dd23ceb7febeff17.*\[REVOKED\].*a4bbb77370217bca2307ad0ddd23ceb7febeff17.*')
        # Try again without the '--force' parameter
        pipe = pswd_pipe(PASSWORD)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'DD23CEB7FEBEFF17', '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertNotEqual(ret, 0)
        self.assertEqual(len(out), 0)
        self.assertRegex(err, r'(?s)^.*Error: key \'DD23CEB7FEBEFF17\' is revoked already. Use --force to generate another revocation signature.*', err)
        # Try again with --force parameter
        pipe = pswd_pipe(PASSWORD)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--revoke', 'DD23CEB7FEBEFF17', '--pass-fd', str(pipe), "--force", "--rev-type", "2", "--rev-reason", "Other"])
        os.close(pipe)
        self.assertEqual(ret, 0)
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*sub.*dd23ceb7febeff17.*\[REVOKED\].*a4bbb77370217bca2307ad0ddd23ceb7febeff17.*')

    def _test_userid_genkey(self, userid_beginning, weird_part, userid_end, weird_part2=''):
        clear_keyrings()
        USERS = [userid_beginning + weird_part + userid_end]
        if weird_part2:
            USERS.append(userid_beginning + weird_part2 + userid_end)
        # Run key generation
        for userid in USERS:
            rnp_genkey_rsa(userid, 1024)
        # Read with GPG
        ret, out, err = run_proc(GPG, ['--homedir', path_for_gpg(RNPDIR), '--list-keys', '--charset', CONSOLE_ENCODING])
        self.assertEqual(ret, 0, 'gpg : failed to read keystore')
        tracker_escaped = re.findall(r'' + userid_beginning + '.*' + userid_end + '', out)
        tracker_gpg = list(map(decode_string_escape, tracker_escaped))
        self.assertEqual(tracker_gpg, USERS, 'gpg : failed to find expected userids from keystore')
        # Read with rnpkeys
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, 'rnpkeys : failed to read keystore')
        tracker_escaped = re.findall(r'' + userid_beginning + '.*' + userid_end + '', out)
        tracker_rnp = list(map(decode_string_escape, tracker_escaped))
        self.assertEqual(tracker_rnp, USERS, 'rnpkeys : failed to find expected userids from keystore')
        clear_keyrings()

    def test_userid_unicode_genkeys(self):
        self._test_userid_genkey('track', WEIRD_USERID_UNICODE_1, 'end', WEIRD_USERID_UNICODE_2)

    def test_userid_special_chars_genkeys(self):
        self._test_userid_genkey('track', WEIRD_USERID_SPECIAL_CHARS, 'end')
        self._test_userid_genkey('track', WEIRD_USERID_SPACE, 'end')
        self._test_userid_genkey('track', WEIRD_USERID_QUOTE, 'end')
        self._test_userid_genkey('track', WEIRD_USERID_SPACE_AND_QUOTE, 'end')

    def test_userid_too_long_genkeys(self):
        clear_keyrings()
        userid = WEIRD_USERID_TOO_LONG
        # Open pipe for password
        pipe = pswd_pipe(PASSWORD)
        # Run key generation
        ret, _, _ = run_proc(RNPK, ['--gen-key', '--userid', userid,
                                    '--homedir', RNPDIR, '--pass-fd', str(pipe)])
        os.close(pipe)
        self.assertNotEqual(ret, 0, 'should have failed on too long id')

    def test_key_remove(self):
        if RNP_CAST5:
            MSG_KEYS_NOT_FOUND = r'Key\(s\) not found\.'
            clear_keyrings()
            # Import public keyring
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(PUBRING_1)])
            self.assertEqual(ret, 0)
            # Remove without parameters
            ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key'])
            self.assertNotEqual(ret, 0)
            # Remove all imported public keys with subkeys
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a', '2fcadf05ffa501bb'])
            self.assertEqual(ret, 0)
            # Check that keyring is empty
            ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
            self.assertNotEqual(ret, 0)
            self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Invalid no-keys output')
            # Import secret keyring
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path('keyrings/1/secring.gpg')])
            self.assertEqual(ret, 0, 'Secret keyring import failed')
            # Remove all secret keys with subkeys
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a', '2fcadf05ffa501bb', '--force'])
            self.assertEqual(ret, 0, 'Failed to remove 2 secret keys')
            # Check that keyring is empty
            ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
            self.assertNotEqual(ret, 0)
            self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Failed to remove secret keys')
            # Import public keyring
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(PUBRING_1)])
            self.assertEqual(ret, 0, 'Public keyring import failed')
            # Remove all subkeys
            ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key',
                                            '326ef111425d14a5', '54505a936a4a970e', '8a05b89fad5aded1', '1d7e8a5393c997a8', '1ed63ee56fadc34d'])
            self.assertEqual(ret, 0, 'Failed to remove 5 keys')
            # Check that subkeys are removed
            ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
            self.assertEqual(ret, 0)
            self.assertRegex(out, r'2 keys found', 'Failed to remove subkeys')
            self.assertFalse(re.search('326ef111425d14a5|54505a936a4a970e|8a05b89fad5aded1|1d7e8a5393c997a8|1ed63ee56fadc34d', out))
            # Remove remaining public keys
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a', '2fcadf05ffa501bb'])
            self.assertEqual(ret, 0, 'Failed to remove public keys')
            # Try to remove again
            ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', '7bc6709b15c23a4a'])
            self.assertNotEqual(ret, 0)
            self.assertRegex(err, r'Key matching \'7bc6709b15c23a4a\' not found\.', 'Unexpected result')
            # Check that keyring is empty
            ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
            self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Failed to list empty keyring')
            # Import public keyring
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(PUBRING_1)])
            self.assertEqual(ret, 0, 'Public keyring import failed')
            # Try to remove by uid substring, should match multiple keys and refuse to remove
            ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', 'uid0'])
            self.assertNotEqual(ret, 0)
            self.assertRegex(err, r'Ambiguous input: too many keys found for \'uid0\'\.', 'Unexpected result')
            # Remove keys by uids
            ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--remove-key', 'key0-uid0', 'key1-uid1'])
            self.assertEqual(ret, 0, 'Failed to remove keys')
            # Check that keyring is empty
            ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
            self.assertNotEqual(ret, 0)
            self.assertRegex(out, MSG_KEYS_NOT_FOUND, 'Failed to remove keys')

    def test_additional_subkeys_default(self):
        '''
        Generate default key (primary + sub) then add more subkeys.
        '''
        # Open pipe for password
        pipe = pswd_pipe(PASSWORD)
        # Run key generation
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
                                    '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
        os.close(pipe)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Edit generated key, generate & add one more subkey with default parameters
        pipe = pswd_pipe(PASSWORD)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
                                    '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertEqual(ret, 0, 'Failed to add new subkey')
        # list keys, check result
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, KEY_LIST_FAILED)
        self.assertRegex(out, RE_MULTIPLE_SUBKEY_3, KEY_LIST_WRONG)
        clear_keyrings()

    def test_additional_subkeys_invalid_parameters(self):
        # Run primary key generation
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
                                    '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Attempt to generate subkey for non-existing key
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
                                      '--edit-key', '--add-subkey', 'unknown'])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'Secret keys matching \'unknown\' not found.')
        # Attempt to generate subkey using the invalid password
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'wrong',
                                      '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)Failed to unlock primary key.*Subkey generation failed')
        # Attempt to generate subkey using the invalid password, asked via tty
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key',
                                      '--add-subkey', 'primary_for_many_subs@rnp'], 'password2\n')
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)Failed to unlock primary key.*Subkey generation failed')
        # Attempt to generate ECDH subkey with invalid curve
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, '--edit-key', '--add-subkey',
                                      'primary_for_many_subs@rnp', '--expert'],
                                      '\n\n0\n101\n18\n-10\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n')
        self.assertEqual(ret, 1)
        self.assertRegex(out, r'(?s)Too many attempts. Aborting.')
        self.assertRegex(err, r'(?s)Subkey generation setup failed')
        # Attempt to generate ECDSA subkey with invalid curve
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD, '--edit-key', '--add-subkey',
                                      'primary_for_many_subs@rnp', '--expert'],
                                      '19\n-10\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n0\n')
        self.assertEqual(ret, 1)
        self.assertRegex(out, r'(?s)Too many attempts. Aborting.')
        self.assertRegex(err, r'(?s)Subkey generation setup failed')
        # Pass invalid numbits
        ret, _, err = run_proc(RNPK, ['--numbits', 'wrong', '--homedir', RNPDIR, '--password', PASSWORD,
                                      '--userid', 'wrong', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*wrong bits value: wrong.*')
        # Too small
        ret, _, err = run_proc(RNPK, ['--numbits', '768', '--homedir', RNPDIR, '--password', PASSWORD,
                                      '--userid', '768', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*wrong bits value: 768.*')
        # ElGamal too large and wrong numbits
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', 'wrong', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
                                        '--expert'], '16\n2048zzz\n99999999999999999999999999\n2048\n')
        self.assertRegex(err, r'(?s)Unexpected end of line.*Number out of range.*')
        self.assertEqual(ret, 1)
        # Wrong hash algorithm
        ret, _, err = run_proc(RNPK, ['--hash', 'BAD_HASH', '--homedir', RNPDIR, '--password', PASSWORD,
                                      '--userid', 'bad_hash', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Unsupported hash algorithm: BAD_HASH.*')
        # Wrong S2K iterations
        ret, _, err = run_proc(RNPK, ['--s2k-iterations', 'WRONG_ITER', '--homedir', RNPDIR, '--password', PASSWORD,
                                      '--userid', 'wrong_iter', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Wrong iterations value: WRONG_ITER.*')
        # Wrong S2K msec
        ret, _, err = run_proc(RNPK, ['--s2k-msec', 'WRONG_MSEC', '--homedir', RNPDIR, '--password', PASSWORD,
                                      '--userid', 'wrong_msec', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Invalid s2k msec value: WRONG_MSEC.*')
        # Wrong cipher
        ret, _, err = run_proc(RNPK, ['--cipher', 'WRONG_AES', '--homedir', RNPDIR, '--password', PASSWORD,
                                      '--userid', 'wrong_aes', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: WRONG_AES.*Failed to process argument --cipher.*')
        # Ambiguous primary key
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
                                    '--userid', 'primary_for_many_subs2@rnp', '--generate-key'])
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--password', PASSWORD,
                                      '--edit-key', '--add-subkey', 'primary_for_many'])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'Ambiguous input: too many keys found for \'primary_for_many\'')

        clear_keyrings()

    def test_additional_subkeys_expert_mode(self):
        # Run primary key generation
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
                                    '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
        # RSA subkey
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
                                      '--expert'], '\n\n0\n101\n1\n1023\n4097\n3072\n')
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # ElGamal subkey
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
                                      '--expert'], '\n\n0\n101\n16\n1023\n4097\n1025\n')
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # DSA subkey
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
                                      '--expert'], '\n\n0\n101\n17\n1023\n3073\n1025\n')
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # ECDH subkey
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
                                      '--expert'], '\n\n0\n101\n18\n0\n8\n1\n')
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # ECDSA subkey
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
                                      '--expert'], '\n\n0\n101\n19\n0\n8\n1\n')
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # EDDSA subkey
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp',
                                      '--expert'], '\n\n0\n101\n22\n')
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # list keys, check result
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, KEY_LIST_FAILED)
        self.assertRegex(out, RE_MULTIPLE_SUBKEY_8, KEY_LIST_WRONG)

        clear_keyrings()

    def test_additional_subkeys_reuse_password(self):
        pipe = pswd_pipe('primarypassword')
        # Primary key with password
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--pass-fd', str(pipe),
                                    '--userid', 'primary_for_many_subs@rnp', '--generate-key'])
        os.close(pipe)
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Provide password to add subkey, reuse password for subkey, say "yes"
        stdinstr = 'primarypassword\ny\n'
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'],
                             stdinstr)
        self.assertEqual(ret, 0, 'Failed to add new subkey')
        self.assertRegex(out, r'Would you like to use the same password to protect subkey')
        # Do not reuse same password for subkey, say "no"
        stdinstr = 'primarypassword\nN\nsubkeypassword\nsubkeypassword\n'
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_for_many_subs@rnp'],
                             stdinstr)
        self.assertEqual(ret, 0)
        # Primary key with empty password
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
                                    '--userid', 'primary_with_empty_password@rnp', '--generate-key'])
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        # Set empty password for generated subkey
        stdinstr = '\n\ny\n'
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_with_empty_password@rnp'],
                             stdinstr)
        self.assertEqual(ret, 0)
        # Set password for generated subkey
        stdinstr = 'subkeypassword\nsubkeypassword\n'
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--notty', '--edit-key', '--add-subkey', 'primary_with_empty_password@rnp'],
                             stdinstr)
        self.assertEqual(ret, 0)
        clear_keyrings()

    def test_edit_key_single_option(self):
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_25519_NOTWEAK_SEC)])
        self.assertEqual(ret, 0)
        # Try to pass multiple  --edit-key sub-options at once
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--check-cv25519-bits', '--fix-cv25519-bits',
                                      '--add-subkey', '--set-expire', '0', '3176fc1486aa2528'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Only one key edit option can be executed at a time..*$')
        clear_keyrings()

    def test_set_expire(self):
        kpath = os.path.join(RNPDIR, PUBRING)
        # Primary key with empty password
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
                                    '--userid', 'primary_with_empty_password@rnp', '--generate-key'])
        self.assertEqual(ret, 0, KEY_GEN_FAILED)

        # Wrong expiration argument
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '-1', 'primary_with_empty_password@rnp'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'Failed to set key expiration.')

        ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
        self.assertEqual(ret, 0)
        matches = re.findall(r'(key expiration time: 63072000 seconds \(730 days\))', out)
        self.assertEqual(len(matches), 2)

        # Non-existing key argument
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '0', 'wrongkey'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'Secret keys matching \'wrongkey\' not found.')

        # Remove expiration date
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '0', 'primary_with_empty_password@rnp'])
        self.assertEqual(ret, 0)
        self.assertNotRegex(out, r'(?s)^.*\[EXPIRES .*', 'Failed to remove expiration!')

        ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
        self.assertEqual(ret, 0)
        matches = re.findall(r'(key expiration time: 63072000 seconds \(730 days\))', out)
        self.assertEqual(len(matches), 1)

        # Expires in 10 seconds
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10', 'primary_with_empty_password@rnp'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')

        ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*key expiration time: 10 seconds \(0 days\).*')

        # Expires in 10 hours
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10h', 'primary_with_empty_password@rnp'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')
        ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*key expiration time: 36000 seconds \(0 days\).*')

        # Expires in 10 months
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10m', 'primary_with_empty_password@rnp'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')
        ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*key expiration time: 26784000 seconds \(310 days\).*')

        # Expires in 10 years
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '10y', 'primary_with_empty_password@rnp'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*\[EXPIRES .*')
        ret, out, _ = run_proc(RNP, ['--list-packets', kpath])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*key expiration time: 315360000 seconds \(3650 days\).*')

        # Additional primary for ambiguous key uid
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--password=',
                                    '--userid', 'primary2@rnp', '--generate-key'])
        self.assertEqual(ret, 0, KEY_GEN_FAILED)
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--edit-key', '--set-expire', '0', 'primary'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'Ambiguous input: too many keys found for \'primary\'')

        clear_keyrings()

class Misc(unittest.TestCase):

    @classmethod
    def setUpClass(cls):
        rnp_genkey_rsa(KEY_ENCRYPT)
        rnp_genkey_rsa(KEY_SIGN_GPG)
        gpg_import_pubring()
        gpg_import_secring()

    @classmethod
    def tearDownClass(cls):
        clear_keyrings()

    def tearDown(self):
        clear_workfiles()

    def test_encryption_unicode(self):
        if sys.version_info >= (3,):
            filename = UNICODE_SEQUENCE_1
        else:
            filename = UNICODE_SEQUENCE_1.encode(CONSOLE_ENCODING)

        src, dst, dec = reg_workfiles(filename, '.txt', '.rnp', '.dec')
        # Generate random file of required size
        random_text(src, 128000)

        rnp_encrypt_file_ex(src, dst, [KEY_ENCRYPT])
        rnp_decrypt_file(dst, dec)
        compare_files(src, dec, RNP_DATA_DIFFERS)

        remove_files(src, dst, dec)

    def test_encryption_no_mdc(self):
        src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
        # Generate random file of required size
        random_text(src, 64000)
        # Encrypt cleartext file with GPG
        params = ['--homedir', GPGHOME, '-c', '-z', '0', '--disable-mdc', '--s2k-count',
                  '65536', '--batch', '--passphrase', PASSWORD, '--output',
                  path_for_gpg(dst), path_for_gpg(src)]
        ret, _, _ = run_proc(GPG, params)
        self.assertEqual(ret, 0, 'gpg symmetric encryption failed')
        # Decrypt encrypted file with RNP
        rnp_decrypt_file(dst, dec)
        compare_files(src, dec, RNP_DATA_DIFFERS)

    def test_encryption_s2k(self):
        src, dst, dec = reg_workfiles('cleartext', '.txt', '.gpg', '.rnp')
        random_text(src, 1000)

        ciphers = rnp_supported_ciphers(False)
        hashes = ['SHA1', 'RIPEMD160', 'SHA256', 'SHA384', 'SHA512', 'SHA224']
        s2kmodes = [0, 1, 3]

        if not RNP_RIPEMD160:
            hashes.remove('RIPEMD160')

        def rnp_encryption_s2k_gpg(cipher, hash_alg, s2k=None, iterations=None):
            params = ['--homedir', GPGHOME, '-c', '--s2k-cipher-algo', cipher,
                      '--s2k-digest-algo', hash_alg, '--batch', '--passphrase', PASSWORD,
                      '--output', dst, src]

            if s2k is not None:
                params.insert(7, '--s2k-mode')
                params.insert(8, str(s2k))

                if iterations is not None:
                    params.insert(9, '--s2k-count')
                    params.insert(10, str(iterations))

            if GPG_NO_OLD:
                params.insert(3, '--allow-old-cipher-algos')

            ret, _, _ = run_proc(GPG, params)
            self.assertEqual(ret, 0, 'gpg symmetric encryption failed')
            rnp_decrypt_file(dst, dec)
            compare_files(src, dec, RNP_DATA_DIFFERS)
            remove_files(dst, dec)

        for i in range(0, 20):
            rnp_encryption_s2k_gpg(ciphers[i % len(ciphers)], hashes[
                                i % len(hashes)], s2kmodes[i % len(s2kmodes)])

    def test_armor(self):
        src_beg, dst_beg, dst_mid, dst_fin = reg_workfiles('beg', '.src', '.dst',
                                                           '.mid.dst', '.fin.dst')
        armor_types = [('msg', 'MESSAGE'), ('pubkey', 'PUBLIC KEY BLOCK'),
                       ('seckey', 'PRIVATE KEY BLOCK'), ('sign', 'SIGNATURE')]

        random_text(src_beg, 1000)
        # Wrong armor type
        ret, _, err = run_proc(RNP, ['--enarmor=wrong', src_beg, '--output', dst_beg])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Wrong enarmor argument: wrong.*$')

        # Default armor type
        ret, _, _ = run_proc(RNP, ['--enarmor', src_beg, '--output', dst_beg])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Wrong enarmor argument: wrong.*$')
        txt = file_text(dst_beg).strip('\r\n')
        self.assertTrue(txt.startswith('-----BEGIN PGP MESSAGE-----'), 'wrong armor header')
        self.assertTrue(txt.endswith('-----END PGP MESSAGE-----'), 'wrong armor trailer')
        remove_files(dst_beg)

        for data_type, header in armor_types:
            prefix = '-----BEGIN PGP ' + header + '-----'
            suffix = '-----END PGP ' + header + '-----'

            ret, _, _ = run_proc(RNP, ['--enarmor=' + data_type, src_beg, '--output', dst_beg])
            self.assertEqual(ret, 0)
            txt = file_text(dst_beg).strip('\r\n')

            self.assertTrue(txt.startswith(prefix), 'wrong armor header')
            self.assertTrue(txt.endswith(suffix), 'wrong armor trailer')

            ret, _, _ = run_proc(RNP, ['--dearmor', dst_beg, '--output', dst_mid])
            self.assertEqual(ret, 0)
            ret, _, _ = run_proc(RNP, ['--enarmor=' + data_type, dst_mid, '--output', dst_fin])
            self.assertEqual(ret, 0)

            compare_files(dst_beg, dst_fin, "RNP armor/dearmor test failed")
            compare_files(src_beg, dst_mid, "RNP armor/dearmor test failed")
            remove_files(dst_beg, dst_mid, dst_fin)

        # 3-byte last chunk with missing crc
        msg = '-----BEGIN PGP MESSAGE-----\n\nMTIzNDU2Nzg5\n-----END PGP MESSAGE-----\n'
        ret, out, err = run_proc(RNP, ['--dearmor'], msg)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*123456789.*')
        self.assertRegex(err, r'(?s)^.*Warning: missing or malformed CRC line.*')
        # No invalid CRC message
        R_CRC = r'(?s)^.*Warning: CRC mismatch.*$'
        dec = 'decoded.pgp'
        ret, _, err = run_proc(RNP, ['--dearmor', data_path('test_stream_key_load/ecc-25519-pub.asc'), '--output', dec])
        remove_files(dec)
        self.assertEqual(ret, 0)
        self.assertNotRegex(err, R_CRC)
        # Invalid CRC message
        ret, _, err = run_proc(RNP, ['--dearmor', data_path('test_stream_armor/ecc-25519-pub-bad-crc.asc'), '--output', dec])
        remove_files(dec)
        self.assertEqual(ret, 0)
        self.assertRegex(err, R_CRC)

    def test_rnpkeys_lists(self):
        KEYRING_1 = data_path(KEYRING_DIR_1)
        KEYRING_2 = data_path('keyrings/2')
        KEYRING_3 = data_path(KEYRING_DIR_3)
        KEYRING_5 = data_path('keyrings/5')
        path = data_path('test_cli_rnpkeys') + '/'

        if RNP_CAST5:
            _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '--list-keys'])
            compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_keys'), out, 'keyring 1 key listing failed')
            _, out, _ = run_proc(RNPK, ['--home', KEYRING_1, '-l', '--with-sigs'])
            compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_sigs'), out, 'keyring 1 sig listing failed')
            _, out, _ = run_proc(RNPK, ['--home', KEYRING_1, '--list-keys', '--secret'])
            compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_keys_sec'), out, 'keyring 1 sec key listing failed')
            _, out, _ = run_proc(RNPK, ['--home', KEYRING_1, '--list-keys',
                                        '--secret', '--with-sigs'])
            compare_file_any(allow_y2k38_on_32bit(path + 'keyring_1_list_sigs_sec'), out, 'keyring 1 sec sig listing failed')

        _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_2, '--list-keys'])
        compare_file(path + 'keyring_2_list_keys', out, 'keyring 2 key listing failed')
        _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_2, '-l', '--with-sigs'])
        compare_file(path + 'keyring_2_list_sigs', out, 'keyring 2 sig listing failed')

        _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_3, '--list-keys'])
        compare_file_any(allow_y2k38_on_32bit(path + 'keyring_3_list_keys'), out, 'keyring 3 key listing failed')
        _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_3, '-l', '--with-sigs'])
        compare_file_any(allow_y2k38_on_32bit(path + 'keyring_3_list_sigs'), out, 'keyring 3 sig listing failed')

        _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_5, '--list-keys'])
        compare_file(path + 'keyring_5_list_keys', out, 'keyring 5 key listing failed')
        _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_5, '-l', '--with-sigs'])
        compare_file(path + 'keyring_5_list_sigs', out, 'keyring 5 sig listing failed')

        _, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
                                    '--list-keys'])
        if RNP_BRAINPOOL:
            self.assertEqual(file_text(path + 'test_stream_key_load_keys'), out, 'g10 keyring key listing failed')
        else:
            self.assertEqual(file_text(path + 'test_stream_key_load_keys_no_bp'), out, 'g10 keyring key listing failed')
        _, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
                                    '-l', '--with-sigs'])
        if RNP_BRAINPOOL:
            self.assertEqual(file_text(path + 'test_stream_key_load_sigs'), out, 'g10 keyring sig listing failed')
        else:
            self.assertEqual(file_text(path + 'test_stream_key_load_sigs_no_bp'), out, 'g10 keyring sig listing failed')
        # Below are disabled until we have some kind of sorting which doesn't depend on
        # readdir order
        #_, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
        #                            '-l', '--secret'])
        #compare_file(path + 'test_stream_key_load_keys_sec', out,
        #             'g10 sec keyring key listing failed')
        #_, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
        #                            '-l', '--secret', '--with-sigs'])
        #compare_file(path + 'test_stream_key_load_sigs_sec', out,
        #             'g10 sec keyring sig listing failed')

        if RNP_CAST5:
            _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', '2fcadf05ffa501bb'])
            compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb'), out, 'list key 2fcadf05ffa501bb failed')
            _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l',
                                        '--with-sigs', '2fcadf05ffa501bb'])
            compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb_sig'), out, 'list sig 2fcadf05ffa501bb failed')
            _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l',
                                        '--secret', '2fcadf05ffa501bb'])
            compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb_sec'), out, 'list sec 2fcadf05ffa501bb failed')

            _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', '00000000'])
            compare_file(path + 'getkey_00000000', out, 'list key 00000000 failed')
            _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', 'zzzzzzzz'])
            compare_file(path + 'getkey_zzzzzzzz', out, 'list key zzzzzzzz failed')

            _, out, _ = run_proc(RNPK, ['--homedir', KEYRING_1, '-l', '--userid', '2fcadf05ffa501bb'])
            compare_file_any(allow_y2k38_on_32bit(path + 'getkey_2fcadf05ffa501bb'), out, 'list key 2fcadf05ffa501bb failed')

    def test_rnpkeys_list_invalid_keys(self):
        RNPDIR2 = RNPDIR + '2'
        os.mkdir(RNPDIR2, 0o700)
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR2, '--import', data_path('test_forged_keys/eddsa-2012-md5-pub.pgp')])
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR2, '--list-keys', '--with-sigs'])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)2 keys found.*8801eafbd906bd21.*\[INVALID\].*expired-md5-key-sig.*\[INVALID\].*sig.*\[unknown\] \[invalid\]')
        self.assertRegex(err, r'(?s)Insecure hash algorithm 1, marking signature as invalid')
        shutil.rmtree(RNPDIR2, ignore_errors=True)

    def test_rnpkeys_g10_list_order(self):
        ret, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10), '--list-keys'])
        self.assertEqual(ret, 0)
        if RNP_BRAINPOOL:
            self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys')), out, 'g10 key listing failed')
        else:
            self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys_no_bp')), out, 'g10 key listing failed')
        ret, out, _ = run_proc(RNPK, ['--homedir', data_path(SECRING_G10), '--secret', '--list-keys'])
        self.assertEqual(ret, 0)
        if RNP_BRAINPOOL:
            self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys_sec')), out, 'g10 secret key listing failed')
        else:
            self.assertEqual(file_text(data_path('test_cli_rnpkeys/g10_list_keys_sec_no_bp')), out, 'g10 secret key listing failed')

    def test_rnpkeys_g10_def_key(self):
        RE_SIG = r'(?s)^.*' \
        r'Good signature made .*' \
        r'using (.*) key (.*)' \
        r'pub .*' \
        r'b54fdebbb673423a5d0aa54423674f21b2441527.*' \
        r'uid\s+(ecc-p256)\s*' \
        r'Signature\(s\) verified successfully.*$'

        src, dst = reg_workfiles('cleartext', '.txt', '.rnp')
        random_text(src, 1000)
        # Sign file with rnp using the default g10 key
        params = ['--homedir', data_path('test_cli_g10_defkey/g10'),
                  '--password', PASSWORD, '--output', dst, '-s', src]
        ret, _, err = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'rnp signing failed')
        # Verify signed file
        params = ['--homedir', data_path('test_cli_g10_defkey/g10'), '-v', dst]
        ret, _, err = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'verification failed')
        self.assertRegex(err, RE_SIG, 'wrong rnp g10 verification output')

    def test_large_packet(self):
        # Verifying large packet file with GnuPG
        kpath = path_for_gpg(data_path(PUBRING_1))
        dpath = path_for_gpg(data_path('test_large_packet/4g.bzip2.gpg'))
        ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', dpath])
        self.assertEqual(ret, 0, 'large packet verification failed')

    def test_partial_length_signature(self):
        # Verifying partial length signature with GnuPG
        kpath = path_for_gpg(data_path(PUBRING_1))
        mpath = path_for_gpg(data_path('test_partial_length/message.txt.partial-signed'))
        ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', mpath])
        self.assertNotEqual(ret, 0, 'partial length signature packet should result in failure but did not')

    def test_partial_length_public_key(self):
        # Reading keyring that has a public key packet with partial length using GnuPG
        kpath = data_path('test_partial_length/pubring.gpg.partial')
        ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--list-keys'])
        self.assertNotEqual(ret, 0, 'partial length public key packet should result in failure but did not')

    def test_partial_length_zero_last_chunk(self):
        # Verifying message in partial packets having 0-size last chunk with GnuPG
        kpath = path_for_gpg(data_path(PUBRING_1))
        mpath = path_for_gpg(data_path('test_partial_length/message.txt.partial-zero-last'))
        ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', mpath])
        self.assertEqual(ret, 0, 'message in partial packets having 0-size last chunk verification failed')

    def test_partial_length_largest(self):
        # Verifying message having largest possible partial packet with GnuPG
        kpath = path_for_gpg(data_path(PUBRING_1))
        mpath = path_for_gpg(data_path('test_partial_length/message.txt.partial-1g'))
        ret, _, _ = run_proc(GPG, ['--homedir', GPGHOME, '--no-default-keyring', '--keyring', kpath, '--verify', mpath])
        self.assertEqual(ret, 0, 'message having largest possible partial packet verification failed')

    def test_rnp_single_export(self):
        # Import key with subkeys, then export it, test that it is exported once.
        # See issue #1153
        clear_keyrings()
        # Import Alice's secret key and subkey
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', data_path(KEY_ALICE_SUB_SEC)])
        self.assertEqual(ret, 0, 'Alice secret key import failed')
        # Export key
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--export', 'Alice'])
        self.assertEqual(ret, 0, 'key export failed')
        pubpath = os.path.join(RNPDIR, 'Alice-export-test.asc')
        with open(pubpath, 'w+') as f:
            f.write(out)
        # List exported key packets
        params = ['--list-packets', pubpath]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_single_export_subkeys/list_key_export_single.txt'), out,
                        'exported packets mismatch')

    def test_rnp_permissive_key_import(self):
        # Import keys while skipping bad packets, see #1160
        clear_keyrings()
        # Try to import  without --permissive option, should fail.
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_key_edge_cases/pubring-malf-cert.pgp')])
        self.assertNotEqual(ret, 0, 'Imported bad packets without --permissive option set!')
        # Import with --permissive
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', '--permissive',data_path('test_key_edge_cases/pubring-malf-cert.pgp')])
        self.assertEqual(ret, 0, 'Failed to import keys with --permissive option')

        # List imported keys and sigs
        params = ['--homedir', RNPDIR, '--list-keys', '--with-sigs']
        ret, out, _ = run_proc(RNPK, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_any(allow_y2k38_on_32bit(data_path('test_cli_rnpkeys/pubring-malf-cert-permissive-import.txt')),
            out, 'listing mismatch')

    def test_rnp_autocrypt_key_import(self):
        R_25519 = r'(?s)^.*pub.*255/EdDSA.*21fc68274aae3b5de39a4277cc786278981b0728.*$'
        R_256K1 = r'(?s)^.*pub.*3ea5bb6f9692c1a0.*7635401f90d3e533.*$'
        # Import misc configurations of base64-encoded autocrypt keys
        clear_keyrings()
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub.b64')])
        self.assertEqual(ret, 0)
        self.assertRegex(out, R_25519)
        # No trailing EOL after the base64 data
        clear_keyrings()
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub-2.b64')])
        self.assertEqual(ret, 0)
        self.assertRegex(out, R_25519)
        # Extra spaces/eols/tabs after the base64 data
        clear_keyrings()
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub-3.b64')])
        self.assertEqual(ret, 0)
        self.assertRegex(out, R_25519)
        # Invalid symbols after the base64 data
        clear_keyrings()
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-25519-pub-4.b64')])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)^.*wrong base64 padding: ==zz.*Failed to init/check dearmor.*failed to import key\(s\) from .*, stopping.*')
        # Binary data size is multiple of 3, single base64 line
        clear_keyrings()
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-p256k1-pub.b64')])
        self.assertEqual(ret, 0)
        self.assertRegex(out, R_256K1)
        # Binary data size is multiple of 3, multiple base64 lines
        clear_keyrings()
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_key_load/ecc-p256k1-pub-2.b64')])
        self.assertEqual(ret, 0)
        self.assertRegex(out, R_256K1)
        # Too long base64 trailer ('===')
        clear_keyrings()
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_armor/long_b64_trailer.b64')])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)^.*wrong base64 padding length 3.*Failed to init/check dearmor.*$')
        # Extra data after the base64-encoded data
        ret, out, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import-keys', data_path('test_stream_armor/b64_trailer_extra_data.b64')])
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*warning: extra data after the base64 stream.*Failed to init/check dearmor.*warning: not all data was processed.*')
        self.assertRegex(out, R_25519)

    def test_rnp_list_packets(self):
        KEY_P256 = data_path('test_list_packets/ecc-p256-pub.asc')
        # List packets in humand-readable format
        params = ['--list-packets', KEY_P256]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_list_packets/list_standard.txt'), out,
                        'standard listing mismatch')
        # List packets with mpi values
        params = ['--mpi', '--list-packets', KEY_P256]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'packet listing with mpi failed')
        compare_file_ex(data_path('test_list_packets/list_mpi.txt'), out, 'mpi listing mismatch')
        # List packets with grip/fingerprint values
        params = ['--list-packets', KEY_P256, '--grips']
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'packet listing with grips failed')
        compare_file_ex(data_path('test_list_packets/list_grips.txt'), out,
                        'grips listing mismatch')
        # List packets with raw packet contents
        params = ['--list-packets', KEY_P256, '--raw']
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'packet listing with raw packets failed')
        compare_file_ex(data_path('test_list_packets/list_raw.txt'), out, 'raw listing mismatch')
        # List packets with all options enabled
        params = ['--list-packets', KEY_P256, '--grips', '--raw', '--mpi']
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'packet listing with all options failed')
        compare_file_ex(data_path('test_list_packets/list_all.txt'), out, 'all listing mismatch')

        # List packets with JSON output
        params = ['--json', '--list-packets', KEY_P256]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'json packet listing failed')
        compare_file_ex(data_path('test_list_packets/list_json.txt'), out, 'json listing mismatch')
        # List packets with mpi values, JSON output
        params = ['--json', '--mpi', '--list-packets', KEY_P256]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'json mpi packet listing failed')
        compare_file_ex(data_path('test_list_packets/list_json_mpi.txt'), out,
                        'json mpi listing mismatch')
        # List packets with grip/fingerprint values, JSON output
        params = ['--json', '--grips', '--list-packets', KEY_P256]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'json grips packet listing failed')
        compare_file_ex(data_path('test_list_packets/list_json_grips.txt'), out,
                        'json grips listing mismatch')
        # List packets with raw packet values, JSON output
        params = ['--json', '--raw', '--list-packets', KEY_P256]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'json raw packet listing failed')
        compare_file_ex(data_path('test_list_packets/list_json_raw.txt'), out,
                        'json raw listing mismatch')
        # List packets with all values, JSON output
        params = ['--json', '--raw', '--list-packets', KEY_P256, '--mpi', '--grips']
        ret, out, err = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'json all listing failed')
        compare_file_ex(data_path('test_list_packets/list_json_all.txt'), out,
                        'json all listing mismatch')
        # List packets with notations
        params = ['--list-packets', data_path('test_key_edge_cases/key-critical-notations.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*notation data: critical text = critical value.*$')
        self.assertRegex(out, r'(?s)^.*notation data: critical binary = 0x000102030405060708090a0b0c0d0e0f \(16 bytes\).*$')
        # List packets with notations via JSON
        params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-critical-notations.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*\"human\":true.*\"name\":\"critical text\".*\"value\":\"critical value\".*$')
        self.assertRegex(out, r'(?s)^.*\"human\":false.*\"name\":\"critical binary\".*\"value\":\"000102030405060708090a0b0c0d0e0f\".*$')
        # List test file with critical notation
        params = ['--list-packets', data_path('test_messages/message.txt.signed.crit-notation')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*:type 20, len 35, critical.*notation data: critical text = critical value.*$')
        # List signature with signer's userid subpacket
        params = ['--list-packets', data_path(MSG_SIG_CRCR)]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*:type 28, len 9.*signer\'s user ID: alice@rnp.*$')
        # JSON list signature with signer's userid subpacket
        params = ['--list-packets', '--json', data_path(MSG_SIG_CRCR)]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*"type.str":"signer\'s user ID".*"length":9.*"uid":"alice@rnp".*$')
        # List signature with reason for revocation subpacket
        params = ['--list-packets', data_path('test_uid_validity/key-sig-revocation.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*:type 29, len 24.*reason for revocation: 32 \(No longer valid\).*message: Testing revoked userid.*$')
        # JSON list signature with reason for revocation subpacket
        params = ['--list-packets', '--json', data_path('test_uid_validity/key-sig-revocation.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*"type.str":"reason for revocation".*"code":32.*"message":"Testing revoked userid.".*$')

    def test_rnp_list_packets_edge_cases(self):
        KEY_EMPTY_UID = data_path('test_key_edge_cases/key-empty-uid.pgp')
        # List empty key packets
        params = ['--list-packets', data_path('test_key_edge_cases/key-empty-packets.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-empty-packets.txt'), out,
                        'key-empty-packets listing mismatch')

        # List empty key packets json
        params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-empty-packets.pgp')]
        ret, _, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)

        # List empty uid
        params = ['--list-packets', KEY_EMPTY_UID]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-empty-uid.txt'), out,
                        'key-empty-uid listing mismatch')

        # List empty uid with raw packet contents
        params = ['--list-packets', '--raw', KEY_EMPTY_UID]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-empty-uid-raw.txt'), out,
                        'key-empty-uid-raw listing mismatch')

        # List empty uid packet contents to JSON
        params = ['--list-packets', '--json', KEY_EMPTY_UID]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-empty-uid.json'), out,
                        'key-empty-uid json listing mismatch')

        # List experimental subpackets
        params = ['--list-packets', data_path('test_key_edge_cases/key-subpacket-101-110.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-subpacket-101-110.txt'), out,
                        'key-subpacket-101-110 listing mismatch')

        # List experimental subpackets JSON
        params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-subpacket-101-110.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-subpacket-101-110.json'), out,
                        'key-subpacket-101-110 json listing mismatch')

        # List malformed signature
        params = ['--list-packets', data_path('test_key_edge_cases/key-malf-sig.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-malf-sig.txt'), out,
                        'key-malf-sig listing mismatch')

        # List malformed signature JSON
        params = ['--list-packets', '--json', data_path('test_key_edge_cases/key-malf-sig.pgp')]
        ret, out, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, PKT_LIST_FAILED)
        compare_file_ex(data_path('test_key_edge_cases/key-malf-sig.json'), out,
                        'key-malf-sig json listing mismatch')

    def test_debug_log(self):
        if RNP_CAST5:
            run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_1), '--list-keys', '--debug', '--all'])
        run_proc(RNPK, ['--homedir', data_path('keyrings/2'), '--list-keys', '--debug', '--all'])
        run_proc(RNPK, ['--homedir', data_path(KEYRING_DIR_3), '--list-keys', '--debug', '--all'])
        run_proc(RNPK, ['--homedir', data_path(SECRING_G10),
                        '--list-keys', '--debug', '--all'])

    def test_pubring_loading(self):
        NO_PUBRING = r'(?s)^.*warning: keyring at path \'.*/pubring.gpg\' doesn\'t exist.*$'
        EMPTY_HOME = r'(?s)^.*Keyring directory .* is empty.*rnpkeys.*GnuPG.*'
        NO_USERID = 'No userid or default key for operation'

        test_dir = tempfile.mkdtemp(prefix='rnpctmp')
        test_data = data_path(MSG_TXT)
        output = os.path.join(test_dir, 'output')
        params = ['--symmetric', '--password', 'pass', '--homedir', test_dir, test_data, '--output', output]
        ret, _, err = run_proc(RNP, ['--encrypt'] + params)
        self.assertEqual(ret, 1, 'encrypt w/o pubring didn\'t fail')
        self.assertNotRegex(err, NO_PUBRING, 'wrong no-keyring message')
        self.assertRegex(err, EMPTY_HOME)
        self.assertIn(NO_USERID, err, 'Unexpected no key output')
        self.assertIn('Failed to build recipients key list', err, 'Unexpected key list output')

        ret, _, err = run_proc(RNP, ['--sign'] + params)
        self.assertEqual(ret, 1, 'sign w/o pubring didn\'t fail')
        self.assertNotRegex(err, NO_PUBRING, 'wrong failure output')
        self.assertRegex(err, EMPTY_HOME)
        self.assertIn(NO_USERID, err, 'wrong no userid message')
        self.assertIn('Failed to build signing keys list', err, 'wrong signing list failure message')

        ret, _, err = run_proc(RNP, ['--clearsign'] + params)
        self.assertEqual(ret, 1, 'clearsign w/o pubring didn\'t fail')
        self.assertNotRegex(err, NO_PUBRING, 'wrong clearsign no pubring message')
        self.assertRegex(err, EMPTY_HOME)
        self.assertIn(NO_USERID, err, 'Unexpected clearsign no key output')
        self.assertIn('Failed to build signing keys list', err, 'Unexpected clearsign key list output')

        ret, _, _ = run_proc(RNP, params)
        self.assertEqual(ret, 0, 'symmetric w/o pubring failed')

        shutil.rmtree(test_dir)

    def test_homedir_accessibility(self):
        ret, _, err = run_proc(RNPK, ['--homedir', os.path.join(RNPDIR, 'non-existing'), '--generate', '--password=none'])
        self.assertNotEqual(ret, 0, 'failed to check for homedir accessibility')
        self.assertRegex(err, r'(?s)^.*Home directory .*.rnp.non-existing.* does not exist or is not writable!')
        self.assertRegex(err, RE_KEYSTORE_INFO)
        os.mkdir(os.path.join(RNPDIR, 'existing'), 0o700)
        ret, _, err = run_proc(RNPK, ['--homedir', os.path.join(RNPDIR, 'existing'), '--generate', '--password=none'])
        self.assertEqual(ret, 0, 'failed to use writeable and existing homedir')
        self.assertNotRegex(err, r'(?s)^.*Home directory .* does not exist or is not writable!')
        self.assertNotRegex(err, RE_KEYSTORE_INFO)

    def test_no_home_dir(self):
        home = os.environ['HOME']
        del os.environ['HOME']
        ret, _, err = run_proc(RNP, ['-v', 'non-existing.pgp'])
        os.environ['HOME'] = home
        self.assertEqual(ret, 2, 'failed to run without HOME env variable')
        self.assertRegex(err, r'(?s)^.*Home directory .* does not exist or is not writable!')
        self.assertRegex(err, RE_KEYSTORE_INFO)

    def test_exit_codes(self):
        ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--help'])
        self.assertEqual(ret, 0, 'invalid exit code of \'rnp --help\'')
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--help'])
        self.assertEqual(ret, 0, 'invalid exit code of \'rnpkeys --help\'')
        ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '--unknown-option', '--help'])
        self.assertNotEqual(ret, 0, 'rnp should return non-zero exit code for unknown command line options')
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--unknown-option', '--help'])
        self.assertNotEqual(ret, 0, 'rnpkeys should return non-zero exit code for unknown command line options')

    def test_input_from_specifier(self):
        KEY_LIST = r'(?s)^.*' \
        r'1 key found.*' \
        r'pub .*255/EdDSA.*0451409669ffde3c.*' \
        r'73edcc9119afc8e2dbbdcde50451409669ffde3c.*$'
        NO_KEY_LIST = r'(?s)^.*' \
        r'Key\(s\) not found.*$'
        WRONG_VAR = r'(?s)^.*' \
        r'Failed to get value of the environment variable \'SOMETHING_UNSET\'.*' \
        r'Failed to create input for env:SOMETHING_UNSET.*$'
        WRONG_DATA = r'(?s)^.*' \
        r'failed to import key\(s\) from env:KEY_FILE, stopping.*$'
        PGP_MSG = r'(?s)^.*' \
        r'-----BEGIN PGP MESSAGE-----.*' \
        r'-----END PGP MESSAGE-----.*$'
        ENV_KEY = 'env:KEY_FILE'

        clear_keyrings()
        # Import key from the stdin
        ktext = file_text(data_path(KEY_ALICE_SEC))
        ret, _, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', '-'], ktext)
        self.assertEqual(ret, 0, 'failed to import key from stdin')
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, KEY_LIST_FAILED)
        self.assertRegex(out, KEY_LIST, KEY_LIST_WRONG)
        # Cleanup and import key from the env variable
        clear_keyrings()
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertNotEqual(ret, 0, 'no key list failed')
        self.assertRegex(out, NO_KEY_LIST, KEY_LIST_WRONG)
        # Pass unset variable
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', 'env:SOMETHING_UNSET'])
        self.assertNotEqual(ret, 0, 'key import from env must fail')
        self.assertRegex(err, WRONG_VAR, 'wrong output')
        # Pass incorrect value in environment variable
        os.environ['KEY_FILE'] = "something"
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--import', ENV_KEY])
        self.assertNotEqual(ret, 0, 'key import failed')
        self.assertRegex(err, WRONG_DATA, 'wrong output')
        # Now import the correct key
        os.environ['KEY_FILE'] = ktext
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--import', ENV_KEY])
        self.assertEqual(ret, 0, 'key import failed')
        ret, out, _ = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0, KEY_LIST_FAILED)
        self.assertRegex(out, KEY_LIST, KEY_LIST_WRONG)

        # Sign message from the stdin, using the env keyfile
        ret, out, _ = run_proc(RNP, ['-s', '-', '--password', 'password', '--armor', '--keyfile', ENV_KEY], 'Message to sign')
        self.assertEqual(ret, 0, 'Message signing failed')
        self.assertRegex(out, PGP_MSG, 'wrong signing output')
        os.environ['SIGN_MSG'] = out
        # Verify message from the env variable
        ret, out, _ = run_proc(RNP, ['-d', 'env:SIGN_MSG', '--keyfile', ENV_KEY])
        self.assertEqual(ret, 0, 'Message verification failed')
        self.assertEqual(out, 'Message to sign', 'wrong verification output')

    def test_output_to_specifier(self):
        src, enc, encasc, dec = reg_workfiles('source', '.txt', EXT_PGP, EXT_ASC, '.dec')
        with open(src, 'w+') as f:
            f.write('Hello world')
        # Encrypt file and make sure result is stored with .pgp extension
        ret, out, _ = run_proc(RNP, ['-c', src, '--password', 'password'])
        self.assertEqual(ret, 0, ENC_FAILED)
        ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', enc, '--output', dec, '--password', 'password'])
        self.assertEqual(ret, 0, DEC_FAILED)
        self.assertEqual(file_text(src), file_text(dec), DEC_DIFFERS)
        remove_files(enc, dec)
        # Encrypt file with armor and make sure result is stored with .asc extension
        ret, _, _ = run_proc(RNP, ['-c', src, '--armor', '--password', 'password'])
        self.assertEqual(ret, 0, ENC_FAILED)
        ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', encasc, '--output', '-', '--password', 'password'])
        self.assertEqual(ret, 0, DEC_FAILED)
        self.assertEqual(file_text(src), out, DEC_DIFFERS)
        remove_files(encasc)
        # Encrypt file and write result to the stdout
        ret, out, _ = run_proc(RNP, ['-c', src, '--armor', '--output', '-', '--password', 'password'])
        self.assertEqual(ret, 0, ENC_FAILED)
        ret, _, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', '--output', dec, '--password', 'password', '-'], out)
        self.assertEqual(ret, 0, DEC_FAILED)
        self.assertEqual(file_text(src), file_text(dec), DEC_DIFFERS)
        remove_files(dec)
        # Encrypt file and write armored result to the stdout
        ret, out, _ = run_proc(RNP, ['-c', src, '--armor','--output', '-', '--password', 'password'])
        self.assertEqual(ret, 0, ENC_FAILED)
        ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', '--output', '-', '--password', 'password', '-'], out)
        self.assertEqual(ret, 0, DEC_FAILED)
        self.assertEqual(file_text(src), out, DEC_DIFFERS)
        # Encrypt stdin and write result to the stdout
        srctxt = file_text(src)
        ret, out, _ = run_proc(RNP, ['-c', '--armor', '--password', 'password'], srctxt)
        self.assertEqual(ret, 0, ENC_FAILED)
        ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '-d', '--password', 'password'], out)
        self.assertEqual(ret, 0, DEC_FAILED)
        self.assertEqual(out, srctxt, DEC_DIFFERS)
        # Encrypt stdin and attempt to write to non-existing dir
        ret, _, err = run_proc(RNP, ['-c', '--armor', '--password', 'password', '--output', 'nonexisting/output.pgp'], srctxt)
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*init_file_dest.*failed to create file.*output.pgp.*Error 2.*$')
        self.assertNotRegex(err, r'(?s)^.*failed to initialize encryption.*$')
        self.assertRegex(err, r'(?s)^.*failed to open source or create output.*$')
        # Sign stdin and then verify it using non-existing directory for output
        ret, out, _ = run_proc(RNP, ['--homedir', RNPDIR, '--armor', '--password', 'password', '-s'], srctxt)
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*BEGIN PGP MESSAGE.*END PGP MESSAGE.*$')
        ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '-v', '--output', 'nonexisting/output.pgp'], out)
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)^.*init_file_dest.*failed to create file.*output.pgp.*Error 2.*$')

    def test_literal_filename(self):
        EMPTY_FNAME = r'(?s)^.*literal data packet.*mode b.*created 0, name="".*$'
        HELLO_FNAME = r'(?s)^.*literal data packet.*mode b.*created 0, name="hello".*$'
        src, enc, dec = reg_workfiles('source', '.txt', EXT_PGP, '.dec')
        with open(src, 'w+') as f:
            f.write('Literal filename check')
        # Encrypt file and make sure it's name is stored in literal data packet
        ret, out, _ = run_proc(RNP, ['-c', src, '--password', 'password'])
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*literal data packet.*mode b.*created \d+.*name="source.txt".*$')
        remove_files(enc)
        # Encrypt file, overriding it's name
        ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', src, '--password', 'password'])
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out, HELLO_FNAME)
        remove_files(enc)
        # Encrypt file, using empty name
        ret, out, _ = run_proc(RNP, ['--set-filename', '', '-c', src, '--password', 'password'])
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out, EMPTY_FNAME)
        remove_files(enc)
        # Encrypt stdin, making sure empty name is stored
        ret, out, _ = run_proc(RNP, ['-c', '--password', 'password', '--output', enc], 'Data from stdin')
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out, EMPTY_FNAME)
        remove_files(enc)
        # Encrypt stdin, setting the file name
        ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', '--password', 'password', '--output', enc], 'Data from stdin')
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out, HELLO_FNAME)
        remove_files(enc)
        # Encrypt env, making sure empty name is stored
        ret, out, _ = run_proc(RNP, ['-c', 'env:HOME', '--password', 'password', '--output', enc])
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out, EMPTY_FNAME)
        remove_files(enc)
        # Encrypt env, setting the file name
        ret, out, _ = run_proc(RNP, ['--set-filename', 'hello', '-c', 'env:HOME', '--password', 'password', '--output', enc])
        self.assertEqual(ret, 0)
        ret, out, err = run_proc(GPG, ['--homedir', GPGHOME, GPG_LOOPBACK, '--passphrase', 'password', '--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out, HELLO_FNAME)
        remove_files(enc)

    def test_empty_keyrings(self):
        NO_KEYRING = r'(?s)^.*' \
        r'warning: keyring at path \'.*.\.rnp.pubring\.gpg\' doesn\'t exist.*' \
        r'warning: keyring at path \'.*.\.rnp.secring\.gpg\' doesn\'t exist.*$'
        EMPTY_KEYRING = r'(?s)^.*' \
        r'Warning: no keys were loaded from the keyring \'.*.\.rnp.pubring\.gpg\'.*' \
        r'Warning: no keys were loaded from the keyring \'.*.\.rnp.secring\.gpg\'.*$'
        PUB_IMPORT= r'(?s)^.*pub\s+255/EdDSA 0451409669ffde3c .* \[SC\].*$'
        EMPTY_SECRING = r'(?s)^.*Warning: no keys were loaded from the keyring \'.*\.rnp.secring.gpg\'.*$'
        SEC_IMPORT= r'(?s)^.*sec\s+255/EdDSA 0451409669ffde3c .* \[SC\].*$'
        EMPTY_HOME = r'(?s)^.*Keyring directory .* is empty.*rnpkeys.*GnuPG.*'

        os.rename(RNPDIR, RNPDIR + '-old')
        home = os.environ['HOME']
        os.environ['HOME'] = WORKDIR
        try:
            self.assertFalse(os.path.isdir(RNPDIR), '.rnp directory should not exists')
            src, enc, dec = reg_workfiles('source', '.txt', EXT_PGP, '.dec')
            random_text(src, 2000)
            # Run symmetric encryption/decryption without .rnp home directory
            ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password'])
            self.assertEqual(ret, 0, 'Symmetric encryption without home failed')
            self.assertNotRegex(err, NO_KEYRING, 'No keyring msg in encryption output')
            ret, _, err = run_proc(RNP, ['-d', enc, '--password', 'password', '--output', dec])
            self.assertEqual(ret, 0, 'Symmetric decryption without home failed')
            self.assertNotRegex(err, NO_KEYRING, 'No keyring msg in decryption output')
            self.assertRegex(err, EMPTY_HOME)
            self.assertIn(WORKDIR, err, 'No workdir in decryption output')
            compare_files(src, dec, DEC_DIFFERS)
            remove_files(enc, dec)
            # Import key without .rnp home directory
            ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)])
            self.assertEqual(ret, 0, 'Key import failed without home')
            self.assertNotRegex(err, NO_KEYRING, 'No keyring msg in key import output')
            self.assertRegex(err, EMPTY_HOME)
            self.assertIn(WORKDIR, err, 'No workdir in key import output')
            self.assertRegex(out, PUB_IMPORT, 'Wrong key import output')
            ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_SEC)])
            self.assertEqual(ret, 0, 'Secret key import without home failed')
            self.assertNotRegex(err, NO_KEYRING, 'no keyring message in key import output')
            self.assertNotRegex(err, EMPTY_HOME)
            self.assertRegex(err, EMPTY_SECRING, 'no empty secring in key import output')
            self.assertIn(WORKDIR, err, 'no workdir in key import output')
            self.assertRegex(out, SEC_IMPORT, 'Wrong secret key import output')
            # Run with empty .rnp home directory
            shutil.rmtree(RNPDIR, ignore_errors=True)
            os.mkdir(RNPDIR, 0o700)
            ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password'])
            self.assertEqual(ret, 0)
            self.assertNotRegex(err, NO_KEYRING)
            ret, out, err = run_proc(RNP, ['-d', enc, '--password', 'password', '--output', dec])
            self.assertEqual(ret, 0, 'Symmetric decryption failed')
            self.assertRegex(err, EMPTY_HOME)
            self.assertNotRegex(err, NO_KEYRING, 'No keyring message in decryption output')
            self.assertIn(WORKDIR, err, 'No workdir in decryption output')
            compare_files(src, dec, DEC_DIFFERS)
            remove_files(enc, dec)
            # Import key with empty .rnp home directory
            ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)])
            self.assertEqual(ret, 0, 'Public key import with empty home failed')
            self.assertNotRegex(err, NO_KEYRING, 'No keyring message in key import output')
            self.assertRegex(err, EMPTY_HOME)
            self.assertIn(WORKDIR, err, 'No workdir in key import output')
            self.assertRegex(out, PUB_IMPORT, 'Wrong pub key import output')
            ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_SEC)])
            self.assertEqual(ret, 0, 'Secret key import failed')
            self.assertNotRegex(err, NO_KEYRING, 'No-keyring message in secret key import output')
            self.assertRegex(err, EMPTY_SECRING, 'No empty secring msg in secret key import output')
            self.assertNotRegex(err, EMPTY_HOME)
            self.assertIn(WORKDIR, err, 'No workdir in secret key import output')
            self.assertRegex(out, SEC_IMPORT, 'wrong secret key import output')
            if not is_windows():
                # Attempt ro run with non-writable HOME
                newhome = os.path.join(WORKDIR, 'new')
                os.mkdir(newhome, 0o400)
                os.environ['HOME'] = newhome
                ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)])
                self.assertEqual(ret, 1)
                self.assertRegex(err, r'(?s)^.*Home directory \'.*new\' does not exist or is not writable!')
                self.assertRegex(err, RE_KEYSTORE_INFO)
                self.assertIn(WORKDIR, err)
                os.environ['HOME'] = WORKDIR
                shutil.rmtree(newhome, ignore_errors=True)
                # Attempt to load keyring with invalid permissions
                os.chmod(os.path.join(RNPDIR, PUBRING), 0o000)
                ret, out, err = run_proc(RNPK, ['--list-keys'])
                self.assertEqual(ret, 0)
                self.assertRegex(err, r'(?s)^.*Warning: failed to open keyring at path \'.*pubring\.gpg\' for reading.')
                self.assertRegex(out, r'(?s)^.*Alice <alice@rnp>')
                os.chmod(os.path.join(RNPDIR, SECRING), 0o000)
                ret, out, err = run_proc(RNPK, ['--list-keys'])
                self.assertEqual(ret, 1)
                self.assertRegex(err, r'(?s)^.*Warning: failed to open keyring at path \'.*pubring\.gpg\' for reading.')
                self.assertRegex(err, r'(?s)^.*Warning: failed to open keyring at path \'.*secring\.gpg\' for reading.')
                self.assertRegex(out, r'(?s)^.*Key\(s\) not found.')
            # Attempt to load keyring with random data
            shutil.rmtree(RNPDIR, ignore_errors=True)
            os.mkdir(RNPDIR, 0o700)
            random_text(os.path.join(RNPDIR, PUBRING), 1000)
            random_text(os.path.join(RNPDIR, SECRING), 1000)
            ret, out, err = run_proc(RNPK, ['--list-keys'])
            self.assertEqual(ret, 1)
            self.assertRegex(err, r'(?s)^.*Error: failed to load keyring from \'.*pubring\.gpg\'')
            self.assertNotRegex(err, r'(?s)^.*Error: failed to load keyring from \'.*secring\.gpg\'')
            self.assertRegex(out, r'(?s)^.*Key\(s\) not found.')
            # Run with .rnp home directory with empty keyrings
            shutil.rmtree(RNPDIR, ignore_errors=True)
            os.mkdir(RNPDIR, 0o700)
            random_text(os.path.join(RNPDIR, PUBRING), 0)
            random_text(os.path.join(RNPDIR, SECRING), 0)
            ret, out, err = run_proc(RNP, ['-c', src, '--password', 'password'])
            self.assertEqual(ret, 0, 'Symmetric encryption failed')
            self.assertNotRegex(err, EMPTY_KEYRING, 'Invalid encryption output')
            ret, out, err = run_proc(RNP, ['-d', enc, '--password', 'password', '--output', dec])
            self.assertEqual(ret, 0, 'Symmetric decryption failed')
            self.assertRegex(err, EMPTY_KEYRING, 'wrong decryption output')
            self.assertIn(WORKDIR, err, 'wrong decryption output')
            compare_files(src, dec, DEC_DIFFERS)
            remove_files(enc, dec)
            # Import key with empty keyrings in .rnp home directory
            ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_PUB)])
            self.assertEqual(ret, 0, 'Public key import failed')
            self.assertRegex(err, EMPTY_KEYRING, 'No empty keyring msg in key import output')
            self.assertIn(WORKDIR, err, 'No workdir in empty keyring key import output')
            self.assertRegex(out, PUB_IMPORT, 'Wrong pubkey import output')
            ret, out, err = run_proc(RNPK, ['--import', data_path(KEY_ALICE_SEC)])
            self.assertEqual(ret, 0, 'Secret key import failed')
            self.assertNotRegex(err, EMPTY_KEYRING, 'No empty keyring in key import output')
            self.assertRegex(err, EMPTY_SECRING, 'No empty secring in key import output')
            self.assertIn(WORKDIR, err, 'wrong key import output')
            self.assertRegex(out, SEC_IMPORT, 'wrong secret key import output')
        finally:
            os.environ['HOME'] = home
            shutil.rmtree(RNPDIR, ignore_errors=True)
            os.rename(RNPDIR + '-old', RNPDIR)
            clear_workfiles()

    def test_alg_aliases(self):
        src, enc = reg_workfiles('source', '.txt', EXT_PGP)
        with open(src, 'w+') as f:
            f.write('Hello world')
        # Encrypt file but forget to pass cipher name
        ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password', '--cipher'])
        self.assertEqual(ret, 1)
        self.assertRegex(err, r'(?s)^.*rnp(|\.exe): option( .--cipher.|) requires an argument.*')
        # Encrypt file using the unknown symmetric algorithm
        ret, _, err = run_proc(RNP, ['-c', src, '--cipher', 'bad', '--password', 'password'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Unsupported encryption algorithm: bad.*$')
        # Encrypt file but forget to pass hash algorithm name
        ret, _, err = run_proc(RNP, ['-c', src, '--password', 'password', '--hash'])
        self.assertNotEqual(ret, 0)
        # Encrypt file using the unknown hash algorithm
        ret, _, err = run_proc(RNP, ['-c', src, '--hash', 'bad', '--password', 'password'])
        self.assertNotEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Unsupported hash algorithm: bad.*$')
        # Encrypt file using the AES algorithm instead of AES-128
        ret, _, err = run_proc(RNP, ['-c', src, '--cipher', 'AES', '--password', 'password'])
        self.assertEqual(ret, 0)
        self.assertNotRegex(err, r'(?s)^.*Warning, unsupported encryption algorithm: AES.*$')
        self.assertNotRegex(err, r'(?s)^.*Unsupported encryption algorithm: AES.*$')
        # Make sure AES-128 was used
        ret, out, _ = run_proc(RNP, ['--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out,r'(?s)^.*Symmetric-key encrypted session key packet.*symmetric algorithm: 7 \(AES-128\).*$')
        remove_files(enc)
        # Encrypt file using the 3DES instead of tripledes
        ret, _, err = run_proc(RNP, ['-c', src, '--cipher', '3DES', '--password', 'password'])
        self.assertEqual(ret, 0)
        self.assertNotRegex(err, r'(?s)^.*Warning, unsupported encryption algorithm: 3DES.*$')
        self.assertNotRegex(err, r'(?s)^.*Unsupported encryption algorithm: 3DES.*$')
        # Make sure 3DES was used
        ret, out, _ = run_proc(RNP, ['--list-packets', enc])
        self.assertEqual(ret, 0)
        self.assertRegex(out,r'(?s)^.*Symmetric-key encrypted session key packet.*symmetric algorithm: 2 \(TripleDES\).*$')
        remove_files(enc)
        if RNP_RIPEMD160:
            # Use ripemd-160 hash instead of RIPEMD160
            ret, _, err = run_proc(RNP, ['-c', src, '--hash', 'ripemd-160', '--password', 'password'])
            self.assertEqual(ret, 0)
            self.assertNotRegex(err, r'(?s)^.*Unsupported hash algorithm: ripemd-160.*$')
            # Make sure RIPEMD160 was used
            ret, out, _ = run_proc(RNP, ['--list-packets', enc])
            self.assertEqual(ret, 0)
            self.assertRegex(out,r'(?s)^.*Symmetric-key encrypted session key packet.*s2k hash algorithm: 3 \(RIPEMD160\).*$')
            remove_files(enc)

    def test_core_dumps(self):
        CORE_DUMP = r'(?s)^.*warning: core dumps may be enabled, sensitive data may be leaked to disk.*$'
        NO_CORE_DUMP = r'(?s)^.*warning: --coredumps doesn\'t make sense on windows systems.*$'
        # Check rnpkeys for the message
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys'])
        self.assertEqual(ret, 0)
        self.assertNotRegex(err, CORE_DUMP)
        # Check rnp for the message
        ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--armor', '--password', 'password', '-c'], 'message')
        self.assertEqual(ret, 0)
        self.assertNotRegex(err, CORE_DUMP)
        # Enable coredumps for rnpkeys
        ret, _, err = run_proc(RNPK, ['--homedir', RNPDIR, '--list-keys', '--coredumps'])
        self.assertEqual(ret, 0)
        if is_windows():
            self.assertNotRegex(err, CORE_DUMP)
            self.assertRegex(err, NO_CORE_DUMP)
        else:
            self.assertRegex(err, CORE_DUMP)
            self.assertNotRegex(err, NO_CORE_DUMP)
        # Enable coredumps for rnp
        ret, _, err = run_proc(RNP, ['--homedir', RNPDIR, '--armor', '--password', 'password', '-c', '--coredumps'], 'message')
        self.assertEqual(ret, 0)
        if is_windows():
            self.assertNotRegex(err, CORE_DUMP)
            self.assertRegex(err, NO_CORE_DUMP)
        else:
            self.assertRegex(err, CORE_DUMP)
            self.assertNotRegex(err, NO_CORE_DUMP)

    def test_backend_version(self):
        BOTAN_BACKEND_VERSION = r'(?s)^.*.' \
        'Backend: Botan.*' \
        'Backend version: ([a-zA-Z\\.0-9]+).*$'
        OPENSSL_BACKEND_VERSION = r'(?s)^.*' \
        'Backend: OpenSSL.*' \
        'Backend version: ([a-zA-Z\\.0-9]+).*$'
        # Run without parameters and make sure it matches
        ret, out, _ = run_proc(RNP, [])
        self.assertNotEqual(ret, 0)
        match = re.match(BOTAN_BACKEND_VERSION, out) or re.match(OPENSSL_BACKEND_VERSION, out)
        self.assertTrue(match)
        # Run with version parameters
        ret, out, err = run_proc(RNP, ['--version'])
        self.assertEqual(ret, 0)
        match = re.match(BOTAN_BACKEND_VERSION, out)
        backend_prog = 'botan'
        if not match:
            match = re.match(OPENSSL_BACKEND_VERSION, out)
            backend_prog = 'openssl'
            openssl_root = os.getenv('RNP_TESTS_OPENSSL_ROOT')
        else:
            openssl_root = None
        self.assertTrue(match)
        # check there is no unexpected output
        self.assertNotRegex(err, r'(?is)^.*Unsupported.*$')
        self.assertNotRegex(err, r'(?is)^.*pgp_sa_to_openssl_string.*$')
        self.assertNotRegex(err, r'(?is)^.*unknown.*$')

        # In case when there are several openssl installations
        # testing environment is supposed to point to the right one
        # through OPENSSL_ROOT_DIR environment variable
        if is_windows():
            backend_prog += '.exe'
        backend_prog_ext = None
        if openssl_root is not None:
            backend_prog_ext = shutil.which(backend_prog, path = openssl_root + '/bin')
        else:
        # In all other cases
        # check that botan or openssl executable binary exists in PATH
            backend_prog_ext = shutil.which(backend_prog)

        if backend_prog_ext is None:
            return
        ret, out, _ = run_proc(backend_prog_ext, ['version'])
        self.assertEqual(ret, 0)
        self.assertIn(match.group(1), out)

    def test_help_message(self):
        # rnp help message
        # 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)

    def test_armored_detection_on_cleartext(self):
        ret, out, err = run_proc(RNP, ['--keyfile', data_path(SECRING_1), '--password', PASSWORD, '--clearsign'], 'Hello\n')
        self.assertEqual(ret, 0)
        self.assertRegex(out, r'(?s)^.*BEGIN PGP SIGNED MESSAGE.*$')
        self.assertRegex(out, r'(?s)^.*BEGIN PGP SIGNATURE.*$')
        ret, _, err = run_proc(RNP, ['--keyfile', data_path(PUBRING_1), '--verify', '-'], out)
        self.assertEqual(ret, 0)
        self.assertRegex(err, r'(?s)^.*Good signature made.*$')
        self.assertNotRegex(err, r'(?s)^.*Warning: missing or malformed CRC line.*$')
        self.assertNotRegex(err, r'(?s)^.*wrong armor trailer.*$')

class Encryption(unittest.TestCase):
    '''
        Things to try later:
        - 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)
        SIZES = Encryption.SIZES_R
        random.shuffle(SIZES)

        # Encrypt and decrypt cleartext using the AEAD
        for size, cipher, aead, bits, z in zip(SIZES, AEAD_C,
                                               AEAD_M, AEAD_B, Encryption.Z_R):
            if RNP_BOTAN_OCB_AV and (aead == 'ocb') and (size > 30000):
                continue
            rnp_sym_encryption_rnp_aead(size, cipher, z, [aead, bits], GPG_AEAD)

    def test_sym_encrypted__rnp_aead_botan_crash(self):
        if RNP_BOTAN_OCB_AV:
            return
        dst, = reg_workfiles('cleartext', '.txt')
        rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue'), dst)
        remove_files(dst)
        rnp_decrypt_file(data_path('test_messages/message.aead-windows-issue2'), dst)
        remove_files(dst)                                                 

    def test_aead_chunk_edge_cases(self):
        if not RNP_AEAD:
            print('AEAD is not available for RNP - skipping.')
            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
        if sign_key_size == 1024:
            return
        self._encrypt_decrypt(self.gpg, self.rnp)

    def do_test_decrypt(self, sign_key_size, enc_key_size):
        pfx = EncryptElgamal.key_pfx(sign_key_size, enc_key_size)
        self.operation_key_location = tuple((key_path(pfx, False), key_path(pfx, True)))
        self.rnp.userid = self.gpg.userid = pfx + AT_EXAMPLE
        if sign_key_size == 1024:
            return
        self._encrypt_decrypt(self.rnp, self.gpg)

    def test_encrypt_P1024_1024(self): self.do_test_encrypt(1024, 1024)
    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)

    # 1024-bit key generation test was removed since it uses SHA1, which is not allowed for key signatures since Jan 19, 2024.

    def test_generate_elgamal_key1536_in_gpg_and_encrypt(self):
        cmd = EncryptElgamal.GPG_GENERATE_DSA_ELGAMAL_PATTERN.format(1536, 1536, self.gpg.userid)
        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())
