File: utils.py

package info (click to toggle)
acme-tiny 1%3A5.0.2-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 216 kB
  • sloc: python: 755; makefile: 4
file content (283 lines) | stat: -rw-r--r-- 12,693 bytes parent folder | download | duplicates (2)
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
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
import os
import sys
import json
import time
from tempfile import NamedTemporaryFile, mkdtemp
from subprocess import Popen
try:
    from urllib.request import urlopen # Python 3
except ImportError: # pragma: no cover
    from urllib2 import urlopen # Python 2

def gen_keys(domain):
    """ Generate test account and domain keys """

    # openssl config is system dependent
    openssl_cnf = None
    for possible_cnf in ['/etc/pki/tls/openssl.cnf', '/etc/ssl/openssl.cnf']:
        if os.path.exists(possible_cnf):
            with open(possible_cnf) as f:
                openssl_cnf = f.read().encode("utf8")

    # good account key
    account_key = NamedTemporaryFile()
    Popen(["openssl", "genrsa", "-out", account_key.name, "2048"]).wait()

    # weak 1024 bit key
    weak_key = NamedTemporaryFile()
    Popen(["openssl", "genrsa", "-out", weak_key.name, "1024"]).wait()

    # good domain key
    domain_key = NamedTemporaryFile()
    Popen(["openssl", "genrsa", "-out", domain_key.name, "2048"]).wait()

    # good domain csr
    domain_csr = NamedTemporaryFile()
    domain_conf = NamedTemporaryFile()
    domain_conf.write(openssl_cnf)
    domain_conf.write("\n[SAN]\nsubjectAltName=DNS:{0}\n".format(domain).encode("utf8"))
    domain_conf.flush()
    domain_conf.seek(0)
    Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key.name,
        "-subj", "/", "-reqexts", "SAN", "-config", domain_conf.name,
        "-out", domain_csr.name]).wait()

    # good domain via the Common Name
    cn_key = NamedTemporaryFile()
    cn_csr = NamedTemporaryFile()
    Popen(["openssl", "req", "-newkey", "rsa:2048", "-nodes", "-keyout", cn_key.name,
        "-subj", "/CN={0}".format(domain), "-out", cn_csr.name]).wait()

    # invalid domain csr
    invalid_csr = NamedTemporaryFile()
    invalid_conf = NamedTemporaryFile()
    invalid_conf.write(openssl_cnf)
    invalid_conf.write(u"\n[SAN]\nsubjectAltName=DNS:\xC3\xA0\xC2\xB2\xC2\xA0_\xC3\xA0\xC2\xB2\xC2\xA0.com\n".encode("utf8"))
    invalid_conf.seek(0)
    Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key.name,
        "-subj", "/", "-reqexts", "SAN", "-config", invalid_conf.name,
        "-out", invalid_csr.name]).wait()

    # nonexistent domain csr
    nonexistent_csr = NamedTemporaryFile()
    nonexistent_conf = NamedTemporaryFile()
    nonexistent_conf.write(openssl_cnf)
    nonexistent_conf.write("\n[SAN]\nsubjectAltName=DNS:404.gethttpsforfree.com\n".encode("utf8"))
    nonexistent_conf.seek(0)
    Popen(["openssl", "req", "-new", "-sha256", "-key", domain_key.name,
        "-subj", "/", "-reqexts", "SAN", "-config", nonexistent_conf.name,
        "-out", nonexistent_csr.name]).wait()

    # account-signed domain csr
    account_csr = NamedTemporaryFile()
    account_conf = NamedTemporaryFile()
    account_conf.write(openssl_cnf)
    account_conf.write("\n[SAN]\nsubjectAltName=DNS:{0}\n".format(domain).encode("utf8"))
    account_conf.seek(0)
    Popen(["openssl", "req", "-new", "-sha256", "-key", account_key.name,
        "-subj", "/", "-reqexts", "SAN", "-config", account_conf.name,
        "-out", account_csr.name]).wait()

    return {
        "account_key": account_key,
        "weak_key": weak_key,
        "cn_key": cn_key,
        "cn_csr": cn_csr,
        "domain_key": domain_key,
        "domain_csr": domain_csr,
        "invalid_csr": invalid_csr,
        "nonexistent_csr": nonexistent_csr,
        "account_csr": account_csr,
    }

