Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
91 changes: 49 additions & 42 deletions gatesentryproxy/ssl.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package gatesentryproxy

import (
"context"
"io"
"log"
"net"
Expand All @@ -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"
)

Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -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")
}
Expand All @@ -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)
Expand Down Expand Up @@ -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,
Expand All @@ -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)
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion gatesentryproxy/transparent.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
75 changes: 38 additions & 37 deletions gatesentryproxy/transparent_listener.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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,
Expand All @@ -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
Expand All @@ -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 != "" {
Expand All @@ -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 {
Expand Down
2 changes: 1 addition & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Loading