TLS Placeholders

1. The problem I’m having:

Potential bug because placeholder doesn’t return anything?
trying to access certificate details from webserver {http.request.tls.client.certificate_pem} but that one doesn’t return anything but der encoded and everything else from there works.

Also have suggestion to add more of those placeholders like how long cert is valid and valid from and valid to i seen other servers have these, and not sure is there issue adding extensions of certificate to be accessed? I’m using 3rd party library to access those so i can get oids from Certificate Policies would be neat if it was possible without 3rd party libraries.

Response Headers

content-encoding: zstd
content-type: text/html;charset=utf-8
date: Thu, 06 Mar 2025 07:59:44 GMT
etag: "16PlJV3r4JsyX3B5hBM7jY/scXLLBzWgcPyILUH1NQPr7czVi4/0r58Ey9uznkmNkmmAIBPBxF89nimlGfgvug==-zstd"
expires: Sun, 27 Jul 1997 13:00:00 GMT
issuercert: CN=Coreit Sub CA,O=Coreit,C=ME,2.5.4.97=#130e5641544d452d3032373735303138
pragma: no-cache
referrer-policy: strict-origin
serialcert: <REDACTED>
server: Caddy
subjectcert: <REDACTED>
vary: Accept-Encoding
x-content-type-options: nosniff
x-frame-options: SAMEORIGIN
x-xss-protection: 1; mode=block

2. Error messages and/or full log output:

2025/03/06 07:59:42.698 e[33mWARNe[0m   http.handlers.reverse_proxy     aborting with incomplete response       {"upstream": "127.0.0.1:8080", "duration": 0.0834991, "request": {"remote_ip": "::1", "remote_port": "34969", "client_ip": "::1", "proto": "HTTP/2.0", "method": "GET", "host": "localhost", "uri": "/ords/r/test/100/files/static/v13/forge.min.js.map", "headers": {"Accept-Language": ["en-US,en;q=0.9"], "X-Forwarded-Host": ["localhost"], "Priority": ["u=4, i"], "Sec-Fetch-Dest": ["empty"], "Pragma": ["no-cache"], "User-Agent": ["Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"], "Sec-Fetch-Mode": ["no-cors"], "X-Forwarded-For": ["::1"], "X-Forwarded-Proto": ["https"], "Cookie": ["REDACTED"], "Cache-Control": ["no-cache"], "Sec-Fetch-Site": ["same-origin"], "Accept-Encoding": ["gzip, deflate, br, zstd"]}, "tls": {"resumed": false, "version": 772, "cipher_suite": 4865, "proto": "h2", "server_name": "localhost", "client_common_name": "<REDACTED>", "client_serial": "<REDACTED>"}}, "error": "writing: http2: stream closed"}

3. Caddy version:

v2.9.1 h1:OEYiZ7DbCzAWVb6TNEkjRcSCRGHVoZsJinoDR/n9oaY=

4. How I installed and ran Caddy:

Just downloaded binary from GitHub

a. System environment:

Windows 10 Enterprise, x64

b. Command:

caddy.exe run

d. My complete Caddy config:

localhost {
	reverse_proxy 127.0.0.1:8080
	redir / /ords/r/test/sample-reporting/login

	encode zstd gzip
	
	tls "C:\Caddy\server.csr" "C:\Caddy\server.key" {
		protocols tls1.3
		client_auth {
			mode require_and_verify
			trust_pool file "C:\Caddy\ca.pem"
		}
	}
	
	header {
		IssuerCert {http.request.tls.client.issuer}
		SubjectCert {http.request.tls.client.subject}
		SerialCert {http.request.tls.client.serial}
		PemCert {http.request.tls.client.certificate_pem}
	}
}

Sorry it’s been a while, but I need to test this myself to check what’s going on, and I haven’t had the time. I’ll circle back.

No worries at all, take your time!

Something about HTTP/2 causes the header to be skipped by Go standard library because it contains new-line \n. I don’t understand why \n is problematic in the context of headers in HTTP/2 only, but this is what I was able to find so far. @matt, you’re more knowledgeable on H2. Any idea why Go stdlib would skip writing this specific header value in h2 because it contains unescaped \n?

The DER placeholder is available, i.e. certificate_der_base64, and avoids newlines completely. This should be a reasonable workaround for now.

2 Likes

Yeah I have no clue, but it makes sense since it’s invalid to have a newline. Sounds like a mistake to use newlines in headers in the first place.

2 Likes

So, yeah, it isn’t sent because the newline is forbidden character in HTTP headers. In HTTP/1.1, Go standard library replaces the newlines with spaces. In HTTP/2, the Go standard library just rejects the header. In this case, I believe using the DER is the optimal alternative.

2 Likes

Yeah, I can use DER; it’s just some extra work to extract the PEM from it because I need it specifically. I used it to access Certificate Policies with a certain library to retrieve OIDs from there. Would it be possible to expose Certificate Policies?

I haven’t seen any other servers exposing these, but I’d like to have them if possible. Other details like the serial number, valid-from, and valid-to dates are commonly available, so I assume they can be included. I’m just not sure about Certificate Policies since they fall under the certificate extension category.

1 Like