#!python3 import collections import inspect import logging import os import subprocess import sys import tempfile import pytest # append cwd to module importable paths (pytest-3 should be executed from main # directory). sys.path.append(os.path.realpath(".")) import acme_dns_tiny ACME_STAGING_DIRECTORY = \ 'https://acme-staging-v02.api.letsencrypt.org/directory' DOMAIN = os.getenv('TEST_DOMAIN') CONTACT = os.getenv('TEST_CONTACT') # form: mailto:name@domain SCRIPT = os.getenv('TEST_SCRIPT') def funcname(): return inspect.currentframe().f_back.f_code.co_name def parent_funcname(): return inspect.currentframe().f_back.f_back.f_code.co_name def assert_success(capsys, args): name = parent_funcname() logging.info(f'assert_success({name}, {args})') acme_dns_tiny.main(args) captured = capsys.readouterr() #assert not captured.err certlist = captured.out.split('-----BEGIN CERTIFICATE-----') assert len(certlist) == 3 assert certlist[0] == '' assert '-----END CERTIFICATE-----\n' in certlist[1] assert '-----END CERTIFICATE-----\n' in certlist[2] textchain = acme_dns_tiny.openssl(['x509', '-text', '-noout'], stdin=captured.out) assert 'Issuer' in textchain def assert_assertion(args): name = parent_funcname() logging.info(f'assert_assertion({name}, {args})') with pytest.raises(ValueError): acme_dns_tiny.main(args) @pytest.fixture(scope='module') def keys(tmpdir_factory): tmpdir = tmpdir_factory.mktemp("data") def _gen_key(name, size): path = str(tmpdir.join(name)) acme_dns_tiny.openssl(['genrsa', '-out', path, str(size)]) return path return { 'account_key': _gen_key('account_key', 4096), 'domain_key': _gen_key('domain_key', 4096), 'weak_account_key': _gen_key('weak_account_key', 1024), 'weak_domain_key': _gen_key('weak_domain_key', 1024), } @pytest.fixture(scope='module') def csr_args(tmpdir_factory, keys): tmpdir = tmpdir_factory.mktemp("data") default_args = { '--acme-directory': ACME_STAGING_DIRECTORY, '--script': SCRIPT, '--account-key': keys['account_key'], #'--ttl': 60, # already the default '--ttl': None, '--contact': None, '--verbose': None, '--quiet': None, } def _csr(subj, key_name='domain_key', san=None, args={}): name = parent_funcname() domain_key = keys[key_name] path = str(tmpdir.join(name)) if san is None: acme_dns_tiny.openssl([ 'req', '-new', '-key', domain_key, '-subj', subj, '-out', path ]) else: with tempfile.NamedTemporaryFile('w', encoding='utf8') as conf, \ open('/etc/ssl/openssl.cnf', 'r', encoding='utf8') as orig: conf.write(orig.read()) conf.write(f'\n[SAN]\nsubjectAltName={san}\n') conf.flush() acme_dns_tiny.openssl([ 'req', '-new', '-key', domain_key, '-subj', subj, '-reqexts', 'SAN', '-config', conf.name, '-out', path ]) return {**default_args, '--csr': path, **args} return _csr def test_sanity_env(): assert bool(DOMAIN) assert bool(CONTACT) assert bool(SCRIPT) def test_sanity_command(): subprocess.run([SCRIPT, 'add', f'_acme-challenge.{DOMAIN}.', 'dummy']) def test_success_single_domain_in_cn(csr_args, capsys): subj = f'/CN={DOMAIN}' assert_success(capsys, csr_args(subj)) def test_success_wildcard_in_cn(csr_args, capsys): subj = f'/CN=*.{DOMAIN}' assert_success(capsys, csr_args(subj)) def test_success_san_only(csr_args, capsys): subj = '/' san = f'DNS:{DOMAIN},DNS:www.{DOMAIN}' assert_success(capsys, csr_args(subj=subj, san=san)) def test_success_san_only_contact(csr_args, capsys): subj = '/' san = f'DNS:{DOMAIN},DNS:www.{DOMAIN}' args = {'--contact': [CONTACT]} assert_success(capsys, csr_args(subj=subj, san=san, args=args)) def test_success_san_contact(csr_args, capsys): subj = f'/CN={DOMAIN}' san = f'DNS:www.{DOMAIN}' args = {'--contact': [CONTACT]} assert_success(capsys, csr_args(subj=subj, san=san, args=args)) def test_success_wildcard_san(csr_args, capsys): subj = '/' san = f'DNS:{DOMAIN},DNS:*.{DOMAIN}' assert_success(capsys, csr_args(subj=subj, san=san)) def test_fail_weak_account_key(csr_args, keys): subj = f'/CN={DOMAIN}' args = {'--account-key': keys['weak_account_key']} assert_assertion(csr_args(subj, args=args)) def test_fail_weak_domain_key(csr_args): subj = f'/CN={DOMAIN}' assert_assertion(csr_args(subj, key_name='weak_domain_key')) def test_fail_csr_by_account(csr_args): subj = f'/CN={DOMAIN}' # single, csr wrongly signed by account key assert_assertion(csr_args(subj, key_name='account_key'))