Stripping SNI information from TLS WebSocket conne

2019-08-01 12:27发布

I find myself needing to set up a WebSocket connection in a hostile environment in which a firewall sniffs SNI information from TLS which I'd rather it didn't. In my particular case, the WebSocket server does not use SNI for request handling, so as such, the SNI part of the handshake could be safely removed.

My question then becomes: In the golang.org WebSocket package, golang.org/x/net/websocket, what is the simplest way to strip SNI information while retaining validation of the provided chain?

The best I have been able to come up with is to simply replace the hostname of the URL to be dialled with its corresponding IP. This causes crypto/tls to never add the problematic SNI information, but, in the solution I was able to come up with, a custom validator ends up having to be provided to validate the chain:

func dial(url string, origin string) (*websocket.Conn, error) {
    // Use system resolver to get IP of host
    hostRegExp := regexp.MustCompile("//([^/]+)/")
    host := hostRegExp.FindStringSubmatch(url)[1]
    addrs, err := net.LookupHost(host)
    if err != nil {
        return fmt.Errorf("Could not resolve address of %s: %v", host, err)
    }
    ip := addrs[0]

    // Replace the hostname in the given URL with its IP instead
    newURL := strings.Replace(url, host, ip, 1)
    config, _ := websocket.NewConfig(newURL, origin)

    // As we have removed the hostname, the Go TLS package will not know what to
    // validate the certificate DNS names against, so we have to provide a custom
    // verifier based on the hostname we threw away.
    config.TlsConfig = &tls.Config{
        InsecureSkipVerify:    true,
        VerifyPeerCertificate: verifier(host),
    }
    return websocket.DialConfig(config)
}

func verifier(host string) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
    return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
        // For simplicity, let us only consider the case in which the first certificate is the one
        // to validate, and in which it is signed directly by a CA, with no parsing of
        // intermediate certificates required.
        opts := x509.VerifyOptions{
            DNSName: host,
        }
        rawCert := rawCerts[0]
        cert, err := x509.ParseCertificate(rawCert)
        if err != nil {
            return err
        }
        _, err = cert.Verify(opts)
        return err
    }
}

This totally works but seems rather clunky. Is there a simpler approach? (Ideally one that is not specific to WebSocket applications but works for TLS in general; the exact same idea as above could be applied to HTTPS.)

0条回答
登录 后发表回答