From f9e4604f9dcbf340a1d09e1e546f9ed2bf559653 Mon Sep 17 00:00:00 2001 From: Chris McClymont Date: Sun, 29 Mar 2015 17:55:49 +1100 Subject: [PATCH 1/4] Added VNC standard DES authentication --- client_auth_test.go | 8 ++++++ client_auth_vnc.go | 59 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) create mode 100644 client_auth_vnc.go diff --git a/client_auth_test.go b/client_auth_test.go index 746b46f..9f62cae 100644 --- a/client_auth_test.go +++ b/client_auth_test.go @@ -9,3 +9,11 @@ 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") + } +} diff --git a/client_auth_vnc.go b/client_auth_vnc.go new file mode 100644 index 0000000..1fcafd0 --- /dev/null +++ b/client_auth_vnc.go @@ -0,0 +1,59 @@ +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 +} + +func (auth *ClientAuthVNC) Handshake(conn net.Conn) error { + // Read challenge block + var challenge [16]byte + if err := binary.Read(conn, binary.BigEndian, &challenge); err != nil { + return err + } + + // 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] = reverse(key[i]) + } + cipher, err := des.NewCipher(key) + if err != nil { + return err + } + + // Encrypt the challenge low 8 bytes then high 8 bytes + challengeLow := challenge[0:8] + challengeHigh := challenge[8:16] + cipher.Encrypt(challengeLow, challengeLow) + cipher.Encrypt(challengeHigh, challengeHigh) + + // Send the encrypted challenge back to server + err = binary.Write(conn, binary.BigEndian, challenge) + if err != nil { + return err + } + + return nil +} + +func reverse(x byte) byte { + x = (x&0x55)<<1 | (x&0xAA)>>1 + x = (x&0x33)<<2 | (x&0xCC)>>2 + x = (x&0x0F)<<4 | (x&0xF0)>>4 + return x +} From 5f6ad112e67b77628364e266463efa5c17b92702 Mon Sep 17 00:00:00 2001 From: Chris McClymont Date: Mon, 30 Mar 2015 22:01:26 +1100 Subject: [PATCH 2/4] Inline-d the byte reversal logic of VNC auth --- client_auth_vnc.go | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client_auth_vnc.go b/client_auth_vnc.go index 1fcafd0..99d7c1e 100644 --- a/client_auth_vnc.go +++ b/client_auth_vnc.go @@ -29,8 +29,11 @@ func (auth *ClientAuthVNC) Handshake(conn net.Conn) error { // 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] = reverse(key[i]) + 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 } + cipher, err := des.NewCipher(key) if err != nil { return err @@ -50,10 +53,3 @@ func (auth *ClientAuthVNC) Handshake(conn net.Conn) error { return nil } - -func reverse(x byte) byte { - x = (x&0x55)<<1 | (x&0xAA)>>1 - x = (x&0x33)<<2 | (x&0xCC)>>2 - x = (x&0x0F)<<4 | (x&0xF0)>>4 - return x -} From c13ada223f7b1a1422a453d951d9ec39c59936fe Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Sun, 3 May 2015 13:38:46 +0200 Subject: [PATCH 3/4] Use an empty struct{} instead of byte when implementing an interface. --- client_auth.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client_auth.go b/client_auth.go index c88f911..91d73e7 100644 --- a/client_auth.go +++ b/client_auth.go @@ -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 From a126d2daa711eaf3be191dc96eaf093b159fa225 Mon Sep 17 00:00:00 2001 From: Kate Ward Date: Sun, 3 May 2015 13:39:17 +0200 Subject: [PATCH 4/4] Split out encoding of client challenge, and add unit tests. --- client_auth_test.go | 43 ++++++++++++++++++++++++++++++++++++++++++- client_auth_vnc.go | 36 ++++++++++++++++++++++++------------ 2 files changed, 66 insertions(+), 13 deletions(-) diff --git a/client_auth_test.go b/client_auth_test.go index 9f62cae..78a7935 100644 --- a/client_auth_test.go +++ b/client_auth_test.go @@ -1,6 +1,10 @@ package vnc -import "testing" +import ( + "encoding/hex" + "strings" + "testing" +) func TestClientAuthNone_Impl(t *testing.T) { var raw interface{} @@ -17,3 +21,40 @@ func TestClientAuthVNC_Impl(t *testing.T) { 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) + } + } +} diff --git a/client_auth_vnc.go b/client_auth_vnc.go index 99d7c1e..df81aa8 100644 --- a/client_auth_vnc.go +++ b/client_auth_vnc.go @@ -1,3 +1,9 @@ +/* +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 ( @@ -15,13 +21,27 @@ 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 [16]byte + 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) @@ -34,21 +54,13 @@ func (auth *ClientAuthVNC) Handshake(conn net.Conn) error { 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 } - - // Encrypt the challenge low 8 bytes then high 8 bytes - challengeLow := challenge[0:8] - challengeHigh := challenge[8:16] - cipher.Encrypt(challengeLow, challengeLow) - cipher.Encrypt(challengeHigh, challengeHigh) - - // Send the encrypted challenge back to server - err = binary.Write(conn, binary.BigEndian, challenge) - 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