Instantly share code, notes, and snippets.

@gpjt /letsencrypt.py
Last active Nov 16, 2018

Embed
What would you like to do?
Sample acme code to get a certificate from Let's Encrypt
# There's a lack of sample code for acme/Let's Encrypt out there, and
# this is an attempt to at least slightly remedy that. It's the result
# of my first day's hacking on this stuff, so almost certainly contains
# errors and oversights.
#
# It's not designed to be specifically useful if what you want is
# just a cert -- certbot or dehydrated are better for that. It is sample
# code for people who are building automated systems to deal with large
# numbers of Let's Encrypt certificates to play with.
#
# request_cert will create a new user on Let's Encrypt's staging server,
# then apply for a certificate for the given domain. Production systems
# would more probably have a persistent private key for the user; the
# reason this code doesn't is just to make it self-contained.
#
# It only handles HTTP-based verification; when it is ready for you
# to do so, it will present you with a URL where you'll need to serve
# a particular bit of text.
#
# The code is Python 2.7, and needs you to "pip install acme".
#
# See http://www.gilesthomas.com/2018/11/python-code-to-generate-lets-encrypt-certificates/
# for a code walkthrough.
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
from OpenSSL import crypto
from OpenSSL.SSL import FILETYPE_PEM
import time
from acme import client
from acme import messages
import josepy
DIRECTORY_URL = 'https://acme-staging.api.letsencrypt.org/directory'
KEY_SIZE = 2048
def get_http_challenge(authzr):
for challenge in authzr.body.challenges:
if challenge.chall.typ == 'http-01':
return challenge
else:
raise Exception("Could not find an HTTP challenge!")
def request_cert(domain):
domain = domain.lower()
print("Generating user key")
user_key = josepy.JWKRSA(
key=rsa.generate_private_key(
public_exponent=65537,
key_size=KEY_SIZE,
backend=default_backend()
)
)
print("Connecting to Let's Encrypt on {}".format(DIRECTORY_URL))
acme = client.Client(DIRECTORY_URL, user_key)
print("Registering")
regr = acme.register()
print("Agreeing to ToS")
acme.agree_to_tos(regr)
print("Requesting challenges")
authzr = acme.request_challenges(
identifier=messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=domain)
)
print("Looking for HTTP challenge")
challenge = get_http_challenge(authzr)
print("You need to set up the challenge response.")
print("URL: http://{}{}".format(domain, challenge.chall.path))
print("Content: {}".format(challenge.chall.validation(user_key)))
response = challenge.chall.response(user_key)
while not response.simple_verify(challenge.chall, domain, user_key.public_key()):
raw_input("It doesn't look like it's set up yet; press return when it is.")
print("Authorizing -- here goes...")
auth_response = acme.answer_challenge(challenge, challenge.chall.response(user_key))
print("Response was {}".format(auth_response))
print("Waiting for authorization to become valid")
while True:
print("Polling")
authzr, authzr_response = acme.poll(authzr)
challenge = get_http_challenge(authzr)
if challenge.status.name == "valid":
break
print("HTTP challenge is currently {}".format(challenge))
time.sleep(1)
print("Auth valid")
print("Generating CSR")
certificate_key = crypto.PKey()
certificate_key.generate_key(crypto.TYPE_RSA, 2048)
csr = crypto.X509Req()
csr.get_subject().CN = domain
csr.set_pubkey(certificate_key)
csr.sign(certificate_key, "sha256")
print("Requesting certificate")
certificate_response = acme.request_issuance(josepy.util.ComparableX509(csr), [authzr])
print("Got it!")
print("Fetching chain")
chain = acme.fetch_chain(certificate_response)
print("Done!")
print("Here are the details:")
print("Private key:")
print(crypto.dump_privatekey(FILETYPE_PEM, certificate_key))
print("Combined cert:")
print(crypto.dump_certificate(FILETYPE_PEM, certificate_response.body.wrapped))
for cert in chain:
print(crypto.dump_certificate(FILETYPE_PEM, cert.wrapped))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment