github.com/sagernet/sing-box@v1.2.7/common/tls/ech_client.go (about)

     1  //go:build with_ech
     2  
     3  package tls
     4  
     5  import (
     6  	"context"
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"encoding/base64"
    10  	"net"
    11  	"net/netip"
    12  	"os"
    13  
    14  	cftls "github.com/sagernet/cloudflare-tls"
    15  	"github.com/sagernet/sing-box/adapter"
    16  	"github.com/sagernet/sing-box/option"
    17  	"github.com/sagernet/sing-dns"
    18  	E "github.com/sagernet/sing/common/exceptions"
    19  
    20  	mDNS "github.com/miekg/dns"
    21  )
    22  
    23  type ECHClientConfig struct {
    24  	config *cftls.Config
    25  }
    26  
    27  func (e *ECHClientConfig) ServerName() string {
    28  	return e.config.ServerName
    29  }
    30  
    31  func (e *ECHClientConfig) SetServerName(serverName string) {
    32  	e.config.ServerName = serverName
    33  }
    34  
    35  func (e *ECHClientConfig) NextProtos() []string {
    36  	return e.config.NextProtos
    37  }
    38  
    39  func (e *ECHClientConfig) SetNextProtos(nextProto []string) {
    40  	e.config.NextProtos = nextProto
    41  }
    42  
    43  func (e *ECHClientConfig) Config() (*STDConfig, error) {
    44  	return nil, E.New("unsupported usage for ECH")
    45  }
    46  
    47  func (e *ECHClientConfig) Client(conn net.Conn) (Conn, error) {
    48  	return &echConnWrapper{cftls.Client(conn, e.config)}, nil
    49  }
    50  
    51  func (e *ECHClientConfig) Clone() Config {
    52  	return &ECHClientConfig{
    53  		config: e.config.Clone(),
    54  	}
    55  }
    56  
    57  type echConnWrapper struct {
    58  	*cftls.Conn
    59  }
    60  
    61  func (c *echConnWrapper) ConnectionState() tls.ConnectionState {
    62  	state := c.Conn.ConnectionState()
    63  	return tls.ConnectionState{
    64  		Version:                     state.Version,
    65  		HandshakeComplete:           state.HandshakeComplete,
    66  		DidResume:                   state.DidResume,
    67  		CipherSuite:                 state.CipherSuite,
    68  		NegotiatedProtocol:          state.NegotiatedProtocol,
    69  		NegotiatedProtocolIsMutual:  state.NegotiatedProtocolIsMutual,
    70  		ServerName:                  state.ServerName,
    71  		PeerCertificates:            state.PeerCertificates,
    72  		VerifiedChains:              state.VerifiedChains,
    73  		SignedCertificateTimestamps: state.SignedCertificateTimestamps,
    74  		OCSPResponse:                state.OCSPResponse,
    75  		TLSUnique:                   state.TLSUnique,
    76  	}
    77  }
    78  
    79  func (c *echConnWrapper) Upstream() any {
    80  	return c.Conn
    81  }
    82  
    83  func NewECHClient(router adapter.Router, serverAddress string, options option.OutboundTLSOptions) (Config, error) {
    84  	var serverName string
    85  	if options.ServerName != "" {
    86  		serverName = options.ServerName
    87  	} else if serverAddress != "" {
    88  		if _, err := netip.ParseAddr(serverName); err != nil {
    89  			serverName = serverAddress
    90  		}
    91  	}
    92  	if serverName == "" && !options.Insecure {
    93  		return nil, E.New("missing server_name or insecure=true")
    94  	}
    95  
    96  	var tlsConfig cftls.Config
    97  	tlsConfig.Time = router.TimeFunc()
    98  	if options.DisableSNI {
    99  		tlsConfig.ServerName = "127.0.0.1"
   100  	} else {
   101  		tlsConfig.ServerName = serverName
   102  	}
   103  	if options.Insecure {
   104  		tlsConfig.InsecureSkipVerify = options.Insecure
   105  	} else if options.DisableSNI {
   106  		tlsConfig.InsecureSkipVerify = true
   107  		tlsConfig.VerifyConnection = func(state cftls.ConnectionState) error {
   108  			verifyOptions := x509.VerifyOptions{
   109  				DNSName:       serverName,
   110  				Intermediates: x509.NewCertPool(),
   111  			}
   112  			for _, cert := range state.PeerCertificates[1:] {
   113  				verifyOptions.Intermediates.AddCert(cert)
   114  			}
   115  			_, err := state.PeerCertificates[0].Verify(verifyOptions)
   116  			return err
   117  		}
   118  	}
   119  	if len(options.ALPN) > 0 {
   120  		tlsConfig.NextProtos = options.ALPN
   121  	}
   122  	if options.MinVersion != "" {
   123  		minVersion, err := ParseTLSVersion(options.MinVersion)
   124  		if err != nil {
   125  			return nil, E.Cause(err, "parse min_version")
   126  		}
   127  		tlsConfig.MinVersion = minVersion
   128  	}
   129  	if options.MaxVersion != "" {
   130  		maxVersion, err := ParseTLSVersion(options.MaxVersion)
   131  		if err != nil {
   132  			return nil, E.Cause(err, "parse max_version")
   133  		}
   134  		tlsConfig.MaxVersion = maxVersion
   135  	}
   136  	if options.CipherSuites != nil {
   137  	find:
   138  		for _, cipherSuite := range options.CipherSuites {
   139  			for _, tlsCipherSuite := range cftls.CipherSuites() {
   140  				if cipherSuite == tlsCipherSuite.Name {
   141  					tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID)
   142  					continue find
   143  				}
   144  			}
   145  			return nil, E.New("unknown cipher_suite: ", cipherSuite)
   146  		}
   147  	}
   148  	var certificate []byte
   149  	if options.Certificate != "" {
   150  		certificate = []byte(options.Certificate)
   151  	} else if options.CertificatePath != "" {
   152  		content, err := os.ReadFile(options.CertificatePath)
   153  		if err != nil {
   154  			return nil, E.Cause(err, "read certificate")
   155  		}
   156  		certificate = content
   157  	}
   158  	if len(certificate) > 0 {
   159  		certPool := x509.NewCertPool()
   160  		if !certPool.AppendCertsFromPEM(certificate) {
   161  			return nil, E.New("failed to parse certificate:\n\n", certificate)
   162  		}
   163  		tlsConfig.RootCAs = certPool
   164  	}
   165  
   166  	// ECH Config
   167  
   168  	tlsConfig.ECHEnabled = true
   169  	tlsConfig.PQSignatureSchemesEnabled = options.ECH.PQSignatureSchemesEnabled
   170  	tlsConfig.DynamicRecordSizingDisabled = options.ECH.DynamicRecordSizingDisabled
   171  	if options.ECH.Config != "" {
   172  		clientConfigContent, err := base64.StdEncoding.DecodeString(options.ECH.Config)
   173  		if err != nil {
   174  			return nil, err
   175  		}
   176  		clientConfig, err := cftls.UnmarshalECHConfigs(clientConfigContent)
   177  		if err != nil {
   178  			return nil, err
   179  		}
   180  		tlsConfig.ClientECHConfigs = clientConfig
   181  	} else {
   182  		tlsConfig.GetClientECHConfigs = fetchECHClientConfig(router)
   183  	}
   184  	return &ECHClientConfig{&tlsConfig}, nil
   185  }
   186  
   187  func fetchECHClientConfig(router adapter.Router) func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
   188  	return func(ctx context.Context, serverName string) ([]cftls.ECHConfig, error) {
   189  		message := &mDNS.Msg{
   190  			MsgHdr: mDNS.MsgHdr{
   191  				RecursionDesired: true,
   192  			},
   193  			Question: []mDNS.Question{
   194  				{
   195  					Name:   serverName + ".",
   196  					Qtype:  mDNS.TypeHTTPS,
   197  					Qclass: mDNS.ClassINET,
   198  				},
   199  			},
   200  		}
   201  		response, err := router.Exchange(ctx, message)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		if response.Rcode != mDNS.RcodeSuccess {
   206  			return nil, dns.RCodeError(response.Rcode)
   207  		}
   208  		for _, rr := range response.Answer {
   209  			switch resource := rr.(type) {
   210  			case *mDNS.HTTPS:
   211  				for _, value := range resource.Value {
   212  					if value.Key().String() == "ech" {
   213  						echConfig, err := base64.StdEncoding.DecodeString(value.String())
   214  						if err != nil {
   215  							return nil, E.Cause(err, "decode ECH config")
   216  						}
   217  						return cftls.UnmarshalECHConfigs(echConfig)
   218  					}
   219  				}
   220  			default:
   221  				return nil, E.New("unknown resource record type: ", resource.Header().Rrtype)
   222  			}
   223  		}
   224  		return nil, E.New("no ECH config found")
   225  	}
   226  }