package onboarding import ( "fmt" "html" "net" "net/http" "github.com/bnema/gordon/internal/adapters/in/http/middleware" ) // Handler serves CA onboarding endpoints for direct/Tailnet clients. type Handler struct { rootPEM []byte mobileconfig []byte fingerprint string httpPort int tlsPort int } // NewHandler creates an onboarding handler. // fingerprint is the SHA-256 fingerprint of the root CA certificate. // httpPort is the configured HTTP listener port (used to map to tlsPort). // tlsPort is the configured HTTPS listener port. func NewHandler(rootPEM, mobileconfig []byte, fingerprint string, httpPort, tlsPort int) *Handler { return &Handler{ rootPEM: rootPEM, mobileconfig: mobileconfig, fingerprint: fingerprint, httpPort: httpPort, tlsPort: tlsPort, } } // ServeCACert serves the root CA certificate as PEM. func (h *Handler) ServeCACert(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/x-x509-ca-cert") w.Header().Set("Content-Disposition", "attachment; filename=gordon-ca.crt") _, _ = w.Write(h.rootPEM) } // ServeMobileconfig serves an iOS configuration profile with the root CA. func (h *Handler) ServeMobileconfig(w http.ResponseWriter, _ *http.Request) { w.Header().Set("Content-Type", "application/x-apple-aspen-config") w.Header().Set("Content-Disposition", "attachment; filename=gordon-ca.mobileconfig") _, _ = w.Write(h.mobileconfig) } // publicHTTPSURL derives the public HTTPS URL from the request Host header. // The result is HTML-escaped to prevent XSS via a crafted Host header. func (h *Handler) publicHTTPSURL(r *http.Request) string { authority := middleware.HTTPSAuthority(r.Host, h.httpPort, h.tlsPort) return html.EscapeString("https://" + authority + "/") } // isRawIP reports whether the host (without port) is a raw IP literal. func isRawIP(hostHeader string) bool { host, _, err := net.SplitHostPort(hostHeader) if err != nil { host = hostHeader } if len(host) >= 2 && host[0] == '[' && host[len(host)-1] == ']' { host = host[1 : len(host)-1] } return net.ParseIP(host) != nil } // ServeOnboardingPage serves the CA trust onboarding HTML page. func (h *Handler) ServeOnboardingPage(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "text/html; charset=utf-8") redirectURL := h.publicHTTPSURL(r) showSiteLink := !isRawIP(r.Host) siteLink := "" if showSiteLink { siteLink = fmt.Sprintf( ``, redirectURL, ) } _, _ = fmt.Fprintf(w, ` Gordon — Trust CA Certificate

Gordon — Trust CA Certificate

This Gordon instance uses its own internal CA for HTTPS. Import the CA certificate into your device or browser to connect securely.

CA Fingerprint (SHA-256) %s

Verify this fingerprint matches the output of gordon ca info on the server.

iOS / iPadOS

Download Profile

After downloading, go to Settings → General → VPN & Device Management to install, then Settings → General → About → Certificate Trust Settings to enable full trust.

Android

Download Certificate

Go to Settings → Security → Install a certificate → CA certificate and select the downloaded file.

macOS / Linux / Windows

Download Certificate

Double-click the downloaded file and add it to your system trust store, or import it via your OS certificate manager.

Firefox / Zen

Download Certificate

Firefox and Zen use their own certificate store (NSS). Go to Settings → Privacy & Security → Certificates → View Certificates → Authorities → Import and select the downloaded file. Check Trust this CA to identify websites.

%s

The gordon ca install command is for the Gordon server host only, not for client devices.

`, html.EscapeString(h.fingerprint), siteLink) }