Browse Source

Use stdlib ech

世界 11 months ago
parent
commit
658d4a180b
5 changed files with 138 additions and 10 deletions
  1. 4 3
      common/tls/client.go
  2. 1 4
      common/tls/ech_keygen.go
  3. 4 3
      common/tls/server.go
  4. 82 0
      common/tls/std_client.go
  5. 47 0
      common/tls/std_server.go

+ 4 - 3
common/tls/client.go

@@ -30,14 +30,15 @@ func NewClient(ctx context.Context, serverAddress string, options option.Outboun
 		return nil, nil
 	}
 	if options.ECH != nil && options.ECH.Enabled {
-		return NewECHClient(ctx, serverAddress, options)
+		if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
+			return NewECHClient(ctx, serverAddress, options)
+		}
 	} else if options.Reality != nil && options.Reality.Enabled {
 		return NewRealityClient(ctx, serverAddress, options)
 	} else if options.UTLS != nil && options.UTLS.Enabled {
 		return NewUTLSClient(ctx, serverAddress, options)
-	} else {
-		return NewSTDClient(ctx, serverAddress, options)
 	}
+	return NewSTDClient(ctx, serverAddress, options)
 }
 
 func ClientHandshake(ctx context.Context, conn net.Conn, config Config) (Conn, error) {

+ 1 - 4
common/tls/ech_keygen.go

@@ -7,7 +7,6 @@ import (
 	"encoding/binary"
 	"encoding/pem"
 
-	cftls "github.com/sagernet/cloudflare-tls"
 	E "github.com/sagernet/sing/common/exceptions"
 
 	"github.com/cloudflare/circl/hpke"
@@ -59,7 +58,6 @@ func ECHKeygenDefault(serverName string, pqSignatureSchemesEnabled bool) (config
 
 type echKeyConfigPair struct {
 	id      uint8
-	key     cftls.EXP_ECHKey
 	rawKey  []byte
 	conf    myECHKeyConfig
 	rawConf []byte
@@ -153,14 +151,13 @@ func echKeygen(version uint16, serverName string, conf []myECHKeyConfig, suite [
 		sk = be.AppendUint16(sk, uint16(len(b)))
 		sk = append(sk, b...)
 
-		cfECHKeys, err := cftls.EXP_UnmarshalECHKeys(sk)
+		cfECHKeys, err := UnmarshalECHKeys(sk)
 		if err != nil {
 			return nil, E.Cause(err, "bug: can't parse generated ECH server key")
 		}
 		if len(cfECHKeys) != 1 {
 			return nil, E.New("bug: unexpected server key count")
 		}
-		pair.key = cfECHKeys[0]
 		pair.rawKey = sk
 
 		pairs = append(pairs, pair)

+ 4 - 3
common/tls/server.go

@@ -17,12 +17,13 @@ func NewServer(ctx context.Context, logger log.Logger, options option.InboundTLS
 		return nil, nil
 	}
 	if options.ECH != nil && options.ECH.Enabled {
-		return NewECHServer(ctx, logger, options)
+		if options.ECH.PQSignatureSchemesEnabled || options.ECH.DynamicRecordSizingDisabled {
+			return NewECHServer(ctx, logger, options)
+		}
 	} else if options.Reality != nil && options.Reality.Enabled {
 		return NewRealityServer(ctx, logger, options)
-	} else {
-		return NewSTDServer(ctx, logger, options)
 	}
+	return NewSTDServer(ctx, logger, options)
 }
 
 func ServerHandshake(ctx context.Context, conn net.Conn, config ServerConfig) (Conn, error) {

+ 82 - 0
common/tls/std_client.go

@@ -4,16 +4,25 @@ import (
 	"context"
 	"crypto/tls"
 	"crypto/x509"
+	"encoding/base64"
 	"net"
 	"net/netip"
 	"os"
 	"strings"
 
+	"github.com/sagernet/sing-box/adapter"
 	"github.com/sagernet/sing-box/option"
+	"github.com/sagernet/sing-dns"
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/ntp"
+	aTLS "github.com/sagernet/sing/common/tls"
+	"github.com/sagernet/sing/service"
+
+	mDNS "github.com/miekg/dns"
 )
 
+var _ ConfigCompat = (*STDClientConfig)(nil)
+
 type STDClientConfig struct {
 	config *tls.Config
 }
@@ -46,6 +55,63 @@ func (s *STDClientConfig) Clone() Config {
 	return &STDClientConfig{s.config.Clone()}
 }
 
+type STDECHClientConfig struct {
+	STDClientConfig
+}
+
+func (s *STDClientConfig) ClientHandshake(ctx context.Context, conn net.Conn) (aTLS.Conn, error) {
+	if len(s.config.EncryptedClientHelloConfigList) == 0 {
+		message := &mDNS.Msg{
+			MsgHdr: mDNS.MsgHdr{
+				RecursionDesired: true,
+			},
+			Question: []mDNS.Question{
+				{
+					Name:   mDNS.Fqdn(s.config.ServerName),
+					Qtype:  mDNS.TypeHTTPS,
+					Qclass: mDNS.ClassINET,
+				},
+			},
+		}
+		dnsRouter := service.FromContext[adapter.Router](ctx)
+		response, err := dnsRouter.Exchange(ctx, message)
+		if err != nil {
+			return nil, E.Cause(err, "fetch ECH config list")
+		}
+		if response.Rcode != mDNS.RcodeSuccess {
+			return nil, E.Cause(dns.RCodeError(response.Rcode), "fetch ECH config list")
+		}
+		for _, rr := range response.Answer {
+			switch resource := rr.(type) {
+			case *mDNS.HTTPS:
+				for _, value := range resource.Value {
+					if value.Key().String() == "ech" {
+						echConfigList, err := base64.StdEncoding.DecodeString(value.String())
+						if err != nil {
+							return nil, E.Cause(err, "decode ECH config")
+						}
+						s.config.EncryptedClientHelloConfigList = echConfigList
+					}
+				}
+			}
+		}
+		return nil, E.New("no ECH config found in DNS records")
+	}
+	tlsConn, err := s.Client(conn)
+	if err != nil {
+		return nil, err
+	}
+	err = tlsConn.HandshakeContext(ctx)
+	if err != nil {
+		return nil, err
+	}
+	return tlsConn, nil
+}
+
+func (s *STDECHClientConfig) Clone() Config {
+	return &STDECHClientConfig{STDClientConfig{s.config.Clone()}}
+}
+
 func NewSTDClient(ctx context.Context, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
 	var serverName string
 	if options.ServerName != "" {
@@ -128,5 +194,21 @@ func NewSTDClient(ctx context.Context, serverAddress string, options option.Outb
 		}
 		tlsConfig.RootCAs = certPool
 	}
+	if options.ECH != nil && options.ECH.Enabled {
+		var echConfig []byte
+		if len(options.ECH.Config) > 0 {
+			echConfig = []byte(strings.Join(options.ECH.Config, "\n"))
+		} else if options.ECH.ConfigPath != "" {
+			content, err := os.ReadFile(options.ECH.ConfigPath)
+			if err != nil {
+				return nil, E.Cause(err, "read ECH config")
+			}
+			echConfig = content
+		}
+		if echConfig != nil {
+			tlsConfig.EncryptedClientHelloConfigList = echConfig
+		}
+		return &STDECHClientConfig{STDClientConfig{&tlsConfig}}, nil
+	}
 	return &STDClientConfig{&tlsConfig}, nil
 }

+ 47 - 0
common/tls/std_server.go

@@ -3,6 +3,7 @@ package tls
 import (
 	"context"
 	"crypto/tls"
+	"encoding/pem"
 	"net"
 	"os"
 	"strings"
@@ -14,6 +15,8 @@ import (
 	"github.com/sagernet/sing/common"
 	E "github.com/sagernet/sing/common/exceptions"
 	"github.com/sagernet/sing/common/ntp"
+
+	"golang.org/x/crypto/cryptobyte"
 )
 
 var errInsecureUnused = E.New("tls: insecure unused")
@@ -238,6 +241,31 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
 			tlsConfig.Certificates = []tls.Certificate{keyPair}
 		}
 	}
+	if options.ECH != nil && options.ECH.Enabled {
+		var echKey []byte
+		if len(options.ECH.Key) > 0 {
+			echKey = []byte(strings.Join(options.ECH.Key, "\n"))
+		} else if options.ECH.KeyPath != "" {
+			content, err := os.ReadFile(options.ECH.KeyPath)
+			if err != nil {
+				return nil, E.Cause(err, "read ECH key")
+			}
+			echKey = content
+		} else {
+			return nil, E.New("missing ECH key")
+		}
+
+		block, rest := pem.Decode(echKey)
+		if block == nil || block.Type != "ECH KEYS" || len(rest) > 0 {
+			return nil, E.New("invalid ECH keys pem")
+		}
+
+		echKeys, err := UnmarshalECHKeys(block.Bytes)
+		if err != nil {
+			return nil, E.Cause(err, "parse ECH keys")
+		}
+		tlsConfig.EncryptedClientHelloKeys = echKeys
+	}
 	return &STDServerConfig{
 		config:          tlsConfig,
 		logger:          logger,
@@ -248,3 +276,22 @@ func NewSTDServer(ctx context.Context, logger log.Logger, options option.Inbound
 		keyPath:         options.KeyPath,
 	}, nil
 }
+
+func UnmarshalECHKeys(raw []byte) ([]tls.EncryptedClientHelloKey, error) {
+	var keys []tls.EncryptedClientHelloKey
+	rawString := cryptobyte.String(raw)
+	for !rawString.Empty() {
+		var key tls.EncryptedClientHelloKey
+		if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.PrivateKey)) {
+			return nil, E.New("error parsing private key")
+		}
+		if !rawString.ReadUint16LengthPrefixed((*cryptobyte.String)(&key.Config)) {
+			return nil, E.New("error parsing config")
+		}
+		keys = append(keys, key)
+	}
+	if len(keys) == 0 {
+		return nil, E.New("empty ECH keys")
+	}
+	return keys, nil
+}