diff --git a/internal/server/TLS.go b/internal/server/TLS.go index dcb7b63..cc8aa10 100644 --- a/internal/server/TLS.go +++ b/internal/server/TLS.go @@ -2,11 +2,13 @@ package server import ( "bytes" + "crypto" "crypto/rand" "encoding/binary" "encoding/hex" "errors" "fmt" + "github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/util" "net" @@ -222,6 +224,30 @@ var ErrNotCloak = errors.New("TLS but non-Cloak ClientHello") var ErrReplay = errors.New("duplicate random") var ErrBadProxyMethod = errors.New("invalid proxy method") +func unmarshalClientHello(ch *ClientHello, staticPv crypto.PrivateKey) (ai authenticationInfo, err error) { + ephPub, ok := ecdh.Unmarshal(ch.random) + if !ok { + err = ErrInvalidPubKey + return + } + + ai.nonce = ch.random[:12] + + ai.sharedSecret = ecdh.GenerateSharedSecret(staticPv, ephPub) + var keyShare []byte + keyShare, err = parseKeyShare(ch.extensions[[2]byte{0x00, 0x33}]) + if err != nil { + return + } + + ai.ciphertextWithTag = append(ch.sessionId, keyShare...) + if len(ai.ciphertextWithTag) != 64 { + err = fmt.Errorf("%v: %v", ErrCiphertextLength, len(ai.ciphertextWithTag)) + return + } + return +} + // PrepareConnection checks if the first packet of data is ClientHello, and checks if it was from a Cloak client // if it is from a Cloak client, it returns the ClientInfo with the decrypted fields. It doesn't check if the user // is authorised. It also returns a finisher callback function to be called when the caller wishes to proceed with @@ -239,8 +265,12 @@ func PrepareConnection(firstPacket []byte, sta *State, conn net.Conn) (info Clie return } - var sharedSecret []byte - info, sharedSecret, err = touchStone(ch, sta.staticPv, sta.Now) + var ai authenticationInfo + ai, err = unmarshalClientHello(ch, sta.staticPv) + if err != nil { + return + } + info, err = touchStone(ai, sta.Now) if err != nil { log.Debug(err) err = ErrNotCloak @@ -252,7 +282,7 @@ func PrepareConnection(firstPacket []byte, sta *State, conn net.Conn) (info Clie } finisher = func(sessionKey []byte) error { - reply, err := composeReply(ch, sharedSecret, sessionKey) + reply, err := composeReply(ch, ai.sharedSecret, sessionKey) if err != nil { return err } diff --git a/internal/server/auth.go b/internal/server/auth.go index 1afa14f..a7f15b4 100644 --- a/internal/server/auth.go +++ b/internal/server/auth.go @@ -2,11 +2,9 @@ package server import ( "bytes" - "crypto" "encoding/binary" "errors" "fmt" - "github.com/cbeuw/Cloak/internal/ecdh" "github.com/cbeuw/Cloak/internal/util" "time" ) @@ -19,6 +17,12 @@ type ClientInfo struct { Unordered bool } +type authenticationInfo struct { + sharedSecret []byte + nonce []byte + ciphertextWithTag []byte +} + const ( UNORDERED_FLAG = 0x01 // 0000 0001 ) @@ -29,28 +33,10 @@ var ErrTimestampOutOfWindow = errors.New("timestamp is outside of the accepting // touchStone checks if a ClientHello came from a Cloak client by checking and decrypting the fields Cloak hides data in // It returns the ClientInfo, but it doesn't check if the UID is authorised -func touchStone(ch *ClientHello, staticPv crypto.PrivateKey, now func() time.Time) (info ClientInfo, sharedSecret []byte, err error) { - ephPub, ok := ecdh.Unmarshal(ch.random) - if !ok { - err = ErrInvalidPubKey - return - } - - sharedSecret = ecdh.GenerateSharedSecret(staticPv, ephPub) - var keyShare []byte - keyShare, err = parseKeyShare(ch.extensions[[2]byte{0x00, 0x33}]) - if err != nil { - return - } - - ciphertext := append(ch.sessionId, keyShare...) - if len(ciphertext) != 64 { - err = fmt.Errorf("%v: %v", ErrCiphertextLength, len(ciphertext)) - return - } +func touchStone(ai authenticationInfo, now func() time.Time) (info ClientInfo, err error) { var plaintext []byte - plaintext, err = util.AESGCMDecrypt(ch.random[0:12], sharedSecret, ciphertext) + plaintext, err = util.AESGCMDecrypt(ai.nonce, ai.sharedSecret, ai.ciphertextWithTag) if err != nil { return } diff --git a/internal/server/auth_test.go b/internal/server/auth_test.go index 2833a45..cd5900a 100644 --- a/internal/server/auth_test.go +++ b/internal/server/auth_test.go @@ -16,9 +16,14 @@ func TestTouchStone(t *testing.T) { t.Run("correct time", func(t *testing.T) { chBytes, _ := hex.DecodeString("1603010200010001fc0303ac530b5778469dbbc3f9a83c6ac35b63aa6a70c2014026ade30f2faf0266f0242068424f320bcad49b4315a761f9f6dec32b0a403c2d8c0ab337608a694c6e411c0024130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a0100018f00000011000f00000c7777772e62696e672e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000033006b0069001d00204655c2c83aaed1db2e89ed17d671fcdc76dc96e36bde8840022f1bda2f31019600170041543af1f8d28b37d984073f40e8361613da502f16e4039f00656f427de0f66480b2e77e3e552e126bb0cc097168f6e5454c7f9501126a2377fb40151f6cfc007e0e002b0009080304030303020301000d0018001604030503060308040805080604010501060102030201002d00020101001c00024001001500920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") ch, _ := parseClientHello(chBytes) + ai, err := unmarshalClientHello(ch, staticPv) + if err != nil { + t.Errorf("expecting no error, got %v", err) + return + } nineSixSix := func() time.Time { return time.Unix(1565998966, 0) } - cinfo, _, err := touchStone(ch, staticPv, nineSixSix) + cinfo, err := touchStone(ai, nineSixSix) if err != nil { t.Errorf("expecting no error, got %v", err) return @@ -30,9 +35,14 @@ func TestTouchStone(t *testing.T) { t.Run("over interval", func(t *testing.T) { chBytes, _ := hex.DecodeString("1603010200010001fc0303ac530b5778469dbbc3f9a83c6ac35b63aa6a70c2014026ade30f2faf0266f0242068424f320bcad49b4315a761f9f6dec32b0a403c2d8c0ab337608a694c6e411c0024130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a0100018f00000011000f00000c7777772e62696e672e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000033006b0069001d00204655c2c83aaed1db2e89ed17d671fcdc76dc96e36bde8840022f1bda2f31019600170041543af1f8d28b37d984073f40e8361613da502f16e4039f00656f427de0f66480b2e77e3e552e126bb0cc097168f6e5454c7f9501126a2377fb40151f6cfc007e0e002b0009080304030303020301000d0018001604030503060308040805080604010501060102030201002d00020101001c00024001001500920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") ch, _ := parseClientHello(chBytes) + ai, err := unmarshalClientHello(ch, staticPv) + if err != nil { + t.Errorf("expecting no error, got %v", err) + return + } nineSixSixOver := func() time.Time { return time.Unix(1565998966, 0).Add(TIMESTAMP_TOLERANCE + 10) } - _, _, err := touchStone(ch, staticPv, nineSixSixOver) + _, err = touchStone(ai, nineSixSixOver) if err == nil { t.Errorf("expecting %v, got %v", ErrTimestampOutOfWindow, err) return @@ -41,9 +51,14 @@ func TestTouchStone(t *testing.T) { t.Run("under interval", func(t *testing.T) { chBytes, _ := hex.DecodeString("1603010200010001fc0303ac530b5778469dbbc3f9a83c6ac35b63aa6a70c2014026ade30f2faf0266f0242068424f320bcad49b4315a761f9f6dec32b0a403c2d8c0ab337608a694c6e411c0024130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a0100018f00000011000f00000c7777772e62696e672e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000033006b0069001d00204655c2c83aaed1db2e89ed17d671fcdc76dc96e36bde8840022f1bda2f31019600170041543af1f8d28b37d984073f40e8361613da502f16e4039f00656f427de0f66480b2e77e3e552e126bb0cc097168f6e5454c7f9501126a2377fb40151f6cfc007e0e002b0009080304030303020301000d0018001604030503060308040805080604010501060102030201002d00020101001c00024001001500920000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") ch, _ := parseClientHello(chBytes) + ai, err := unmarshalClientHello(ch, staticPv) + if err != nil { + t.Errorf("expecting no error, got %v", err) + return + } nineSixSixUnder := func() time.Time { return time.Unix(1565998966, 0).Add(TIMESTAMP_TOLERANCE - 10) } - _, _, err := touchStone(ch, staticPv, nineSixSixUnder) + _, err = touchStone(ai, nineSixSixUnder) if err == nil { t.Errorf("expecting %v, got %v", ErrTimestampOutOfWindow, err) return @@ -52,9 +67,14 @@ func TestTouchStone(t *testing.T) { t.Run("not cloak psk", func(t *testing.T) { chBytes, _ := hex.DecodeString("1603010246010002420303794ae79c6db7a31e67e2ce91b8afcb82995ae79ad1d0dc885f933e4193bf95cd208abd7a70f3b82cc31c02f1c2b94ba74d5222a66695a5cf92a366421d7f5eb9530022fafa130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a010001d75a5a00000000001e001c0000196c68332e676f6f676c6575736572636f6e74656e742e636f6d00170000ff01000100000a000a0008baba001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d00140012040308040401050308050501080606010201001200000033002b0029baba000100001d002074bfe93336c364b43cf0879d997b2e11dc97068b86fc90174e0f2bcea1d4ed1c002d00020101002b000b0ababa0304030303020301001b00030200029a9a0001000029010500e000da00d1f6c0918f865390ae3ca33c77f61a1974cb4533456071b214ec018d17dc22845f2f72cf1dba48f9cdc0758803002dda9b964fad5522e82442af7cbbe242241e39233386f2383bce3ced8e16b1ae3f0ef52a706f58e1e6a1bca0cd3b3a2a4c4cb738770b01b56bf3e73c472bf4fb238cab510aa78f8427a3ca99f741aa433f548be460705f43a3abe878cec6ee3158c129406910b93e798e8a7aaffc2e7ff7b8fd872778d3687a0beaa1452fe7ec418070d537344b64d09f6edd053346ff9c9678eef6b8886882aba81d4be11d9df653de35659f93a22ac39399e3ba400021204e22b73261693967a9216fe4a3b004571c53f316309e76671a18d78931b5b072") ch, _ := parseClientHello(chBytes) + ai, err := unmarshalClientHello(ch, staticPv) + if err != nil { + t.Errorf("expecting no error, got %v", err) + return + } fiveOSix := func() time.Time { return time.Unix(1565999506, 0) } - cinfo, _, err := touchStone(ch, staticPv, fiveOSix) + cinfo, err := touchStone(ai, fiveOSix) if err == nil { t.Errorf("not a cloak, got nil error and cinfo %v", cinfo) return @@ -63,9 +83,14 @@ func TestTouchStone(t *testing.T) { t.Run("not cloak no psk", func(t *testing.T) { chBytes, _ := hex.DecodeString("1603010200010001fc0303eae4c204a867390a758fcff3afa5803cac3e07011cf0c9f3befc1267445aabee20fc398df698113617f8161cbcb89534efa892088a6c5e49246534e05f790ea36f00220a0a130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a010001910a0a000000000014001200000f63646e2e62697a69626c652e636f6d00170000ff01000100000a000a0008caca001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d00140012040308040401050308050501080606010201001200000033002b0029caca000100001d00204c8f1563fb70c261bc0c32c1b568b8d02fab25f4094711e7868b1712751dc754002d00020101002b000b0a2a2a0304030303020301001b00030200026a6a000100001500c9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") ch, _ := parseClientHello(chBytes) + ai, err := unmarshalClientHello(ch, staticPv) + if err != nil { + t.Errorf("expecting no error, got %v", err) + return + } sixOneFive := func() time.Time { return time.Unix(1565999615, 0) } - cinfo, _, err := touchStone(ch, staticPv, sixOneFive) + cinfo, err := touchStone(ai, sixOneFive) if err == nil { t.Errorf("not a cloak, got nil error and cinfo %v", cinfo) return