# Pebble server TLS certs
# !!! DO NOT USE FOR ANYTHING EXCEPT TESTS !!!
# Generated using the following commands:
#   openssl genrsa -out pebble_cert.key 4096
#   openssl req -x509 -new -nodes -key pebble_cert.key -days 9999 -subj "/" -addext "subjectAltName=DNS:localhost" -out pebble_cert.crt
PEBBLE_CERT_KEY = """
-----BEGIN RSA PRIVATE KEY-----
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
-----END RSA PRIVATE KEY-----
"""
PEBBLE_CERT = """
-----BEGIN CERTIFICATE-----
MIIE9zCCAt+gAwIBAgIUTKfSOxaM/Gy3XrDc/3+xn4uy44YwDQYJKoZIhvcNAQEL
BQAwADAeFw0yMTA4MDIyMTQ3MjZaFw00ODEyMTcyMTQ3MjZaMAAwggIiMA0GCSqG
SIb3DQEBAQUAA4ICDwAwggIKAoICAQDm9avXjGUpl3gdhnZYvIhl2xMO1WcEVHt6
DermNjF9lEfPU13XtPcz3gajl6mHkbeHx/eWlA2IsIIZ6cwDXitC3BJ5SiUUgDPn
zP0/H7EEzaFbHrTJx8nylEC9jmCH1uSBn9xkNVrfc8WJgBcAn41yPAxDNWLBfLqy
OYrdC2CNBplbZBTz/UAdW9nJUgJSFFKWyvYt9Oq2gjI2yOZZKq5pYHZsEJxUAqtx
/CHg5QPFYMdmXddELAGeu8CpVdrrxN1hwfjvO07G5mTYAf39nw2ZNtcMnrlplZVb
Ud1v4fd+fhb5mDVzIA7TawR7LoppUlq7KeJmE/atryeDmFjmX3g0JTiWXplhogOe
SOD4Rvttj9aJFVx6yBJN609eVtzQTsM+MTdT6XHilCFU/zJRfZVAumhV4pwyf0xs
K/prRs1HXbGZBuW5HRUPeEcoC+N/3YUVWqvUqIP/Jkr1oBCPBE3ZO+uoi1mkOYlz
raDBmAtFKlCGl88ZpKa0QS6EppIcnGDZbHM1Q/RC1pMuNPUWYG7BklNw33Pkir2k
Q0urxc+vYvWDaSb8IrVafnzhaaay8sTXJZK4gxE9TmBDqsvvdborn493zY0Y1xQc
mBqlUScddt0pyAJUjF5VgFPD2x9HsxG9VuX07x6yt5l/IWM1O0OXe3PDrJDxSAsr
NL8A80WxzwIDAQABo2kwZzAdBgNVHQ4EFgQUsA4MGHUxvlXDhDUm2K7LN9u7DhUw
HwYDVR0jBBgwFoAUsA4MGHUxvlXDhDUm2K7LN9u7DhUwDwYDVR0TAQH/BAUwAwEB
/zAUBgNVHREEDTALgglsb2NhbGhvc3QwDQYJKoZIhvcNAQELBQADggIBAILebPEw
06Fow41ZbnSxTiuD9AaAL4PJe4l0gO33BoaES/kmoJMa9cEEkRTzScwljhC4eekc
EnbqT2H1pgBwYSg9SW/fYrhGzSlKHCA62VNQ1benZSO78IY12ld+g6OaYT+QLtXf
lBiRF4+L/9BOzYprfymr2HwdyaOLZc6Mf3YhGmMYqMdKriqrsqMYZmLEZ5Z2pNoF
kiIFnWJeXcuSMkML/TWHMqL1IY1PN07bXeeWWsC8xD+YsLQIfdbEbf/hGSoby6nO
JDDVUNHiSwEEhreXgBc/sLj/7G/BEEuU/u/fOi+NoK/gy4PHzNE7ZmmsDVyedByh
3L5bBsZGJwK+Rbz7fnthKe3ghhd+fSwRvfx07V3QBwlcD1iq/Im/UR5p3zbSR0gt
zplW4F6fLAjkkGBNVNKEgRdYTF8FzHJWoHH1+kBKylb9L1p6kbUhJAbtYfkhZf2E
5QehOPt3WnVJDeDVKTyhFUWsOrOVmXuY5QV114jJaEfrBKrsJ/DTDrBiOS0jKDdI
MQ2xZK0fvI15Osnr2OCggZk5kdAyaOM3ERWPVetBF9aKKFpIQMi1keOM/U5vBAJy
LYEIK0jwMTf3vctsHkeWGVVMf2P498/+KHbomtUBBJU/0jp9G62xWukle5pfzfM9
F5OzP6TuNVIGpCKuPMLZTfcSCPV3ZUEizOVX
-----END CERTIFICATE-----
"""

class PebbleServerException(Exception):
    pass

def setup_pebble(pebble_bin_path, bad_nonces=0):
    """ Start a pebble server and challenge file server """

    # make testing cert temp files
    pebble_crt = NamedTemporaryFile(delete=False)  # keep until manually cleaned up in tearDown
    pebble_crt.write(PEBBLE_CERT.encode("utf8"))
    pebble_crt.flush()

    pebble_key = NamedTemporaryFile(delete=False)  # keep until manually cleaned up in tearDown
    pebble_key.write(PEBBLE_CERT_KEY.encode("utf8"))
    pebble_key.flush()

    # generate the pebble config
    pebble_config = {
        "pebble": {
            "listenAddress": "127.0.0.1:14000",
            "managementListenAddress": "127.0.0.1:15000",
            "certificate": pebble_crt.name,
            "privateKey": pebble_key.name,
            "httpPort": 5002,
            "tlsPort": 5001,
            "ocspResponderURL": "",
            "externalAccountBindingRequired": False,
        }
    }
    pebble_conf_file = NamedTemporaryFile()
    pebble_conf_file.write(json.dumps(pebble_config, indent=4, sort_keys=True).encode("utf8"))
    pebble_conf_file.flush()

    # start the pebble server
    os.environ['PEBBLE_AUTHZREUSE'] = str(100)
    os.environ['PEBBLE_WFE_NONCEREJECT'] = str(bad_nonces)
    pebble_server_proc = Popen([pebble_bin_path, "-config", pebble_conf_file.name])

    # trust the pebble server cert by default
    os.environ['SSL_CERT_FILE'] = pebble_config['pebble']['certificate']

    # wait until the pebble server responds
    wait_start = time.time()
    MAX_WAIT = 10  # 10 seconds
    while (time.time() - wait_start) < MAX_WAIT:
        try:
            resp = urlopen("https://localhost:14000/dir")
            if resp.getcode() == 200:
                break  # done!
        except IOError:
            pass  # don't care about failed connections
        time.sleep(0.5)  # wait a bit and try again
    else: # pragma: no cover
        pebble_server_proc.terminate()
        raise PebbleServerException("pebble failed to start :(")

    return pebble_server_proc, pebble_config

class ChallengeFileServerException(Exception):
    pass

def setup_local_fileserver(test_port, pebble_proc=None):
    """ Start a local challenge file server """

    # set challenge file temporary directory
    base_tempdir = mkdtemp()
    acme_tempdir = os.path.join(base_tempdir, ".well-known", "acme-challenge")
    os.makedirs(acme_tempdir)

    # start a fileserver for serving up challenges
    local_fileserver_proc = Popen([
        "python",
        "-m", "SimpleHTTPServer" if sys.version_info.major == 2 else "http.server",
        test_port,
    ], cwd=base_tempdir)

    # make sure the fileserver is running
    testchallenge_text = "aaa".encode("utf8")
    testchallenge_path = os.path.join(acme_tempdir, "a.txt")
    testchallenge_file = open(testchallenge_path, "wb")
    testchallenge_file.write(testchallenge_text)
    testchallenge_file.close()
    wait_start = time.time()
    MAX_WAIT = 10  # 10 seconds
    while (time.time() - wait_start) < MAX_WAIT:
        try:
            resp = urlopen("http://localhost:{}/.well-known/acme-challenge/a.txt".format(test_port))
            if resp.getcode() == 200 and resp.read() == testchallenge_text:
                os.remove(testchallenge_path)
                break  # done!
        except IOError:
            pass  # don't care about failed connections
        time.sleep(0.5)  # wait a bit and try again
    else: # pragma: no cover
        os.remove(testchallenge_path)
        local_fileserver_proc.terminate()
        if pebble_proc is not None:
            pebble_proc.terminate()  # also shut down pebble server (if any) before raising exception
        raise ChallengeFileServerException("challenge file server failed to start :(")

    return local_fileserver_proc, base_tempdir, acme_tempdir