mihomo/component/tls/reality.go

192 lines
5.2 KiB
Go
Raw Permalink Normal View History

2023-03-08 17:18:46 +08:00
package tls
import (
"bytes"
"context"
"crypto/aes"
"crypto/cipher"
2023-12-09 12:28:19 +08:00
"crypto/ecdh"
2023-03-08 17:18:46 +08:00
"crypto/ed25519"
"crypto/hmac"
"crypto/sha256"
"crypto/sha512"
"crypto/tls"
"crypto/x509"
"encoding/binary"
"errors"
"net"
"net/http"
"reflect"
"strings"
"time"
"unsafe"
2023-11-03 21:01:45 +08:00
"github.com/metacubex/mihomo/common/utils"
"github.com/metacubex/mihomo/log"
"github.com/metacubex/mihomo/ntp"
2023-03-08 17:18:46 +08:00
utls "github.com/sagernet/utls"
"github.com/zhangyunhao116/fastrand"
"golang.org/x/crypto/chacha20poly1305"
2023-03-08 17:18:46 +08:00
"golang.org/x/crypto/curve25519"
"golang.org/x/crypto/hkdf"
"golang.org/x/net/http2"
)
2023-03-11 12:23:27 +08:00
const RealityMaxShortIDLen = 8
2023-03-08 17:18:46 +08:00
type RealityConfig struct {
2023-03-08 20:28:12 +08:00
PublicKey [curve25519.ScalarSize]byte
2023-03-11 12:23:27 +08:00
ShortID [RealityMaxShortIDLen]byte
2023-03-08 17:18:46 +08:00
}
//go:linkname aesgcmPreferred crypto/tls.aesgcmPreferred
func aesgcmPreferred(ciphers []uint16) bool
2023-03-08 17:18:46 +08:00
func GetRealityConn(ctx context.Context, conn net.Conn, ClientFingerprint string, tlsConfig *tls.Config, realityConfig *RealityConfig) (net.Conn, error) {
2023-10-23 23:33:44 +08:00
retry := 0
for fingerprint, exists := GetFingerprint(ClientFingerprint); exists; retry++ {
2023-03-08 17:18:46 +08:00
verifier := &realityVerifier{
2023-03-08 20:28:12 +08:00
serverName: tlsConfig.ServerName,
}
uConfig := &utls.Config{
ServerName: tlsConfig.ServerName,
InsecureSkipVerify: true,
SessionTicketsDisabled: true,
VerifyPeerCertificate: verifier.VerifyPeerCertificate,
2023-03-08 17:18:46 +08:00
}
clientID := utls.ClientHelloID{
Client: fingerprint.Client,
Version: fingerprint.Version,
Seed: fingerprint.Seed,
}
uConn := utls.UClient(conn, uConfig, clientID)
verifier.UConn = uConn
err := uConn.BuildHandshakeState()
if err != nil {
return nil, err
}
hello := uConn.HandshakeState.Hello
rawSessionID := hello.Raw[39 : 39+32] // the location of session ID
for i := range rawSessionID { // https://github.com/golang/go/issues/5373
rawSessionID[i] = 0
2023-03-15 15:55:18 +08:00
}
2023-03-08 17:18:46 +08:00
2023-09-01 03:11:35 +08:00
binary.BigEndian.PutUint64(hello.SessionId, uint64(ntp.Now().Unix()))
2023-03-08 17:18:46 +08:00
copy(hello.SessionId[8:], realityConfig.ShortID[:])
2023-03-08 17:18:46 +08:00
hello.SessionId[0] = 1
2023-03-22 23:45:26 +08:00
hello.SessionId[1] = 8
hello.SessionId[2] = 2
2023-03-08 17:18:46 +08:00
//log.Debugln("REALITY hello.sessionId[:16]: %v", hello.SessionId[:16])
2023-12-09 12:28:19 +08:00
publicKey, err := ecdh.X25519().NewPublicKey(realityConfig.PublicKey[:])
if err != nil {
return nil, err
}
ecdheKey := uConn.HandshakeState.State13.EcdheKey
if ecdheKey == nil {
2023-10-23 23:33:44 +08:00
// WTF???
if retry > 2 {
2023-12-09 12:28:19 +08:00
return nil, errors.New("nil ecdheKey")
2023-10-23 23:33:44 +08:00
}
continue // retry
}
2023-12-09 12:28:19 +08:00
authKey, err := ecdheKey.ECDH(publicKey)
if err != nil {
return nil, err
}
2023-03-08 17:18:46 +08:00
if authKey == nil {
return nil, errors.New("nil auth_key")
}
verifier.authKey = authKey
_, err = hkdf.New(sha256.New, authKey, hello.Random[:20], []byte("REALITY")).Read(authKey)
if err != nil {
return nil, err
}
var aeadCipher cipher.AEAD
if aesgcmPreferred(hello.CipherSuites) {
aesBlock, _ := aes.NewCipher(authKey)
aeadCipher, _ = cipher.NewGCM(aesBlock)
} else {
aeadCipher, _ = chacha20poly1305.New(authKey)
}
aeadCipher.Seal(hello.SessionId[:0], hello.Random[20:], hello.SessionId[:16], hello.Raw)
2023-03-08 17:18:46 +08:00
copy(hello.Raw[39:], hello.SessionId)
//log.Debugln("REALITY hello.sessionId: %v", hello.SessionId)
//log.Debugln("REALITY uConn.AuthKey: %v", authKey)
err = uConn.HandshakeContext(ctx)
if err != nil {
return nil, err
}
log.Debugln("REALITY Authentication: %v, AEAD: %T", verifier.verified, aeadCipher)
2023-03-08 17:18:46 +08:00
if !verifier.verified {
go realityClientFallback(uConn, uConfig.ServerName, clientID)
return nil, errors.New("REALITY authentication failed")
}
return uConn, nil
}
return nil, errors.New("unknown uTLS fingerprint")
}
func realityClientFallback(uConn net.Conn, serverName string, fingerprint utls.ClientHelloID) {
defer uConn.Close()
2023-03-11 12:23:27 +08:00
client := http.Client{
2023-03-08 17:18:46 +08:00
Transport: &http2.Transport{
DialTLSContext: func(ctx context.Context, network, addr string, config *tls.Config) (net.Conn, error) {
return uConn, nil
},
},
}
request, _ := http.NewRequest("GET", "https://"+serverName, nil)
request.Header.Set("User-Agent", fingerprint.Client)
request.AddCookie(&http.Cookie{Name: "padding", Value: strings.Repeat("0", fastrand.Intn(32)+30)})
response, err := client.Do(request)
if err != nil {
return
}
//_, _ = io.Copy(io.Discard, response.Body)
2023-03-15 15:55:18 +08:00
time.Sleep(time.Duration(5+fastrand.Int63n(10)) * time.Second)
2023-03-08 17:18:46 +08:00
response.Body.Close()
client.CloseIdleConnections()
}
type realityVerifier struct {
*utls.UConn
serverName string
authKey []byte
verified bool
}
var pOffset = utils.MustOK(reflect.TypeOf((*utls.Conn)(nil)).Elem().FieldByName("peerCertificates")).Offset
2023-03-11 12:23:27 +08:00
2023-03-08 17:18:46 +08:00
func (c *realityVerifier) VerifyPeerCertificate(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
2023-03-11 12:23:27 +08:00
//p, _ := reflect.TypeOf(c.Conn).Elem().FieldByName("peerCertificates")
2023-05-06 15:49:10 +08:00
certs := *(*[]*x509.Certificate)(unsafe.Add(unsafe.Pointer(c.Conn), pOffset))
2023-03-08 17:18:46 +08:00
if pub, ok := certs[0].PublicKey.(ed25519.PublicKey); ok {
h := hmac.New(sha512.New, c.authKey)
h.Write(pub)
if bytes.Equal(h.Sum(nil), certs[0].Signature) {
c.verified = true
return nil
}
}
opts := x509.VerifyOptions{
DNSName: c.serverName,
Intermediates: x509.NewCertPool(),
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
if _, err := certs[0].Verify(opts); err != nil {
return err
}
return nil
}