package quicktunnelauth import ( "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/json" "errors" "fmt" "io" "net" "net/http" "net/url" "strconv" "strings" "time" ) const ( QuickTunnelAuthCallbackPath = "/.cloudflared/qt-auth/callback" QuickTunnelAuthBrokerAuthorizeURL = "https://login.trycloudflare.com/authorize" QuickTunnelAuthStateTTL = 10 * time.Minute quickTunnelAuthSigningKeySize = 32 quickTunnelAuthStateSize = 32 quickTunnelAuthMaxStateCookieBytes = 4 * 1024 // __Secure- enforces HTTPS and the Secure attribute. __Host- cannot be used // because it requires Path=/, while this cookie is scoped to the callback path. // See https://developer.mozilla.org/en-US/docs/Web/HTTP/Guides/Cookies#cookie_prefixes. quickTunnelAuthStateCookiePrefix = "__Secure-cloudflared-qt-auth-state-" quickTunnelAuthHTTPSPort = 443 // quickTunnelAuthMaxLabelLength is the maximum length of a DNS label. // Quick Tunnel labels are restricted to ASCII alphanumeric characters // and hyphens. quickTunnelAuthMaxLabelLength = 63 ) var errInvalidQuickTunnelReturnPath = errors.New("invalid Quick Tunnel authentication return path") // QuickTunnelAuthStateManager creates browser-bound authentication states. type QuickTunnelAuthStateManager struct { hostname string brokerURL url.URL signingKey []byte random io.Reader now func() time.Time } type quickTunnelAuthStateCookiePayload struct { State string `json:"state"` Hostname string `json:"hostname"` ReturnPath string `json:"return_path"` ExpiresAt int64 `json:"exp"` } // QuickTunnelAuthLogin contains the browser state needed to start one login. type QuickTunnelAuthLogin struct { State string RedirectURL *url.URL Cookie *http.Cookie } func NewQuickTunnelAuthStateManager(hostname string) (*QuickTunnelAuthStateManager, error) { brokerURL, err := url.Parse(QuickTunnelAuthBrokerAuthorizeURL) if err != nil { return nil, fmt.Errorf("parse broker URL: %w", err) } normalizedHostname := strings.ToLower(hostname) if !isQuickTunnelHostname(normalizedHostname) { return nil, fmt.Errorf("%q is not a valid Quick Tunnel hostname", hostname) } signingKey := make([]byte, quickTunnelAuthSigningKeySize) if _, err := rand.Read(signingKey); err != nil { return nil, fmt.Errorf("generate authentication-state signing key: %w", err) } return &QuickTunnelAuthStateManager{ hostname: normalizedHostname, brokerURL: *brokerURL, signingKey: signingKey, random: rand.Reader, now: time.Now, }, nil } // isQuickTunnelHostname reports whether hostname is a valid protected Quick // Tunnel hostname of the form