|
@@ -1,5 +1,5 @@
|
|
|
#!/usr/bin/env python
|
|
|
-import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap
|
|
|
+import argparse, subprocess, json, os, sys, base64, binascii, time, hashlib, re, copy, textwrap, logging
|
|
|
try:
|
|
|
from urllib.request import urlopen # Python 3
|
|
|
except ImportError:
|
|
@@ -8,14 +8,18 @@ except ImportError:
|
|
|
#CA = "https://acme-staging.api.letsencrypt.org"
|
|
|
CA = "https://acme-v01.api.letsencrypt.org"
|
|
|
|
|
|
-def get_crt(account_key, csr, acme_dir):
|
|
|
+LOGGER = logging.getLogger(__name__)
|
|
|
+LOGGER.addHandler(logging.StreamHandler())
|
|
|
+LOGGER.setLevel(logging.INFO)
|
|
|
+
|
|
|
+def get_crt(account_key, csr, acme_dir, log=LOGGER):
|
|
|
|
|
|
# helper function base64 encode for jose spec
|
|
|
def _b64(b):
|
|
|
return base64.urlsafe_b64encode(b).decode('utf8').replace("=", "")
|
|
|
|
|
|
# parse account key to get public key
|
|
|
- sys.stderr.write("Parsing account key...")
|
|
|
+ log.info("Parsing account key...")
|
|
|
proc = subprocess.Popen(["openssl", "rsa", "-in", account_key, "-noout", "-text"],
|
|
|
stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
out, err = proc.communicate()
|
|
@@ -39,7 +43,6 @@ def get_crt(account_key, csr, acme_dir):
|
|
|
}
|
|
|
accountkey_json = json.dumps(header['jwk'], sort_keys=True, separators=(',', ':'))
|
|
|
thumbprint = _b64(hashlib.sha256(accountkey_json.encode('utf8')).digest())
|
|
|
- sys.stderr.write("parsed!\n")
|
|
|
|
|
|
# helper function make signed requests
|
|
|
def _send_signed_request(url, payload):
|
|
@@ -66,7 +69,7 @@ def get_crt(account_key, csr, acme_dir):
|
|
|
return e.code, e.read()
|
|
|
|
|
|
# find domains
|
|
|
- sys.stderr.write("Parsing CSR...")
|
|
|
+ log.info("Parsing CSR...")
|
|
|
proc = subprocess.Popen(["openssl", "req", "-in", csr, "-noout", "-text"],
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
out, err = proc.communicate()
|
|
@@ -81,24 +84,23 @@ def get_crt(account_key, csr, acme_dir):
|
|
|
for san in subject_alt_names.group(1).split(", "):
|
|
|
if san.startswith("DNS:"):
|
|
|
domains.add(san[4:])
|
|
|
- sys.stderr.write("parsed!\n")
|
|
|
|
|
|
# get the certificate domains and expiration
|
|
|
- sys.stderr.write("Registering account...")
|
|
|
+ log.info("Registering account...")
|
|
|
code, result = _send_signed_request(CA + "/acme/new-reg", {
|
|
|
"resource": "new-reg",
|
|
|
"agreement": "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf",
|
|
|
})
|
|
|
if code == 201:
|
|
|
- sys.stderr.write("registered!\n")
|
|
|
+ log.info("Registered!")
|
|
|
elif code == 409:
|
|
|
- sys.stderr.write("already registered!\n")
|
|
|
+ log.info("Already registered!")
|
|
|
else:
|
|
|
raise ValueError("Error registering: {0} {1}".format(code, result))
|
|
|
|
|
|
# verify each domain
|
|
|
for domain in domains:
|
|
|
- sys.stderr.write("Verifying {0}...".format(domain))
|
|
|
+ log.info("Verifying {0}...".format(domain))
|
|
|
|
|
|
# get new challenge
|
|
|
code, result = _send_signed_request(CA + "/acme/new-authz", {
|
|
@@ -147,7 +149,7 @@ def get_crt(account_key, csr, acme_dir):
|
|
|
if challenge_status['status'] == "pending":
|
|
|
time.sleep(2)
|
|
|
elif challenge_status['status'] == "valid":
|
|
|
- sys.stderr.write("verified!\n")
|
|
|
+ log.info("{0} verified!".format(domain))
|
|
|
os.remove(wellknown_path)
|
|
|
break
|
|
|
else:
|
|
@@ -155,7 +157,7 @@ def get_crt(account_key, csr, acme_dir):
|
|
|
domain, challenge_status))
|
|
|
|
|
|
# get the new certificate
|
|
|
- sys.stderr.write("Signing certificate...")
|
|
|
+ log.info("Signing certificate...")
|
|
|
proc = subprocess.Popen(["openssl", "req", "-in", csr, "-outform", "DER"],
|
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
|
csr_der, err = proc.communicate()
|
|
@@ -167,7 +169,7 @@ def get_crt(account_key, csr, acme_dir):
|
|
|
raise ValueError("Error signing certificate: {0} {1}".format(code, result))
|
|
|
|
|
|
# return signed certificate!
|
|
|
- sys.stderr.write("signed!\n")
|
|
|
+ log.info("Certificate signed!")
|
|
|
return """-----BEGIN CERTIFICATE-----\n{0}\n-----END CERTIFICATE-----\n""".format(
|
|
|
"\n".join(textwrap.wrap(base64.b64encode(result).decode('utf8'), 64)))
|
|
|
|
|
@@ -192,7 +194,9 @@ if __name__ == "__main__":
|
|
|
parser.add_argument("--account-key", required=True, help="path to your Let's Encrypt account private key")
|
|
|
parser.add_argument("--csr", required=True, help="path to your certificate signing request")
|
|
|
parser.add_argument("--acme-dir", required=True, help="path to the .well-known/acme-challenge/ directory")
|
|
|
+ parser.add_argument("--quiet", action="store_const", const=logging.ERROR, help="suppress output except for errors")
|
|
|
|
|
|
args = parser.parse_args()
|
|
|
- signed_crt = get_crt(args.account_key, args.csr, args.acme_dir)
|
|
|
+ LOGGER.setLevel(args.quiet or LOGGER.level)
|
|
|
+ signed_crt = get_crt(args.account_key, args.csr, args.acme_dir, LOGGER)
|
|
|
sys.stdout.write(signed_crt)
|