Originally created by @toasterparty on GitHub (May 14, 2023).
I'm trying to get my first cert for my first domain, toasterparty.net. As I understand, tls-alpn-01 via dehydrated are the best tools for the job since my ISP blocks port 80. I'm running Debian 11 in a headless configuration on standalone hardware. This error is what's stopping me from progressing:
urn:ietf:params:acme:error:unauthorized
Incorrect validation certificate for tls-alpn-01 challenge. Requested toasterparty.net from 72.197.16.252:443. Received certificate with unexpected extensions: "Required extension OID 1.3.6.1.5.5.7.1.31 is not present"
echo | openssl s_client -tls1_3 -connect tls13.cloudflare.com:443
openssl version
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
DONE
OpenSSL 1.1.1n 15 Mar 2022
alpn-responder.py
Here's the responder I'm using. It's the same as the provided example but using port 443 and ALPNDIR is set to match the environment variable.
#!/usr/bin/env python3importsslimportsocketserverimportreimportosALPNDIR="/data/alpn-certs"PROXY_PROTOCOL=FalseFALLBACK_CERTIFICATE="/etc/ssl/certs/ssl-cert-snakeoil.pem"FALLBACK_KEY="/etc/ssl/private/ssl-cert-snakeoil.key"classThreadedTCPServer(socketserver.ThreadingMixIn,socketserver.TCPServer):passclassThreadedTCPRequestHandler(socketserver.BaseRequestHandler):defcreate_context(self,certfile,keyfile,first=False):ssl_context=ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)ssl_context.set_ciphers('ECDHE+AESGCM')ssl_context.set_alpn_protocols(["acme-tls/1"])ssl_context.options|=ssl.OP_NO_TLSv1|ssl.OP_NO_TLSv1_1iffirst:ssl_context.set_servername_callback(self.load_certificate)ssl_context.load_cert_chain(certfile=certfile,keyfile=keyfile)returnssl_contextdefload_certificate(self,sslsocket,sni_name,sslcontext):print("Got request for %s"%sni_name)ifnotre.match(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$',sni_name):returncertfile=os.path.join(ALPNDIR,"%s.crt.pem"%sni_name)keyfile=os.path.join(ALPNDIR,"%s.key.pem"%sni_name)ifnotos.path.exists(certfile)ornotos.path.exists(keyfile):returnsslsocket.context=self.create_context(certfile,keyfile)defhandle(self):ifPROXY_PROTOCOL:buf=b""whileb"\r\n"notinbuf:buf+=self.request.recv(1)ssl_context=self.create_context(FALLBACK_CERTIFICATE,FALLBACK_KEY,True)newsock=ssl_context.wrap_socket(self.request,server_side=True)if__name__=="__main__":HOST,PORT="0.0.0.0",443server=ThreadedTCPServer((HOST,PORT),ThreadedTCPRequestHandler,bind_and_activate=False)server.allow_reuse_address=Truetry:server.server_bind()server.server_activate()server.serve_forever()except:server.shutdown()
Thanks for any time spent looking into this!
Originally created by @toasterparty on GitHub (May 14, 2023).
I'm trying to get my first cert for my first domain, toasterparty.net. As I understand, tls-alpn-01 via dehydrated are the best tools for the job since my ISP blocks port 80. I'm running Debian 11 in a headless configuration on standalone hardware. This error is what's stopping me from progressing:
```
urn:ietf:params:acme:error:unauthorized
Incorrect validation certificate for tls-alpn-01 challenge. Requested toasterparty.net from 72.197.16.252:443. Received certificate with unexpected extensions: "Required extension OID 1.3.6.1.5.5.7.1.31 is not present"
```
# Script
This is how I'm using dehydrated.
```bash
#!/usr/bin/env bash
set -e
SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
CONFIG_DIR=$SCRIPT_DIR/config
cd $SCRIPT_DIR
mkdir -p $CONFIG_DIR
mkdir -p $SCRIPT_DIR/data/www/dehydrated/
# Start an SSL client to serve challenge data
sudo pkill -f alpn-responder.py
sudo python3 alpn-responder.py &
sleep 1
# Configure dehydrated
DEHYDRATED_VER=master
DEHYDRATED=$SCRIPT_DIR/data/dehydrated
wget -nv -nc https://raw.githubusercontent.com/dehydrated-io/dehydrated/$DEHYDRATED_VER/docs/examples/config -O $CONFIG_DIR/config.conf || true
wget -nv https://raw.githubusercontent.com/dehydrated-io/dehydrated/$DEHYDRATED_VER/dehydrated -O $DEHYDRATED
chmod +x $DEHYDRATED
# Request Certificate
sudo $DEHYDRATED --register --accept-terms -f $CONFIG_DIR/config.conf --domain toasterparty.net
sudo $DEHYDRATED -c -f $CONFIG_DIR/config.conf --domain toasterparty.net
# Cleanup
sudo pkill -f alpn-responder.py
```
# Error Log
This is the log of a full run:
```
2023-05-13 14:10:28 URL:https://raw.githubusercontent.com/dehydrated-io/dehydrated/master/dehydrated [88973/88973] -> "/home/toaster/git/toaster-server/services/nginx/ssl-certs/data/dehydrated" [1]
# INFO: Using main config file /home/toaster/git/toaster-server/services/nginx/ssl-certs/config/config.conf
+ Account already registered!
# INFO: Using main config file /home/toaster/git/toaster-server/services/nginx/ssl-certs/config/config.conf
Processing toasterparty.net
+ Signing domains...
+ Generating private key...
+ Generating signing request...
+ Requesting new certificate order from CA...
+ Received 1 authorizations URLs from the CA
+ Handling authorization for toasterparty.net
+ Generating ALPN certificate and key for toasterparty.net...
+ 1 pending challenge(s)
+ Deploying challenge tokens...
+ Responding to challenge for toasterparty.net authorization...
Got request for toasterparty.net
Got request for toasterparty.net
Got request for toasterparty.net
+ Cleaning challenge tokens...
+ Challenge validation has failed :(
ERROR: Challenge is invalid! (returned: invalid) (result: ["type"] "tls-alpn-01"
["status"] "invalid"
["error","type"] "urn:ietf:params:acme:error:unauthorized"
["error","detail"] "Incorrect validation certificate for tls-alpn-01 challenge. Requested toasterparty.net from 72.197.16.252:443. Received certificate with unexpected extensions: \"Required extension OID 1.3.6.1.5.5.7.1.31 is not present\""
["error","status"] 403
["error"] {"type":"urn:ietf:params:acme:error:unauthorized","detail":"Incorrect validation certificate for tls-alpn-01 challenge. Requested toasterparty.net from 72.197.16.252:443. Received certificate with unexpected extensions: \"Required extension OID 1.3.6.1.5.5.7.1.31 is not present\"","status":403}
["url"] "https://acme-v02.api.letsencrypt.org/acme/chall-v3/227519890297/yQrYAw"
["token"] "--ze3nEpYDbpWNC-ncSv2V-_6GTo5sRUlc7oP7ZbUag"
["validationRecord",0,"hostname"] "toasterparty.net"
["validationRecord",0,"port"] "443"
["validationRecord",0,"addressesResolved",0] "72.197.16.252"
["validationRecord",0,"addressesResolved"] ["72.197.16.252"]
["validationRecord",0,"addressUsed"] "72.197.16.252"
["validationRecord",0] {"hostname":"toasterparty.net","port":"443","addressesResolved":["72.197.16.252"],"addressUsed":"72.197.16.252"}
["validationRecord"] [{"hostname":"toasterparty.net","port":"443","addressesResolved":["72.197.16.252"],"addressUsed":"72.197.16.252"}]
["validated"] "2023-05-13T21:10:31Z")
```
# dehydrated -e
These are my environment vars after loading `/config/config.conf`
```bash
# dehydrated configuration
# INFO: Using main config file /home/toaster/git/toaster-server/services/nginx/ssl-certs/config/config.conf
declare -- CA="https://acme-v02.api.letsencrypt.org/directory"
declare -- CERTDIR="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../data/certs"
declare -- ALPNCERTDIR="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../data/alpn-certs"
declare -- CHALLENGETYPE="tls-alpn-01"
declare -- DOMAINS_D=""
declare -- DOMAINS_TXT="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../../config/domains.conf"
declare -- HOOK=""
declare -- HOOK_CHAIN="no"
declare -- RENEW_DAYS="30"
declare -- ACCOUNT_KEY="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../data/accounts/aHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo/account_key.pem"
declare -- ACCOUNT_KEY_JSON="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../data/accounts/aHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo/registration_info.json"
declare -- ACCOUNT_ID_JSON="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../data/accounts/aHR0cHM6Ly9hY21lLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2RpcmVjdG9yeQo/account_id.json"
declare -- KEYSIZE="4096"
declare -- WELLKNOWN="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../data/www/dehydrated"
declare -- PRIVATE_KEY_RENEW="yes"
declare -- OPENSSL_CNF="/usr/lib/ssl/openssl.cnf"
declare -- CONTACT_EMAIL=""
declare -- LOCKFILE="/home/toaster/git/toaster-server/services/nginx/ssl-certs/config/../data/lock"
```
# Let's Debug
Here's proof my domain records are correct:

