Tls-alpn-01 with acme4j -> unauthorized

Hi, i am trying to get a certificate using the tls-alpn-01 challenge against acme://letsencrypt.org/staging.

My domain is: enpasos.com

I am using acme4j to trigger the process and generate the TLS server certificate as the challenge response. An example of the certificate

-----BEGIN CERTIFICATE-----
MIIC+zCCAeOgAwIBAgIGAXZBZ5PAMA0GCSqGSIb3DQEBCwUAMBcxFTATBgNVBAMM
DGFjbWUuaW52YWxpZDAeFw0yMDEyMDgwODExMTVaFw0yMDEyMTUwODExMTVaMBcx
FTATBgNVBAMMDGFjbWUuaW52YWxpZDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC
AQoCggEBAKtuqFfkFKagugRQ7AI9mAym7XQf0UDvQI8LGGfkq/dONkDvBjZpacIE
CM+lKH4JWAU3lTIujlX9/Tt5LLmxsExKyOGNRmy+kguBFdAXpx2M0URQNWWMGJfs
MVgCRqut17KyKdA5N6TVe4eTWxucMem6E+Owf5IWQpKNg829gp3xaEwBIYMgEYWS
Z7GjMDgQDW8xEt9l9abb1GHY+8BJeU4EZQ4Iff8758lrJJhO/ItEtuvKuFZFg7o3
t+Cb/G1LqLUNFD0gG4yrGQ8Yh05GFHlVUutAbI4iYjJynFZSExIsDc4bmYWx4Nwn
QR+xhoEzRZAF1QFMBZgIyJIg0Omq+qsCAwEAAaNNMEswFgYDVR0RBA8wDYILZW5w
YXNvcy5jb20wMQYIKwYBBQUHAR8BAf8EIgQgQ4UcWvo2T1+3r2mcHCVlnO4/2x2b
9PJ2Ifkp8ZDidl4wDQYJKoZIhvcNAQELBQADggEBAJamEbqmlv/J9jIABesev3k+
qIW94XWm396lJNN0X4QZV92EZZM553mZ+bfhotFb52UirEd8JFFM4Qu08Ip/x1AW
Gw5wlpexMGH/yLGhd4T3VV/nYj44YRGfEUyClawz/jhCj8bqS7shwDixn+2SVYw4
AdDpegyHQOINhjezVUYmYoWS3qZGQcE0hDy4ybD+M/UaMLAcECojWqSdhZu/fLXz
x/V+vCrsGRMz4xuZEtJl1IwPSOPj08sKX09lfynIcyMflY/B5IrkxdHqiIBOCHwT
3JTgWYtn+OWOcZ+S/2VeuElHDwX2Sg1PCRWcqm3SAPzUisRKhUNF654/PBZmMDw=
-----END CERTIFICATE-----

From wireshark trace the tls handshake initiated by letsencrypt to retrieve the challenge response certificate runs through and serves the certificate.

However, the letsencrypt server is somewhat not happy and responds to me with responses like

{
	"type": "tls-alpn-01",
	"status": "invalid",
	"error": {
		"type": "urn:ietf:params:acme:error:unauthorized",
		"detail": "Cannot negotiate ALPN protocol \"acme-tls/1\" for tls-alpn-01 challenge",
		"status": 403
	},
	"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/169091067/OfgCaw",
	"token": "A8b8y7rKuoX24pzXyr_WKK4Rc0YdRXS3PT3g3pwvJRU",
	"validationRecord": [
		{
			"hostname": "enpasos.com",
			"port": "443",
			"addressesResolved": [
				"93.90.193.219"
			],
			"addressUsed": "93.90.193.219"
		}
	]
}

Any idea what is wrong and how to fix it?

1 Like

The certificate looks fine. What you are missing is the negotiation of the acme-tls/1. ALPN

Let's Encrypt connects to your TLS server and requests the protocol in an ALPN extension. Your TLS server needs to respond during the handshake with the same ALPN. That way, both peers agree that they are "speaking acme-tls/1".

