Last active
February 29, 2024 03:50
-
-
Save gpjt/2bd2a223b410d8fcfb782d0df1be2e00 to your computer and use it in GitHub Desktop.
Sample acme code to get a certificate from Let's Encrypt
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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
Can you teach me how to use this function ??