diff --git a/internal/client/TLS.go b/internal/client/TLS.go index 5bdbf61..787bc89 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -9,7 +9,7 @@ import ( ) type Browser interface { - composeClientHello(*State) ([]byte, []byte) + composeClientHello(chHiddenData) []byte } func makeServerName(serverName string) []byte { @@ -37,15 +37,11 @@ func addExtRec(typ []byte, data []byte) []byte { 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) { - clientHello, sharedSecret := composeClientHello(sta) - _, err = conn.Write(clientHello) + hd, sharedSecret := makeHiddenData(sta) + chOnly := sta.Browser.composeClientHello(hd) + chWithRecordLayer := util.AddRecordLayer(chOnly, []byte{0x16}, []byte{0x03, 0x01}) + _, err = conn.Write(chWithRecordLayer) if err != nil { return } diff --git a/internal/client/auth.go b/internal/client/auth.go index d26ebc3..b7e34cc 100644 --- a/internal/client/auth.go +++ b/internal/client/auth.go @@ -12,11 +12,18 @@ const ( 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 // 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) - random = ecdh.Marshal(ephPub) + ret.chRandom = ecdh.Marshal(ephPub) plaintext := make([]byte, 48) copy(plaintext, sta.UID) @@ -30,9 +37,10 @@ func makeHiddenData(sta *State) (random, TLSsessionID, keyShare, sharedSecret [] } sharedSecret = ecdh.GenerateSharedSecret(ephPv, sta.staticPub) - nonce := random[0:12] + nonce := ret.chRandom[0:12] ciphertext, _ := util.AESGCMEncrypt(nonce, sharedSecret, plaintext) - TLSsessionID = ciphertext[0:32] - keyShare = ciphertext[32:64] + ret.chSessionId = ciphertext[0:32] + ret.chX25519KeyShare = ciphertext[32:64] + ret.chExtSNI = makeServerName(sta.ServerName) return } diff --git a/internal/client/chrome.go b/internal/client/chrome.go index 54108d0..2eccb0e 100644 --- a/internal/client/chrome.go +++ b/internal/client/chrome.go @@ -21,7 +21,7 @@ func makeGREASE() []byte { return doubleGREASE } -func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte { +func (c *Chrome) composeExtensions(sni []byte, keyShare []byte) []byte { makeSupportedGroups := func() []byte { 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 var ext [17][]byte - ext[0] = addExtRec(makeGREASE(), nil) // First GREASE - ext[1] = addExtRec([]byte{0x00, 0x00}, makeServerName(serverName)) // server name indication - ext[2] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret - ext[3] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info - ext[4] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups - ext[5] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats - ext[6] = addExtRec([]byte{0x00, 0x23}, nil) // Session tickets + ext[0] = addExtRec(makeGREASE(), nil) // First GREASE + ext[1] = addExtRec([]byte{0x00, 0x00}, sni) // server name indication + ext[2] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret + ext[3] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info + ext[4] = addExtRec([]byte{0x00, 0x0a}, makeSupportedGroups()) // supported groups + ext[5] = addExtRec([]byte{0x00, 0x0b}, []byte{0x01, 0x00}) // ec point formats + ext[6] = addExtRec([]byte{0x00, 0x23}, nil) // Session tickets APLN, _ := hex.DecodeString("000c02683208687474702f312e31") 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 @@ -79,26 +79,25 @@ func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte { return ret } -func (c *Chrome) composeClientHello(sta *State) (ch []byte, sharedSecret []byte) { - random, sessionID, keyShare, sharedSecret := makeHiddenData(sta) +func (c *Chrome) composeClientHello(hd chHiddenData) (ch []byte) { var clientHello [12][]byte clientHello[0] = []byte{0x01} // handshake type clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508 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[5] = sessionID // session id + clientHello[5] = hd.chSessionId // session id clientHello[6] = []byte{0x00, 0x22} // cipher suites length 34 cipherSuites, _ := hex.DecodeString("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035000a") clientHello[7] = append(makeGREASE(), cipherSuites...) // cipher suites clientHello[8] = []byte{0x01} // compression methods length 1 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 binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11]))) var ret []byte for _, c := range clientHello { ret = append(ret, c...) } - return ret, sharedSecret + return ret } diff --git a/internal/client/chrome_test.go b/internal/client/chrome_test.go index 763e059..7a418cf 100644 --- a/internal/client/chrome_test.go +++ b/internal/client/chrome_test.go @@ -30,7 +30,9 @@ func TestComposeExtension(t *testing.T) { serverName := "cdn.bizible.com" keyShare, _ := hex.DecodeString("010a8896b68fb16e2a245ed87be2699348ab72068bb326eac5beaa00fa56ff17") - result := (&Chrome{}).composeExtensions(serverName, keyShare) + sni := makeServerName(serverName) + + result := (&Chrome{}).composeExtensions(sni, keyShare) target, _ := hex.DecodeString("5a5a000000000014001200000f63646e2e62697a69626c652e636f6d00170000ff01000100000a000a0008fafa001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d00140012040308040401050308050501080606010201001200000033002b0029fafa000100001d0020010a8896b68fb16e2a245ed87be2699348ab72068bb326eac5beaa00fa56ff17002d00020101002b000b0aaaaa0304030303020301001b0003020002eaea000100001500c9000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") for p := 0; p < len(result); { // skip GREASEs diff --git a/internal/client/firefox.go b/internal/client/firefox.go index 6ed1517..99a9399 100644 --- a/internal/client/firefox.go +++ b/internal/client/firefox.go @@ -9,7 +9,7 @@ import ( 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 { ret := make([]byte, 107) 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 var ext [14][]byte - ext[0] = addExtRec([]byte{0x00, 0x00}, makeServerName(serverName)) // server name indication - ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret - ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info + ext[0] = addExtRec([]byte{0x00, 0x00}, SNI) // server name indication + ext[1] = addExtRec([]byte{0x00, 0x17}, nil) // extended_master_secret + ext[2] = addExtRec([]byte{0xff, 0x01}, []byte{0x00}) // renegotiation_info suppGroup, _ := hex.DecodeString("000c001d00170018001901000101") ext[3] = addExtRec([]byte{0x00, 0x0a}, suppGroup) // supported groups 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 // len(ext[0]) + 237 + 4 + len(padding) = 399 // 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 for _, e := range ext { ret = append(ret, e...) @@ -50,23 +50,21 @@ func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte { return ret } -func (f *Firefox) composeClientHello(sta *State) (ch []byte, sharedSecret []byte) { - random, sessionID, keyShare, sharedSecret := makeHiddenData(sta) - +func (f *Firefox) composeClientHello(hd chHiddenData) (ch []byte) { var clientHello [12][]byte clientHello[0] = []byte{0x01} // handshake type clientHello[1] = []byte{0x00, 0x01, 0xfc} // length 508 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[5] = sessionID // session id + clientHello[5] = hd.chSessionId // session id clientHello[6] = []byte{0x00, 0x24} // cipher suites length 36 cipherSuites, _ := hex.DecodeString("130113031302c02bc02fcca9cca8c02cc030c00ac009c013c01400330039002f0035000a") clientHello[7] = cipherSuites // cipher suites clientHello[8] = []byte{0x01} // compression methods length 1 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 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 { ret = append(ret, c...) } - return ret, sharedSecret + return ret } diff --git a/internal/client/firefox_test.go b/internal/client/firefox_test.go index 76ac80f..a5ef900 100644 --- a/internal/client/firefox_test.go +++ b/internal/client/firefox_test.go @@ -11,7 +11,8 @@ func TestComposeExtensions(t *testing.T) { serverName := "consent.google.com" keyShare, _ := hex.DecodeString("6075db0a43812b2e4e0f44157f04295b484ccfc6d70e577c1e6113aa18e08827") - result := (&Firefox{}).composeExtensions(serverName, keyShare) + sni := makeServerName(serverName) + result := (&Firefox{}).composeExtensions(sni, keyShare) // skip random secp256r1 if !bytes.Equal(result[:137], target[:137]) || !bytes.Equal(result[202:], target[202:]) { t.Errorf("got %x", result)