Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

x/net/websocket: sockets served with http.ListenAndServeTLS have SSL handshake problems with most clients #11602

Closed
integrii opened this issue Jul 5, 2015 · 7 comments

Comments

@integrii
Copy link

integrii commented Jul 5, 2015

I originally posted this here but was asked to make a new issue.

Websockets served over ListenAndServeTLS in the example program below fail to initiate SSL connections on nearly every client I have tried. Regular non-TLS connections work fine, and websites served also seem to be working fine over TLS. This is occurring on go version 1.4.2 x64.

Safari (version 9.0) works just great, but Firefox (version 39.0) and Chrome do not (version 43 on OSX). It also appears that javax.websocket gets a 403.

I get this error in Chrome:

WebSocket connection to 'wss://my.domain.com:8000/copyTest' failed: Error during WebSocket handshake: Unexpected response code: 403

Nothing is produced at the go terminal, unless i hit it with javax.websocket - which produces this:

http: TLS handshake error from 127.0.0.1:44249: tls: first record does not look like a TLS handshake

In Safari I get the expected result in the javascript console:

[Log] sent data:test (testClient.html, line 31)
[Log] got data:test (testClient.html, line 19)

in Firefox I get a 403 similar to Chrome and the console shows this:

GET 
https://my.domain.com:8000/copyTest [HTTP/1.1 403 Forbidden 3ms]
"got error:" error { target: WebSocket, isTrusted: true, currentTarget: WebSocket, eventPhase: 2, bubbles: false, cancelable: false, defaultPrevented: false, timeStamp: 1435967773819792, originalTarget: WebSocket, explicitOriginalTarget: WebSocket, NONE: 0 } testClient.html:22:5
"got close:" close { target: WebSocket, isTrusted: true, wasClean: false, code: 1006, reason: "", currentTarget: WebSocket, eventPhase: 2, bubbles: false, cancelable: false, defaultPrevented: false, timeStamp: 1435967773821310 } testClient.html:25:5

Here is some go code to serve TLS websockets. sub in your own cert.key and cert.pem files.

package main

import (
    "golang.org/x/net/websocket"
    "io"
    "log"
    "net/http"
)

func copyTest(ws *websocket.Conn) {
    io.Copy(ws, ws)
}

func main() {

    // setup http handler
    http.Handle("/copyTest", websocket.Handler(copyTest))

    // start secure websocket server
    log.Println("Listening for secure websocket connections on localhost port 8000...")
    if err := http.ListenAndServeTLS(":8000", "cert.pem", "cert.key", nil); err != nil {
        panic("ListenAndServeTLS Error: " + err.Error())
    }
}

Here is a javascript client. Sub in the proper domain name for your cert instead of localhost and connect using it. Results will be written to the javascript console.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>Websocket connector</title>
        <script src="http://code.jquery.com/jquery-compat-git.js">
        </script>
    </head>
    <body>
            <input id="name" type="text" />
            <button id="sendBtn">send</a>

        <script>
            var ws = new WebSocket("wss://localhost:8000/copyTest");

            ws.onmessage = function(e) {
                console.log("got data:" + e.data);
            };
            ws.onerror = function(e) {
                console.log("got error:", e);
            };
            ws.onclose = function(e) {
                console.log("got close:", e);
            };

            $('#sendBtn').click(function(){
                var data = $('#name').val();
                ws.send(data);
                console.log("sent data:" + data);
            });
        </script>
    </body>
</html>
@integrii integrii changed the title Websockets server with http.ListenAndServeTLS has SSL handshake problems with most clients Websockets served with http.ListenAndServeTLS has SSL handshake problems with most clients Jul 5, 2015
@integrii integrii changed the title Websockets served with http.ListenAndServeTLS has SSL handshake problems with most clients Websockets served with http.ListenAndServeTLS have SSL handshake problems with most clients Jul 5, 2015
@adg adg changed the title Websockets served with http.ListenAndServeTLS have SSL handshake problems with most clients x/net/websocket: sockets served with http.ListenAndServeTLS have SSL handshake problems with most clients Jul 5, 2015
@integrii
Copy link
Author

integrii commented Jul 6, 2015

It actually turns out that this isn't an SSL problem at all. The same issue occurs if you just connect with Chrome and ListenAndServe()...

So websocket servers written in go are currently incompatible with most clients?

I re-wrote the sample program to ignore TLS and Chrome still has a Forbidden 403:

package main

import (
        "golang.org/x/net/websocket"
        "io"
        "log"
        "net/http"
)

func copyTest(ws *websocket.Conn) {
        io.Copy(ws, ws)
}

func main() {

        // setup http handler
        http.Handle("/test", websocket.Handler(copyTest))

        // start secure websocket server
        log.Println("Listening for secure websocket connections on localhost port 8000...")

        if err := http.ListenAndServe(":8000", nil); err != nil {
                panic("ListenAndServe Error: " + err.Error())
        }
}

I also updated the client to not use SSL like so:

            var ws = new WebSocket("ws://localhost:8000/test");

@integrii
Copy link
Author

integrii commented Jul 6, 2015

Some more analysis:

Here is a packet capture of the headers coming from Safari and Chrome. Remeber that the Safari request works, but Chrome does not. Perhaps the origin header can not be null?

Safari:

GET /ws HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: localhost:8000
Origin: file://
Pragma: no-cache
Cache-Control: no-cache
Sec-WebSocket-Key: CmWqwJrG/A7aYIsuadq4JA==
Sec-WebSocket-Version: 13
Sec-WebSocket-Extensions: x-webkit-deflate-frame
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11) AppleWebKit/601.1.35 (KHTML, like Gecko) Version/9.0 Safari/601.1

Chrome:

GET /ws HTTP/1.1
Host: localhost:8000
Connection: Upgrade
Pragma: no-cache
Cache-Control: no-cache
Upgrade: websocket
Origin: null
Sec-WebSocket-Version: 13
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/43.0.2357.130 Safari/537.36
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Sec-WebSocket-Key: vB6nVTFyG0W+Yl7jDMPQag==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

@integrii
Copy link
Author

integrii commented Jul 6, 2015

Here are the headers that are different between the two:

Safari:

Origin: file://
Sec-WebSocket-Extensions: x-webkit-deflate-frame

Chrome:

Pragma: no-cache
Accept-Encoding: gzip, deflate, sdch
Accept-Language: en-US,en;q=0.8
Origin: null
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits

@integrii
Copy link
Author

integrii commented Jul 6, 2015

For anyone else stuck on this... I have rewritten using github.com/gorilla/websocket and disabled the CheckOrigin function.

This program works with Chrome and Safari.

package main

import (
    "github.com/gorilla/websocket"
    "io"
    "log"
    "net/http"
)

// Specify options for gorilla websocket
var upgrader = websocket.Upgrader{
    ReadBufferSize:  1024,
    WriteBufferSize: 1024,
    CheckOrigin: func(r *http.Request) bool {
        return true
    },
}

// Create a handler function that calls an upgrade to websocket session using our upgrader parameters
func handler(w http.ResponseWriter, r *http.Request) {
    conn, err := upgrader.Upgrade(w, r, nil)
    if err != nil {
        log.Println(err)
        return
    }

    for {
        messageType, r, err := conn.NextReader()
        if err != nil {
            return
        }
        w, err := conn.NextWriter(messageType)
        if err != nil {
            //return err
        }
        if _, err := io.Copy(w, r); err != nil {
            //return err
        }
        if err := w.Close(); err != nil {
            //return err
        }
    }
}

func main() {

    http.HandleFunc("/", handler)
    log.Printf("Listening for SECURE websocket connections on port 8000.")
    log.Fatal(http.ListenAndServeTLS(":8000", "cert.pem", "cert.key", nil))

}

@mikioh
Copy link
Contributor

mikioh commented Jul 7, 2015

Looks like it's #10102?

@integrii
Copy link
Author

integrii commented Jul 7, 2015

I think it is. For now, I am using gorilla websockets which is working okay. However, Gorilla requires more wasteful readers and writers than the x/net/websocket package does (and a lot more confusing code). I would like to see this added so I can switch back!

Thanks for reading.

@integrii integrii closed this as completed Jul 7, 2015
@mikioh mikioh modified the milestone: Unreleased Jul 30, 2015
@minaandrawos
Copy link

If the issue is with checking the origin, can't we do something like below in case of /x/net/wesocket?:
`func handler(w http.ResponseWriter, r *http.Request) {

//....
checkorigin := func(_websocket.Config,_http.Request) (err error) {return nil}
s := websocket.Server{Handler:websocket.Handler(copyTest), Handshake:checkorigin}
s.ServeHTTP(w,r)
}
`

I didn't test the code but I think it's one way to write our own logic regarding how the handshake should behave.

@golang golang locked and limited conversation to collaborators Jun 19, 2017
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests

4 participants