mirror of
https://github.com/XTLS/Xray-core.git
synced 2026-01-18 00:18:24 +08:00
Compare commits
7 Commits
ss2022-udp
...
cert-pin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f68c23aabb | ||
|
|
760223ad70 | ||
|
|
d75b33a3a3 | ||
|
|
7c418486c8 | ||
|
|
a384be0f84 | ||
|
|
649e989fa2 | ||
|
|
0443de7798 |
@@ -112,11 +112,11 @@
|
||||
- [SimpleXray](https://github.com/lhear/SimpleXray)
|
||||
- [AnyPortal](https://github.com/AnyPortal/AnyPortal)
|
||||
- iOS & macOS arm64 & tvOS
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) ([tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274))
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973) | [Happ tvOS](https://apps.apple.com/us/app/happ-proxy-utility-for-tv/id6748297274)
|
||||
- [Streisand](https://apps.apple.com/app/streisand/id6450534064)
|
||||
- [OneXray](https://github.com/OneXray/OneXray)
|
||||
- macOS arm64 & x64
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215)
|
||||
- [Happ](https://apps.apple.com/app/happ-proxy-utility/id6504287215) | [Happ RU](https://apps.apple.com/ru/app/happ-proxy-utility-plus/id6746188973)
|
||||
- [V2rayU](https://github.com/yanue/V2rayU)
|
||||
- [V2RayXS](https://github.com/tzmax/V2RayXS)
|
||||
- [Furious](https://github.com/LorenEteval/Furious)
|
||||
|
||||
@@ -64,7 +64,11 @@ func GetMergedConfig(args cmdarg.Arg) (string, error) {
|
||||
var files []*ConfigSource
|
||||
supported := []string{"json", "yaml", "toml"}
|
||||
for _, file := range args {
|
||||
format := GetFormat(file)
|
||||
format := "json"
|
||||
if file != "stdin:" {
|
||||
format = GetFormat(file)
|
||||
}
|
||||
|
||||
if slices.Contains(supported, format) {
|
||||
files = append(files, &ConfigSource{
|
||||
Name: file,
|
||||
|
||||
2
go.mod
2
go.mod
@@ -12,7 +12,7 @@ require (
|
||||
github.com/miekg/dns v1.1.70
|
||||
github.com/pelletier/go-toml v1.9.5
|
||||
github.com/pires/go-proxyproto v0.8.1
|
||||
github.com/refraction-networking/utls v1.8.1
|
||||
github.com/refraction-networking/utls v1.8.2
|
||||
github.com/sagernet/sing v0.5.1
|
||||
github.com/sagernet/sing-shadowsocks v0.2.7
|
||||
github.com/seiflotfy/cuckoofilter v0.0.0-20240715131351-a2f2c23f1771
|
||||
|
||||
4
go.sum
4
go.sum
@@ -52,8 +52,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8=
|
||||
github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII=
|
||||
github.com/refraction-networking/utls v1.8.1 h1:yNY1kapmQU8JeM1sSw2H2asfTIwWxIkrMJI0pRUOCAo=
|
||||
github.com/refraction-networking/utls v1.8.1/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/refraction-networking/utls v1.8.2 h1:j4Q1gJj0xngdeH+Ox/qND11aEfhpgoEvV+S9iJ2IdQo=
|
||||
github.com/refraction-networking/utls v1.8.2/go.mod h1:jkSOEkLqn+S/jtpEHPOsVv/4V4EVnelwbMQl4vCWXAM=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3 h1:f/FNXud6gA3MNr8meMVVGxhp+QBTqY91tM8HjEuMjGg=
|
||||
github.com/riobard/go-bloom v0.0.0-20200614022211-cdc8013cb5b3/go.mod h1:HgjTstvQsPGkxUsCd2KWxErBblirPizecHcpD3ffK+s=
|
||||
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
|
||||
|
||||
@@ -1,19 +1,25 @@
|
||||
package conf
|
||||
|
||||
import (
|
||||
"github.com/xtls/xray-core/common/errors"
|
||||
"github.com/xtls/xray-core/common/protocol"
|
||||
"github.com/xtls/xray-core/proxy/hysteria"
|
||||
"google.golang.org/protobuf/proto"
|
||||
)
|
||||
|
||||
type HysteriaClientConfig struct {
|
||||
Version int32 `json:"version"`
|
||||
Address *Address `json:"address"`
|
||||
Port uint16 `json:"port"`
|
||||
}
|
||||
|
||||
func (c *HysteriaClientConfig) Build() (proto.Message, error) {
|
||||
config := new(hysteria.ClientConfig)
|
||||
if c.Version != 2 {
|
||||
return nil, errors.New("version != 2")
|
||||
}
|
||||
|
||||
config := &hysteria.ClientConfig{}
|
||||
config.Version = c.Version
|
||||
config.Server = &protocol.ServerEndpoint{
|
||||
Address: c.Address.Build(),
|
||||
Port: uint32(c.Port),
|
||||
|
||||
@@ -453,7 +453,7 @@ func (c *HysteriaConfig) Build() (proto.Message, error) {
|
||||
}
|
||||
|
||||
config := &hysteria.Config{}
|
||||
config.Version = int32(c.Version)
|
||||
config.Version = c.Version
|
||||
config.Auth = c.Auth
|
||||
config.Up = up
|
||||
config.Down = down
|
||||
|
||||
@@ -24,7 +24,8 @@ const (
|
||||
|
||||
type ClientConfig struct {
|
||||
state protoimpl.MessageState `protogen:"open.v1"`
|
||||
Server *protocol.ServerEndpoint `protobuf:"bytes,1,opt,name=server,proto3" json:"server,omitempty"`
|
||||
Version int32 `protobuf:"varint,1,opt,name=version,proto3" json:"version,omitempty"`
|
||||
Server *protocol.ServerEndpoint `protobuf:"bytes,2,opt,name=server,proto3" json:"server,omitempty"`
|
||||
unknownFields protoimpl.UnknownFields
|
||||
sizeCache protoimpl.SizeCache
|
||||
}
|
||||
@@ -59,6 +60,13 @@ func (*ClientConfig) Descriptor() ([]byte, []int) {
|
||||
return file_proxy_hysteria_config_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ClientConfig) GetVersion() int32 {
|
||||
if x != nil {
|
||||
return x.Version
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ClientConfig) GetServer() *protocol.ServerEndpoint {
|
||||
if x != nil {
|
||||
return x.Server
|
||||
@@ -70,9 +78,10 @@ var File_proxy_hysteria_config_proto protoreflect.FileDescriptor
|
||||
|
||||
const file_proxy_hysteria_config_proto_rawDesc = "" +
|
||||
"\n" +
|
||||
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\"L\n" +
|
||||
"\fClientConfig\x12<\n" +
|
||||
"\x06server\x18\x01 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06serverB[\n" +
|
||||
"\x1bproxy/hysteria/config.proto\x12\x13xray.proxy.hysteria\x1a!common/protocol/server_spec.proto\"f\n" +
|
||||
"\fClientConfig\x12\x18\n" +
|
||||
"\aversion\x18\x01 \x01(\x05R\aversion\x12<\n" +
|
||||
"\x06server\x18\x02 \x01(\v2$.xray.common.protocol.ServerEndpointR\x06serverB[\n" +
|
||||
"\x17com.xray.proxy.hysteriaP\x01Z(github.com/xtls/xray-core/proxy/hysteria\xaa\x02\x13Xray.Proxy.Hysteriab\x06proto3"
|
||||
|
||||
var (
|
||||
|
||||
@@ -9,5 +9,6 @@ option java_multiple_files = true;
|
||||
import "common/protocol/server_spec.proto";
|
||||
|
||||
message ClientConfig {
|
||||
xray.common.protocol.ServerEndpoint server = 1;
|
||||
int32 version = 1;
|
||||
xray.common.protocol.ServerEndpoint server = 2;
|
||||
}
|
||||
|
||||
@@ -195,6 +195,14 @@ func (c *PacketConnWrapper) SetWriteDeadline(t time.Time) error {
|
||||
return c.Conn.SetWriteDeadline(t)
|
||||
}
|
||||
|
||||
func (c *PacketConnWrapper) SyscallConn() (syscall.RawConn, error) {
|
||||
sc, ok := c.Conn.(syscall.Conn)
|
||||
if !ok {
|
||||
return nil, syscall.EINVAL
|
||||
}
|
||||
return sc.SyscallConn()
|
||||
}
|
||||
|
||||
type SystemDialerAdapter interface {
|
||||
Dial(network string, address string) (net.Conn, error)
|
||||
}
|
||||
|
||||
@@ -281,35 +281,41 @@ func (c *Config) parseServerName() string {
|
||||
}
|
||||
|
||||
func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) (err error) {
|
||||
// extract x509 certificates from rawCerts(verifiedChains will be nil if InsecureSkipVerify is true)
|
||||
// extract x509 certificates from rawCerts (verifiedChains will be nil if InsecureSkipVerify is true)
|
||||
certs := make([]*x509.Certificate, len(rawCerts))
|
||||
for i, asn1Data := range rawCerts {
|
||||
certs[i], _ = x509.ParseCertificate(asn1Data)
|
||||
}
|
||||
if len(certs) == 0 {
|
||||
return errors.New("unexpected certs")
|
||||
}
|
||||
if certs[0].IsCA {
|
||||
slices.Reverse(certs)
|
||||
}
|
||||
|
||||
// directly return success if pinned cert is leaf
|
||||
// or add the CA to RootCAs if pinned cert is CA(and can be used in VerifyPeerCertInNames for Self signed CA)
|
||||
RootCAs := r.RootCAs
|
||||
// or replace RootCAs if pinned cert is CA (and can be used in VerifyPeerCertInNames)
|
||||
CAs := r.RootCAs
|
||||
var verifyResult verifyResult
|
||||
var verifiedCert *x509.Certificate
|
||||
if r.PinnedPeerCertSha256 != nil {
|
||||
verifyResult, verifiedCert = verifyChain(certs, r.PinnedPeerCertSha256)
|
||||
switch verifyResult {
|
||||
case certNotFound:
|
||||
return errors.New("peer cert is unrecognized")
|
||||
return errors.New("peer cert is unrecognized (againsts pinnedPeerCertSha256)")
|
||||
case foundLeaf:
|
||||
return nil
|
||||
case foundCA:
|
||||
RootCAs = x509.NewCertPool()
|
||||
RootCAs.AddCert(verifiedCert)
|
||||
CAs = x509.NewCertPool()
|
||||
CAs.AddCert(verifiedCert)
|
||||
default:
|
||||
panic("impossible PinnedPeerCertificateSha256 verify result")
|
||||
panic("impossible pinnedPeerCertSha256 verify result")
|
||||
}
|
||||
}
|
||||
|
||||
if len(r.VerifyPeerCertInNames) > 0 {
|
||||
if r.VerifyPeerCertInNames != nil { // RAW's Dial() may make it empty but not nil
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: RootCAs,
|
||||
Roots: CAs,
|
||||
CurrentTime: time.Now(),
|
||||
Intermediates: x509.NewCertPool(),
|
||||
}
|
||||
@@ -321,9 +327,15 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509
|
||||
return nil
|
||||
}
|
||||
}
|
||||
} else if len(verifiedChains) == 0 && verifyResult == foundCA { // if found ca and verifiedChains is empty, we need to verify here
|
||||
if verifyResult == foundCA {
|
||||
errors.New("peer cert is invalid (againsts pinned CA and verifyPeerCertInNames)")
|
||||
}
|
||||
return errors.New("peer cert is invalid (againsts root CAs and verifyPeerCertInNames)")
|
||||
}
|
||||
|
||||
if verifyResult == foundCA { // if found CA, we need to verify here
|
||||
opts := x509.VerifyOptions{
|
||||
Roots: RootCAs,
|
||||
Roots: CAs,
|
||||
CurrentTime: time.Now(),
|
||||
Intermediates: x509.NewCertPool(),
|
||||
DNSName: r.Config.ServerName,
|
||||
@@ -334,8 +346,10 @@ func (r *RandCarrier) verifyPeerCert(rawCerts [][]byte, verifiedChains [][]*x509
|
||||
if _, err := certs[0].Verify(opts); err == nil {
|
||||
return nil
|
||||
}
|
||||
return errors.New("peer cert is invalid (againsts pinned CA and serverName)")
|
||||
}
|
||||
return nil
|
||||
|
||||
return nil // len(r.PinnedPeerCertSha256)==nil && len(r.VerifyPeerCertInNames)==nil
|
||||
}
|
||||
|
||||
type RandCarrier struct {
|
||||
@@ -386,6 +400,11 @@ func (c *Config) GetTLSConfig(opts ...Option) *tls.Config {
|
||||
} else {
|
||||
randCarrier.VerifyPeerCertInNames = nil
|
||||
}
|
||||
if len(c.PinnedPeerCertSha256) > 0 {
|
||||
config.InsecureSkipVerify = true
|
||||
} else {
|
||||
randCarrier.PinnedPeerCertSha256 = nil
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(config)
|
||||
@@ -540,17 +559,16 @@ const (
|
||||
foundCA
|
||||
)
|
||||
|
||||
func verifyChain(certs []*x509.Certificate, PinnedPeerCertificateSha256 [][]byte) (verifyResult, *x509.Certificate) {
|
||||
func verifyChain(certs []*x509.Certificate, pinnedPeerCertSha256 [][]byte) (verifyResult, *x509.Certificate) {
|
||||
for _, cert := range certs {
|
||||
certHash := GenerateCertHash(cert)
|
||||
for _, c := range PinnedPeerCertificateSha256 {
|
||||
for _, c := range pinnedPeerCertSha256 {
|
||||
if hmac.Equal(certHash, c) {
|
||||
if cert.IsCA {
|
||||
return foundCA, cert
|
||||
} else {
|
||||
return foundLeaf, cert
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
package tls
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/hex"
|
||||
"encoding/pem"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/xtls/xray-core/common"
|
||||
"github.com/xtls/xray-core/common/protocol/tls/cert"
|
||||
)
|
||||
|
||||
func TestCalculateCertHash(t *testing.T) {
|
||||
@@ -95,3 +98,60 @@ uI6HqHFD3iEct8fBkYfQiwH2e1eu9OwgujiWHsutyK8VvzVB3/YnhQ/TzciRjPqz
|
||||
assert.Equal(t, fingerprint, hash)
|
||||
})
|
||||
}
|
||||
|
||||
func TestVerifyPeerLeafCert(t *testing.T) {
|
||||
leafCert := cert.MustGenerate(nil, cert.DNSNames("example.com"))
|
||||
leaf := common.Must2(x509.ParseCertificate(leafCert.Certificate))
|
||||
|
||||
caHash := GenerateCertHash(leafCert.Certificate)
|
||||
|
||||
r := &RandCarrier{
|
||||
Config: &tls.Config{
|
||||
ServerName: "example.com",
|
||||
},
|
||||
PinnedPeerCertSha256: [][]byte{caHash},
|
||||
}
|
||||
|
||||
rawCerts := [][]byte{leaf.Raw}
|
||||
err := r.verifyPeerCert(rawCerts, nil)
|
||||
if err != nil {
|
||||
t.Fatal("expected to verify leaf cert signed by pinned CA, but got error:", err)
|
||||
}
|
||||
|
||||
// make the pinned hash incorrect
|
||||
r.PinnedPeerCertSha256[0][0] += 1
|
||||
err = r.verifyPeerCert(rawCerts, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected to fail verifying leaf cert with incorrect pinned CA hash, but got no error")
|
||||
}
|
||||
}
|
||||
|
||||
func TestVerifyPeerCACert(t *testing.T) {
|
||||
caCert := cert.MustGenerate(nil, cert.Authority(true), cert.KeyUsage(x509.KeyUsageCertSign))
|
||||
ca := common.Must2(x509.ParseCertificate(caCert.Certificate))
|
||||
|
||||
leafCert := cert.MustGenerate(caCert, cert.DNSNames("example.com"))
|
||||
leaf := common.Must2(x509.ParseCertificate(leafCert.Certificate))
|
||||
|
||||
caHash := GenerateCertHash(ca)
|
||||
|
||||
r := &RandCarrier{
|
||||
Config: &tls.Config{
|
||||
ServerName: "example.com",
|
||||
},
|
||||
PinnedPeerCertSha256: [][]byte{caHash},
|
||||
}
|
||||
|
||||
rawCerts := [][]byte{leaf.Raw, ca.Raw}
|
||||
err := r.verifyPeerCert(rawCerts, nil)
|
||||
if err != nil {
|
||||
t.Fatal("expected to verify leaf cert signed by pinned CA, but got error:", err)
|
||||
}
|
||||
|
||||
// make the pinned hash incorrect
|
||||
r.PinnedPeerCertSha256[0][0] += 1
|
||||
err = r.verifyPeerCert(rawCerts, nil)
|
||||
if err == nil {
|
||||
t.Fatal("expected to fail verifying leaf cert with incorrect pinned CA hash, but got no error")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user