arblant
firefox-vpn-client / core / internal / proxy / socks_auth_test.go
socks_auth_test.go92 lines · 2.8 KB
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
package proxy

import (
	"errors"
	"io"
	"net"
	"testing"
	"time"
)

// runHandshake drives a client script against handshakeSOCKS5Auth.
func runHandshake(t *testing.T, auth *socksCredentials, client func(c net.Conn)) (string, error) {
	t.Helper()
	server, clientConn := net.Pipe()
	defer server.Close()
	defer clientConn.Close()
	_ = server.SetDeadline(time.Now().Add(3 * time.Second))
	_ = clientConn.SetDeadline(time.Now().Add(3 * time.Second))
	go client(clientConn)
	target, _, err := handshakeSOCKS5Auth(server, auth)
	return target, err
}

func expectBytes(t *testing.T, c net.Conn, want []byte) {
	t.Helper()
	got := make([]byte, len(want))
	if _, err := io.ReadFull(c, got); err != nil {
		t.Errorf("reading %v: %v", want, err)
		return
	}
	for i := range want {
		if got[i] != want[i] {
			t.Errorf("got %v, want %v", got, want)
			return
		}
	}
}

func userPassMessage(user, pass string) []byte {
	msg := []byte{socksUserPassVer, byte(len(user))}
	msg = append(msg, user...)
	msg = append(msg, byte(len(pass)))
	return append(msg, pass...)
}

var connectExample = []byte{socksVersion5, socksCmdConnect, 0x00, socksAtypDomain, 11, 'e', 'x', 'a', 'm', 'p', 'l', 'e', '.', 'c', 'o', 'm', 0x01, 0xbb}

func TestSOCKSUsernamePassword(t *testing.T) {
	auth := SOCKSCredentials("alice", "s3cret")

	target, err := runHandshake(t, auth, func(c net.Conn) {
		_, _ = c.Write([]byte{socksVersion5, 2, socksAuthNoAuth, socksAuthUserPass})
		expectBytes(t, c, []byte{socksVersion5, socksAuthUserPass})
		_, _ = c.Write(userPassMessage("alice", "s3cret"))
		expectBytes(t, c, []byte{socksUserPassVer, 0x00})
		_, _ = c.Write(connectExample)
	})
	if err != nil || target != "example.com:443" {
		t.Fatalf("valid login: %q %v", target, err)
	}

	_, err = runHandshake(t, auth, func(c net.Conn) {
		_, _ = c.Write([]byte{socksVersion5, 1, socksAuthUserPass})
		expectBytes(t, c, []byte{socksVersion5, socksAuthUserPass})
		_, _ = c.Write(userPassMessage("alice", "wrong"))
		expectBytes(t, c, []byte{socksUserPassVer, 0x01})
	})
	if !errors.Is(err, errSOCKSAuth) {
		t.Fatalf("wrong password: %v", err)
	}

	_, err = runHandshake(t, auth, func(c net.Conn) {
		_, _ = c.Write([]byte{socksVersion5, 1, socksAuthNoAuth})
		expectBytes(t, c, []byte{socksVersion5, socksAuthNoAccept})
	})
	if !errors.Is(err, errSOCKSAuth) {
		t.Fatalf("a client without credentials must be refused: %v", err)
	}

	// Without configured credentials nothing changes.
	target, err = runHandshake(t, nil, func(c net.Conn) {
		_, _ = c.Write([]byte{socksVersion5, 1, socksAuthNoAuth})
		expectBytes(t, c, []byte{socksVersion5, socksAuthNoAuth})
		_, _ = c.Write(connectExample)
	})
	if err != nil || target != "example.com:443" {
		t.Fatalf("no auth: %q %v", target, err)
	}
	if SOCKSCredentials("", "") != nil {
		t.Fatal("empty settings mean no authentication")
	}
}