...

  1. The ACME server initiates a TLS connection to the chosen IP
    address. This connection MUST use TCP port 443. The ACME server
    MUST provide an ALPN extension with the single protocol name
    "acme-tls/1" and an SNI extension containing only the domain name
    being validated during the TLS handshake.

  2. The ACME server verifies that during the TLS handshake the
    application-layer protocol "acme-tls/1" was successfully
    negotiated (and that the ALPN extension contained only the value
    "acme-tls/1") and that the certificate returned contains:

...

How to do this varies by what server you are using to terminate TLS. It will require a different kind of configuration in nginx, haproxy, netty etc.

3 Likes

Did you have a look at this page?:
tls-alpn-01 Challenge - acme4j (shredzone.org)

Also, there is an nginx server seen serving HTTP.
It would be very simple to use that one:

curl -Iki http://enpasos.com/
HTTP/1.1 301 Moved Permanently
Server: nginx/1.16.1
Date: Tue, 08 Dec 2020 18:35:15 GMT
Content-Type: text/html
Content-Length: 169
Connection: keep-alive
Location: https://enpasos.com/
1 Like

Thank you very much for your help.
I changed the TLS Server implementation accordingly.
Now it returns "acme-tls/1" as an ALPN extension

As a result I get

{
	"type": "tls-alpn-01",
	"status": "invalid",
	"error": {
		"type": "urn:ietf:params:acme:error:connection",
		"detail": "Timeout during read (your server may be slow or overloaded)",
		"status": 400
	},
	"url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/169592106/N9uZNA",
	"token": "8QQWXuAKmjUZlNcXGt1hUkFh99ztEWGNVouyJATnFjg",
	"validationRecord": [
		{
			"hostname": "enpasos.com",
			"port": "443",
			"addressesResolved": [
				"93.90.193.219"
			],
			"addressUsed": "93.90.193.219"
		}
	]
}

I see initiated communication con TLSv1 and TLSv1.2 beside the TLSv1.3.
Is there support for TLSv1 and TLSv1.2 needed to pass the challenge?

Thank you for your help.
Yes, I followed https://shredzone.org/maven/acme4j/challenge/tls-alpn-01.html when implementing the process that initiates the challenge procedure.
Yes, there is an nginx running on the tls-server. It redirects http to https. It does not terminate TLS but streams to two java based processes in the background, one serving the challenge requests, one serving normal traffic.
If you see an option to get rid off the java based challenge reply process and to move it to nginx it would be great. However, it is no option for me (requirement by my customer) to replace the java process by a python process or the like.

1 Like

I get the same thing. When I try to connect to your server with the ALPN set, it just hangs forever:

$ openssl s_client -connect  enpasos.com:443 -alpn acme-tls/1 -msg -showcerts
CONNECTED(00000003)
>>> ??? [length 0005]
    16 03 01 01 3b
>>> TLS 1.3, Handshake [length 013b], ClientHello
    01 00 01 37 03 03 71 6d 87 ad dc 35 7d c7 a3 81
    94 57 7f 87 cb 55 34 69 8e 1b 5f 2c 2e e8 2f c0
    b1 33 a4 10 e8 88 20 53 a2 e0 9e 60 c1 ad 33 ac
    28 2a 49 7b ec 6c 19 f7 a7 91 10 93 ff 85 ae 6a
    5d ec 3e 0f 29 46 83 00 3e 13 02 13 03 13 01 c0
    2c c0 30 00 9f cc a9 cc a8 cc aa c0 2b c0 2f 00
    9e c0 24 c0 28 00 6b c0 23 c0 27 00 67 c0 0a c0
    14 00 39 c0 09 c0 13 00 33 00 9d 00 9c 00 3d 00
    3c 00 35 00 2f 00 ff 01 00 00 b0 00 00 00 10 00
    0e 00 00 0b 65 6e 70 61 73 6f 73 2e 63 6f 6d 00
    0b 00 04 03 00 01 02 00 0a 00 0c 00 0a 00 1d 00
    17 00 1e 00 19 00 18 00 23 00 00 00 10 00 0d 00
    0b 0a 61 63 6d 65 2d 74 6c 73 2f 31 00 16 00 00
    00 17 00 00 00 0d 00 2a 00 28 04 03 05 03 06 03
    08 07 08 08 08 09 08 0a 08 0b 08 04 08 05 08 06
    04 01 05 01 06 01 03 03 03 01 03 02 04 02 05 02
    06 02 00 2b 00 05 04 03 04 03 03 00 2d 00 02 01
    01 00 33 00 26 00 24 00 1d 00 20 91 4f 17 fe 60
    aa ed 0a 12 a6 4c 60 63 fd 00 3a 58 3f cd 9c ed
    e0 71 6a 00 55 71 ec c1 a7 3b 24
