Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Supporting tls-sni-01 #92

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 48 additions & 23 deletions sign_csr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
hashlib, tempfile, re, copy, textwrap


def sign_csr(pubkey, csr, email=None, file_based=False):
def sign_csr(pubkey, csr, mode, email=None):
"""Use the ACME protocol to get an ssl certificate signed by a
certificate authority.

Expand All @@ -30,6 +30,13 @@ def _b64(b):
"Shortcut function to go from bytes to jwt base64 string"
return base64.urlsafe_b64encode(b).replace("=", "")

modes_mapping = {
'http-standalone': 'http-01',
'http-file': 'http-01',
'tls': 'tls-sni-01',
}
challenge_type = modes_mapping[mode]

# Step 1: Get account public key
sys.stderr.write("Reading pubkey file...\n")
proc = subprocess.Popen(["openssl", "rsa", "-pubin", "-in", pubkey, "-noout", "-text"],
Expand Down Expand Up @@ -214,6 +221,7 @@ def _b64(b):
# Step 8: Request challenges for each domain
responses = []
tests = []
sans = []
for n, i in enumerate(ids):
sys.stderr.write("Requesting challenges for {0}...\n".format(i['domain']))
id_data = json.dumps({
Expand All @@ -234,8 +242,9 @@ def _b64(b):
sys.stderr.write(e.read())
sys.stderr.write("\n")
raise
challenge = [c for c in result['challenges'] if c['type'] == "http-01"][0]
challenge = [c for c in result['challenges'] if c['type'] == challenge_type][0]
keyauthorization = "{0}.{1}".format(challenge['token'], thumbprint)
sans.append(hashlib.sha256(keyauthorization).hexdigest())

# challenge request
sys.stderr.write("Building challenge responses for {0}...\n".format(i['domain']))
Expand Down Expand Up @@ -292,8 +301,8 @@ def _b64(b):

# Step 11: Ask the user to host the token on their server
for n, i in enumerate(ids):
if file_based:
sys.stderr.write("""\
if mode == "http-file":
instructions = """\
STEP {0}: Please update your server to serve the following file at this URL:

--------------
Expand All @@ -305,14 +314,10 @@ def _b64(b):
- Do not include the quotes in the file.
- The file should be one line without any spaces.

""".format(n + 4, i['domain'], responses[n]['uri'], responses[n]['data']))

stdout = sys.stdout
sys.stdout = sys.stderr
raw_input("Press Enter when you've got the file hosted on your server...")
sys.stdout = stdout
else:
sys.stderr.write("""\
""".format(n + 4, i['domain'], responses[n]['uri'], responses[n]['data'])
wait_message = "Press Enter when you've got the file hosted on your server..."
elif mode == "http-standalone":
instructions = """\
STEP {0}: You need to run this command on {1} (don't stop the python command until the next step).

sudo python -c "import BaseHTTPServer; \\
Expand All @@ -321,12 +326,28 @@ def _b64(b):
s = BaseHTTPServer.HTTPServer(('0.0.0.0', 80), h); \\
s.serve_forever()"

""".format(n + 4, i['domain'], responses[n]['data']))
""".format(n + 4, i['domain'], responses[n]['data'])
wait_message = "Press Enter when you've got the python command running on your server..."
elif mode == "tls":
instructions = """\
STEP {0}: Generate a self-signed cert on your server using the following command

openssl req -x509 -sha256 -newkey rsa:2048 -keyout ephemeral-{1}.key -nodes -out ephemeral-{1}.cer -days 365 \
-subj "/CN={1}" -reqexts SAN -extensions SAN \
-config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\\nsubjectAltName=DNS:{2}.{3}.acme.invalid"))

stdout = sys.stdout
sys.stdout = sys.stderr
raw_input("Press Enter when you've got the python command running on your server...")
sys.stdout = stdout
Then configure your web server to use the generated files
ephemeral-{1}.key and ephemeral-{1}.cer
as private key and certificate for its HTTPS listener.

""".format(n + 4, i['domain'], sans[n][0:32], sans[n][32:64])
wait_message = "Press Enter when you've got the cert configured on your server..."

sys.stderr.write(instructions)
stdout = sys.stdout
sys.stdout = sys.stderr
raw_input(wait_message)
sys.stdout = stdout

# Step 12: Let the CA know you're ready for the challenge
sys.stderr.write("Requesting verification for {0}...\n".format(i['domain']))
Expand Down Expand Up @@ -397,10 +418,13 @@ def _b64(b):
# Step 15: Convert the signed cert from DER to PEM
sys.stderr.write("Certificate signed!\n")

if file_based:
sys.stderr.write("You can remove the acme-challenge file from your webserver now.\n")
else:
sys.stderr.write("You can stop running the python command on your server (Ctrl+C works).\n")
if mode == "http-file":
end_message = "You can remove the acme-challenge file from your webserver now."
elif mode == "http-standalone":
end_message = "You can stop running the python command on your server (Ctrl+C works)."
elif mode == 'tls':
end_message = "You can remove the ephemeral certs from your webserver configuration now."
sys.stderr.write(end_message+"\n")

signed_der64 = base64.b64encode(signed_der)
signed_pem = """\
Expand Down Expand Up @@ -439,10 +463,11 @@ def _b64(b):
""")
parser.add_argument("-p", "--public-key", required=True, help="path to your account public key")
parser.add_argument("-e", "--email", default=None, help="contact email, default is webmaster@<shortest_domain>")
parser.add_argument("-f", "--file-based", action='store_true', help="if set, a file-based response is used")
parser.add_argument("-m", "--mode", choices=['http-standalone', 'http-file', 'tls'], default='http-standalone',
help="operation mode, default is http-standalone")
parser.add_argument("csr_path", help="path to your certificate signing request")

args = parser.parse_args()
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, file_based=args.file_based)
signed_crt = sign_csr(args.public_key, args.csr_path, email=args.email, mode=args.mode)
sys.stdout.write(signed_crt)