mirror of
https://github.com/daeuniverse/dae.git
synced 2025-07-14 09:48:47 +07:00
feat(juicity): support certificate pinning (#256)
This commit is contained in:
@ -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
|
||||||
|
}
|
||||||
|
@ -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
4
go.mod
@ -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
8
go.sum
@ -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=
|
||||||
|
Reference in New Issue
Block a user