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 }