mirror of https://github.com/cbeuw/Cloak
Merge remote-tracking branch 'upstream/master' into notsure2
This commit is contained in:
commit
4831cfac61
|
|
@ -169,7 +169,7 @@ TCP connection will spawn a separate short-lived session that will be closed aft
|
||||||
behave like GoQuiet. This maybe useful for people with unstable connections.
|
behave like GoQuiet. This maybe useful for people with unstable connections.
|
||||||
|
|
||||||
`BrowserSig` is the browser you want to **appear** to be using. It's not relevant to the browser you are actually using.
|
`BrowserSig` is the browser you want to **appear** to be using. It's not relevant to the browser you are actually using.
|
||||||
Currently, `chrome` and `firefox` are supported.
|
Currently, `chrome`, `firefox` and `safari` are supported.
|
||||||
|
|
||||||
`KeepAlive` is the number of seconds to tell the OS to wait after no activity before sending TCP KeepAlive probes to the
|
`KeepAlive` is the number of seconds to tell the OS to wait after no activity before sending TCP KeepAlive probes to the
|
||||||
Cloak server. Zero or negative value disables it. Default is 0 (disabled). Warning: Enabling it might make your server
|
Cloak server. Zero or negative value disables it. Default is 0 (disabled). Warning: Enabling it might make your server
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
"net"
|
"net"
|
||||||
|
|
||||||
"github.com/cbeuw/Cloak/internal/common"
|
"github.com/cbeuw/Cloak/internal/common"
|
||||||
|
|
@ -17,6 +18,14 @@ type clientHelloFields struct {
|
||||||
serverName string
|
serverName string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeHex(s string) []byte {
|
||||||
|
b, err := hex.DecodeString(s)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return b
|
||||||
|
}
|
||||||
|
|
||||||
type browser interface {
|
type browser interface {
|
||||||
composeClientHello(clientHelloFields) []byte
|
composeClientHello(clientHelloFields) []byte
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
// Fingerprint of Chrome 99
|
// Fingerprint of Chrome 112
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/cbeuw/Cloak/internal/common"
|
"github.com/cbeuw/Cloak/internal/common"
|
||||||
|
"math/rand"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Chrome struct{}
|
type Chrome struct{}
|
||||||
|
|
@ -45,36 +44,49 @@ func (c *Chrome) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
|
|
||||||
// extension length is always 403, and server name length is variable
|
shuffle := func(exts [][]byte) {
|
||||||
|
var qword [8]byte
|
||||||
|
common.CryptoRandRead(qword[:])
|
||||||
|
seed := int64(binary.BigEndian.Uint64(qword[:]))
|
||||||
|
source := rand.NewSource(seed)
|
||||||
|
r := rand.New(source)
|
||||||
|
r.Shuffle(len(exts), func(i, j int) { exts[i], exts[j] = exts[j], exts[i] })
|
||||||
|
}
|
||||||
|
|
||||||
|
// extension length is always 403, and server name length is variable
|
||||||
var ext [18][]byte
|
var ext [18][]byte
|
||||||
ext[0] = addExtRec(makeGREASE(), nil) // First GREASE
|
ext[0] = addExtRec(makeGREASE(), nil) // First GREASE
|
||||||
|
|
||||||
|
// Start shufflable extensions: https://chromestatus.com/feature/5124606246518784
|
||||||
ext[1] = addExtRec([]byte{0x00, 0x00}, generateSNI(serverName)) // server name indication
|
ext[1] = addExtRec([]byte{0x00, 0x00}, generateSNI(serverName)) // server name indication
|
||||||
|
sniLen := len(ext[1])
|
||||||
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
|
||||||
ALPN, _ := hex.DecodeString("000c02683208687474702f312e31")
|
ext[7] = addExtRec([]byte{0x00, 0x10}, decodeHex("000c02683208687474702f312e31")) // app layer proto negotiation
|
||||||
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
|
ext[8] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
|
||||||
sigAlgo, _ := hex.DecodeString("001004030804040105030805050108060601")
|
ext[9] = addExtRec([]byte{0x00, 0x0d}, decodeHex("001004030804040105030805050108060601")) // Signature Algorithms
|
||||||
ext[9] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
|
|
||||||
ext[10] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp
|
ext[10] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp
|
||||||
ext[11] = addExtRec([]byte{0x00, 0x33}, makeKeyShare(keyShare)) // key share
|
ext[11] = addExtRec([]byte{0x00, 0x33}, makeKeyShare(keyShare)) // key share
|
||||||
ext[12] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes
|
ext[12] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes
|
||||||
suppVersions, _ := hex.DecodeString("069A9A03040303") // 9A9A needs to be a GREASE
|
suppVersions := decodeHex("069A9A03040303") // 9A9A needs to be a GREASE
|
||||||
copy(suppVersions[1:3], makeGREASE())
|
copy(suppVersions[1:3], makeGREASE())
|
||||||
ext[13] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions
|
ext[13] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions
|
||||||
ext[14] = addExtRec([]byte{0x00, 0x1b}, []byte{0x02, 0x00, 0x02}) // compress certificate
|
ext[14] = addExtRec([]byte{0x00, 0x1b}, []byte{0x02, 0x00, 0x02}) // compress certificate
|
||||||
applicationSettings, _ := hex.DecodeString("0003026832")
|
ext[15] = addExtRec([]byte{0x44, 0x69}, decodeHex("0003026832")) // application settings
|
||||||
ext[15] = addExtRec([]byte{0x44, 0x69}, applicationSettings) // application settings
|
// End shufflable extensions
|
||||||
|
|
||||||
|
shuffle(ext[1:16])
|
||||||
|
|
||||||
ext[16] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE
|
ext[16] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE
|
||||||
// len(ext[1]) + 175 + len(ext[16]) = 403
|
// sniLen + len(all other ext) + len(ext[17]) = 403
|
||||||
// len(ext[16]) = 228 - len(ext[1])
|
// len(all other ext) = 175
|
||||||
// 2+2+len(padding) = 228 - len(ext[1])
|
// len(ext[17]) = 228 - sniLen
|
||||||
// len(padding) = 224 - len(ext[1])
|
// 2+2+len(padding) = 228 - sniLen
|
||||||
ext[17] = addExtRec([]byte{0x00, 0x15}, make([]byte, 224-len(ext[1]))) // padding
|
// len(padding) = 224 - sniLen
|
||||||
|
ext[17] = addExtRec([]byte{0x00, 0x15}, make([]byte, 224-sniLen)) // padding
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for _, e := range ext {
|
for _, e := range ext {
|
||||||
ret = append(ret, e...)
|
ret = append(ret, e...)
|
||||||
|
|
@ -91,13 +103,15 @@ func (c *Chrome) composeClientHello(hd clientHelloFields) (ch []byte) {
|
||||||
clientHello[4] = []byte{0x20} // session id length 32
|
clientHello[4] = []byte{0x20} // session id length 32
|
||||||
clientHello[5] = hd.sessionId // session id
|
clientHello[5] = hd.sessionId // session id
|
||||||
clientHello[6] = []byte{0x00, 0x20} // cipher suites length 32
|
clientHello[6] = []byte{0x00, 0x20} // cipher suites length 32
|
||||||
cipherSuites, _ := hex.DecodeString("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035")
|
clientHello[7] = append(makeGREASE(), decodeHex("130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f0035")...) // 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(hd.serverName, hd.x25519KeyShare)
|
|
||||||
clientHello[10] = []byte{0x00, 0x00} // extensions length 403
|
extensions := c.composeExtensions(hd.serverName, hd.x25519KeyShare)
|
||||||
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
|
clientHello[10] = []byte{0x00, 0x00}
|
||||||
|
binary.BigEndian.PutUint16(clientHello[10], uint16(len(extensions))) // extension length
|
||||||
|
clientHello[11] = extensions
|
||||||
|
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for _, c := range clientHello {
|
for _, c := range clientHello {
|
||||||
ret = append(ret, c...)
|
ret = append(ret, c...)
|
||||||
|
|
|
||||||
|
|
@ -26,21 +26,35 @@ func TestMakeGREASE(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestComposeExtension(t *testing.T) {
|
//func TestChromeJA3(t *testing.T) {
|
||||||
serverName := "github.com"
|
// result := common.AddRecordLayer((&Chrome{}).composeClientHello(hd), common.Handshake, common.VersionTLS11)
|
||||||
keyShare, _ := hex.DecodeString("690f074f5c01756982269b66d58c90c47dc0f281d654c7b2c16f63c9033f5604")
|
// assert.Equal(t, 517, len(result))
|
||||||
|
//
|
||||||
result := (&Chrome{}).composeExtensions(serverName, keyShare)
|
// hello := tlsx.ClientHelloBasic{}
|
||||||
target, _ := hex.DecodeString("3a3a00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000a00080a0a001d00170018000b00020100002300000010000e000c02683208687474702f312e31000500050100000000000d0012001004030804040105030805050108060601001200000033002b00296a6a000100001d0020690f074f5c01756982269b66d58c90c47dc0f281d654c7b2c16f63c9033f5604002d00020101002b000706dada03040303001b0003020002446900050003026832eaea000100001500cd00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
// err := hello.Unmarshal(result)
|
||||||
for p := 0; p < len(result); p++ {
|
// assert.Nil(t, err)
|
||||||
if result[p] != target[p] {
|
//
|
||||||
if result[p]&0x0F == 0xA && target[p]&0x0F == 0xA &&
|
// // Chrome shuffles the order of extensions, so it needs special handling
|
||||||
((p > 0 && result[p-1] == result[p] && target[p-1] == target[p]) ||
|
// full := string(ja3.Bare(&hello))
|
||||||
(p < len(result)-1 && result[p+1] == result[p] && target[p+1] == target[p])) {
|
// // TLSVersion,Ciphers,Extensions,EllipticCurves,EllipticCurvePointFormats
|
||||||
continue
|
// parts := strings.Split(full, ",")
|
||||||
}
|
//
|
||||||
t.Errorf("inequality at %v", p)
|
// // TLSVersion,Ciphers
|
||||||
}
|
// assert.Equal(t,
|
||||||
}
|
// []string{
|
||||||
|
// "771",
|
||||||
}
|
// "4865-4866-4867-49195-49199-49196-49200-52393-52392-49171-49172-156-157-47-53",
|
||||||
|
// }, parts[0:2])
|
||||||
|
// // EllipticCurves,EllipticCurvePointFormats
|
||||||
|
// assert.Equal(t,
|
||||||
|
// []string{
|
||||||
|
// "29-23-24", "0",
|
||||||
|
// }, parts[3:5])
|
||||||
|
//
|
||||||
|
// normaliseExtensions := func(extensions string) []string {
|
||||||
|
// extensionParts := strings.Split(parts[2], "-")
|
||||||
|
// sort.Strings(extensionParts)
|
||||||
|
// return extensionParts
|
||||||
|
// }
|
||||||
|
// assert.Equal(t, normaliseExtensions("10-5-45-0-17513-13-18-11-23-16-35-27-65281-43-51-21"), normaliseExtensions(parts[2]))
|
||||||
|
//}
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,9 @@
|
||||||
// Fingerprint of Firefox 99
|
// Fingerprint of Firefox 112
|
||||||
|
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/hex"
|
|
||||||
|
|
||||||
"github.com/cbeuw/Cloak/internal/common"
|
"github.com/cbeuw/Cloak/internal/common"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -24,29 +22,24 @@ func (f *Firefox) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
return ret
|
return ret
|
||||||
}
|
}
|
||||||
// extension length is always 401, and server name length is variable
|
// extension length is always 401, and server name length is variable
|
||||||
var ext [15][]byte
|
var ext [13][]byte
|
||||||
ext[0] = addExtRec([]byte{0x00, 0x00}, generateSNI(serverName)) // server name indication
|
ext[0] = addExtRec([]byte{0x00, 0x00}, generateSNI(serverName)) // 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")
|
ext[3] = addExtRec([]byte{0x00, 0x0a}, decodeHex("000c001d00170018001901000101")) // 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
|
||||||
ext[5] = addExtRec([]byte{0x00, 0x23}, nil) // session ticket
|
ext[5] = addExtRec([]byte{0x00, 0x10}, decodeHex("000c02683208687474702f312e31")) // app layer proto negotiation
|
||||||
ALPN, _ := hex.DecodeString("000c02683208687474702f312e31")
|
ext[6] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
|
||||||
ext[6] = addExtRec([]byte{0x00, 0x10}, ALPN) // app layer proto negotiation
|
ext[7] = addExtRec([]byte{0x00, 0x22}, decodeHex("00080403050306030203")) // delegated credentials
|
||||||
ext[7] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
|
ext[8] = addExtRec([]byte{0x00, 0x33}, composeKeyShare(keyShare)) // key share
|
||||||
delegatedCredentials, _ := hex.DecodeString("00080403050306030203")
|
ext[9] = addExtRec([]byte{0x00, 0x2b}, decodeHex("0403040303")) // supported versions
|
||||||
ext[8] = addExtRec([]byte{0x00, 0x22}, delegatedCredentials) // delegated credentials
|
ext[10] = addExtRec([]byte{0x00, 0x0d}, decodeHex("001604030503060308040805080604010501060102030201")) // Signature Algorithms
|
||||||
ext[9] = addExtRec([]byte{0x00, 0x33}, composeKeyShare(keyShare)) // key share
|
ext[11] = addExtRec([]byte{0x00, 0x1c}, []byte{0x40, 0x01}) // record size limit
|
||||||
suppVersions, _ := hex.DecodeString("0403040303")
|
// len(ext[0]) + len(all other ext) + len(len field of padding) + len(padding) = 401
|
||||||
ext[10] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions
|
// len(all other ext) = 228
|
||||||
sigAlgo, _ := hex.DecodeString("001604030503060308040805080604010501060102030201")
|
// len(len field of padding) = 4
|
||||||
ext[11] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
|
// len(padding) = 169 - len(ext[0])
|
||||||
ext[12] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes
|
ext[12] = addExtRec([]byte{0x00, 0x15}, make([]byte, 169-len(ext[0]))) // padding
|
||||||
ext[13] = addExtRec([]byte{0x00, 0x1c}, []byte{0x40, 0x01}) // record size limit
|
|
||||||
// len(ext[0]) + 238 + 4 + len(padding) = 401
|
|
||||||
// len(padding) = 177 - len(ext[0])
|
|
||||||
ext[14] = addExtRec([]byte{0x00, 0x15}, make([]byte, 159-len(ext[0]))) // padding
|
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for _, e := range ext {
|
for _, e := range ext {
|
||||||
ret = append(ret, e...)
|
ret = append(ret, e...)
|
||||||
|
|
@ -63,14 +56,14 @@ func (f *Firefox) composeClientHello(hd clientHelloFields) (ch []byte) {
|
||||||
clientHello[4] = []byte{0x20} // session id length 32
|
clientHello[4] = []byte{0x20} // session id length 32
|
||||||
clientHello[5] = hd.sessionId // session id
|
clientHello[5] = hd.sessionId // session id
|
||||||
clientHello[6] = []byte{0x00, 0x22} // cipher suites length 34
|
clientHello[6] = []byte{0x00, 0x22} // cipher suites length 34
|
||||||
cipherSuites, _ := hex.DecodeString("130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f0035")
|
clientHello[7] = decodeHex("130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f0035") // 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(hd.serverName, hd.x25519KeyShare)
|
extensions := f.composeExtensions(hd.serverName, hd.x25519KeyShare)
|
||||||
clientHello[10] = []byte{0x00, 0x00} // extensions length
|
clientHello[10] = []byte{0x00, 0x00}
|
||||||
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
|
binary.BigEndian.PutUint16(clientHello[10], uint16(len(extensions))) // extension length
|
||||||
|
clientHello[11] = extensions
|
||||||
|
|
||||||
var ret []byte
|
var ret []byte
|
||||||
for _, c := range clientHello {
|
for _, c := range clientHello {
|
||||||
|
|
|
||||||
|
|
@ -1,20 +1,40 @@
|
||||||
package client
|
package client
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestComposeExtensions(t *testing.T) {
|
var hd = clientHelloFields{
|
||||||
target, _ := hex.DecodeString("000000170015000012636f6e73656e742e676f6f676c652e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b00020100002300000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d00208d8ea1b80430b7710b65f0d89b0144a5eeb218709ce6613d4fc8bfb117657c1500170041947458330e3553dcde0a8741eb1dde26ebaee8262029c5edb3cbacc9ee1d7c866085b9cf483d943248997a65c5fa1d35725213895d0e5569d4e291863061b7d075002b00050403040303000d0018001604030503060308040805080604010501060102030201002d00020101001c0002400100150084000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
random: decodeHex("ed0117085ed70be0799b1fc96af7f675d4747f86cd03bb36392e03e8d1b0e9a0"),
|
||||||
|
sessionId: decodeHex("47485f67c59ca787009bba83ede4da4f2397169c696c275d96c4c7af803019b9"),
|
||||||
serverName := "consent.google.com"
|
x25519KeyShare: decodeHex("d395003163a6f751b4c68a67bcec1f883885a7ada8a63fda389b29986e51fa44"),
|
||||||
keyShare, _ := hex.DecodeString("8d8ea1b80430b7710b65f0d89b0144a5eeb218709ce6613d4fc8bfb117657c15")
|
serverName: "github.com",
|
||||||
|
}
|
||||||
result := (&Firefox{}).composeExtensions(serverName, keyShare)
|
|
||||||
// skip random secp256r1
|
//func TestFirefoxJA3(t *testing.T) {
|
||||||
if !bytes.Equal(result[:151], target[:151]) || !bytes.Equal(result[216:], target[216:]) {
|
// result := common.AddRecordLayer((&Firefox{}).composeClientHello(hd), common.Handshake, common.VersionTLS11)
|
||||||
t.Errorf("got %x", result)
|
//
|
||||||
}
|
// hello := tlsx.ClientHelloBasic{}
|
||||||
|
// err := hello.Unmarshal(result)
|
||||||
|
// assert.Nil(t, err)
|
||||||
|
//
|
||||||
|
// digest := ja3.DigestHex(&hello)
|
||||||
|
// assert.Equal(t, "ad55557b7cbd735c2627f7ebb3b3d493", digest)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func TestFirefoxComposeClientHello(t *testing.T) {
|
||||||
|
result := hex.EncodeToString((&Firefox{}).composeClientHello(hd))
|
||||||
|
target := "010001fc0303ed0117085ed70be0799b1fc96af7f675d4747f86cd03bb36392e03e8d1b0e9a02047485f67c59ca787009bba83ede4da4f2397169c696c275d96c4c7af803019b90022130113031302c02bc02fcca9cca8c02cc030c00ac009c013c014009c009d002f0035010001910000000f000d00000a6769746875622e636f6d00170000ff01000100000a000e000c001d00170018001901000101000b000201000010000e000c02683208687474702f312e310005000501000000000022000a000804030503060302030033006b0069001d0020d395003163a6f751b4c68a67bcec1f883885a7ada8a63fda389b29986e51fa440017004104c49751010e35370cf8e89c23471b40579387b3dd5ce6862c9850b121632b527128b75ef7051c5284ae94894d846cc3dc88ce01ce49b605167f63473c1d772b47002b00050403040303000d0018001604030503060308040805080604010501060102030201001c0002400100150096000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"
|
||||||
|
|
||||||
|
// skip random secp256r1
|
||||||
|
secp256r1 := "04c49751010e35370cf8e89c23471b40579387b3dd5ce6862c9850b121632b527128b75ef7051c5284ae94894d846cc3dc88ce01ce49b605167f63473c1d772b47"
|
||||||
|
start := strings.Index(target, secp256r1)
|
||||||
|
|
||||||
|
target = strings.Replace(target, secp256r1, "", 1)
|
||||||
|
result = strings.Replace(result, result[start:start+len(secp256r1)], "", 1)
|
||||||
|
|
||||||
|
assert.Equal(t, target, result)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
// Fingerprint of Safari 16.4
|
||||||
|
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Safari struct{}
|
||||||
|
|
||||||
|
func (s *Safari) composeExtensions(serverName string, keyShare []byte) []byte {
|
||||||
|
makeSupportedGroups := func() []byte {
|
||||||
|
suppGroupListLen := []byte{0x00, 0x0a}
|
||||||
|
ret := make([]byte, 2+2+8)
|
||||||
|
copy(ret[0:2], suppGroupListLen)
|
||||||
|
copy(ret[2:4], makeGREASE())
|
||||||
|
copy(ret[4:], []byte{0x00, 0x1d, 0x00, 0x17, 0x00, 0x18, 0x00, 0x19})
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
makeKeyShare := func(hidden []byte) []byte {
|
||||||
|
ret := make([]byte, 43)
|
||||||
|
ret[0], ret[1] = 0x00, 0x29 // length 41
|
||||||
|
copy(ret[2:4], makeGREASE())
|
||||||
|
ret[4], ret[5] = 0x00, 0x01 // length 1
|
||||||
|
ret[6] = 0x00
|
||||||
|
ret[7], ret[8] = 0x00, 0x1d // group x25519
|
||||||
|
ret[9], ret[10] = 0x00, 0x20 // length 32
|
||||||
|
copy(ret[11:43], hidden)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
// extension length is always 393, and server name length is variable
|
||||||
|
var ext [16][]byte
|
||||||
|
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, 0x10}, decodeHex("000c02683208687474702f312e31")) // app layer proto negotiation
|
||||||
|
ext[7] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
|
||||||
|
ext[8] = addExtRec([]byte{0x00, 0x0d}, decodeHex("001604030804040105030203080508050501080606010201")) // Signature Algorithms
|
||||||
|
ext[9] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp
|
||||||
|
ext[10] = addExtRec([]byte{0x00, 0x33}, makeKeyShare(keyShare)) // key share
|
||||||
|
ext[11] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes
|
||||||
|
suppVersions := decodeHex("0a5a5a0304030303020301") // 5a5a needs to be a GREASE
|
||||||
|
copy(suppVersions[1:3], makeGREASE())
|
||||||
|
ext[12] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions
|
||||||
|
ext[13] = addExtRec([]byte{0x00, 0x1b}, []byte{0x02, 0x00, 0x01}) // compress certificate
|
||||||
|
ext[14] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE
|
||||||
|
// len(ext[1]) + len(all other ext) + len(ext[15]) = 393
|
||||||
|
// len(all other ext) = 174
|
||||||
|
// len(ext[15]) = 219 - len(ext[1])
|
||||||
|
// 2+2+len(padding) = 219 - len(ext[1])
|
||||||
|
// len(padding) = 215 - len(ext[1])
|
||||||
|
ext[15] = addExtRec([]byte{0x00, 0x15}, make([]byte, 215-len(ext[1]))) // padding
|
||||||
|
var ret []byte
|
||||||
|
for _, e := range ext {
|
||||||
|
ret = append(ret, e...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Safari) composeClientHello(hd clientHelloFields) (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] = hd.random // random
|
||||||
|
clientHello[4] = []byte{0x20} // session id length 32
|
||||||
|
clientHello[5] = hd.sessionId // session id
|
||||||
|
clientHello[6] = []byte{0x00, 0x2a} // cipher suites length 42
|
||||||
|
clientHello[7] = append(makeGREASE(), decodeHex("130113021303c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a")...) // cipher suites
|
||||||
|
clientHello[8] = []byte{0x01} // compression methods length 1
|
||||||
|
clientHello[9] = []byte{0x00} // compression methods
|
||||||
|
|
||||||
|
extensions := s.composeExtensions(hd.serverName, hd.x25519KeyShare)
|
||||||
|
clientHello[10] = []byte{0x00, 0x00}
|
||||||
|
binary.BigEndian.PutUint16(clientHello[10], uint16(len(extensions))) // extension length
|
||||||
|
clientHello[11] = extensions
|
||||||
|
|
||||||
|
var ret []byte
|
||||||
|
for _, c := range clientHello {
|
||||||
|
ret = append(ret, c...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
package client
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var safariHd = clientHelloFields{
|
||||||
|
random: decodeHex("977ecef48c0fc5640fea4dbd638da89704d6d85ed2e81b8913ae5b27f9a5cc17"),
|
||||||
|
sessionId: decodeHex("c2d5b91e77371bf154363b39194ac77c05617cc6164724d0ba7ded4aa349c6a3"),
|
||||||
|
x25519KeyShare: decodeHex("c99fbe80dda71f6e24d9b798dc3f3f33cef946f0b917fa90154a4b95114fae2a"),
|
||||||
|
serverName: "github.com",
|
||||||
|
}
|
||||||
|
|
||||||
|
//func TestSafariJA3(t *testing.T) {
|
||||||
|
// result := common.AddRecordLayer((&Safari{}).composeClientHello(safariHd), common.Handshake, common.VersionTLS11)
|
||||||
|
//
|
||||||
|
// hello := tlsx.ClientHelloBasic{}
|
||||||
|
// err := hello.Unmarshal(result)
|
||||||
|
// assert.Nil(t, err)
|
||||||
|
//
|
||||||
|
// digest := ja3.DigestHex(&hello)
|
||||||
|
// assert.Equal(t, "773906b0efdefa24a7f2b8eb6985bf37", digest)
|
||||||
|
//}
|
||||||
|
|
||||||
|
func TestSafariComposeClientHello(t *testing.T) {
|
||||||
|
result := (&Safari{}).composeClientHello(safariHd)
|
||||||
|
target := decodeHex("010001fc0303977ecef48c0fc5640fea4dbd638da89704d6d85ed2e81b8913ae5b27f9a5cc1720c2d5b91e77371bf154363b39194ac77c05617cc6164724d0ba7ded4aa349c6a3002acaca130113021303c02cc02bcca9c030c02fcca8c00ac009c014c013009d009c0035002fc008c012000a01000189fafa00000000000f000d00000a6769746875622e636f6d00170000ff01000100000a000c000a7a7a001d001700180019000b000201000010000e000c02683208687474702f312e31000500050100000000000d0018001604030804040105030203080508050501080606010201001200000033002b00297a7a000100001d0020c99fbe80dda71f6e24d9b798dc3f3f33cef946f0b917fa90154a4b95114fae2a002d00020101002b000b0a2a2a0304030303020301001b00030200017a7a000100001500c400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000")
|
||||||
|
for p := 0; p < len(result); p++ {
|
||||||
|
if result[p] != target[p] {
|
||||||
|
if result[p]&0x0F == 0xA && target[p]&0x0F == 0xA &&
|
||||||
|
((p > 0 && result[p-1] == result[p] && target[p-1] == target[p]) ||
|
||||||
|
(p < len(result)-1 && result[p+1] == result[p] && target[p+1] == target[p])) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
t.Errorf("inequality at %v", p)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -242,6 +242,8 @@ func (raw *RawConfig) ProcessRawConfig(worldState common.WorldState) (local Loca
|
||||||
switch strings.ToLower(raw.BrowserSig) {
|
switch strings.ToLower(raw.BrowserSig) {
|
||||||
case "firefox":
|
case "firefox":
|
||||||
browser = &Firefox{}
|
browser = &Firefox{}
|
||||||
|
case "safari":
|
||||||
|
browser = &Safari{}
|
||||||
case "chrome":
|
case "chrome":
|
||||||
browser = &Chrome{}
|
browser = &Chrome{}
|
||||||
case "steam":
|
case "steam":
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ var singleplexTCPConfig = client.RawConfig{
|
||||||
RemotePort: "9999",
|
RemotePort: "9999",
|
||||||
LocalHost: "127.0.0.1",
|
LocalHost: "127.0.0.1",
|
||||||
LocalPort: "9999",
|
LocalPort: "9999",
|
||||||
BrowserSig: "chrome",
|
BrowserSig: "safari",
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateClientConfigs(rawConfig client.RawConfig, state common.WorldState) (client.LocalConnConfig, client.RemoteConnConfig, client.AuthInfo) {
|
func generateClientConfigs(rawConfig client.RawConfig, state common.WorldState) (client.LocalConnConfig, client.RemoteConnConfig, client.AuthInfo) {
|
||||||
|
|
|
||||||
|
|
@ -31,3 +31,5 @@ pushd cmd/ck-server || exit 1
|
||||||
CGO_ENABLED=0 gox -ldflags "-X main.version=${v}" -os="$os" -arch="$arch" -osarch="$osarch" -output="$output"
|
CGO_ENABLED=0 gox -ldflags "-X main.version=${v}" -os="$os" -arch="$arch" -osarch="$osarch" -output="$output"
|
||||||
mv ck-server-* ../../release
|
mv ck-server-* ../../release
|
||||||
popd
|
popd
|
||||||
|
|
||||||
|
sha256sum release/*
|
||||||
Loading…
Reference in New Issue