mirror of https://github.com/cbeuw/Cloak
Add Safari browser signature
parent
641f6b2a9c
commit
bc67074610
@ -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,42 @@
|
||||
package client
|
||||
|
||||
import (
|
||||
"github.com/cbeuw/Cloak/internal/common"
|
||||
"github.com/dreadl0ck/ja3"
|
||||
"github.com/dreadl0ck/tlsx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"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)
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue