annotate host/acme_tiny @ 2057:634a44d10c96 acme-tiny

https.luan cleanup and add test
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 12 Nov 2025 23:18:04 -0700
parents 75cd3c7bda02
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
2056
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
1 #!/usr/bin/env python3
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
2 # Copyright Daniel Roesler, under MIT license, see LICENSE at github.com/diafygi/acme-tiny
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
3 import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
4 try:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
5 from urllib.request import urlopen, Request # Python 3
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
6 except ImportError: # pragma: no cover
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
7 from urllib2 import urlopen, Request # Python 2
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
8
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
9 DEFAULT_CA = "https://acme-v02.api.letsencrypt.org" # DEPRECATED! USE DEFAULT_DIRECTORY_URL INSTEAD
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
10 DEFAULT_DIRECTORY_URL = "https://acme-v02.api.letsencrypt.org/directory"
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
11
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
12 LOGGER = logging.getLogger(__name__)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
13 LOGGER.addHandler(logging.StreamHandler())
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
14 LOGGER.setLevel(logging.INFO)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
15
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
16 def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, disable_check=False, directory_url=DEFAULT_DIRECTORY_URL, contact=None, check_port=None):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
17 directory, acct_headers, alg, jwk = None, None, None, None # global variables
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
18
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
19 # helper functions - base64 encode for jose spec
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
20 def _b64(b):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
21 return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
22
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
23 # helper function - run external commands
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
24 def _cmd(cmd_list, stdin=None, cmd_input=None, err_msg="Command Line Error"):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
25 proc = subprocess.Popen(cmd_list, stdin=stdin, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
26 out, err = proc.communicate(cmd_input)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
27 if proc.returncode != 0:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
28 raise IOError("{0}\n{1}".format(err_msg, err))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
29 return out
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
30
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
31 # helper function - make request and automatically parse json response
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
32 def _do_request(url, data=None, err_msg="Error", depth=0):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
33 try:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
34 resp = urlopen(Request(url, data=data, headers={"Content-Type": "application/jose+json", "User-Agent": "acme-tiny"}))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
35 resp_data, code, headers = resp.read().decode("utf8"), resp.getcode(), resp.headers
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
36 except IOError as e:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
37 resp_data = e.read().decode("utf8") if hasattr(e, "read") else str(e)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
38 code, headers = getattr(e, "code", None), {}
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
39 try:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
40 resp_data = json.loads(resp_data) # try to parse json results
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
41 except ValueError:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
42 pass # ignore json parsing errors
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
43 if depth < 100 and code == 400 and resp_data['type'] == "urn:ietf:params:acme:error:badNonce":
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
44 raise IndexError(resp_data) # allow 100 retrys for bad nonces
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
45 if code not in [200, 201, 204]:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
46 raise ValueError("{0}:\nUrl: {1}\nData: {2}\nResponse Code: {3}\nResponse: {4}".format(err_msg, url, data, code, resp_data))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
47 return resp_data, code, headers
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
48
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
49 # helper function - make signed requests
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
50 def _send_signed_request(url, payload, err_msg, depth=0):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
51 payload64 = "" if payload is None else _b64(json.dumps(payload).encode('utf8'))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
52 new_nonce = _do_request(directory['newNonce'])[2]['Replay-Nonce']
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
53 protected = {"url": url, "alg": alg, "nonce": new_nonce}
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
54 protected.update({"jwk": jwk} if acct_headers is None else {"kid": acct_headers['Location']})
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
55 protected64 = _b64(json.dumps(protected).encode('utf8'))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
56 protected_input = "{0}.{1}".format(protected64, payload64).encode('utf8')
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
57 out = _cmd(["openssl", "dgst", "-sha256", "-sign", account_key], stdin=subprocess.PIPE, cmd_input=protected_input, err_msg="OpenSSL Error")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
58 data = json.dumps({"protected": protected64, "payload": payload64, "signature": _b64(out)})
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
59 try:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
60 return _do_request(url, data=data.encode('utf8'), err_msg=err_msg, depth=depth)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
61 except IndexError: # retry bad nonces (they raise IndexError)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
62 return _send_signed_request(url, payload, err_msg, depth=(depth + 1))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
63
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
64 # helper function - poll until complete
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
65 def _poll_until_not(url, pending_statuses, err_msg):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
66 result, t0 = None, time.time()
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
67 while result is None or result['status'] in pending_statuses:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
68 assert (time.time() - t0 < 3600), "Polling timeout" # 1 hour timeout
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
69 time.sleep(0 if result is None else 2)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
70 result, _, _ = _send_signed_request(url, None, err_msg)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
71 return result
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
72
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
73 # parse account key to get public key
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
74 log.info("Parsing account key...")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
75 out = _cmd(["openssl", "rsa", "-in", account_key, "-noout", "-text"], err_msg="OpenSSL Error")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
76 pub_pattern = r"modulus:[\s]+?00:([a-f0-9\:\s]+?)\npublicExponent: ([0-9]+)"
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
77 pub_hex, pub_exp = re.search(pub_pattern, out.decode('utf8'), re.MULTILINE|re.DOTALL).groups()
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
78 pub_exp = "{0:x}".format(int(pub_exp))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
79 pub_exp = "0{0}".format(pub_exp) if len(pub_exp) % 2 else pub_exp
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
80 alg, jwk = "RS256", {
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
81 "e": _b64(binascii.unhexlify(pub_exp.encode("utf-8"))),
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
82 "kty": "RSA",
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
83 "n": _b64(binascii.unhexlify(re.sub(r"(\s|:)", "", pub_hex).encode("utf-8"))),
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
84 }
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
85 accountkey_json = json.dumps(jwk, sort_keys=True, separators=(',', ':'))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
86 thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest())
2051
331c1853af25 bundle acme-tiny as a pyinstaller executable to avoid python dependency
Violet7
parents:
diff changeset
87
2056
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
88 # find domains
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
89 log.info("Parsing CSR...")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
90 out = _cmd(["openssl", "req", "-in", csr, "-noout", "-text"], err_msg="Error loading {0}".format(csr))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
91 domains = set([])
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
92 common_name = re.search(r"Subject:.*? CN\s?=\s?([^\s,;/]+)", out.decode('utf8'))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
93 if common_name is not None:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
94 domains.add(common_name.group(1))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
95 subject_alt_names = re.search(r"X509v3 Subject Alternative Name: (?:critical)?\n +([^\n]+)\n", out.decode('utf8'), re.MULTILINE|re.DOTALL)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
96 if subject_alt_names is not None:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
97 for san in subject_alt_names.group(1).split(", "):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
98 if san.startswith("DNS:"):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
99 domains.add(san[4:])
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
100 log.info(u"Found domains: {0}".format(", ".join(domains)))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
101
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
102 # get the ACME directory of urls
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
103 log.info("Getting directory...")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
104 directory_url = CA + "/directory" if CA != DEFAULT_CA else directory_url # backwards compatibility with deprecated CA kwarg
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
105 directory, _, _ = _do_request(directory_url, err_msg="Error getting directory")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
106 log.info("Directory found!")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
107
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
108 # create account, update contact details (if any), and set the global key identifier
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
109 log.info("Registering account...")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
110 reg_payload = {"termsOfServiceAgreed": True} if contact is None else {"termsOfServiceAgreed": True, "contact": contact}
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
111 account, code, acct_headers = _send_signed_request(directory['newAccount'], reg_payload, "Error registering")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
112 log.info("{0} Account ID: {1}".format("Registered!" if code == 201 else "Already registered!", acct_headers['Location']))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
113 if contact is not None:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
114 account, _, _ = _send_signed_request(acct_headers['Location'], {"contact": contact}, "Error updating contact details")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
115 log.info("Updated contact details:\n{0}".format("\n".join(account.get('contact') or [])))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
116
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
117 # create a new order
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
118 log.info("Creating new order...")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
119 order_payload = {"identifiers": [{"type": "dns", "value": d} for d in domains]}
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
120 order, _, order_headers = _send_signed_request(directory['newOrder'], order_payload, "Error creating new order")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
121 log.info("Order created!")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
122
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
123 # get the authorizations that need to be completed
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
124 for auth_url in order['authorizations']:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
125 authorization, _, _ = _send_signed_request(auth_url, None, "Error getting challenges")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
126 domain = authorization['identifier']['value']
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
127
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
128 # skip if already valid
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
129 if authorization['status'] == "valid":
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
130 log.info("Already verified: {0}, skipping...".format(domain))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
131 continue
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
132 log.info("Verifying {0}...".format(domain))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
133
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
134 # find the http-01 challenge and write the challenge file
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
135 challenge = [c for c in authorization['challenges'] if c['type'] == "http-01"][0]
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
136 token = re.sub(r"[^A-Za-z0-9_\-]", "_", challenge['token'])
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
137 keyauthorization = "{0}.{1}".format(token, thumbprint)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
138 wellknown_path = os.path.join(acme_dir, token)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
139 with open(wellknown_path, "w") as wellknown_file:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
140 wellknown_file.write(keyauthorization)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
141
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
142 # check that the file is in place
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
143 try:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
144 wellknown_url = "http://{0}{1}/.well-known/acme-challenge/{2}".format(domain, "" if check_port is None else ":{0}".format(check_port), token)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
145 assert (disable_check or _do_request(wellknown_url)[0] == keyauthorization)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
146 except (AssertionError, ValueError) as e:
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
147 raise ValueError("Wrote file to {0}, but couldn't download {1}: {2}".format(wellknown_path, wellknown_url, e))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
148
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
149 # say the challenge is done
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
150 _send_signed_request(challenge['url'], {}, "Error submitting challenges: {0}".format(domain))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
151 authorization = _poll_until_not(auth_url, ["pending"], "Error checking challenge status for {0}".format(domain))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
152 if authorization['status'] != "valid":
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
153 raise ValueError("Challenge did not pass for {0}: {1}".format(domain, authorization))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
154 os.remove(wellknown_path)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
155 log.info("{0} verified!".format(domain))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
156
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
157 # finalize the order with the csr
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
158 log.info("Signing certificate...")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
159 csr_der = _cmd(["openssl", "req", "-in", csr, "-outform", "DER"], err_msg="DER Export Error")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
160 _send_signed_request(order['finalize'], {"csr": _b64(csr_der)}, "Error finalizing order")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
161
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
162 # poll the order to monitor when it's done
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
163 order = _poll_until_not(order_headers['Location'], ["pending", "processing"], "Error checking order status")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
164 if order['status'] != "valid":
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
165 raise ValueError("Order failed: {0}".format(order))
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
166
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
167 # download the certificate
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
168 certificate_pem, _, _ = _send_signed_request(order['certificate'], None, "Certificate download failed")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
169 log.info("Certificate signed!")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
170 return certificate_pem
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
171
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
172 def main(argv=None):
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
173 parser = argparse.ArgumentParser(
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
174 formatter_class=argparse.RawDescriptionHelpFormatter,
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
175 description=textwrap.dedent("""\
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
176 This script automates the process of getting a signed TLS certificate from Let's Encrypt using the ACME protocol.
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
177 It will need to be run on your server and have access to your private account key, so PLEASE READ THROUGH IT!
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
178 It's only ~200 lines, so it won't take long.
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
179
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
180 Example Usage: python acme_tiny.py --account-key ./account.key --csr ./domain.csr --acme-dir /usr/share/nginx/html/.well-known/acme-challenge/ > signed_chain.crt
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
181 """)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
182 )
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
183 parser.add_argument("--account-key", required=True, help="path to your Let's Encrypt account private key")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
184 parser.add_argument("--csr", required=True, help="path to your certificate signing request")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
185 parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
186 parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
187 parser.add_argument("--disable-check", default=False, action="store_true", help="disable checking if the challenge file is hosted correctly before telling the CA")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
188 parser.add_argument("--directory-url", default=DEFAULT_DIRECTORY_URL, help="certificate authority directory url, default is Let's Encrypt")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
189 parser.add_argument("--ca", default=DEFAULT_CA, help="DEPRECATED! USE --directory-url INSTEAD!")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
190 parser.add_argument("--contact", metavar="CONTACT", default=None, nargs="*", help="Contact details (e.g. mailto:aaa@bbb.com) for your account-key")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
191 parser.add_argument("--check-port", metavar="PORT", default=None, help="what port to use when self-checking the challenge file, default is port 80")
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
192
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
193 args = parser.parse_args(argv)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
194 LOGGER.setLevel(args.quiet or LOGGER.level)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
195 signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca, disable_check=args.disable_check, directory_url=args.directory_url, contact=args.contact, check_port=args.check_port)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
196 sys.stdout.write(signed_crt)
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
197
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
198 if __name__ == "__main__": # pragma: no cover
75cd3c7bda02 revert to using acme_tiny as a python script
Violet7
parents: 2051
diff changeset
199 main(sys.argv[1:])