^C

Same thing happens if I set -tls1_2.

nginx can process the validation requests via http.
[and redirect all else to https]

Here is sample nginx code:

  location ^/(?!\.well-known) {            # skip challenge requests
    return 301 https://$host$request_uri;  # send all requests to HTTPS
  }# location

All you need is a root statement for the challenge requests to base their path.
Then you can use almost any Linux ACME client quite easily.
Like:
certbot certonly --webroot -w /root/path/from/nginx/config -d example.com -d www.example.com

I served tls1.3 ... What is needed for letsencrypt? ... for now I changed it to "TLSv1", "TLSv1.1", "TLSv1.2", "TLSv1.3"

The final production environment of my customer (not enpasos.com) is only accessible via port 443 from the internet - not even nginx can be reached by port 80.

Therefore I have to use Tls-alpn-01.

1 Like

OK I understand now better your situation.
I think you might be able to get the nginx to work with TLS-ALPN faster then the Java.
If so, you could use the nginx as a TLS reverse proxy to the Java [which could be http or https].

certbot is a good idea but looks like it is not supporting tls-alpn-01, yet.

Either 1.2 or 1.3 is fine.

However, your server doesn't complete the handshake with either when the ALPN is requested. It just stalls after the client sends its ClientHello.

That's what the openssl s_client command shows, and that's the same problem as the read timeout reported by Let's Encrypt.

I've used nginx with acme.sh via TLS-ALPN; And also as a reverse proxy to other internal web servers.

If I could understand, what the remaining problem with my response to the challenge request is, I think I could simply change the behaviour of the responding java process.

Thinking out loud - does your TLS server implementation allow for multiple clients to fetch the TLS-ALPN certificate? Or will it stop working after a single client has requested it?

Let's Encrypt will connect to your server ~4 times and try to perform the TLS-ALPN challenge, from different IPs. (Background reading; ACME v1/v2: Validating challenges from multiple network vantage points).

I noticed in your screenshot that there's one ALPN handshake that succeeds, but then there's 3 more ClientHello messages from other IPs which do not appear to have responses.

Is it possible you're only allowing a single client to fetch the ALPN certificate and then you delete it?

Here is sample nginx alpn code:
[simply add to the main nginx.conf]

stream {
  map $ssl_preread_alpn_protocols $tls_port { # define tls_ports
    ~\bacme-tls/1\b 5443;                     # when alpn
    default 443;                              # otherwise
  }#map
  server {
    listen localhost:6443;                    # this is where the firewall/router nats inbound port 443
    proxy_pass localhost:$tls_port;           # this local system proxies to itself on the defined tls_port
    ssl_preread on; #####
  }#server
}#stream

# Notes:
# initial connections come in on 6443
# all the alpn connections go to 5443
# all other tls connections go to 443

Then using acme.sh as follows:
/root/.acme.sh/acme.sh --issue -d do.main,www.do.main --alpn --tlsport 5443

could be ... I will investigate ...

Success! Thanks a lot to the helpful support!

Two problems were in my way:

  1. I did not answer to the alpn negotiation request
  2. I programmed a simple "one socket" server, but the test included many clients.
2 Likes

Thank you for the summary. Yes, the acme4j documentation should point out that there are multiple incoming requests. I will change it in the docs. :slight_smile:

1 Like

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.