edge: tls-alpn-01 hellos bypass the wildcard to reach autocert's challenge cert
wrapGetCertificate answered a tls-alpn-01 validation ClientHello (SupportedProtos == ["acme-tls/1"]) for a covered host with the wildcard instead of autocert's per-host challenge cert, failing that validation. The CA is probing the per-host order the inner path holds, so those hellos must always reach inner, never the wildcard.
2 files changed,
+14
−0
internal/edge/wildcard.go+6 −0internal/edge/wildcard_test.go+8 −0
diff --git a/internal/edge/wildcard.go b/internal/edge/wildcard.go| index d3d7a2988..251c8e0fc 100644 |
| --- a/internal/edge/wildcard.go |
| +++ b/internal/edge/wildcard.go |
| @@ -540,6 +540,12 @@ func newWildcardFromEnv(e *Edge, cache autocert.Cache) *wildcardManager { |
| // fall-through contract is testable without a TLS listener. |
| func wrapGetCertificate(w *wildcardManager, inner func(*tls.ClientHelloInfo) (*tls.Certificate, error)) func(*tls.ClientHelloInfo) (*tls.Certificate, error) { |
| return func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { |
| + // A tls-alpn-01 validation handshake must reach autocert's challenge |
| + // cert, never the wildcard — the CA is probing the per-host order the |
| + // inner path holds. |
| + if len(hello.SupportedProtos) == 1 && hello.SupportedProtos[0] == acme.ALPNProto { |
| + return inner(hello) |
| + } |
| if c := w.certFor(hello.ServerName); c != nil { |
| return c, nil |
| } |
diff --git a/internal/edge/wildcard_test.go b/internal/edge/wildcard_test.go| index e16e6bf2c..4925d6918 100644 |
| --- a/internal/edge/wildcard_test.go |
| +++ b/internal/edge/wildcard_test.go |
| @@ -345,6 +345,14 @@ func TestWrappedGetCertificateFallsThrough(t *testing.T) { |
| if _, err := wrapped(&tls.ClientHelloInfo{ServerName: "custom.example.com"}); err == nil || fallbackHit != "custom.example.com" { |
| t.Fatalf("uncovered name must fall through to inner; fallbackHit=%q err=%v", fallbackHit, err) |
| } |
| + |
| + // A tls-alpn-01 validation ClientHello for a COVERED name must still |
| + // reach autocert's challenge cert, not the wildcard — otherwise that |
| + // validation fails every time. |
| + fallbackHit = "" |
| + if _, err := wrapped(&tls.ClientHelloInfo{ServerName: "new.abs.oncarlos.com", SupportedProtos: []string{"acme-tls/1"}}); err == nil || fallbackHit != "new.abs.oncarlos.com" { |
| + t.Fatalf("tls-alpn-01 hello for covered name must reach inner; fallbackHit=%q err=%v", fallbackHit, err) |
| + } |
| } |
| func TestNewWildcardFromEnv(t *testing.T) { |