From 61f0b7ed7a2e805e08aa1f2412fb9b869a04fdeb Mon Sep 17 00:00:00 2001 From: Lukas Schauer Date: Sat, 5 Dec 2015 02:31:06 +0100 Subject: [PATCH] initial commit --- .gitignore | 5 +++ README.md | 3 ++ certs/.keep | 0 config.sh.example | 2 + domains.txt.example | 2 + json_to_pem.pl | 24 ++++++++++++ letsencrypt.sh | 96 +++++++++++++++++++++++++++++++++++++++++++++ 7 files changed, 132 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 certs/.keep create mode 100644 config.sh.example create mode 100644 domains.txt.example create mode 100644 json_to_pem.pl create mode 100755 letsencrypt.sh diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..6ffe292 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +private_key.pem +domains.txt +config.sh +certs/* +!certs/.keep diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e00322 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# letsencrypt.sh + +letsencrypt client implemented as a shell-script diff --git a/certs/.keep b/certs/.keep new file mode 100644 index 0000000..e69de29 diff --git a/config.sh.example b/config.sh.example new file mode 100644 index 0000000..9ed0e42 --- /dev/null +++ b/config.sh.example @@ -0,0 +1,2 @@ +CA="https://acme-v01.api.letsencrypt.org" +WELLKNOWN="/var/www/letsencrypt/.well-known/acme-challenge" diff --git a/domains.txt.example b/domains.txt.example new file mode 100644 index 0000000..50b4816 --- /dev/null +++ b/domains.txt.example @@ -0,0 +1,2 @@ +example.org www.example.org +example.com www.example.com wiki.example.com diff --git a/json_to_pem.pl b/json_to_pem.pl new file mode 100644 index 0000000..be821a4 --- /dev/null +++ b/json_to_pem.pl @@ -0,0 +1,24 @@ +use strict; + +use Crypt::OpenSSL::RSA; +use Crypt::OpenSSL::Bignum; +use JSON; +use File::Slurp; +use MIME::Base64; + +my $json_file = "private_key.json"; +my $json_content = read_file($json_file); +$json_content =~ tr/-/+/; +$json_content =~ tr/_/\//; + +my $json = decode_json($json_content); + +my $n = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($json->{n})); +my $e = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($json->{e})); +my $d = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($json->{d})); +my $p = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($json->{p})); +my $q = Crypt::OpenSSL::Bignum->new_from_bin(decode_base64($json->{q})); + +my $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d, $p, $q); + +print($rsa->get_private_key_string()); diff --git a/letsencrypt.sh b/letsencrypt.sh new file mode 100755 index 0000000..9c7812b --- /dev/null +++ b/letsencrypt.sh @@ -0,0 +1,96 @@ +#!/bin/bash + +set -e + +source config.sh + +declare -A header +declare -A request + +urlbase64() { + base64 -w 0 | sed -r 's/=*$//g' | tr '+/' '-_' +} + +pubExponent64="$(printf "%06x" "$(openssl rsa -in private_key.pem -noout -text | grep publicExponent | head -1 | cut -d' ' -f2)" | perl -pe 's/([0-9a-f]{2})/chr hex $1/gie' | urlbase64)" +pubMod64="$(echo -n "$(openssl rsa -in private_key.pem -noout -modulus | cut -d'=' -f2 | perl -pe 's/([0-9a-f]{2})/chr hex $1/gie')" | urlbase64)" + +thumbprint="$(echo -n "$(echo -n '{"e":"'"${pubExponent64}"'","kty":"RSA","n":"'"${pubMod64}"'"}' | sha256sum | awk '{print $1}' | perl -pe 's/([0-9a-f]{2})/chr hex $1/gie')" | urlbase64)" + +signed_request() { + payload64="$(echo -n "${2}" | urlbase64)" + + nonce="$(curl -s -I ${CA}/directory | grep Replay-Nonce | awk -F ': ' '{print $2}' | tr -d '\n\r')" + + header='{"alg": "RS256", "jwk": {"e": "'"${pubExponent64}"'", "kty": "RSA", "n": "'"${pubMod64}"'"}}' + + protected='{"alg": "RS256", "jwk": {"e": "'"${pubExponent64}"'", "kty": "RSA", "n": "'"${pubMod64}"'"}, "nonce": "'"${nonce}"'"}' + protected64="$(echo -n "${protected}" | urlbase64)" + + signed64="$(echo -n "${protected64}.${payload64}" | openssl dgst -sha256 -sign private_key.pem | urlbase64)" + + data='{"header": '"${header}"', "protected": "'"${protected64}"'", "payload": "'"${payload64}"'", "signature": "'"${signed64}"'"}' + + curl -s -d "${data}" "${1}" +} + +register() { + signed_request "${CA}/acme/new-reg" '{"resource": "new-reg", "agreement": "https://letsencrypt.org/documents/LE-SA-v1.0.1-July-27-2015.pdf"}' +} + +sign_domain() { + domain="${1}" + altnames="${@}" + echo "Signing domain ${1} (${@})..." + if [ ! -e "certs/${domain}" ]; then + SAN="" + for altname in $altnames; do + SAN+="DNS:${altname}, " + done + SAN="$(echo -n $SAN | sed 's/,\s*$//g')" + + mkdir "certs/${domain}" + + echo " + Generating private key..." + openssl genrsa -out "certs/${domain}/privkey.pem" 4096 > /dev/null + echo " + Generating signing request..." + openssl req -new -sha256 -key "certs/${domain}/privkey.pem" -out "certs/${domain}/cert.csr" -subj "/CN=${domain}/" -reqexts SAN -config <(cat /etc/ssl/openssl.cnf <(printf "[SAN]\nsubjectAltName=${SAN}")) > /dev/null + fi + + for altname in $altnames; do + echo " + Requesting challenge for ${altname}..." + response="$(signed_request "${CA}/acme/new-authz" '{"resource": "new-authz", "identifier": {"type": "dns", "value": "'"${altname}"'"}}')" + + challenge_token="$(echo $response | grep -Eo '"challenges":[^\[]*\[[^]]*]' | sed 's/{/\n{/g' | grep 'http-01' | grep -Eo '"token":\s*"[^"]*"' | cut -d'"' -f4 | sed 's/[^A-Za-z0-9_\-]/_/g')" + challenge_uri="$(echo $response | grep -Eo '"challenges":[^\[]*\[[^]]*]' | sed 's/{/\n{/g' | grep 'http-01' | grep -Eo '"uri":\s*"[^"]*"' | cut -d'"' -f4)" + + keyauth="${challenge_token}.${thumbprint}" + + echo -n "${keyauth}" > "${WELLKNOWN}/${challenge_token}" + + echo " + Responding to challenge for ${altname}..." + result="$(signed_request "${challenge_uri}" '{"resource": "challenge", "keyAuthorization": "'"${keyauth}"'"}')" + + status="$(echo "${result}" | grep -Eo '"status":\s*"[^"]*"' | cut -d'"' -f4)" + + if [ ! "${status}" = "pending" ] && [ ! "${status}" = "valid" ]; then + echo " + Challenge is invalid!" + exit 1 + fi + + while [ ! "${status}" = "valid" ]; do + status="$(curl -s "${challenge_uri}" | grep -Eo '"status":\s*"[^"]*"' | cut -d'"' -f4)" + done + + echo " + Challenge is valid!" + done + + echo " + Requesting certificate..." + csr64="$(openssl req -in "certs/${domain}/cert.csr" -outform DER | urlbase64)" + crt64="$(signed_request "${CA}/acme/new-cert" '{"resource": "new-cert", "csr": "'"${csr64}"'"}' | base64 -w 64)" + echo -e "-----BEGIN CERTIFICATE-----\n${crt64}\n-----END CERTIFICATE-----\n" > "certs/${domain}/cert.pem" + echo " + Done!" +} + +cat domains.txt | sed 's/^\s*//g;s/\s*$//g' | grep -v '^#' | grep -v '^$' | while read line; do + sign_domain $line +done -- 2.39.5