diff --git a/.gitignore b/.gitignore index a82796c..9d8deca 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ corpus/ suppressions/ crashers/ -*.zip \ No newline at end of file +*.zip +.idea/ +build/ \ No newline at end of file diff --git a/internal/client/TLS.go b/internal/client/TLS.go index 39e8bb3..7e21724 100644 --- a/internal/client/TLS.go +++ b/internal/client/TLS.go @@ -13,14 +13,14 @@ type clientHelloFields struct { random []byte sessionId []byte x25519KeyShare []byte - sni []byte + serverName string } type browser interface { composeClientHello(clientHelloFields) []byte } -func makeServerName(serverName string) []byte { +func generateSNI(serverName string) []byte { serverNameListLength := make([]byte, 2) binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3)) serverNameType := []byte{0x00} // host_name @@ -45,16 +45,6 @@ func addExtRec(typ []byte, data []byte) []byte { return ret } -func genStegClientHello(ai authenticationPayload, serverName string) (ret clientHelloFields) { - // random is marshalled ephemeral pub key 32 bytes - // The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare - ret.random = ai.randPubKey[:] - ret.sessionId = ai.ciphertextWithTag[0:32] - ret.x25519KeyShare = ai.ciphertextWithTag[32:64] - ret.sni = makeServerName(serverName) - return -} - type DirectTLS struct { *common.TLSConn browser browser @@ -64,7 +54,16 @@ type DirectTLS struct { // if the server proceed with Cloak authentication func (tls *DirectTLS) Handshake(rawConn net.Conn, authInfo AuthInfo) (sessionKey [32]byte, err error) { payload, sharedSecret := makeAuthenticationPayload(authInfo) - chOnly := tls.browser.composeClientHello(genStegClientHello(payload, authInfo.MockDomain)) + + // random is marshalled ephemeral pub key 32 bytes + // The authentication ciphertext and its tag are then distributed among SessionId and X25519KeyShare + fields := clientHelloFields{ + random: payload.randPubKey[:], + sessionId: payload.ciphertextWithTag[0:32], + x25519KeyShare: payload.ciphertextWithTag[32:64], + serverName: authInfo.MockDomain, + } + chOnly := tls.browser.composeClientHello(fields) chWithRecordLayer := common.AddRecordLayer(chOnly, common.Handshake, common.VersionTLS11) _, err = rawConn.Write(chWithRecordLayer) if err != nil { diff --git a/internal/client/TLS_test.go b/internal/client/TLS_test.go index b8bdd81..bd3b051 100644 --- a/internal/client/TLS_test.go +++ b/internal/client/TLS_test.go @@ -33,6 +33,6 @@ func TestMakeServerName(t *testing.T) { } for _, p := range pairs { - assert.Equal(t, p.target, makeServerName(p.serverName)) + assert.Equal(t, p.target, generateSNI(p.serverName)) } } diff --git a/internal/client/chrome.go b/internal/client/chrome.go index 34e394f..58c1050 100644 --- a/internal/client/chrome.go +++ b/internal/client/chrome.go @@ -21,7 +21,7 @@ func makeGREASE() []byte { return doubleGREASE } -func (c *Chrome) composeExtensions(sni []byte, keyShare []byte) []byte { +func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte { makeSupportedGroups := func() []byte { suppGroupListLen := []byte{0x00, 0x08} @@ -47,13 +47,13 @@ func (c *Chrome) composeExtensions(sni []byte, keyShare []byte) []byte { // extension length is always 403, and server name length is variable var ext [17][]byte - 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 + ext[0] = addExtRec(makeGREASE(), nil) // First GREASE + ext[1] = addExtRec([]byte{0x00, 0x00}, generateSNI(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 ALPN, _ := hex.DecodeString("000c02683208687474702f312e31") ext[7] = addExtRec([]byte{0x00, 0x10}, ALPN) // app layer proto negotiation ext[8] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request @@ -87,12 +87,12 @@ func (c *Chrome) composeClientHello(hd clientHelloFields) (ch []byte) { clientHello[3] = hd.random // random clientHello[4] = []byte{0x20} // session id length 32 clientHello[5] = hd.sessionId // session id - clientHello[6] = []byte{0x00, 0x20} // cipher suites length 34 + clientHello[6] = []byte{0x00, 0x20} // cipher suites length 32 cipherSuites, _ := hex.DecodeString("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035") 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(hd.sni, hd.x25519KeyShare) + clientHello[11] = c.composeExtensions(hd.serverName, hd.x25519KeyShare) clientHello[10] = []byte{0x00, 0x00} // extensions length 403 binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11]))) var ret []byte diff --git a/internal/client/chrome_test.go b/internal/client/chrome_test.go index 8ddb6f8..629e23c 100644 --- a/internal/client/chrome_test.go +++ b/internal/client/chrome_test.go @@ -30,9 +30,7 @@ func TestComposeExtension(t *testing.T) { serverName := "github.com" keyShare, _ := hex.DecodeString("690f074f5c01756982269b66d58c90c47dc0f281d654c7b2c16f63c9033f5604") - sni := makeServerName(serverName) - - result := (&Chrome{}).composeExtensions(sni, keyShare) + result := (&Chrome{}).composeExtensions(serverName, keyShare) target, _ := hex.DecodeString("8a8a00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000a00088a8a001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0012001004030804040105030805050108060601001200000033002b00298a8a000100001d0020690f074f5c01756982269b66d58c90c47dc0f281d654c7b2c16f63c9033f5604002d00020101002b000b0a3a3a0304030303020301001b00030200024a4a000100001500d2000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000") for p := 0; p < len(result); p++ { if result[p] != target[p] { diff --git a/internal/client/firefox.go b/internal/client/firefox.go index 89631ee..6e66955 100644 --- a/internal/client/firefox.go +++ b/internal/client/firefox.go @@ -10,7 +10,7 @@ import ( type Firefox struct{} -func (f *Firefox) composeExtensions(SNI []byte, keyShare []byte) []byte { +func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte { composeKeyShare := func(hidden []byte) []byte { ret := make([]byte, 107) ret[0], ret[1] = 0x00, 0x69 // length 105 @@ -24,9 +24,9 @@ func (f *Firefox) composeExtensions(SNI []byte, keyShare []byte) []byte { } // extension length is always 399, and server name length is variable var ext [12][]byte - 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 + ext[0] = addExtRec([]byte{0x00, 0x00}, generateSNI(serverName)) // 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 @@ -63,7 +63,7 @@ func (f *Firefox) composeClientHello(hd clientHelloFields) (ch []byte) { clientHello[8] = []byte{0x01} // compression methods length 1 clientHello[9] = []byte{0x00} // compression methods - clientHello[11] = f.composeExtensions(hd.sni, hd.x25519KeyShare) + clientHello[11] = f.composeExtensions(hd.serverName, hd.x25519KeyShare) clientHello[10] = []byte{0x00, 0x00} // extensions length binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11]))) diff --git a/internal/client/firefox_test.go b/internal/client/firefox_test.go index fd04fda..6b6a338 100644 --- a/internal/client/firefox_test.go +++ b/internal/client/firefox_test.go @@ -11,8 +11,8 @@ func TestComposeExtensions(t *testing.T) { serverName := "consent.google.com" keyShare, _ := hex.DecodeString("8d8ea1b80430b7710b65f0d89b0144a5eeb218709ce6613d4fc8bfb117657c15") - sni := makeServerName(serverName) - result := (&Firefox{}).composeExtensions(sni, keyShare) + + result := (&Firefox{}).composeExtensions(serverName, keyShare) // skip random secp256r1 if !bytes.Equal(result[:133], target[:133]) || !bytes.Equal(result[198:], target[198:]) { t.Errorf("got %x", result)