Skip to content
This repository was archived by the owner on Jul 22, 2024. It is now read-only.
Open
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
2 changes: 1 addition & 1 deletion client_auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type ClientAuth interface {
}

// ClientAuthNone is the "none" authentication. See 7.1.2
type ClientAuthNone byte
type ClientAuthNone struct{}

func (*ClientAuthNone) SecurityType() uint8 {
return 1
Expand Down
51 changes: 50 additions & 1 deletion client_auth_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package vnc

import "testing"
import (
"encoding/hex"
"strings"
"testing"
)

func TestClientAuthNone_Impl(t *testing.T) {
var raw interface{}
Expand All @@ -9,3 +13,48 @@ func TestClientAuthNone_Impl(t *testing.T) {
t.Fatal("ClientAuthNone doesn't implement ClientAuth")
}
}

func TestClientAuthVNC_Impl(t *testing.T) {
var raw interface{}
raw = new(ClientAuthVNC)
if _, ok := raw.(ClientAuth); !ok {
t.Fatal("ClientAuthVNC doesn't implement ClientAuth")
}
}

// wiresharkToChallenge converts VNC authentication challenge and response
// values captured with Wireshark (https://www.wireshark.org) into usable byte
// streams.
func wiresharkToChallenge(h string) [challengeSize]byte {
var c [challengeSize]byte
r := strings.NewReplacer(":", "")
b, err := hex.DecodeString(r.Replace(h))
if err != nil {
return c
}
copy(c[:], b)
return c
}

func TestClientAuthVNCEncode(t *testing.T) {
tests := []struct {
pw string
challenge, response string
}{
{".", "7f:e2:e1:3d:a4:ae:10:9c:54:c5:5f:52:74:aa:db:31", "1d:86:92:71:1f:00:24:35:02:d3:91:ef:e9:bc:c5:d5"},
{"12345678", "13:8e:a4:2e:0e:66:f3:ad:2d:f3:08:c3:04:cd:c4:2a", "5b:e1:56:fa:49:49:ef:56:d3:f8:44:97:73:27:95:9f"},
{"abc123", "c6:30:45:d2:57:9e:e7:f2:f9:0c:62:3e:52:40:86:c6", "a3:63:59:e4:28:c8:7f:b3:45:2c:d7:e0:ca:d6:70:3e"},
}

for _, tt := range tests {
challenge := wiresharkToChallenge(tt.challenge)
a := ClientAuthVNC{tt.pw}
if err := a.encode(&challenge); err != nil {
t.Errorf("ClientAuthVNC.encode() failed: key=%v, err=%v", tt.pw, err)
}
response := wiresharkToChallenge(tt.response)
if challenge != response {
t.Errorf("ClientAuthVNC.encode() failed: key=%v got=%v, want=%v", tt.pw, challenge, response)
}
}
}
67 changes: 67 additions & 0 deletions client_auth_vnc.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*
ClientAuthVNC implements the ClientAuth interface to provide support for
VNC Authentication.

See http://tools.ietf.org/html/rfc6143#section-7.2.2 for more info.
*/
package vnc

import (
"crypto/des"
"encoding/binary"
"net"
)

// ClientAuthVNC is the standard password authentication
type ClientAuthVNC struct {
Password string
}

func (*ClientAuthVNC) SecurityType() uint8 {
return 2
}

// 7.2.2. VNC Authentication uses a 16-byte challenge.
const challengeSize = 16

func (auth *ClientAuthVNC) Handshake(conn net.Conn) error {
// Read challenge block
var challenge [challengeSize]byte
if err := binary.Read(conn, binary.BigEndian, &challenge); err != nil {
return err
}

auth.encode(&challenge)

// Send the encrypted challenge back to server
if err := binary.Write(conn, binary.BigEndian, challenge); err != nil {
return err
}

return nil
}

func (auth *ClientAuthVNC) encode(c *[challengeSize]byte) error {
// Copy password string to 8 byte 0-padded slice
key := make([]byte, 8)
copy(key, auth.Password)

// Each byte of the password needs to be reversed. This is a
// non RFC-documented behaviour of VNC clients and servers
for i := range key {
key[i] = (key[i]&0x55)<<1 | (key[i]&0xAA)>>1 // Swap adjacent bits
key[i] = (key[i]&0x33)<<2 | (key[i]&0xCC)>>2 // Swap adjacent pairs
key[i] = (key[i]&0x0F)<<4 | (key[i]&0xF0)>>4 // Swap the 2 halves
}

// Encrypt challenge with key.
cipher, err := des.NewCipher(key)
if err != nil {
return err
}
for i := 0; i < challengeSize; i += cipher.BlockSize() {
cipher.Encrypt(c[i:i+cipher.BlockSize()], c[i:i+cipher.BlockSize()])
}

return nil
}