# Checking for TLS 1.3
Here's proof my machine can handle TLS 1.3:
```
echo | openssl s_client -tls1_3 -connect tls13.cloudflare.com:443
openssl version
```
```
---
New, TLSv1.3, Cipher is TLS_AES_256_GCM_SHA384
Server public key is 256 bit
Secure Renegotiation IS NOT supported
Compression: NONE
Expansion: NONE
No ALPN negotiated
Early data was not sent
Verify return code: 0 (ok)
---
DONE
OpenSSL 1.1.1n 15 Mar 2022
```
# alpn-responder.py
Here's the responder I'm using. It's the same as the provided example but using port 443 and `ALPNDIR` is set to match the environment variable.
```py
#!/usr/bin/env python3
import ssl
import socketserver
import re
import os
ALPNDIR="/data/alpn-certs"
PROXY_PROTOCOL=False
FALLBACK_CERTIFICATE="/etc/ssl/certs/ssl-cert-snakeoil.pem"
FALLBACK_KEY="/etc/ssl/private/ssl-cert-snakeoil.key"
class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
pass
class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
def create_context(self, certfile, keyfile, first=False):
ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
ssl_context.set_ciphers('ECDHE+AESGCM')
ssl_context.set_alpn_protocols(["acme-tls/1"])
ssl_context.options |= ssl.OP_NO_TLSv1 | ssl.OP_NO_TLSv1_1
if first:
ssl_context.set_servername_callback(self.load_certificate)
ssl_context.load_cert_chain(certfile=certfile, keyfile=keyfile)
return ssl_context
def load_certificate(self, sslsocket, sni_name, sslcontext):
print("Got request for %s" % sni_name)
if not re.match(r'^(([a-zA-Z]{1})|([a-zA-Z]{1}[a-zA-Z]{1})|([a-zA-Z]{1}[0-9]{1})|([0-9]{1}[a-zA-Z]{1})|([a-zA-Z0-9][-_.a-zA-Z0-9]{0,61}[a-zA-Z0-9]))\.([a-zA-Z]{2,13}|[a-zA-Z0-9-]{2,30}.[a-zA-Z]{2,3})$', sni_name):
return
certfile = os.path.join(ALPNDIR, "%s.crt.pem" % sni_name)
keyfile = os.path.join(ALPNDIR, "%s.key.pem" % sni_name)
if not os.path.exists(certfile) or not os.path.exists(keyfile):
return
sslsocket.context = self.create_context(certfile, keyfile)
def handle(self):
if PROXY_PROTOCOL:
buf = b""
while b"\r\n" not in buf:
buf += self.request.recv(1)
ssl_context = self.create_context(FALLBACK_CERTIFICATE, FALLBACK_KEY, True)
newsock = ssl_context.wrap_socket(self.request, server_side=True)
if __name__ == "__main__":
HOST, PORT = "0.0.0.0", 443
server = ThreadedTCPServer((HOST, PORT), ThreadedTCPRequestHandler, bind_and_activate=False)
server.allow_reuse_address = True
try:
server.server_bind()
server.server_activate()
server.serve_forever()
except:
server.shutdown()
```
Thanks for any time spent looking into this!
To rule out file permission issues, I've already tried:
sudo chown -R toaster .
sudo chmod -R 777 .
I've also tried calling dehydrated and/or the responder with and without sudo
@toasterparty commented on GitHub (May 14, 2023):
To rule out file permission issues, I've already tried:
```
sudo chown -R toaster .
sudo chmod -R 777 .
```
I've also tried calling dehydrated and/or the responder with and without `sudo`
Let's Debug doesn't really seem to check for the required certificate so that output is kinda useless.
Just to make sure the alpn validation certs are generated correctly maybe try running openssl x509 -noout -text -in path/to/alpn/cert (you might need to kill dehydrated before it deletes the generated cert, maybe Ctrl+c works too, can't check that right now). The option mentioned in the error message should be somewhere in there.
If you can see the option in the certificate I'd guess that there is still something either running on your machine and listening on port 443, or there's something in the path between Let's Encrypt and you, maybe your provider is also running some weird firewall that actually blocks the request or something like that.
@lukas2511 commented on GitHub (May 14, 2023):
Let's Debug doesn't really seem to check for the required certificate so that output is kinda useless.
Just to make sure the alpn validation certs are generated correctly maybe try running `openssl x509 -noout -text -in path/to/alpn/cert` (you might need to kill dehydrated before it deletes the generated cert, maybe Ctrl+c works too, can't check that right now). The option mentioned in the error message should be somewhere in there.
If you can see the option in the certificate I'd guess that there is still something either running on your machine and listening on port 443, or there's something in the path between Let's Encrypt and you, maybe your provider is also running some weird firewall that actually blocks the request or something like that.
The problem is simple, but the error is misleading:
In my responder I had:
ALPNDIR="/data/alpn-certs"
When I needed:
ALPNDIR="./data/alpn-certs"
This edit to load_certificate made it super obvious what was happening:
@toasterparty commented on GitHub (May 15, 2023):
I figured it out!
The problem is simple, but the error is misleading:
In my responder I had:
```
ALPNDIR="/data/alpn-certs"
```
When I needed:
```
ALPNDIR="./data/alpn-certs"
```
This edit to `load_certificate` made it super obvious what was happening:

Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Originally created by @toasterparty on GitHub (May 14, 2023).
I'm trying to get my first cert for my first domain, toasterparty.net. As I understand, tls-alpn-01 via dehydrated are the best tools for the job since my ISP blocks port 80. I'm running Debian 11 in a headless configuration on standalone hardware. This error is what's stopping me from progressing:
Script
This is how I'm using dehydrated.
Error Log
This is the log of a full run:
dehydrated -e
These are my environment vars after loading
/config/config.confLet's Debug
Here's proof my domain records are correct:
Checking for TLS 1.3
Here's proof my machine can handle TLS 1.3:
alpn-responder.py
Here's the responder I'm using. It's the same as the provided example but using port 443 and
ALPNDIRis set to match the environment variable.Thanks for any time spent looking into this!
@toasterparty commented on GitHub (May 14, 2023):
To rule out file permission issues, I've already tried:
I've also tried calling dehydrated and/or the responder with and without
sudo@lukas2511 commented on GitHub (May 14, 2023):
Let's Debug doesn't really seem to check for the required certificate so that output is kinda useless.
Just to make sure the alpn validation certs are generated correctly maybe try running
openssl x509 -noout -text -in path/to/alpn/cert(you might need to kill dehydrated before it deletes the generated cert, maybe Ctrl+c works too, can't check that right now). The option mentioned in the error message should be somewhere in there.If you can see the option in the certificate I'd guess that there is still something either running on your machine and listening on port 443, or there's something in the path between Let's Encrypt and you, maybe your provider is also running some weird firewall that actually blocks the request or something like that.
@toasterparty commented on GitHub (May 15, 2023):
I figured it out!
The problem is simple, but the error is misleading:
In my responder I had:
When I needed:
This edit to

load_certificatemade it super obvious what was happening:@toasterparty commented on GitHub (May 15, 2023):
(and for the record you can examine the certificate with a well-timed CTRL+C)