mirror of https://github.com/cbeuw/Cloak
Refactor client ClientHello composition
This commit is contained in:
parent
b5c6d7fedc
commit
b98a74f49b
|
|
@ -9,7 +9,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type Browser interface {
|
type Browser interface {
|
||||||
composeClientHello(*State) ([]byte, []byte)
|
composeClientHello(chHiddenData) []byte
|
||||||
}
|
}
|
||||||
|
|
||||||
func makeServerName(serverName string) []byte {
|
func makeServerName(serverName string) []byte {
|
||||||
|
|
@ -37,15 +37,11 @@ func addExtRec(typ []byte, data []byte) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// composeClientHello composes ClientHello with record layer
|
|
||||||
func composeClientHello(sta *State) ([]byte, []byte) {
|
|
||||||
ch, sharedSecret := sta.Browser.composeClientHello(sta)
|
|
||||||
return util.AddRecordLayer(ch, []byte{0x16}, []byte{0x03, 0x01}), sharedSecret
|
|
||||||
}
|
|
||||||
|
|
||||||
func PrepareConnection(sta *State, conn net.Conn) (sessionKey []byte, err error) {
|
func PrepareConnection(sta *State, conn net.Conn) (sessionKey []byte, err error) {
|
||||||
clientHello, sharedSecret := composeClientHello(sta)
|
hd, sharedSecret := makeHiddenData(sta)
|
||||||
_, err = conn.Write(clientHello)
|
chOnly := sta.Browser.composeClientHello(hd)
|
||||||
|
chWithRecordLayer := util.AddRecordLayer(chOnly, []byte{0x16}, []byte{0x03, 0x01})
|
||||||
|
_, err = conn.Write(chWithRecordLayer)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,11 +12,18 @@ const (
|
||||||
UNORDERED_FLAG = 0x01 // 0000 0001
|
UNORDERED_FLAG = 0x01 // 0000 0001
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []byte) {
|
type chHiddenData struct {
|
||||||
|
chRandom []byte
|
||||||
|
chSessionId []byte
|
||||||
|
chX25519KeyShare []byte
|
||||||
|
chExtSNI []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeHiddenData(sta *State) (ret chHiddenData, sharedSecret []byte) {
|
||||||
// random is marshalled ephemeral pub key 32 bytes
|
// random is marshalled ephemeral pub key 32 bytes
|
||||||
// TLSsessionID || keyShare is [encrypted UID 16 bytes, proxy method 12 bytes, encryption method 1 byte, timestamp 8 bytes, sessionID 4 bytes] [1 byte flag] [6 bytes reserved] [16 bytes authentication tag]
|
// TLSsessionID || keyShare is [encrypted UID 16 bytes, proxy method 12 bytes, encryption method 1 byte, timestamp 8 bytes, sessionID 4 bytes] [1 byte flag] [6 bytes reserved] [16 bytes authentication tag]
|
||||||
ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader)
|
ephPv, ephPub, _ := ecdh.GenerateKey(rand.Reader)
|
||||||
random = ecdh.Marshal(ephPub)
|
ret.chRandom = ecdh.Marshal(ephPub)
|
||||||
|
|
||||||
plaintext := make([]byte, 48)
|
plaintext := make([]byte, 48)
|
||||||
copy(plaintext, sta.UID)
|
copy(plaintext, sta.UID)
|
||||||
|
|
@ -30,9 +37,10 @@ func makeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret []
|
||||||
}
|
}
|
||||||
|
|
||||||
sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
|
sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub)
|
||||||
nonce := random[0:12]
|
nonce := ret.chRandom[0:12]
|
||||||
ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
|
ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext)
|
||||||
TLSsessionID = ciphertext[0:32]
|
ret.chSessionId = ciphertext[0:32]
|
||||||
keyShare = ciphertext[32:64]
|
ret.chX25519KeyShare = ciphertext[32:64]
|
||||||
|
ret.chExtSNI = makeServerName(sta.ServerName)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,7 +21,7 @@ func makeGREASE() []byte {
|
||||||
return doubleGREASE
|
return doubleGREASE
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte {
|
func (c *Chrome) composeExtensions(sni []byte, keyShare []byte) []byte {
|
||||||
|
|
||||||
makeSupportedGroups := func() []byte {
|
makeSupportedGroups := func() []byte {
|
||||||
suppGroupListLen := []byte{0x00, 0x08}
|
suppGroupListLen := []byte{0x00, 0x08}
|
||||||
|
|
@ -47,13 +47,13 @@ func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
// extension length is always 401, and server name length is variable
|
// extension length is always 401, and server name length is variable
|
||||||
|
|
||||||
var ext [17][]byte
|
var ext [17][]byte
|
||||||
ext[0] = addExtRec(makeGREASE(), nil) // First GREASE
|
ext[0] = addExtRec(makeGREASE(), nil) // First GREASE
|
||||||
ext[1] = addExtRec([]byte{0x00, 0x00}, makeServerName(serverName)) // server name indication
|
ext[1] = addExtRec([]byte{0x00, 0x00}, sni) // server name indication
|
||||||
ext[2] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret
|
ext[2] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret
|
||||||
ext[3] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info
|
ext[3] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info
|
||||||
ext[4] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups
|
ext[4] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups
|
||||||
ext[5] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats
|
ext[5] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats
|
||||||
ext[6] = addExtRec([]byte{0x00, 0x23}, nil) // Session tickets
|
ext[6] = addExtRec([]byte{0x00, 0x23}, nil) // Session tickets
|
||||||
APLN, _ := hex.DecodeString("000c02683208687474702f312e31")
|
APLN, _ := hex.DecodeString("000c02683208687474702f312e31")
|
||||||
ext[7] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation
|
ext[7] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation
|
||||||
ext[8] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
|
ext[8] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
|
||||||
|
|
@ -79,26 +79,25 @@ func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Chrome) composeClientHello(sta *State) (ch []byte, sharedSecret []byte) {
|
func (c *Chrome) composeClientHello(hd chHiddenData) (ch []byte) {
|
||||||
random, sessionID, keyShare, sharedSecret := makeHiddenData(sta)
|
|
||||||
var clientHello [12][]byte
|
var clientHello [12][]byte
|
||||||
clientHello[0] = []byte{0x01} // handshake type
|
clientHello[0] = []byte{0x01} // handshake type
|
||||||
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
|
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
|
||||||
clientHello[2] = []byte{0x03, 0x03} // client version
|
clientHello[2] = []byte{0x03, 0x03} // client version
|
||||||
clientHello[3] = random // random
|
clientHello[3] = hd.chRandom // random
|
||||||
clientHello[4] = []byte{0x20} // session id length 32
|
clientHello[4] = []byte{0x20} // session id length 32
|
||||||
clientHello[5] = sessionID // session id
|
clientHello[5] = hd.chSessionId // session id
|
||||||
clientHello[6] = []byte{0x00, 0x22} // cipher suites length 34
|
clientHello[6] = []byte{0x00, 0x22} // cipher suites length 34
|
||||||
cipherSuites, _ := hex.DecodeString("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a")
|
cipherSuites, _ := hex.DecodeString("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a")
|
||||||
clientHello[7] = append(makeGREASE(), cipherSuites...) // cipher suites
|
clientHello[7] = append(makeGREASE(), cipherSuites...) // cipher suites
|
||||||
clientHello[8] = []byte{0x01} // compression methods length 1
|
clientHello[8] = []byte{0x01} // compression methods length 1
|
||||||
clientHello[9] = []byte{0x00} // compression methods
|
clientHello[9] = []byte{0x00} // compression methods
|
||||||
clientHello[11] = c.composeExtensions(sta.ServerName, keyShare)
|
clientHello[11] = c.composeExtensions(hd.chExtSNI, hd.chX25519KeyShare)
|
||||||
clientHello[10] = []byte{0x00, 0x00} // extensions length 401
|
clientHello[10] = []byte{0x00, 0x00} // extensions length 401
|
||||||
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
|
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for _, c := range clientHello {
|
for _, c := range clientHello {
|
||||||
ret = append(ret, c...)
|
ret = append(ret, c...)
|
||||||
}
|
}
|
||||||
return ret, sharedSecret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,9 @@ func TestComposeExtension(t *testing.T) {
|
||||||
serverName := "cdn.bizible.com"
|
serverName := "cdn.bizible.com"
|
||||||
keyShare, _ := hex.DecodeString("010a8896b68fb16e2a245ed87be2699348ab72068bb326eac5beaa00fa56ff17")
|
keyShare, _ := hex.DecodeString("010a8896b68fb16e2a245ed87be2699348ab72068bb326eac5beaa00fa56ff17")
|
||||||
|
|
||||||
result := (&Chrome{}).composeExtensions(serverName, keyShare)
|
sni := makeServerName(serverName)
|
||||||
|
|
||||||
|
result := (&Chrome{}).composeExtensions(sni, keyShare)
|
||||||
target, _ := hex.DecodeString("5a5a000000000014001200000f63646e2e62697a69626c652e636f6d00170000ff01000100000a000a0008fafa001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d00140012040308040401050308050501080606010201001200000033002b0029fafa000100001d0020010a8896b68fb16e2a245ed87be2699348ab72068bb326eac5beaa00fa56ff17002d00020101002b000b0aaaaa0304030303020301001b0003020002eaea000100001500c9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
target, _ := hex.DecodeString("5a5a000000000014001200000f63646e2e62697a69626c652e636f6d00170000ff01000100000a000a0008fafa001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d00140012040308040401050308050501080606010201001200000033002b0029fafa000100001d0020010a8896b68fb16e2a245ed87be2699348ab72068bb326eac5beaa00fa56ff17002d00020101002b000b0aaaaa0304030303020301001b0003020002eaea000100001500c9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
for p := 0; p < len(result); {
|
for p := 0; p < len(result); {
|
||||||
// skip GREASEs
|
// skip GREASEs
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,7 @@ import (
|
||||||
|
|
||||||
type Firefox struct{}
|
type Firefox struct{}
|
||||||
|
|
||||||
func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte {
|
func (f *Firefox) composeExtensions(SNI []byte, keyShare []byte) []byte {
|
||||||
composeKeyShare := func(hidden []byte) []byte {
|
composeKeyShare := func(hidden []byte) []byte {
|
||||||
ret := make([]byte, 107)
|
ret := make([]byte, 107)
|
||||||
ret[0], ret[1] = 0x00, 0x69 // length 105
|
ret[0], ret[1] = 0x00, 0x69 // length 105
|
||||||
|
|
@ -23,9 +23,9 @@ func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
}
|
}
|
||||||
// extension length is always 399, and server name length is variable
|
// extension length is always 399, and server name length is variable
|
||||||
var ext [14][]byte
|
var ext [14][]byte
|
||||||
ext[0] = addExtRec([]byte{0x00, 0x00}, makeServerName(serverName)) // server name indication
|
ext[0] = addExtRec([]byte{0x00, 0x00}, SNI) // server name indication
|
||||||
ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret
|
ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret
|
||||||
ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info
|
ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info
|
||||||
suppGroup, _ := hex.DecodeString("000c001d00170018001901000101")
|
suppGroup, _ := hex.DecodeString("000c001d00170018001901000101")
|
||||||
ext[3] = addExtRec([]byte{0x00, 0x0a}, suppGroup) // supported groups
|
ext[3] = addExtRec([]byte{0x00, 0x0a}, suppGroup) // supported groups
|
||||||
ext[4] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats
|
ext[4] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats
|
||||||
|
|
@ -42,7 +42,7 @@ func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
ext[12] = addExtRec([]byte{0x00, 0x1c}, []byte{0x40, 0x01}) // record size limit
|
ext[12] = addExtRec([]byte{0x00, 0x1c}, []byte{0x40, 0x01}) // record size limit
|
||||||
// len(ext[0]) + 237 + 4 + len(padding) = 399
|
// len(ext[0]) + 237 + 4 + len(padding) = 399
|
||||||
// len(padding) = 158 - len(ext[0])
|
// len(padding) = 158 - len(ext[0])
|
||||||
ext[13] = addExtRec([]byte{0x00, 0x15}, make([]byte, 158-len(serverName))) // padding
|
ext[13] = addExtRec([]byte{0x00, 0x15}, make([]byte, 163-len(SNI))) // padding
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for _, e := range ext {
|
for _, e := range ext {
|
||||||
ret = append(ret, e...)
|
ret = append(ret, e...)
|
||||||
|
|
@ -50,23 +50,21 @@ func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *Firefox) composeClientHello(sta *State) (ch []byte, sharedSecret []byte) {
|
func (f *Firefox) composeClientHello(hd chHiddenData) (ch []byte) {
|
||||||
random, sessionID, keyShare, sharedSecret := makeHiddenData(sta)
|
|
||||||
|
|
||||||
var clientHello [12][]byte
|
var clientHello [12][]byte
|
||||||
clientHello[0] = []byte{0x01} // handshake type
|
clientHello[0] = []byte{0x01} // handshake type
|
||||||
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
|
clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508
|
||||||
clientHello[2] = []byte{0x03, 0x03} // client version
|
clientHello[2] = []byte{0x03, 0x03} // client version
|
||||||
clientHello[3] = random // random
|
clientHello[3] = hd.chRandom // random
|
||||||
clientHello[4] = []byte{0x20} // session id length 32
|
clientHello[4] = []byte{0x20} // session id length 32
|
||||||
clientHello[5] = sessionID // session id
|
clientHello[5] = hd.chSessionId // session id
|
||||||
clientHello[6] = []byte{0x00, 0x24} // cipher suites length 36
|
clientHello[6] = []byte{0x00, 0x24} // cipher suites length 36
|
||||||
cipherSuites, _ := hex.DecodeString("130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a")
|
cipherSuites, _ := hex.DecodeString("130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a")
|
||||||
clientHello[7] = cipherSuites // cipher suites
|
clientHello[7] = cipherSuites // cipher suites
|
||||||
clientHello[8] = []byte{0x01} // compression methods length 1
|
clientHello[8] = []byte{0x01} // compression methods length 1
|
||||||
clientHello[9] = []byte{0x00} // compression methods
|
clientHello[9] = []byte{0x00} // compression methods
|
||||||
|
|
||||||
clientHello[11] = f.composeExtensions(sta.ServerName, keyShare)
|
clientHello[11] = f.composeExtensions(hd.chExtSNI, hd.chX25519KeyShare)
|
||||||
clientHello[10] = []byte{0x00, 0x00} // extensions length
|
clientHello[10] = []byte{0x00, 0x00} // extensions length
|
||||||
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
|
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
|
||||||
|
|
||||||
|
|
@ -74,5 +72,5 @@ func (f *Firefox) composeClientHello(sta *State) (ch []byte, sharedSecret []byte
|
||||||
for _, c := range clientHello {
|
for _, c := range clientHello {
|
||||||
ret = append(ret, c...)
|
ret = append(ret, c...)
|
||||||
}
|
}
|
||||||
return ret, sharedSecret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,8 @@ func TestComposeExtensions(t *testing.T) {
|
||||||
|
|
||||||
serverName := "consent.google.com"
|
serverName := "consent.google.com"
|
||||||
keyShare, _ := hex.DecodeString("6075db0a43812b2e4e0f44157f04295b484ccfc6d70e577c1e6113aa18e08827")
|
keyShare, _ := hex.DecodeString("6075db0a43812b2e4e0f44157f04295b484ccfc6d70e577c1e6113aa18e08827")
|
||||||
result := (&Firefox{}).composeExtensions(serverName, keyShare)
|
sni := makeServerName(serverName)
|
||||||
|
result := (&Firefox{}).composeExtensions(sni, keyShare)
|
||||||
// skip random secp256r1
|
// skip random secp256r1
|
||||||
if !bytes.Equal(result[:137], target[:137]) || !bytes.Equal(result[202:], target[202:]) {
|
if !bytes.Equal(result[:137], target[:137]) || !bytes.Equal(result[202:], target[202:]) {
|
||||||
t.Errorf("got %x", result)
|
t.Errorf("got %x", result)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue