feat(juicity): support certificate pinning (#256)

This commit is contained in:
mzz
2023-08-04 18:19:28 +08:00
committed by GitHub
parent 0c8cda4b28
commit 4d615205c0
4 changed files with 77 additions and 50 deletions

View File

@ -8,6 +8,7 @@ package common
import ( import (
"crypto/aes" "crypto/aes"
"crypto/cipher" "crypto/cipher"
"crypto/sha256"
"encoding/base64" "encoding/base64"
"encoding/binary" "encoding/binary"
"encoding/hex" "encoding/hex"
@ -495,3 +496,16 @@ func StringSet(list []string) map[string]struct{} {
} }
return m return m
} }
func GenerateCertChainHash(rawCerts [][]byte) (chainHash []byte) {
for _, cert := range rawCerts {
certHash := sha256.Sum256(cert)
if chainHash == nil {
chainHash = certHash[:]
} else {
newHash := sha256.Sum256(append(chainHash, certHash[:]...))
chainHash = newHash[:]
}
}
return chainHash
}

View File

@ -1,7 +1,10 @@
package juicity package juicity
import ( import (
"bytes"
"crypto/tls" "crypto/tls"
"crypto/x509"
"encoding/base64"
"fmt" "fmt"
"net" "net"
"net/url" "net/url"
@ -14,10 +17,10 @@ import (
) )
func init() { func init() {
dialer.FromLinkRegister("juicity", NewJuice) dialer.FromLinkRegister("juicity", NewJuicity)
} }
type Juice struct { type Juicity struct {
Name string Name string
Server string Server string
Port int Port int
@ -26,30 +29,45 @@ type Juice struct {
Sni string Sni string
AllowInsecure bool AllowInsecure bool
CongestionControl string CongestionControl string
PinnedCertchainSha256 string
Protocol string Protocol string
} }
func NewJuice(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) { func NewJuicity(option *dialer.GlobalOption, nextDialer netproxy.Dialer, link string) (netproxy.Dialer, *dialer.Property, error) {
s, err := ParseJuiceURL(link) s, err := ParseJuicityURL(link)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
return s.Dialer(option, nextDialer) return s.Dialer(option, nextDialer)
} }
func (s *Juice) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) { func (s *Juicity) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer) (netproxy.Dialer, *dialer.Property, error) {
d := nextDialer d := nextDialer
var err error var err error
var flags protocol.Flags var flags protocol.Flags
if d, err = protocol.NewDialer("juicity", d, protocol.Header{ tlsConfig := &tls.Config{
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Feature1: s.CongestionControl,
TlsConfig: &tls.Config{
NextProtos: []string{"h3"}, NextProtos: []string{"h3"},
MinVersion: tls.VersionTLS13, MinVersion: tls.VersionTLS13,
ServerName: s.Sni, ServerName: s.Sni,
InsecureSkipVerify: s.AllowInsecure || option.AllowInsecure, InsecureSkipVerify: s.AllowInsecure || option.AllowInsecure,
}, }
if s.PinnedCertchainSha256 != "" {
pinnedHash, err := base64.StdEncoding.DecodeString(s.PinnedCertchainSha256)
if err != nil {
return nil, nil, fmt.Errorf("decode pin_certchain_sha256: %w", err)
}
tlsConfig.InsecureSkipVerify = true
tlsConfig.VerifyPeerCertificate = func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
if !bytes.Equal(common.GenerateCertChainHash(rawCerts), pinnedHash) {
return fmt.Errorf("pinned hash of cert chain does not match")
}
return nil
}
}
if d, err = protocol.NewDialer("juicity", d, protocol.Header{
ProxyAddress: net.JoinHostPort(s.Server, strconv.Itoa(s.Port)),
Feature1: s.CongestionControl,
TlsConfig: tlsConfig,
User: s.User, User: s.User,
Password: s.Password, Password: s.Password,
IsClient: true, IsClient: true,
@ -65,7 +83,7 @@ func (s *Juice) Dialer(option *dialer.GlobalOption, nextDialer netproxy.Dialer)
}, nil }, nil
} }
func ParseJuiceURL(u string) (data *Juice, err error) { func ParseJuicityURL(u string) (data *Juicity, err error) {
//trojan://password@server:port#escape(remarks) //trojan://password@server:port#escape(remarks)
t, err := url.Parse(u) t, err := url.Parse(u)
if err != nil { if err != nil {
@ -89,17 +107,12 @@ func ParseJuiceURL(u string) (data *Juice, err error) {
if sni == "" { if sni == "" {
sni = t.Hostname() sni = t.Hostname()
} }
disableSni, _ := strconv.ParseBool(t.Query().Get("disable_sni"))
if disableSni {
sni = ""
allowInsecure = true
}
port, err := strconv.Atoi(t.Port()) port, err := strconv.Atoi(t.Port())
if err != nil { if err != nil {
return nil, dialer.InvalidParameterErr return nil, dialer.InvalidParameterErr
} }
password, _ := t.User.Password() password, _ := t.User.Password()
data = &Juice{ data = &Juicity{
Name: t.Fragment, Name: t.Fragment,
Server: t.Hostname(), Server: t.Hostname(),
Port: port, Port: port,
@ -108,12 +121,13 @@ func ParseJuiceURL(u string) (data *Juice, err error) {
Sni: sni, Sni: sni,
AllowInsecure: allowInsecure, AllowInsecure: allowInsecure,
CongestionControl: t.Query().Get("congestion_control"), CongestionControl: t.Query().Get("congestion_control"),
PinnedCertchainSha256: t.Query().Get("pinned_certchain_sha256"),
Protocol: "juicity", Protocol: "juicity",
} }
return data, nil return data, nil
} }
func (t *Juice) ExportToURL() string { func (t *Juicity) ExportToURL() string {
u := &url.URL{ u := &url.URL{
Scheme: "juicity", Scheme: "juicity",
User: url.UserPassword(t.User, t.Password), User: url.UserPassword(t.User, t.Password),
@ -125,9 +139,8 @@ func (t *Juice) ExportToURL() string {
q.Set("allow_insecure", "1") q.Set("allow_insecure", "1")
} }
common.SetValue(&q, "sni", t.Sni) common.SetValue(&q, "sni", t.Sni)
if t.CongestionControl != "" {
common.SetValue(&q, "congestion_control", t.CongestionControl) common.SetValue(&q, "congestion_control", t.CongestionControl)
} common.SetValue(&q, "pinned_certchain_sha256", t.PinnedCertchainSha256)
u.RawQuery = q.Encode() u.RawQuery = q.Encode()
return u.String() return u.String()
} }

4
go.mod
View File

@ -12,7 +12,7 @@ require (
github.com/json-iterator/go v1.1.12 github.com/json-iterator/go v1.1.12
github.com/miekg/dns v1.1.55 github.com/miekg/dns v1.1.55
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826
github.com/mzz2017/softwind v0.0.0-20230730190454-1b4f59056283 github.com/mzz2017/softwind v0.0.0-20230803152605-5f1f6bc06934
github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd github.com/okzk/sdnotify v0.0.0-20180710141335-d9becc38acbd
github.com/safchain/ethtool v0.3.0 github.com/safchain/ethtool v0.3.0
github.com/sirupsen/logrus v1.9.3 github.com/sirupsen/logrus v1.9.3
@ -34,7 +34,7 @@ require (
github.com/golang/mock v1.6.0 // indirect github.com/golang/mock v1.6.0 // indirect
github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect github.com/google/pprof v0.0.0-20230705174524-200ffdc848b8 // indirect
github.com/klauspost/compress v1.16.7 // indirect github.com/klauspost/compress v1.16.7 // indirect
github.com/mzz2017/quic-go v0.0.0-20230730161358-da6d530ddb18 // indirect github.com/mzz2017/quic-go v0.0.0-20230731153658-2aabc7c97220 // indirect
github.com/onsi/ginkgo/v2 v2.11.0 // indirect github.com/onsi/ginkgo/v2 v2.11.0 // indirect
github.com/quic-go/qtls-go1-20 v0.3.0 // indirect github.com/quic-go/qtls-go1-20 v0.3.0 // indirect
golang.org/x/mod v0.12.0 // indirect golang.org/x/mod v0.12.0 // indirect

8
go.sum
View File

@ -89,10 +89,10 @@ github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9
github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8=
github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B90M= github.com/mzz2017/disk-bloom v1.0.1 h1:rEF9MiXd9qMW3ibRpqcerLXULoTgRlM21yqqJl1B90M=
github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI= github.com/mzz2017/disk-bloom v1.0.1/go.mod h1:JLHETtUu44Z6iBmsqzkOtFlRvXSlKnxjwiBRDapizDI=
github.com/mzz2017/quic-go v0.0.0-20230730161358-da6d530ddb18 h1:SzaoU9jIglF82Qp2rmPhsXhdtgOYiTwmxbd85qa5+Gs= github.com/mzz2017/quic-go v0.0.0-20230731153658-2aabc7c97220 h1:StI6iNucfDVrm54yhrHL0Dr1vKC1nBqkok2Itkypvdk=
github.com/mzz2017/quic-go v0.0.0-20230730161358-da6d530ddb18/go.mod h1:+8o4sdYYZCkNfMGiP3oKstpdHeSDFJy84A68evIzhw4= github.com/mzz2017/quic-go v0.0.0-20230731153658-2aabc7c97220/go.mod h1:+8o4sdYYZCkNfMGiP3oKstpdHeSDFJy84A68evIzhw4=
github.com/mzz2017/softwind v0.0.0-20230730190454-1b4f59056283 h1:nmDedxylxjWN8CW4xxa4krkZknijTA/qHgRk849o8zs= github.com/mzz2017/softwind v0.0.0-20230803152605-5f1f6bc06934 h1:IAaVbSfNiYP/TuJ4n686SeAwcs+PMvKdzJM5R4Y1rhM=
github.com/mzz2017/softwind v0.0.0-20230730190454-1b4f59056283/go.mod h1:H5th8OX4bXcksqNjwKhyisZS4lmO1ZezAbcoIL3P1dU= github.com/mzz2017/softwind v0.0.0-20230803152605-5f1f6bc06934/go.mod h1:TopiWHnL2ty4V4AkNM1nNVdbLlASWdQqploagf4tcKc=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU=