diff --git a/CHANGELOG.md b/CHANGELOG.md index 0357319..8b5c0be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # CHANGELOG +## v1.20.6 (31 January 2026) + +- Fix HTTP2 not working properly in transparent mode + ## v1.20.5 (31 January 2026) - Fixed HTTP/2 compatibility issue with MITM connections in transparent proxy mode diff --git a/gatesentryproxy/ssl.go b/gatesentryproxy/ssl.go index fc3e229..94b6acf 100644 --- a/gatesentryproxy/ssl.go +++ b/gatesentryproxy/ssl.go @@ -1,6 +1,7 @@ package gatesentryproxy import ( + "context" "io" "log" "net" @@ -14,16 +15,11 @@ import ( "runtime" "strings" - // "io/ioutil" "bytes" - - // "sync" "reflect" "golang.org/x/net/http2" - "strconv" - gsClientHello "bitbucket.org/abdullah_irfan/gatesentryproxy/clienthello" ) @@ -138,7 +134,7 @@ func HandleSSLBump(r *http.Request, w http.ResponseWriter, user string, authUser return } fmt.Fprint(conn, "HTTP/1.1 200 Connection Established\r\n\r\n") - SSLBump(conn, r.URL.Host, user, authUser, r, passthru, gsproxy) + SSLBump(conn, r.URL.Host, user, authUser, r, passthru, gsproxy, nil) } func HandleSSLConnectDirect(r *http.Request, w http.ResponseWriter, user string, passthru *GSProxyPassthru) { @@ -241,7 +237,9 @@ func ConnectDirect(conn net.Conn, serverAddr string, extraData []byte, gpt *GSPr // traffic. serverAddr is the address (host:port) of the server the client was // trying to connect to. user is the username to use for logging; authUser is // the authenticated user, if any; r is the CONNECT request, if any. -func SSLBump(conn net.Conn, serverAddr, user, authUser string, r *http.Request, gpt *GSProxyPassthru, gsproxy *GSProxy) { +// If clientHelloData is provided (non-nil), it will be used instead of reading +// from the connection (used in transparent proxy mode where ClientHello was already read). +func SSLBump(conn net.Conn, serverAddr, user, authUser string, r *http.Request, gpt *GSProxyPassthru, gsproxy *GSProxy, clientHelloData []byte) { if DebugLogging { log.Printf("[SSL] Performing a SSL Bump") } @@ -259,7 +257,13 @@ func SSLBump(conn net.Conn, serverAddr, user, authUser string, r *http.Request, obsoleteVersion := false // Read the client hello so that we can find out the name of the server (not // just the address). - clientHello, err := gsClientHello.ReadClientHello(conn) + var clientHello []byte + var err error + if clientHelloData != nil { + clientHello = clientHelloData + } else { + clientHello, err = gsClientHello.ReadClientHello(conn) + } if err != nil { GSLogSSL(user, serverAddr, "", fmt.Errorf("error reading client hello: %v", err), false) @@ -310,7 +314,6 @@ func SSLBump(conn net.Conn, serverAddr, user, authUser string, r *http.Request, cachedCert := rt != nil if !cachedCert { - log.Println("[SSL] Starting process to cache certificate") serverConn, err := tls.Dial("tcp", serverAddr, &tls.Config{ ServerName: serverName, InsecureSkipVerify: false, @@ -331,8 +334,6 @@ func SSLBump(conn net.Conn, serverAddr, user, authUser string, r *http.Request, cert, err = signCertificate(serverCert, !valid) if err != nil { - log.Println("[SSL] Error signing certificate") - log.Println("[SSL] Closing connection") serverConn.Close() GSLogSSL(user, serverAddr, serverName, fmt.Errorf("error generating certificate: %v", err), false) ConnectDirect(conn, serverAddr, clientHello, gpt) @@ -354,60 +355,68 @@ func SSLBump(conn net.Conn, serverAddr, user, authUser string, r *http.Request, validWithDefaultRoots := err == nil if state.NegotiatedProtocol == "h2" && state.NegotiatedProtocolIsMutual { - log.Println("[SSL] Negotiated Protocol is " + state.NegotiatedProtocol) if validWithDefaultRoots { - log.Println("[SSL] Valid with default Roots using http Transport") rt = http2Transport } else { - log.Println("[SSL] Using Hard Validation Transport") rt = newHardValidationTransport(insecureHTTP2Transport, serverName, state.PeerCertificates) } } else { - log.Println("[SSL] Negotiated Protocol is " + state.NegotiatedProtocol) if validWithDefaultRoots { - log.Println("[SSL] Valid with default Roots using http2 Transport") rt = httpTransport } else { - log.Println("[SSL] Using Hard Validation Transport") rt = newHardValidationTransport(insecureHTTPTransport, serverName, state.PeerCertificates) } } CertCache.Put(serverName, serverAddr, cert, rt) } log.Println("[SSL] Setting up HTTP Server") - server := http.Server{ - Handler: ProxyHandler{ - TLS: true, - connectPort: port, - user: authUser, - rt: rt, - Iproxy: gsproxy, - }, - TLSConfig: &tls.Config{ - NextProtos: []string{"h2", "http/1.1"}, - Certificates: []tls.Certificate{cert, TLSCert}, - }, + // Create TLS config for the handshake (not for the server) + tlsConfig := &tls.Config{ + NextProtos: []string{"h2", "http/1.1"}, + Certificates: []tls.Certificate{cert, TLSCert}, } - log.Println("[SSL] Configuring HTTP2 server with configuration") - err = http2.ConfigureServer(&server, nil) - if err != nil { - log.Println("Error configuring HTTP/2 server:", err) - } - log.Println("[SSL] Setting up TLS Connection with inserted data") - tlsConn := tls.Server(&insertingConn{conn, clientHello}, server.TLSConfig) + tlsConn := tls.Server(&insertingConn{conn, clientHello}, tlsConfig) err = tlsConn.Handshake() - log.Println("[SSL] Performed TLS Handshake") if err != nil { GSLogSSL(user, serverAddr, serverName, fmt.Errorf("error in handshake with client: %v", err), cachedCert) conn.Close() return } - listener := &singleListener{conn: tlsConn} + clientState := tlsConn.ConnectionState() + negotiatedProto := clientState.NegotiatedProtocol + + handler := ProxyHandler{ + TLS: true, + connectPort: port, + user: authUser, + rt: rt, + Iproxy: gsproxy, + } + GSLogSSL(user, serverAddr, serverName, nil, cachedCert) - // conf = nil - server.Serve(listener) + // Set up HTTP server with HTTP/2 support + server := http.Server{ + Handler: handler, + } + + if err := http2.ConfigureServer(&server, nil); err != nil { + log.Printf("[SSL] Error configuring HTTP/2 server: %v", err) + } + + // Handle HTTP/2 directly if negotiated via ALPN + if negotiatedProto == "h2" { + h2s := &http2.Server{} + h2s.ServeConn(tlsConn, &http2.ServeConnOpts{ + Handler: handler, + Context: context.Background(), + }) + } else { + // Use standard HTTP/1.1 server with the TLS connection + listener := &singleListener{conn: tlsConn} + server.Serve(listener) + } } func newHardValidationTransport(rt http.RoundTripper, serverName string, certificates []*x509.Certificate) *hardValidationTransport { @@ -499,8 +508,6 @@ func (t *hardValidationTransport) RoundTrip(req *http.Request) (*http.Response, } func clientHelloServerName(data []byte) (name string, ok bool) { - log.Println("[SSL] client Hello Server name") - log.Println("[SSL] Data length = " + strconv.Itoa(len(data))) var hello = gsClientHello.ClientHello{} err := hello.Unmarshall(data) return hello.SNI, err == nil diff --git a/gatesentryproxy/transparent.go b/gatesentryproxy/transparent.go index 5cf096a..ea71f40 100644 --- a/gatesentryproxy/transparent.go +++ b/gatesentryproxy/transparent.go @@ -114,7 +114,7 @@ func HandleTransparentHTTPS(conn net.Conn, h *ProxyHandler, originalDst string, if DebugLogging { log.Printf("[Transparent] Performing SSL Bump for %s", serverAddr) } - SSLBump(conn, serverAddr, user, "", nil, passthru, h.Iproxy) + SSLBump(conn, serverAddr, user, "", nil, passthru, h.Iproxy, nil) } else { if DebugLogging { log.Printf("[Transparent] Direct tunnel for %s", serverAddr) diff --git a/gatesentryproxy/transparent_listener.go b/gatesentryproxy/transparent_listener.go index 774b2a1..3adfb67 100644 --- a/gatesentryproxy/transparent_listener.go +++ b/gatesentryproxy/transparent_listener.go @@ -175,23 +175,6 @@ func (l *TransparentProxyListener) handleTransparentHTTPS(conn net.Conn, origina passthru := NewGSProxyPassthru() - // First, check if MITM is enabled for this destination (using IP-based check initially) - shouldMitm := false - if IProxy != nil && IProxy.DoMitm != nil { - shouldMitm = IProxy.DoMitm(serverAddr) - } - - // For MITM connections, pass directly to SSLBump which handles SNI extraction internally - // This avoids connection wrapper issues with HTTP/2 - if shouldMitm { - if DebugLogging { - log.Printf("[Transparent] Performing SSL Bump for %s", serverAddr) - } - SSLBump(conn, serverAddr, user, "", nil, passthru, l.ProxyHandler.Iproxy) - return - } - - // For non-MITM connections, read ClientHello to extract SNI for logging and rules var realConn net.Conn if pc, ok := conn.(*prependConn); ok { realConn = pc.Conn @@ -201,7 +184,9 @@ func (l *TransparentProxyListener) handleTransparentHTTPS(conn net.Conn, origina clientHello, err := gsClientHello.ReadClientHello(conn) if err != nil { - log.Printf("[Transparent] Error reading client hello for %s: %v, falling back to direct tunnel", serverAddr, err) + if DebugLogging { + log.Printf("[Transparent] Error reading client hello for %s: %v, falling back to direct tunnel", serverAddr, err) + } if len(clientHello) > 0 { freshConn := &prependConn{ Conn: realConn, @@ -220,14 +205,11 @@ func (l *TransparentProxyListener) handleTransparentHTTPS(conn net.Conn, origina serverName := "" var hello gsClientHello.ClientHello if unmarshalErr := hello.Unmarshall(clientHello); unmarshalErr != nil { - log.Printf("[Transparent] Failed to parse ClientHello for %s: %v (len=%d)", serverAddr, unmarshalErr, len(clientHello)) - } else if hello.SNI == "" { - log.Printf("[Transparent] No SNI in ClientHello for %s (TLS version: %d, extensions: %d)", serverAddr, hello.HandshakeVersion, len(hello.Extensions)) - } else { - serverName = hello.SNI if DebugLogging { - log.Printf("[Transparent] Extracted SNI: %s from connection to %s", serverName, serverAddr) + log.Printf("[Transparent] Failed to parse ClientHello for %s: %v", serverAddr, unmarshalErr) } + } else { + serverName = hello.SNI } // Use SNI for rule matching if available, otherwise fall back to IP @@ -236,8 +218,9 @@ func (l *TransparentProxyListener) handleTransparentHTTPS(conn net.Conn, origina ruleMatchHost = serverName } - // Check proxy rules - shouldBlock, ruleMatch, _ := CheckProxyRules(ruleMatchHost, user) + // Check proxy rules using the domain name (SNI) instead of IP + shouldBlock, ruleMatch, ruleShouldMitm := CheckProxyRules(ruleMatchHost, user) + if shouldBlock { logUrl := "https://" + serverAddr if serverName != "" { @@ -252,24 +235,42 @@ func (l *TransparentProxyListener) handleTransparentHTTPS(conn net.Conn, origina passthru.UserData = ruleMatch } + shouldMitm := false + if IProxy != nil && IProxy.DoMitm != nil { + mitmCheckHost := serverAddr + if serverName != "" { + mitmCheckHost = net.JoinHostPort(serverName, port) + } + shouldMitm = IProxy.DoMitm(mitmCheckHost) + } + + if ruleMatch != nil { + shouldMitm = ruleShouldMitm + } + // Log with domain name if available logUrl := "https://" + serverAddr if serverName != "" { logUrl = "https://" + serverName } - if DebugLogging { - log.Printf("[Transparent] Direct tunnel for %s (SNI: %s)", serverAddr, serverName) - } - LogProxyAction(logUrl, user, ProxyActionSSLDirect) - - // Create fresh connection with clientHello for tunneling - freshConn := &prependConn{ - Conn: realConn, - buf: clientHello, - offset: 0, + if shouldMitm { + if DebugLogging { + log.Printf("[Transparent] Performing SSL Bump for %s (SNI: %s)", serverAddr, serverName) + } + SSLBump(realConn, serverAddr, user, "", nil, passthru, l.ProxyHandler.Iproxy, clientHello) + } else { + if DebugLogging { + log.Printf("[Transparent] Direct tunnel for %s (SNI: %s)", serverAddr, serverName) + } + LogProxyAction(logUrl, user, ProxyActionSSLDirect) + freshConn := &prependConn{ + Conn: realConn, + buf: clientHello, + offset: 0, + } + ConnectDirect(freshConn, serverAddr, nil, passthru) } - ConnectDirect(freshConn, serverAddr, nil, passthru) } type prependConn struct { diff --git a/main.go b/main.go index cd8d75f..8d74abd 100644 --- a/main.go +++ b/main.go @@ -29,7 +29,7 @@ var GSPROXYPORT = "10413" var GSWEBADMINPORT = "10786" var GSBASEDIR = "" var Baseendpointv2 = "https://www.gatesentryfilter.com/api/" -var GATESENTRY_VERSION = "1.20.5" +var GATESENTRY_VERSION = "1.20.6" var GS_BOUND_ADDRESS = ":" var R *application.GSRuntime