ptls transport
This commit is contained in:
parent
8ab2fe6f77
commit
2a991fc7ef
@ -200,6 +200,12 @@ func parseChainNode(ns string) (nodes []gost.Node, err error) {
|
|||||||
tr = gost.FakeTCPTransporter()
|
tr = gost.FakeTCPTransporter()
|
||||||
case "udp":
|
case "udp":
|
||||||
tr = gost.UDPTransporter()
|
tr = gost.UDPTransporter()
|
||||||
|
case "ptls":
|
||||||
|
opts := gost.PTLSOptions{}
|
||||||
|
opts.Host = node.Get("host")
|
||||||
|
opts.BrowserSig = node.Get("browser_sig")
|
||||||
|
opts.Key = node.Get("key")
|
||||||
|
tr = gost.PTLSTransporter(opts)
|
||||||
default:
|
default:
|
||||||
tr = gost.TCPTransporter()
|
tr = gost.TCPTransporter()
|
||||||
}
|
}
|
||||||
@ -513,6 +519,12 @@ func (r *route) GenRouters() ([]router, error) {
|
|||||||
Backlog: node.GetInt("backlog"),
|
Backlog: node.GetInt("backlog"),
|
||||||
QueueSize: node.GetInt("queue"),
|
QueueSize: node.GetInt("queue"),
|
||||||
})
|
})
|
||||||
|
case "ptls":
|
||||||
|
opts := gost.PTLSOptions{}
|
||||||
|
opts.Host = node.Get("host")
|
||||||
|
opts.BrowserSig = node.Get("browser_sig")
|
||||||
|
opts.Key = node.Get("key")
|
||||||
|
ln, err = gost.PTLSListener(node.Addr, opts)
|
||||||
default:
|
default:
|
||||||
ln, err = gost.TCPListener(node.Addr)
|
ln, err = gost.TCPListener(node.Addr)
|
||||||
}
|
}
|
||||||
|
1
node.go
1
node.go
@ -90,6 +90,7 @@ func ParseNode(s string) (node Node, err error) {
|
|||||||
case "ftcp": // fake TCP
|
case "ftcp": // fake TCP
|
||||||
case "dns":
|
case "dns":
|
||||||
case "redu", "redirectu": // UDP tproxy
|
case "redu", "redirectu": // UDP tproxy
|
||||||
|
case "ptls":
|
||||||
default:
|
default:
|
||||||
node.Transport = "tcp"
|
node.Transport = "tcp"
|
||||||
}
|
}
|
||||||
|
915
ptls.go
Normal file
915
ptls.go
Normal file
@ -0,0 +1,915 @@
|
|||||||
|
package gost
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"crypto/aes"
|
||||||
|
"crypto/cipher"
|
||||||
|
"crypto/md5"
|
||||||
|
"crypto/rand"
|
||||||
|
"encoding/binary"
|
||||||
|
"encoding/hex"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/go-log/log"
|
||||||
|
ss_core "github.com/shadowsocks/go-shadowsocks2/core"
|
||||||
|
)
|
||||||
|
|
||||||
|
const TIMESTAMP_TOLERANCE = 180 * time.Second
|
||||||
|
|
||||||
|
const CACHE_CLEAN_INTERVAL = 12 * time.Hour
|
||||||
|
|
||||||
|
type PTLSOptions struct {
|
||||||
|
Key string
|
||||||
|
Host string
|
||||||
|
BrowserSig string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ptlsTransporter struct {
|
||||||
|
tcpTransporter
|
||||||
|
host string
|
||||||
|
browser
|
||||||
|
key [16]byte
|
||||||
|
connCipher ss_core.StreamConnCipher
|
||||||
|
}
|
||||||
|
|
||||||
|
func PTLSTransporter(opts PTLSOptions) Transporter {
|
||||||
|
var browser browser
|
||||||
|
switch opts.BrowserSig {
|
||||||
|
case "chrome":
|
||||||
|
browser = &Chrome{}
|
||||||
|
case "firefox":
|
||||||
|
browser = &Firefox{}
|
||||||
|
default:
|
||||||
|
browser = &Chrome{}
|
||||||
|
}
|
||||||
|
host, _, err := net.SplitHostPort(opts.Host)
|
||||||
|
if err != nil {
|
||||||
|
host = opts.Host
|
||||||
|
}
|
||||||
|
var key [16]byte
|
||||||
|
copy(key[:], evpBytesToKey(opts.Key, 16))
|
||||||
|
cipher, err := ss_core.PickCipher("AES-128-GCM", nil, opts.Key)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return &ptlsTransporter{host: host, browser: browser, key: key, connCipher: cipher}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (tr *ptlsTransporter) Handshake(conn net.Conn, options ...HandshakeOption) (net.Conn, error) {
|
||||||
|
var random [32]byte
|
||||||
|
cryptoRandRead(random[:])
|
||||||
|
authPayload := makeAuthenticationPayload(tr.key, random)
|
||||||
|
ch := tr.browser.composeClientHello(clientHelloFields{
|
||||||
|
random: random[:],
|
||||||
|
sessionId: authPayload.ciphertextWithTag[:32],
|
||||||
|
x25519KeyShare: authPayload.ciphertextWithTag[32:],
|
||||||
|
sni: makeServerName(tr.host),
|
||||||
|
})
|
||||||
|
_, err := conn.Write(ch)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 1024)
|
||||||
|
_, err = readTLS(conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
encrypted := append(buf[11:43], buf[89:121]...)
|
||||||
|
nonce := encrypted[0:12]
|
||||||
|
ciphertextWithTag := encrypted[12:60]
|
||||||
|
_, err = aesGCMDecrypt(nonce, tr.key[:], ciphertextWithTag)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to decrypt: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
// ChangeCipherSpec and EncryptedCert (in the format of application data)
|
||||||
|
_, err = readTLS(conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[ptls] handshake completed")
|
||||||
|
}
|
||||||
|
|
||||||
|
return tr.connCipher.StreamConn(&ptlsConn{Conn: conn}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type ptlsListener struct {
|
||||||
|
net.Listener
|
||||||
|
opts PTLSOptions
|
||||||
|
key [16]byte
|
||||||
|
redirHost string
|
||||||
|
redirPort string
|
||||||
|
closed int32
|
||||||
|
conns chan net.Conn
|
||||||
|
usedRandom sync.Map // map[[32]byte]int64
|
||||||
|
connCipher ss_core.StreamConnCipher
|
||||||
|
}
|
||||||
|
|
||||||
|
func PTLSListener(addr string, opts PTLSOptions) (Listener, error) {
|
||||||
|
tcpln, err := TCPListener(addr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
redirHost, redirPort, err := parseRedirAddr(opts.Host)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
cipher, err := ss_core.PickCipher("AES-128-GCM", nil, opts.Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var key [16]byte
|
||||||
|
copy(key[:], evpBytesToKey(opts.Key, 16))
|
||||||
|
ln := &ptlsListener{
|
||||||
|
Listener: tcpln,
|
||||||
|
opts: opts, key: key,
|
||||||
|
redirHost: redirHost,
|
||||||
|
redirPort: redirPort,
|
||||||
|
conns: make(chan net.Conn),
|
||||||
|
connCipher: cipher,
|
||||||
|
}
|
||||||
|
go ln.acceptLoop()
|
||||||
|
return ln, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *ptlsListener) handshake(conn net.Conn) (net.Conn, error) {
|
||||||
|
firstPacket, err := readFirstPacket(conn)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("failed to read first packet: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
ch, err := parseClientHello(firstPacket)
|
||||||
|
if err != nil {
|
||||||
|
go ln.redirect(conn, firstPacket)
|
||||||
|
return nil, fmt.Errorf("non (or malformed) ClientHello: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := ln.auth(ch, time.Now()); err != nil {
|
||||||
|
go ln.redirect(conn, firstPacket)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
reply, err := composeReply(ch, ln.key[:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to compose TLS reply: %s", err)
|
||||||
|
}
|
||||||
|
conn.Write(reply)
|
||||||
|
if err != nil {
|
||||||
|
conn.Close()
|
||||||
|
return nil, fmt.Errorf("failed to write TLS reploy: %s", err)
|
||||||
|
}
|
||||||
|
if Debug {
|
||||||
|
log.Logf("[ptls] handshake completed")
|
||||||
|
}
|
||||||
|
return ln.connCipher.StreamConn(&ptlsConn{Conn: conn}), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *ptlsListener) auth(ch *ClientHello, serverTime time.Time) error {
|
||||||
|
authPayload, err := unmarshalClientHello(ch, ln.key)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal ClientHello into authenticationPayload")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, loaded := ln.usedRandom.LoadOrStore(authPayload.random, time.Now().Unix())
|
||||||
|
if loaded {
|
||||||
|
return fmt.Errorf("duplicate random")
|
||||||
|
}
|
||||||
|
|
||||||
|
plaintext, err := aesGCMDecrypt(authPayload.random[0:12], ln.key[:], authPayload.ciphertextWithTag[:])
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
timestamp := int64(binary.BigEndian.Uint64(plaintext[1:9]))
|
||||||
|
clientTime := time.Unix(timestamp, 0)
|
||||||
|
if !(clientTime.After(serverTime.Truncate(TIMESTAMP_TOLERANCE)) && clientTime.Before(serverTime.Add(TIMESTAMP_TOLERANCE))) {
|
||||||
|
return fmt.Errorf("timestamp is outside of the accepting window: received timestamp %d", timestamp)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *ptlsListener) cleanupRandoms(deadline time.Time) {
|
||||||
|
|
||||||
|
ln.usedRandom.Range(func(key, value interface{}) bool {
|
||||||
|
ts := value.(int64)
|
||||||
|
if ts < deadline.Unix() {
|
||||||
|
ln.usedRandom.Delete(key)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *ptlsListener) redirect(conn net.Conn, firstPacket []byte) {
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
redirPort := ln.redirPort
|
||||||
|
if redirPort == "" {
|
||||||
|
_, redirPort, _ = net.SplitHostPort(conn.LocalAddr().String())
|
||||||
|
}
|
||||||
|
redirConn, err := net.Dial("tcp", net.JoinHostPort(ln.redirHost, redirPort))
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[ptls] Making connection to redirection server: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer redirConn.Close()
|
||||||
|
|
||||||
|
_, err = redirConn.Write(firstPacket)
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[ptls] Failed to send first packet to redirection server: %s", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
transport(conn, redirConn)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *ptlsListener) Accept() (c net.Conn, err error) {
|
||||||
|
return <-ln.conns, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ln *ptlsListener) acceptLoop() {
|
||||||
|
nextRandomCleanupAt := time.Now().Add(CACHE_CLEAN_INTERVAL)
|
||||||
|
|
||||||
|
for {
|
||||||
|
if time.Now().After(nextRandomCleanupAt) {
|
||||||
|
ln.cleanupRandoms(nextRandomCleanupAt.Add(-CACHE_CLEAN_INTERVAL))
|
||||||
|
nextRandomCleanupAt = nextRandomCleanupAt.Add(CACHE_CLEAN_INTERVAL)
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, err := ln.Listener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
if ne, ok := err.(net.Error); ok && ne.Temporary() {
|
||||||
|
delay := 50 * time.Millisecond
|
||||||
|
log.Logf("[ptls] accept error: %v; retrying in %v", ne, delay)
|
||||||
|
time.Sleep(delay)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if atomic.CompareAndSwapInt32(&ln.closed, 0, 1) {
|
||||||
|
close(ln.conns)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go func(conn net.Conn) {
|
||||||
|
preparedConn, err := ln.handshake(conn)
|
||||||
|
if err != nil {
|
||||||
|
log.Logf("[ptls] failed to handshake conn from %s: %s", conn.RemoteAddr().String(), err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if atomic.LoadInt32(&ln.closed) != 0 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
ln.conns <- preparedConn
|
||||||
|
}(conn)
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ptlsConn struct {
|
||||||
|
net.Conn
|
||||||
|
buffer bytes.Buffer
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ptlsConn) Write(b []byte) (int, error) {
|
||||||
|
record := addRecordLayer(b, []byte{0x17}, []byte{0x03, 0x03})
|
||||||
|
n, err := c.Conn.Write(record)
|
||||||
|
if err != nil {
|
||||||
|
n = 0
|
||||||
|
} else {
|
||||||
|
n = len(b)
|
||||||
|
}
|
||||||
|
return n, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *ptlsConn) Read(b []byte) (int, error) {
|
||||||
|
if c.buffer.Len() > 0 {
|
||||||
|
return c.buffer.Read(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, 5+65536)
|
||||||
|
for {
|
||||||
|
n, err := readTLS(c.Conn, buf)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
if buf[0] != 0x17 {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
data := buf[5:n]
|
||||||
|
|
||||||
|
copied := copy(b, data[:n])
|
||||||
|
if copied < len(data) {
|
||||||
|
c.buffer.Write(data[copied:])
|
||||||
|
}
|
||||||
|
|
||||||
|
return copied, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// func (c *ptlsConn) Close() error {
|
||||||
|
// record := addRecordLayer(make([]byte, 26), []byte{0x15}, []byte{0x03, 0x03})
|
||||||
|
// c.Conn.Write(record)
|
||||||
|
// return c.Conn.Close()
|
||||||
|
// }
|
||||||
|
|
||||||
|
func readFirstPacket(c net.Conn) ([]byte, error) {
|
||||||
|
buf := make([]byte, 1500)
|
||||||
|
c.SetReadDeadline(time.Now().Add(3 * time.Second))
|
||||||
|
i, err := io.ReadAtLeast(c, buf, 1)
|
||||||
|
c.SetReadDeadline(time.Time{})
|
||||||
|
return buf[:i], err
|
||||||
|
}
|
||||||
|
|
||||||
|
type authenticationPayload struct {
|
||||||
|
random [32]byte
|
||||||
|
ciphertextWithTag [64]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeAuthenticationPayload(key [16]byte, random [32]byte) (payload authenticationPayload) {
|
||||||
|
/*
|
||||||
|
Version: 0
|
||||||
|
|
||||||
|
Authentication data (48 bytes):
|
||||||
|
+--------------+-------------+------------+
|
||||||
|
| _Version_ | _Timestamp_ | _reserved_ |
|
||||||
|
+--------------+-------------+------------+
|
||||||
|
| 1 byte (0x0) | 8 bytes | 39 bytes |
|
||||||
|
+--------------+-------------+------------+
|
||||||
|
*/
|
||||||
|
timestamp := uint64(time.Now().Unix())
|
||||||
|
|
||||||
|
plaintext := make([]byte, 48)
|
||||||
|
plaintext[0] = 0x0
|
||||||
|
binary.BigEndian.PutUint64(plaintext[1:9], timestamp)
|
||||||
|
ciphertextWithTag, _ := aesGCMEncrypt(random[:12], key[:], plaintext)
|
||||||
|
copy(payload.ciphertextWithTag[:], ciphertextWithTag[:])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func unmarshalClientHello(ch *ClientHello, key [16]byte) (payload authenticationPayload, err error) {
|
||||||
|
keyShare, err := parseKeyShare(ch.extensions[[2]byte{0x00, 0x33}])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
ciphertextWithTag := append(ch.sessionId, keyShare...)
|
||||||
|
if len(ciphertextWithTag) != 64 {
|
||||||
|
err = fmt.Errorf("ciphertext has the wrong length: %d", len(ciphertextWithTag))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
copy(payload.ciphertextWithTag[:], ciphertextWithTag)
|
||||||
|
copy(payload.random[:], ch.random)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
type clientHelloFields struct {
|
||||||
|
random []byte
|
||||||
|
sessionId []byte
|
||||||
|
x25519KeyShare []byte
|
||||||
|
sni []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type browser interface {
|
||||||
|
composeClientHello(clientHelloFields) []byte
|
||||||
|
}
|
||||||
|
|
||||||
|
type Chrome struct{}
|
||||||
|
|
||||||
|
func makeGREASE() []byte {
|
||||||
|
// see https://tools.ietf.org/html/draft-davidben-tls-grease-01
|
||||||
|
// This is exclusive to Chrome.
|
||||||
|
var one [1]byte
|
||||||
|
cryptoRandRead(one[:])
|
||||||
|
sixteenth := one[0] % 16
|
||||||
|
monoGREASE := byte(sixteenth*16 + 0xA)
|
||||||
|
doubleGREASE := []byte{monoGREASE, monoGREASE}
|
||||||
|
return doubleGREASE
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chrome) composeExtensions(sni []byte, keyShare []byte) []byte {
|
||||||
|
|
||||||
|
makeSupportedGroups := func() []byte {
|
||||||
|
suppGroupListLen := []byte{0x00, 0x08}
|
||||||
|
ret := make([]byte, 2+8)
|
||||||
|
copy(ret[0:2], suppGroupListLen)
|
||||||
|
copy(ret[2:4], makeGREASE())
|
||||||
|
copy(ret[4:], []byte{0x00, 0x1d, 0x00, 0x17, 0x00, 0x18})
|
||||||
|
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 401, 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
|
||||||
|
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
|
||||||
|
sigAlgo, _ := hex.DecodeString("0012040308040401050308050501080606010201")
|
||||||
|
ext[9] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
|
||||||
|
ext[10] = addExtRec([]byte{0x00, 0x12}, nil) // signed cert timestamp
|
||||||
|
ext[11] = addExtRec([]byte{0x00, 0x33}, makeKeyShare(keyShare)) // key share
|
||||||
|
ext[12] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes
|
||||||
|
suppVersions, _ := hex.DecodeString("0a9A9A0304030303020301") // 9A9A needs to be a GREASE
|
||||||
|
copy(suppVersions[1:3], makeGREASE())
|
||||||
|
ext[13] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions
|
||||||
|
ext[14] = addExtRec([]byte{0x00, 0x1b}, []byte{0x02, 0x00, 0x02})
|
||||||
|
ext[15] = addExtRec(makeGREASE(), []byte{0x00}) // Last GREASE
|
||||||
|
// len(ext[1]) + 172 + len(ext[16]) = 401
|
||||||
|
// len(ext[16]) = 229 - len(ext[1])
|
||||||
|
// 2+2+len(padding) = 229 - len(ext[1])
|
||||||
|
// len(padding) = 225 - len(ext[1])
|
||||||
|
ext[16] = addExtRec([]byte{0x00, 0x15}, make([]byte, 225-len(ext[1]))) // padding
|
||||||
|
var ret []byte
|
||||||
|
for _, e := range ext {
|
||||||
|
ret = append(ret, e...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Chrome) 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, 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(hd.sni, hd.x25519KeyShare)
|
||||||
|
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 addRecordLayer(ret, []byte{0x16}, []byte{0x03, 0x01})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Firefox struct{}
|
||||||
|
|
||||||
|
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
|
||||||
|
ret[2], ret[3] = 0x00, 0x1d // group x25519
|
||||||
|
ret[4], ret[5] = 0x00, 0x20 // length 32
|
||||||
|
copy(ret[6:38], hidden)
|
||||||
|
ret[38], ret[39] = 0x00, 0x17 // group secp256r1
|
||||||
|
ret[40], ret[41] = 0x00, 0x41 // length 65
|
||||||
|
cryptoRandRead(ret[42:107])
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
// extension length is always 399, and server name length is variable
|
||||||
|
var ext [14][]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
|
||||||
|
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
|
||||||
|
ext[5] = addExtRec([]byte{0x00, 0x23}, []byte{}) // Session tickets
|
||||||
|
APLN, _ := hex.DecodeString("000c02683208687474702f312e31")
|
||||||
|
ext[6] = addExtRec([]byte{0x00, 0x10}, APLN) // app layer proto negotiation
|
||||||
|
ext[7] = addExtRec([]byte{0x00, 0x05}, []byte{0x01, 0x00, 0x00, 0x00, 0x00}) // status request
|
||||||
|
ext[8] = addExtRec([]byte{0x00, 0x33}, composeKeyShare(keyShare)) // key share
|
||||||
|
suppVersions, _ := hex.DecodeString("080304030303020301")
|
||||||
|
ext[9] = addExtRec([]byte{0x00, 0x2b}, suppVersions) // supported versions
|
||||||
|
sigAlgo, _ := hex.DecodeString("001604030503060308040805080604010501060102030201")
|
||||||
|
ext[10] = addExtRec([]byte{0x00, 0x0d}, sigAlgo) // Signature Algorithms
|
||||||
|
ext[11] = addExtRec([]byte{0x00, 0x2d}, []byte{0x01, 0x01}) // psk key exchange modes
|
||||||
|
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, 163-len(SNI))) // padding
|
||||||
|
var ret []byte
|
||||||
|
for _, e := range ext {
|
||||||
|
ret = append(ret, e...)
|
||||||
|
}
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Firefox) 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, 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(hd.sni, hd.x25519KeyShare)
|
||||||
|
clientHello[10] = []byte{0x00, 0x00} // extensions length
|
||||||
|
binary.BigEndian.PutUint16(clientHello[10], uint16(len(clientHello[11])))
|
||||||
|
|
||||||
|
var ret []byte
|
||||||
|
for _, c := range clientHello {
|
||||||
|
ret = append(ret, c...)
|
||||||
|
}
|
||||||
|
return addRecordLayer(ret, []byte{0x16}, []byte{0x03, 0x01})
|
||||||
|
}
|
||||||
|
|
||||||
|
func makeServerName(serverName string) []byte {
|
||||||
|
serverNameListLength := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(serverNameListLength, uint16(len(serverName)+3))
|
||||||
|
serverNameType := []byte{0x00} // host_name
|
||||||
|
serverNameLength := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(serverNameLength, uint16(len(serverName)))
|
||||||
|
ret := make([]byte, 2+1+2+len(serverName))
|
||||||
|
copy(ret[0:2], serverNameListLength)
|
||||||
|
copy(ret[2:3], serverNameType)
|
||||||
|
copy(ret[3:5], serverNameLength)
|
||||||
|
copy(ret[5:], serverName)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientHello struct {
|
||||||
|
handshakeType byte
|
||||||
|
length int
|
||||||
|
clientVersion []byte
|
||||||
|
random []byte
|
||||||
|
sessionIdLen int
|
||||||
|
sessionId []byte
|
||||||
|
cipherSuitesLen int
|
||||||
|
cipherSuites []byte
|
||||||
|
compressionMethodsLen int
|
||||||
|
compressionMethods []byte
|
||||||
|
extensionsLen int
|
||||||
|
extensions map[[2]byte][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseClientHello parses everything on top of the TLS layer
|
||||||
|
// (including the record layer) into ClientHello type
|
||||||
|
func parseClientHello(data []byte) (ret *ClientHello, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = errors.New("Malformed ClientHello")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if !bytes.Equal(data[0:3], []byte{0x16, 0x03, 0x01}) {
|
||||||
|
return ret, errors.New("wrong TLS1.3 handshake magic bytes")
|
||||||
|
}
|
||||||
|
|
||||||
|
peeled := make([]byte, len(data)-5)
|
||||||
|
copy(peeled, data[5:])
|
||||||
|
pointer := 0
|
||||||
|
// Handshake Type
|
||||||
|
handshakeType := peeled[pointer]
|
||||||
|
if handshakeType != 0x01 {
|
||||||
|
return ret, errors.New("Not a ClientHello")
|
||||||
|
}
|
||||||
|
pointer += 1
|
||||||
|
// Length
|
||||||
|
length := int(binary.BigEndian.Uint32(append([]byte{0x00}, peeled[pointer:pointer+3]...)))
|
||||||
|
pointer += 3
|
||||||
|
if length != len(peeled[pointer:]) {
|
||||||
|
return ret, errors.New("Hello length doesn't match")
|
||||||
|
}
|
||||||
|
// Client Version
|
||||||
|
clientVersion := peeled[pointer : pointer+2]
|
||||||
|
pointer += 2
|
||||||
|
// Random
|
||||||
|
random := peeled[pointer : pointer+32]
|
||||||
|
pointer += 32
|
||||||
|
// Session ID
|
||||||
|
sessionIdLen := int(peeled[pointer])
|
||||||
|
pointer += 1
|
||||||
|
sessionId := peeled[pointer : pointer+sessionIdLen]
|
||||||
|
pointer += sessionIdLen
|
||||||
|
// Cipher Suites
|
||||||
|
cipherSuitesLen := int(binary.BigEndian.Uint16(peeled[pointer : pointer+2]))
|
||||||
|
pointer += 2
|
||||||
|
cipherSuites := peeled[pointer : pointer+cipherSuitesLen]
|
||||||
|
pointer += cipherSuitesLen
|
||||||
|
// Compression Methods
|
||||||
|
compressionMethodsLen := int(peeled[pointer])
|
||||||
|
pointer += 1
|
||||||
|
compressionMethods := peeled[pointer : pointer+compressionMethodsLen]
|
||||||
|
pointer += compressionMethodsLen
|
||||||
|
// Extensions
|
||||||
|
extensionsLen := int(binary.BigEndian.Uint16(peeled[pointer : pointer+2]))
|
||||||
|
pointer += 2
|
||||||
|
extensions, err := parseExtensions(peeled[pointer:])
|
||||||
|
ret = &ClientHello{
|
||||||
|
handshakeType,
|
||||||
|
length,
|
||||||
|
clientVersion,
|
||||||
|
random,
|
||||||
|
sessionIdLen,
|
||||||
|
sessionId,
|
||||||
|
cipherSuitesLen,
|
||||||
|
cipherSuites,
|
||||||
|
compressionMethodsLen,
|
||||||
|
compressionMethods,
|
||||||
|
extensionsLen,
|
||||||
|
extensions,
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseExtensions(input []byte) (ret map[[2]byte][]byte, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = errors.New("Malformed Extensions")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
pointer := 0
|
||||||
|
totalLen := len(input)
|
||||||
|
ret = make(map[[2]byte][]byte)
|
||||||
|
for pointer < totalLen {
|
||||||
|
var typ [2]byte
|
||||||
|
copy(typ[:], input[pointer:pointer+2])
|
||||||
|
pointer += 2
|
||||||
|
length := int(binary.BigEndian.Uint16(input[pointer : pointer+2]))
|
||||||
|
pointer += 2
|
||||||
|
data := input[pointer : pointer+length]
|
||||||
|
pointer += length
|
||||||
|
ret[typ] = data
|
||||||
|
}
|
||||||
|
return ret, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func composeServerHello(sessionId []byte, sharedSecret []byte) ([]byte, error) {
|
||||||
|
nonce := make([]byte, 12)
|
||||||
|
cryptoRandRead(nonce)
|
||||||
|
|
||||||
|
zeros := make([]byte, 32)
|
||||||
|
encryptedKey, err := aesGCMEncrypt(nonce, sharedSecret, zeros) // 32 + 16 = 48 bytes
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
var serverHello [11][]byte
|
||||||
|
serverHello[0] = []byte{0x02} // handshake type
|
||||||
|
serverHello[1] = []byte{0x00, 0x00, 0x76} // length 77
|
||||||
|
serverHello[2] = []byte{0x03, 0x03} // server version
|
||||||
|
serverHello[3] = append(nonce[0:12], encryptedKey[0:20]...) // random 32 bytes
|
||||||
|
serverHello[4] = []byte{0x20} // session id length 32
|
||||||
|
serverHello[5] = sessionId // session id
|
||||||
|
serverHello[6] = []byte{0xc0, 0x30} // cipher suite TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
|
||||||
|
serverHello[7] = []byte{0x00} // compression method null
|
||||||
|
serverHello[8] = []byte{0x00, 0x2e} // extensions length 46
|
||||||
|
|
||||||
|
keyShare, _ := hex.DecodeString("00330024001d0020")
|
||||||
|
keyExchange := make([]byte, 32)
|
||||||
|
copy(keyExchange, encryptedKey[20:48])
|
||||||
|
cryptoRandRead(keyExchange[28:32])
|
||||||
|
serverHello[9] = append(keyShare, keyExchange...)
|
||||||
|
|
||||||
|
serverHello[10], _ = hex.DecodeString("002b00020304")
|
||||||
|
var ret []byte
|
||||||
|
for _, s := range serverHello {
|
||||||
|
ret = append(ret, s...)
|
||||||
|
}
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// composeReply composes the ServerHello, ChangeCipherSpec and an ApplicationData messages
|
||||||
|
// together with their respective record layers into one byte slice.
|
||||||
|
func composeReply(ch *ClientHello, sharedSecret []byte) ([]byte, error) {
|
||||||
|
TLS12 := []byte{0x03, 0x03}
|
||||||
|
sh, err := composeServerHello(ch.sessionId, sharedSecret)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
shBytes := addRecordLayer(sh, []byte{0x16}, TLS12)
|
||||||
|
ccsBytes := addRecordLayer([]byte{0x01}, []byte{0x14}, TLS12)
|
||||||
|
cert := make([]byte, 68) // TODO: add some different lengths maybe?
|
||||||
|
cryptoRandRead(cert)
|
||||||
|
encryptedCertBytes := addRecordLayer(cert, []byte{0x17}, TLS12)
|
||||||
|
ret := append(shBytes, ccsBytes...)
|
||||||
|
ret = append(ret, encryptedCertBytes...)
|
||||||
|
return ret, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cryptoRandRead(buf []byte) {
|
||||||
|
_, err := rand.Read(buf)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
waitDur := [10]time.Duration{5 * time.Millisecond, 10 * time.Millisecond, 30 * time.Millisecond, 50 * time.Millisecond,
|
||||||
|
100 * time.Millisecond, 300 * time.Millisecond, 500 * time.Millisecond, 1 * time.Second,
|
||||||
|
3 * time.Second, 5 * time.Second}
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
log.Logf("Failed to get cryptographic random bytes: %s. Retrying...", err)
|
||||||
|
_, err = rand.Read(buf)
|
||||||
|
if err == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
time.Sleep(time.Millisecond * waitDur[i])
|
||||||
|
}
|
||||||
|
panic("Cannot get cryptographic random bytes after 10 retries")
|
||||||
|
}
|
||||||
|
|
||||||
|
func addExtRec(typ []byte, data []byte) []byte {
|
||||||
|
length := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(length, uint16(len(data)))
|
||||||
|
ret := make([]byte, 2+2+len(data))
|
||||||
|
copy(ret[0:2], typ)
|
||||||
|
copy(ret[2:4], length)
|
||||||
|
copy(ret[4:], data)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func md5sum(d []byte) []byte {
|
||||||
|
h := md5.New()
|
||||||
|
h.Write(d)
|
||||||
|
return h.Sum(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func evpBytesToKey(password string, keyLen int) (key []byte) {
|
||||||
|
const md5Len = 16
|
||||||
|
|
||||||
|
cnt := (keyLen-1)/md5Len + 1
|
||||||
|
m := make([]byte, cnt*md5Len)
|
||||||
|
copy(m, md5sum([]byte(password)))
|
||||||
|
|
||||||
|
// Repeatedly call md5 until bytes generated is enough.
|
||||||
|
// Each call to md5 uses data: prev md5 sum + password.
|
||||||
|
d := make([]byte, md5Len+len(password))
|
||||||
|
start := 0
|
||||||
|
for i := 1; i < cnt; i++ {
|
||||||
|
start += md5Len
|
||||||
|
copy(d, m[start-md5Len:start])
|
||||||
|
copy(d[md5Len:], password)
|
||||||
|
copy(m[start:], md5sum(d))
|
||||||
|
}
|
||||||
|
return m[:keyLen]
|
||||||
|
}
|
||||||
|
|
||||||
|
func aesGCMEncrypt(nonce []byte, key []byte, plaintext []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aesgcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return aesgcm.Seal(nil, nonce, plaintext, nil), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func aesGCMDecrypt(nonce []byte, key []byte, ciphertext []byte) ([]byte, error) {
|
||||||
|
block, err := aes.NewCipher(key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
aesgcm, err := cipher.NewGCM(block)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
plain, err := aesgcm.Open(nil, nonce, ciphertext, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return plain, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func addRecordLayer(input []byte, typ []byte, ver []byte) []byte {
|
||||||
|
length := make([]byte, 2)
|
||||||
|
binary.BigEndian.PutUint16(length, uint16(len(input)))
|
||||||
|
ret := make([]byte, 5+len(input))
|
||||||
|
copy(ret[0:1], typ)
|
||||||
|
copy(ret[1:3], ver)
|
||||||
|
copy(ret[3:5], length)
|
||||||
|
copy(ret[5:], input)
|
||||||
|
return ret
|
||||||
|
}
|
||||||
|
|
||||||
|
func readTLS(conn net.Conn, buffer []byte) (n int, err error) {
|
||||||
|
// TCP is a stream. Multiple TLS messages can arrive at the same time,
|
||||||
|
// a single message can also be segmented due to MTU of the IP layer.
|
||||||
|
// This function guareentees a single TLS message to be read and everything
|
||||||
|
// else is left in the buffer.
|
||||||
|
i, err := io.ReadFull(conn, buffer[:5])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dataLength := int(binary.BigEndian.Uint16(buffer[3:5]))
|
||||||
|
if dataLength > len(buffer) {
|
||||||
|
err = errors.New("Reading TLS message: message size greater than buffer. message size: " + strconv.Itoa(dataLength))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
left := dataLength
|
||||||
|
readPtr := 5
|
||||||
|
|
||||||
|
for left != 0 {
|
||||||
|
// If left > buffer size (i.e. our message got segmented), the entire MTU is read
|
||||||
|
// if left = buffer size, the entire buffer is all there left to read
|
||||||
|
// if left < buffer size (i.e. multiple messages came together),
|
||||||
|
// only the message we want is read
|
||||||
|
|
||||||
|
i, err = conn.Read(buffer[readPtr : readPtr+left])
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
left -= i
|
||||||
|
readPtr += i
|
||||||
|
}
|
||||||
|
|
||||||
|
n = 5 + dataLength
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseKeyShare(input []byte) (ret []byte, err error) {
|
||||||
|
defer func() {
|
||||||
|
if r := recover(); r != nil {
|
||||||
|
err = errors.New("malformed key_share")
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
totalLen := int(binary.BigEndian.Uint16(input[0:2]))
|
||||||
|
// 2 bytes "client key share length"
|
||||||
|
pointer := 2
|
||||||
|
for pointer < totalLen {
|
||||||
|
if bytes.Equal([]byte{0x00, 0x1d}, input[pointer:pointer+2]) {
|
||||||
|
// skip "key exchange length"
|
||||||
|
pointer += 2
|
||||||
|
length := int(binary.BigEndian.Uint16(input[pointer : pointer+2]))
|
||||||
|
pointer += 2
|
||||||
|
if length != 32 {
|
||||||
|
return nil, fmt.Errorf("key share length should be 32, instead of %v", length)
|
||||||
|
}
|
||||||
|
return input[pointer : pointer+length], nil
|
||||||
|
}
|
||||||
|
pointer += 2
|
||||||
|
length := int(binary.BigEndian.Uint16(input[pointer : pointer+2]))
|
||||||
|
pointer += 2
|
||||||
|
_ = input[pointer : pointer+length]
|
||||||
|
pointer += length
|
||||||
|
}
|
||||||
|
return nil, errors.New("x25519 does not exist")
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseRedirAddr(redirAddr string) (string, string, error) {
|
||||||
|
var host string
|
||||||
|
var port string
|
||||||
|
colonSep := strings.Split(redirAddr, ":")
|
||||||
|
if len(colonSep) > 1 {
|
||||||
|
if len(colonSep) == 2 {
|
||||||
|
// domain or ipv4 with port
|
||||||
|
host = colonSep[0]
|
||||||
|
port = colonSep[1]
|
||||||
|
} else {
|
||||||
|
if strings.Contains(redirAddr, "[") {
|
||||||
|
// ipv6 with port
|
||||||
|
port = colonSep[len(colonSep)-1]
|
||||||
|
host = strings.TrimSuffix(redirAddr, "]:"+port)
|
||||||
|
host = strings.TrimPrefix(host, "[")
|
||||||
|
} else {
|
||||||
|
// ipv6 without port
|
||||||
|
host = redirAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// domain or ipv4 without port
|
||||||
|
host = redirAddr
|
||||||
|
}
|
||||||
|
|
||||||
|
redirHost, err := net.ResolveIPAddr("ip", host)
|
||||||
|
if err != nil {
|
||||||
|
return "", "", fmt.Errorf("unable to resolve RedirAddr: %v. ", err)
|
||||||
|
}
|
||||||
|
return redirHost.String(), port, nil
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user