package server import ( "bytes" "crypto/tls" "net" "testing" "time" ) // TestReadClientHello captures a real ClientHello produced by crypto/tls and // checks the parser and the byte-exact replay capture. func TestReadClientHello(t *testing.T) { client, serverConn := net.Pipe() defer client.Close() var written bytes.Buffer go func() { // The client writes its ClientHello and then blocks waiting for the // server; we only need the write side. tlsClient := tls.Client(captureConn{client, &written}, &tls.Config{ ServerName: "api.demo.opentunnel.xyz", NextProtos: []string{"h2", "http/1.1"}, InsecureSkipVerify: true, }) tlsClient.Handshake() // fails: nobody answers }() serverConn.SetReadDeadline(time.Now().Add(5 * time.Second)) raw, sni, alpn, err := readClientHello(serverConn) if err != nil { t.Fatalf("readClientHello: %v", err) } if sni != "api.demo.opentunnel.xyz" { t.Errorf("sni = %q", sni) } if len(alpn) != 2 || alpn[0] != "h2" || alpn[1] != "http/1.1" { t.Errorf("alpn = %v", alpn) } if !bytes.HasPrefix(written.Bytes(), raw) { t.Errorf("captured %d bytes are not a prefix of the %d written", len(raw), written.Len()) } } // captureConn records everything written to it. type captureConn struct { net.Conn w *bytes.Buffer } func (c captureConn) Write(p []byte) (int, error) { c.w.Write(p) return c.Conn.Write(p) } func TestReadClientHelloGarbage(t *testing.T) { client, serverConn := net.Pipe() go func() { client.Write([]byte("not tls at all")) client.Close() }() serverConn.SetReadDeadline(time.Now().Add(2 * time.Second)) if _, _, _, err := readClientHello(serverConn); err == nil { t.Error("expected an error for non-TLS bytes") } } func TestReadClientHelloTrickle(t *testing.T) { // A ClientHello trickling in byte by byte still parses. client, serverConn := net.Pipe() var written bytes.Buffer go func() { tlsClient := tls.Client(captureConn{client, &written}, &tls.Config{ ServerName: "demo.opentunnel.xyz", InsecureSkipVerify: true, }) tlsClient.Handshake() }() serverConn.SetReadDeadline(time.Now().Add(5 * time.Second)) slow := &slowConn{Conn: serverConn} // one-byte reads, a trickling sender raw, sni, _, err := readClientHello(slow) if err != nil { t.Fatalf("readClientHello: %v", err) } if sni != "demo.opentunnel.xyz" { t.Errorf("sni = %q", sni) } if !bytes.HasPrefix(written.Bytes(), raw) { t.Error("capture mismatch") } } type slowConn struct{ net.Conn } func (c *slowConn) Read(p []byte) (int, error) { return c.Conn.Read(p[:1]) }