From f23e4e0d0cf48153dbc5134cf1bf1bb7189c3005 Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Tue, 8 Dec 2015 19:50:09 +0100 Subject: Various fixes. --- acme-slave | 8 ++++---- letsencrypt | 14 ++++++++------ 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/acme-slave b/acme-slave index ee39f8d..5ff9834 100755 --- a/acme-slave +++ b/acme-slave @@ -79,7 +79,7 @@ sub request($$;$) { # ACME client -# https://github.com/letsencrypt/acme-spec/blob/master/draft-barnes-acme.md +# https://tools.ietf.org/html/draft-ietf-acme-acme-01 sub acme($$) { my ($uri, $h) = @_; @@ -119,7 +119,7 @@ if ($COMMAND eq 'new-reg') { acme($RES{'new-reg'}, { resource => 'new-reg', - contact => [ map {"mailto:$_"} split(',', @ARGV) ], + contact => [ map {"mailto:$_"} @ARGV ], agreement => $uri, }); exit; @@ -168,7 +168,7 @@ my @domains = do { open my $fh1, '-|', @req, '-subject' or die "Can't run req(1ssl): $!"; my $subject = <$fh1>; close $fh1; - $domains{$1} = 1 if $subject =~ /\Asubject=\/CN=($RE_domain)(?:,.*)?\n\z/o; + $domains{$1} = 1 if $subject =~ /\Asubject=.*\/CN=($RE_domain)\n\z/o; open my $fh2, '-|', @req, '-text', '-reqopt', 'no_header,no_version,no_subject,no_pubkey,no_sigdump' or die "Can't run req(1ssl): $!"; @@ -224,7 +224,7 @@ foreach my $domain (@domains) { for (my $i=0;; $i++) { my $status = request('GET' => $challenge->{uri})->{status} // 'pending'; - die "Invalid challenge for $domain" if $status eq 'invalid'; + die "Error: Invalid challenge for $domain\n" if $status eq 'invalid'; last if $status eq 'valid'; die "Timeout exceeded while waiting for challenge to pass ($domain)\n" if $i >= $TIMEOUT; sleep 1; diff --git a/letsencrypt b/letsencrypt index 50406f7..5c10ea1 100755 --- a/letsencrypt +++ b/letsencrypt @@ -16,7 +16,7 @@ declare COMMAND ACCOUNTKEY declare -l GENKEY declare RUNAS QUIET= DEBUG= -declare SRVCERT= CHAIN= CSR SRVKEY +declare SRVCRT= CHAIN= CSR SRVKEY declare -l HASH= declare SUBJECT=/ declare SAN= @@ -96,7 +96,7 @@ while [ $# -gt 0 ]; do --quiet|-q) QUIET=1;; --debug) DEBUG=1;; - --output=*) SRVCERT="${1#*=}";; + --output=*) SRVCRT="${1#*=}";; --chain) CHAIN=1;; --csr=*) CSR="${1#*=}";; --key=*) SRVKEY="${1#*=}";; @@ -160,7 +160,7 @@ if [ "$COMMAND" = 'revoke-cert' ]; then exit 1 fi elif [ "$COMMAND" = 'new-cert' ]; then - if [ ! "${SRVCERT:-}" ]; then + if [ ! "${SRVCRT:-}" ]; then echo "Error: Missing --output" >&2 exit 1 fi @@ -270,10 +270,12 @@ if [ "$COMMAND" = 'new-cert' ]; then # TODO # Verify: dump and compare public keys # Valid cert, signed by the right CA - - # Copy "$x509" to "$SRVCERT", possibly chained # https://crt.sh/?q=cse-fresti.cse.chalmers.se&iCAID=7395 - cp "$x509" "$SRVCERT" + + # if it doesn't exist, create the file with mode 0644 minus the process's umask(2) + [ -e "$SRVCRT" ] || touch "$SRVCRT" + cat "$x509" >"$SRVCRT" + [ ! "$DEBUG" ] || openssl x509 -noout -text <"$SRVCRT" for (( i=0; i<${#NOTIFY[@]}; i++ )); do ${NOTIFY[$i]} -- cgit v1.2.3 From 594b65f8960c38fde1a8ffd567df852a69a670ff Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 9 Dec 2015 00:10:14 +0100 Subject: Add an option --min-age. --- letsencrypt | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/letsencrypt b/letsencrypt index 5c10ea1..4c932e4 100755 --- a/letsencrypt +++ b/letsencrypt @@ -17,6 +17,7 @@ declare -l GENKEY declare RUNAS QUIET= DEBUG= declare SRVCRT= CHAIN= CSR SRVKEY +delcale -i MIN_AGE=0 declare -l HASH= declare SUBJECT=/ declare SAN= @@ -62,6 +63,8 @@ usage() { (default: "digitalSignature,keyEncipherment,keyCertSign") --chain Store not only the server certificate in the file specified with --output, but also the CA's + --min-age=SECONDS Don't do anything if the certificate specified by --output exists and its expiration + is more than SECONDS ahead. --output=FILE Where to store the issued (signed) X.509 certificate --notify=COMMAND Command to run upon success. (This option can be repeated.) @@ -97,6 +100,7 @@ while [ $# -gt 0 ]; do --debug) DEBUG=1;; --output=*) SRVCRT="${1#*=}";; + --min-age=*) MIN_AGE="${1#*=}";; --chain) CHAIN=1;; --csr=*) CSR="${1#*=}";; --key=*) SRVKEY="${1#*=}";; @@ -164,6 +168,12 @@ elif [ "$COMMAND" = 'new-cert' ]; then echo "Error: Missing --output" >&2 exit 1 fi + if [ -s "$SRVCRT" ] && \ + exp=$(openssl x509 -noout -enddate <"$SRVCRT" 2>/dev/null) && \ + [ $(( $(date -d "${exp#*=}" +%s) - $(date +%s))) -gt $MIN_AGE ]; then + [ ! "$DEBUG" ] || echo "Expiration date ($(date -d"${exp#*=}")) is too far away, come back later." >&2 + exit 0 + fi # Generate a Certificate Signing Request if need be if [ ${CSR+x} ]; then -- cgit v1.2.3 From 8eb679e08e08b4bb992c8d43f57c3458aab4cf1a Mon Sep 17 00:00:00 2001 From: Guilhem Moulin Date: Wed, 9 Dec 2015 00:17:20 +0100 Subject: Ship LE's intermediate CA files and add cert validation. --- letsencrypt | 31 +++++++++++++++++++++++++------ letsencryptauthorityx1.pem | 32 ++++++++++++++++++++++++++++++++ letsencryptauthorityx2.pem | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 89 insertions(+), 6 deletions(-) create mode 100644 letsencryptauthorityx1.pem create mode 100644 letsencryptauthorityx2.pem diff --git a/letsencrypt b/letsencrypt index 4c932e4..7465378 100755 --- a/letsencrypt +++ b/letsencrypt @@ -11,13 +11,14 @@ WWW_USER=www-data WWW_GROUP=www-data ACME_WEBSERVER=acme-webserver ACME_CLIENT=acme-slave +CAfile=/usr/share/lets-encrypt/letsencryptauthorityx1.pem declare COMMAND ACCOUNTKEY declare -l GENKEY declare RUNAS QUIET= DEBUG= declare SRVCRT= CHAIN= CSR SRVKEY -delcale -i MIN_AGE=0 +declare -i MIN_AGE=0 declare -l HASH= declare SUBJECT=/ declare SAN= @@ -168,7 +169,7 @@ elif [ "$COMMAND" = 'new-cert' ]; then echo "Error: Missing --output" >&2 exit 1 fi - if [ -s "$SRVCRT" ] && \ + if [ -s "$SRVCRT" ] && [ $MIN_AGE -gt 0 ] && \ exp=$(openssl x509 -noout -enddate <"$SRVCRT" 2>/dev/null) && \ [ $(( $(date -d "${exp#*=}" +%s) - $(date +%s))) -gt $MIN_AGE ]; then [ ! "$DEBUG" ] || echo "Expiration date ($(date -d"${exp#*=}")) is too far away, come back later." >&2 @@ -277,17 +278,35 @@ while read data; do done >"$pipe" if [ "$COMMAND" = 'new-cert' ]; then - # TODO - # Verify: dump and compare public keys - # Valid cert, signed by the right CA - # https://crt.sh/?q=cse-fresti.cse.chalmers.se&iCAID=7395 + # https://crt.sh/?q=mail.fripost.org&iCAID=7395 + # https://crt.sh/?spkisha1=$sha1 + + # Ensure the cert's pubkey matches that of the CSR, and that it's signed by the intended CA + if [ ! -s "$x509" ] || + ! diff <(openssl req -in "$CSR" -pubkey -noout) \ + <(openssl x509 -in "$x509" -pubkey -noout) >/dev/null || + ! openssl verify -CAfile "$CAfile" -purpose sslserver -x509_strict <"$x509" >/dev/null; then + echo "Error: Got an invalid X.509 certificate from the ACME server!" >&2 + exit 1 + fi # if it doesn't exist, create the file with mode 0644 minus the process's umask(2) [ -e "$SRVCRT" ] || touch "$SRVCRT" cat "$x509" >"$SRVCRT" [ ! "$DEBUG" ] || openssl x509 -noout -text <"$SRVCRT" + if [ ! "$QUIET" ]; then + echo "X.509 certificate $SRVCRT has been updated or renewed" + echo + openssl x509 -noout \ + -text -certopt no_header,no_version,no_pubkey,no_sigdump \ + -fingerprint -sha256 <"$SRVCRT" + fi + for (( i=0; i<${#NOTIFY[@]}; i++ )); do ${NOTIFY[$i]} done + +else + [ "$QUIET" ] || echo OK fi diff --git a/letsencryptauthorityx1.pem b/letsencryptauthorityx1.pem new file mode 100644 index 0000000..22c095b --- /dev/null +++ b/letsencryptauthorityx1.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIRAOeTkL6SBwNJGF95dYHlyoMwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTIwMDIw +WhcNMjAwNjA0MTIwMDIwWjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDEwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCc0wzwWuUuR7dyXTeDs2hjMOrX +NSYZJeG9vjXxcJIvt7hLQQWrqZ41CFjssSrEaIcLo+N15Obzp2JxunmBYB/XkZqf +89B4Z3HIaQ6Vkc/+5pnpYDxIzH7KTXcSJJ1HG1rrueweNwAcnKx7pwXqzkrrvUHl +Npi5y/1tPJZo3yMqQpAMhnRnyH+lmrhSYRQTP2XpgofL2/oOVvaGifOFP5eGr7Dc +Gu9rDZUWfcQroGWymQQ2dYBrrErzG5BJeC+ilk8qICUpBMZ0wNAxzY8xOJUWuqgz +uEPxsR/DMH+ieTETPS02+OP88jNquTkxxa/EjQ0dZBYzqvqEKbbUC8DYfcOTAgMB +AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU +BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB +FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBSo +SmpjBH3duubRObemRWXv86jsoTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js +LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF +BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG +AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD +VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB +AGvM/XGv8yafGRGMPP6hnggoI9DGWGf4l0mzjBhuCkDVqoG/7rsH1ytzteePxiA3 +7kqSBo0fXu5GmbWOw09GpwPYyAAY0iWOMU6ybrTJHS466Urzoe/4IwLQoQc219EK +lh+4Ugu1q4KxNY1qMDA/1YX2Qm9M6AcAs1UvZKHSpJQAbsYrbN6obNoUGOeG6ONH +Yr8KRQz5FMfZYcA49fmdDTwKn/pyLOkJFeA/dm/oP99UmKCFoeOa5w9YJr2Vi7ic +Xd59CU8mprWhxFXnma1oU3T8ZNovjib3UHocjlEJfNbDy9zgKTYURcMVweo1dkbH +NbLc5mIjIk/kJ+RPD+chR+gJjy3Gh9xMNkDrZQKfsIO93hxTsZMmgZQ4c+vujC1M +jSak+Ai87YZeYQPh1fCGMSTno5III37DUCtIn8BJxJixuPeOMKsjLLD5AtMVy0fp +d19lcUek4bjDY8/Ujb5/wfn2+Kk7z72SxWdekjtHOWBmKxqq8jDuuMw4ymg1g5n7 +R7TZ/Y3y4bTpWUDkBHFo03xNM21wBFDIrCZZeVhvDW4MtT6+Ass2bcpoHwYcGol2 +gaLDa5k2dkG41OGtXa0fY+TjdryY4cOcstJUKjv2MJku4yaTtjjECX1rJvFLnqYe +wC+FmxjgWPuyRNuLDAWK30mmpcJZ3CmD6dFtAi4h7H37 +-----END CERTIFICATE----- diff --git a/letsencryptauthorityx2.pem b/letsencryptauthorityx2.pem new file mode 100644 index 0000000..612b4a5 --- /dev/null +++ b/letsencryptauthorityx2.pem @@ -0,0 +1,32 @@ +-----BEGIN CERTIFICATE----- +MIIFjTCCA3WgAwIBAgIRAJY2TKc4C+SL3JDGzeC33mgwDQYJKoZIhvcNAQELBQAw +TzELMAkGA1UEBhMCVVMxKTAnBgNVBAoTIEludGVybmV0IFNlY3VyaXR5IFJlc2Vh +cmNoIEdyb3VwMRUwEwYDVQQDEwxJU1JHIFJvb3QgWDEwHhcNMTUwNjA0MTIwMDMx +WhcNMjAwNjA0MTIwMDMxWjBKMQswCQYDVQQGEwJVUzEWMBQGA1UEChMNTGV0J3Mg +RW5jcnlwdDEjMCEGA1UEAxMaTGV0J3MgRW5jcnlwdCBBdXRob3JpdHkgWDIwggEi +MA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDhJHRCe7eRMdlz/ziq2M5EXLc5 +CtxErg29RbmXN2evvVBPX9MQVGv3QdqOY+ZtW8DoQKmMQfzRA4n/YmEJYNYHBXia +kL0aZD5P3M93L4lry2evQU3FjQDAa/6NhNy18pUxqOj2kKBDSpN0XLM+Q2lLiSJH +dFE+mWTDzSQB+YQvKHcXIqfdw2wITGYvN3TFb5OOsEY3FmHRUJjIsA9PWFN8rPba +LZZhUK1D3AqmT561Urmcju9O30azMdwg/GnCoyB1Puw4GzZOZmbS3/VmpJMve6YO +lD5gPUpLHG+6tE0cPJFYbi9NxNpw2+0BOXbASefpNbUUBpDB5ZLiEP1rubSFAgMB +AAGjggFnMIIBYzAOBgNVHQ8BAf8EBAMCAYYwEgYDVR0TAQH/BAgwBgEB/wIBADBU +BgNVHSAETTBLMAgGBmeBDAECATA/BgsrBgEEAYLfEwEBATAwMC4GCCsGAQUFBwIB +FiJodHRwOi8vY3BzLnJvb3QteDEubGV0c2VuY3J5cHQub3JnMB0GA1UdDgQWBBTF +satOTLHNZDCTfsGEmQWr5gPiJTAzBgNVHR8ELDAqMCigJqAkhiJodHRwOi8vY3Js +LnJvb3QteDEubGV0c2VuY3J5cHQub3JnMHIGCCsGAQUFBwEBBGYwZDAwBggrBgEF +BQcwAYYkaHR0cDovL29jc3Aucm9vdC14MS5sZXRzZW5jcnlwdC5vcmcvMDAGCCsG +AQUFBzAChiRodHRwOi8vY2VydC5yb290LXgxLmxldHNlbmNyeXB0Lm9yZy8wHwYD +VR0jBBgwFoAUebRZ5nu25eQBc4AIiMgaWPbpm24wDQYJKoZIhvcNAQELBQADggIB +AA4eqMjSEJKCF6XRR5pEutkS/e7xgy2vCYYbw1ospQiGQ4FO5TtbvO+5K4v7WR3b +1peMQ03rX0Dr+ylmGNypZahNxTqDiO0X2sHBwJWj/k61+MYq3bRYxKwI6cduTDXb +YQxilGTDNGZUIFKKIloz4zGAl68sj+8pLg534EqKgl8+rWSxclToS1KrydJezokE +dQRXfxu79iscWA3PIj1vbaUBB16lnWJxA3LhTGhUrhZrCnFuOZ93KO8kCKPM7EVo +7c4FCYKI8eWDsf0FF49A4xMUmxPJAPIyZkwQ8KkjpzcTHOmT4CEXUhNu9eMI9qBK +VSFDDMifJ8HzCaVLyMvY1Kf7iR+840EkX1EGC+Z39EaK1hjm314LYpLoYGvYYLJO +/J76XAx8ZgpofqHz1gAEfiMLMLxLQkOjKLXqoUEd5KdnzaO3aLH91gnasy8aD4D5 +9RfEO2xcaozD2rbYsoAMVzcZZHw0Smdmobaz2YazMBjFRcqGntg6s5Xqwusaleiy +snjMCC/9mvIPqGyuVnBPTBaUDFDEhX6qD2MX4dzODL91Z0ogYDWcFLN+uLnZKHje +4JoNuzkJ2FXWOREcsW93KXb+3T8COjhTDKvK4H6ufdrZxxusx60ajJAMBzW0XTf5 +nm2yGEDtyVoMgJLp0rkiPlormgHxSkFDOJbY94J7yxRK +-----END CERTIFICATE----- \ No newline at end of file -- cgit v1.2.3