package dialer import ( "context" "crypto/tls" "crypto/x509" "net" "net/netip" "os" C "github.com/sagernet/sing-box/constant" "github.com/sagernet/sing-box/option" E "github.com/sagernet/sing/common/exceptions" M "github.com/sagernet/sing/common/metadata" N "github.com/sagernet/sing/common/network" ) type TLSDialer struct { dialer N.Dialer config *tls.Config } func TLSConfig(serverAddress string, options option.OutboundTLSOptions) (*tls.Config, error) { if !options.Enabled { return nil, nil } var serverName string if options.ServerName != "" { serverName = options.ServerName } else if serverAddress != "" { if _, err := netip.ParseAddr(serverName); err == nil { serverName = serverAddress } } if serverName == "" && options.Insecure { return nil, E.New("missing server_name or insecure=true") } var tlsConfig tls.Config if options.DisableSNI { tlsConfig.ServerName = "127.0.0.1" } else { tlsConfig.ServerName = serverName } if options.Insecure { tlsConfig.InsecureSkipVerify = options.Insecure } else if options.DisableSNI { tlsConfig.InsecureSkipVerify = true tlsConfig.VerifyConnection = func(state tls.ConnectionState) error { verifyOptions := x509.VerifyOptions{ DNSName: serverName, Intermediates: x509.NewCertPool(), } for _, cert := range state.PeerCertificates[1:] { verifyOptions.Intermediates.AddCert(cert) } _, err := state.PeerCertificates[0].Verify(verifyOptions) return err } } if len(options.ALPN) > 0 { tlsConfig.NextProtos = options.ALPN } if options.MinVersion != "" { minVersion, err := option.ParseTLSVersion(options.MinVersion) if err != nil { return nil, E.Cause(err, "parse min_version") } tlsConfig.MinVersion = minVersion } if options.MaxVersion != "" { maxVersion, err := option.ParseTLSVersion(options.MaxVersion) if err != nil { return nil, E.Cause(err, "parse max_version") } tlsConfig.MaxVersion = maxVersion } if options.CipherSuites != nil { find: for _, cipherSuite := range options.CipherSuites { for _, tlsCipherSuite := range tls.CipherSuites() { if cipherSuite == tlsCipherSuite.Name { tlsConfig.CipherSuites = append(tlsConfig.CipherSuites, tlsCipherSuite.ID) continue find } } return nil, E.New("unknown cipher_suite: ", cipherSuite) } } var certificate []byte if options.Certificate != "" { certificate = []byte(options.Certificate) } else if options.CertificatePath != "" { content, err := os.ReadFile(options.CertificatePath) if err != nil { return nil, E.Cause(err, "read certificate") } certificate = content } if len(certificate) > 0 { certPool := x509.NewCertPool() if !certPool.AppendCertsFromPEM(certificate) { return nil, E.New("failed to parse certificate:\n\n", certificate) } tlsConfig.RootCAs = certPool } return &tlsConfig, nil } func NewTLS(dialer N.Dialer, serverAddress string, options option.OutboundTLSOptions) (N.Dialer, error) { if !options.Enabled { return dialer, nil } tlsConfig, err := TLSConfig(serverAddress, options) if err != nil { return nil, err } return &TLSDialer{ dialer: dialer, config: tlsConfig, }, nil } func (d *TLSDialer) DialContext(ctx context.Context, network string, destination M.Socksaddr) (net.Conn, error) { if network != N.NetworkTCP { return nil, os.ErrInvalid } conn, err := d.dialer.DialContext(ctx, network, destination) if err != nil { return nil, err } return TLSClient(ctx, conn, d.config) } func (d *TLSDialer) ListenPacket(ctx context.Context, destination M.Socksaddr) (net.PacketConn, error) { return nil, os.ErrInvalid } func TLSClient(ctx context.Context, conn net.Conn, tlsConfig *tls.Config) (*tls.Conn, error) { tlsConn := tls.Client(conn, tlsConfig) ctx, cancel := context.WithTimeout(ctx, C.TCPTimeout) defer cancel() err := tlsConn.HandshakeContext(ctx) return tlsConn, err }