summaryrefslogtreecommitdiffstats
path: root/tests/test_acme_dns_tiny.py
blob: bb967653157a30ae47c4f202639bb531a3277471 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
#!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'))