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( `
Already installed? Go to site →
This Gordon instance uses its own internal CA for HTTPS. Import the CA certificate into your device or browser to connect securely.
Verify this fingerprint matches the output of gordon ca info on the server.
After downloading, go to Settings → General → VPN & Device Management to install, then Settings → General → About → Certificate Trust Settings to enable full trust.
Go to Settings → Security → Install a certificate → CA certificate and select the downloaded file.
Double-click the downloaded file and add it to your system trust store, or import it via your OS certificate manager.
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.
The gordon ca install command is for the Gordon server host only, not for client devices.