|
|
9ddfc06 |
diff -up ./acme_tiny.py.chain ./acme_tiny.py
|
|
|
9ddfc06 |
--- ./acme_tiny.py.chain 2016-08-01 15:53:22.000000000 -0400
|
|
|
9ddfc06 |
+++ ./acme_tiny.py 2016-08-22 19:20:51.208523709 -0400
|
|
|
9ddfc06 |
@@ -1,4 +1,4 @@
|
|
|
9ddfc06 |
-#!/usr/bin/env python
|
|
|
9ddfc06 |
+#!/usr/bin/python
|
|
|
9ddfc06 |
import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging
|
|
|
9ddfc06 |
try:
|
|
|
9ddfc06 |
from urllib.request import urlopen # Python 3
|
|
|
9ddfc06 |
@@ -12,7 +12,7 @@ LOGGER = logging.getLogger(__name__)
|
|
|
9ddfc06 |
LOGGER.addHandler(logging.StreamHandler())
|
|
|
9ddfc06 |
LOGGER.setLevel(logging.INFO)
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
-def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA):
|
|
|
9ddfc06 |
+def get_crt(account_key, csr, acme_dir, log=LOGGER, CA=DEFAULT_CA, chain=False):
|
|
|
9ddfc06 |
# helper function base64 encode for jose spec
|
|
|
9ddfc06 |
def _b64(b):
|
|
|
9ddfc06 |
return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "")
|
|
|
9ddfc06 |
@@ -57,9 +57,9 @@ def get_crt(account_key, csr, acme_dir,
|
|
|
9ddfc06 |
})
|
|
|
9ddfc06 |
try:
|
|
|
9ddfc06 |
resp = urlopen(url, data.encode('utf8'))
|
|
|
9ddfc06 |
- return resp.getcode(), resp.read()
|
|
|
9ddfc06 |
+ return resp.getcode(), resp.read(), resp.info()
|
|
|
9ddfc06 |
except IOError as e:
|
|
|
9ddfc06 |
- return getattr(e, "code", None), getattr(e, "read", e.__str__)()
|
|
|
9ddfc06 |
+ return getattr(e, "code", None), getattr(e, "read", e.__str__)(), None
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
# find domains
|
|
|
9ddfc06 |
log.info("Parsing CSR...")
|
|
|
9ddfc06 |
@@ -80,7 +80,7 @@ def get_crt(account_key, csr, acme_dir,
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
# get the certificate domains and expiration
|
|
|
9ddfc06 |
log.info("Registering account...")
|
|
|
9ddfc06 |
- code, result = _send_signed_request(CA + "/acme/new-reg", {
|
|
|
9ddfc06 |
+ code, result, headers = _send_signed_request(CA + "/acme/new-reg", {
|
|
|
9ddfc06 |
"resource": "new-reg",
|
|
|
9ddfc06 |
"agreement": "https://letsencrypt.org/documents/LE-SA-v1.1.1-August-1-2016.pdf",
|
|
|
9ddfc06 |
})
|
|
|
9ddfc06 |
@@ -96,7 +96,7 @@ def get_crt(account_key, csr, acme_dir,
|
|
|
9ddfc06 |
log.info("Verifying {0}...".format(domain))
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
# get new challenge
|
|
|
9ddfc06 |
- code, result = _send_signed_request(CA + "/acme/new-authz", {
|
|
|
9ddfc06 |
+ code, result, headers = _send_signed_request(CA + "/acme/new-authz", {
|
|
|
9ddfc06 |
"resource": "new-authz",
|
|
|
9ddfc06 |
"identifier": {"type": "dns", "value": domain},
|
|
|
9ddfc06 |
})
|
|
|
9ddfc06 |
@@ -123,7 +123,7 @@ def get_crt(account_key, csr, acme_dir,
|
|
|
9ddfc06 |
wellknown_path, wellknown_url))
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
# notify challenge are met
|
|
|
9ddfc06 |
- code, result = _send_signed_request(challenge['uri'], {
|
|
|
9ddfc06 |
+ code, result, headers = _send_signed_request(challenge['uri'], {
|
|
|
9ddfc06 |
"resource": "challenge",
|
|
|
9ddfc06 |
"keyAuthorization": keyauthorization,
|
|
|
9ddfc06 |
})
|
|
|
9ddfc06 |
@@ -153,17 +153,32 @@ def get_crt(account_key, csr, acme_dir,
|
|
|
9ddfc06 |
proc = subprocess.Popen(["openssl", "req", "-in", csr, "-outform", "DER"],
|
|
|
9ddfc06 |
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
9ddfc06 |
csr_der, err = proc.communicate()
|
|
|
9ddfc06 |
- code, result = _send_signed_request(CA + "/acme/new-cert", {
|
|
|
9ddfc06 |
+ code, result, headers = _send_signed_request(CA + "/acme/new-cert", {
|
|
|
9ddfc06 |
"resource": "new-cert",
|
|
|
9ddfc06 |
"csr": _b64(csr_der),
|
|
|
9ddfc06 |
})
|
|
|
9ddfc06 |
if code != 201:
|
|
|
9ddfc06 |
raise ValueError("Error signing certificate: {0} {1}".format(code, result))
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
+ certchain = [result]
|
|
|
9ddfc06 |
+ if chain:
|
|
|
9ddfc06 |
+ def parse_link_header(line):
|
|
|
9ddfc06 |
+ m = re.search(r"^Link:\s*<([^>]*)>(?:\s*;\s*(.*))?\r\n$", line)
|
|
|
9ddfc06 |
+ return (m.group(1), dict([(a[0],a[1].strip('"'))
|
|
|
9ddfc06 |
+ for a in [attr.split("=")
|
|
|
9ddfc06 |
+ for attr in m.group(2).split("\s*;\s*")]]))
|
|
|
9ddfc06 |
+
|
|
|
9ddfc06 |
+ up = [
|
|
|
9ddfc06 |
+ link for link, attr in [
|
|
|
9ddfc06 |
+ parse_link_header(l) for l in headers.getallmatchingheaders("Link")
|
|
|
9ddfc06 |
+ ] if attr['rel'] == 'up'
|
|
|
9ddfc06 |
+ ]
|
|
|
9ddfc06 |
+ certchain += [urlopen(url).read() for url in up]
|
|
|
9ddfc06 |
+
|
|
|
9ddfc06 |
# return signed certificate!
|
|
|
9ddfc06 |
log.info("Certificate signed!")
|
|
|
9ddfc06 |
- return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format(
|
|
|
9ddfc06 |
- "\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64)))
|
|
|
9ddfc06 |
+ return "".join(["""-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format(
|
|
|
9ddfc06 |
+ "\n".join(textwrap.wrap(base64.b64encode(cert).decode('utf8'), 64))) for cert in certchain])
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
def main(argv):
|
|
|
9ddfc06 |
parser = argparse.ArgumentParser(
|
|
|
9ddfc06 |
@@ -188,11 +203,19 @@ def main(argv):
|
|
|
9ddfc06 |
parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory")
|
|
|
9ddfc06 |
parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors")
|
|
|
9ddfc06 |
parser.add_argument("--ca", default=DEFAULT_CA, help="certificate authority, default is Let's Encrypt")
|
|
|
9ddfc06 |
+ parser.add_argument("--chain", action="store_true",
|
|
|
9ddfc06 |
+ help="fetch and append intermediate certs to output")
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
args = parser.parse_args(argv)
|
|
|
9ddfc06 |
LOGGER.setLevel(args.quiet or LOGGER.level)
|
|
|
9ddfc06 |
- signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, log=LOGGER, CA=args.ca)
|
|
|
9ddfc06 |
- sys.stdout.write(signed_crt)
|
|
|
9ddfc06 |
+ try:
|
|
|
9ddfc06 |
+ signed_crt = get_crt(args.account_key, args.csr, args.acme_dir,
|
|
|
9ddfc06 |
+ log=LOGGER, CA=args.ca, chain=args.chain)
|
|
|
9ddfc06 |
+ sys.stdout.write(signed_crt)
|
|
|
9ddfc06 |
+ except Exception as e:
|
|
|
9ddfc06 |
+ #if not args.quiet: raise e
|
|
|
9ddfc06 |
+ LOGGER.error(e)
|
|
|
9ddfc06 |
+ sys.exit(1)
|
|
|
9ddfc06 |
|
|
|
9ddfc06 |
if __name__ == "__main__": # pragma: no cover
|
|
|
9ddfc06 |
main(sys.argv[1:])
|