UTLS兼容
This commit is contained in:
+145
@@ -0,0 +1,145 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
stdtls "crypto/tls"
|
||||
"encoding/hex"
|
||||
"net"
|
||||
|
||||
tls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
func NewUTLSClient(conn net.Conn, config *stdtls.Config, clientHelloID tls.ClientHelloID) *tls.UConn {
|
||||
return tls.UClient(conn, NewUTLSConfig(config), clientHelloID)
|
||||
}
|
||||
|
||||
func NewUTLSChromeClient(conn net.Conn, config *stdtls.Config) *tls.UConn {
|
||||
return NewUTLSClient(conn, config, tls.HelloChrome_Auto)
|
||||
}
|
||||
|
||||
func NewUTLSWebSocketClient(conn net.Conn, config *stdtls.Config, useH2 bool) (*tls.UConn, error) {
|
||||
utlsConfig := NewLegacyUTLSConfig(config)
|
||||
if useH2 {
|
||||
return tls.UClient(conn, utlsConfig, tls.HelloChrome_Auto), nil
|
||||
}
|
||||
|
||||
client := tls.UClient(conn, utlsConfig, tls.HelloCustom)
|
||||
spec, err := NewWebSocketClientHelloSpec()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := client.ApplyPreset(spec); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return client, nil
|
||||
}
|
||||
|
||||
func NewLegacyUTLSConfig(config *stdtls.Config) *tls.Config {
|
||||
if config == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
|
||||
cfg := &tls.Config{
|
||||
InsecureSkipVerify: config.InsecureSkipVerify,
|
||||
ServerName: config.ServerName,
|
||||
ClientAuth: tls.ClientAuthType(config.ClientAuth),
|
||||
ClientCAs: config.ClientCAs,
|
||||
RootCAs: config.RootCAs,
|
||||
}
|
||||
|
||||
cfg.Certificates = make([]tls.Certificate, 0, len(config.Certificates))
|
||||
for _, cert := range config.Certificates {
|
||||
cfg.Certificates = append(cfg.Certificates, tls.Certificate{
|
||||
Certificate: cert.Certificate,
|
||||
PrivateKey: cert.PrivateKey,
|
||||
OCSPStaple: cert.OCSPStaple,
|
||||
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
||||
Leaf: cert.Leaf,
|
||||
})
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func NewUTLSConfig(config *stdtls.Config) *tls.Config {
|
||||
if config == nil {
|
||||
return &tls.Config{}
|
||||
}
|
||||
|
||||
cfg := &tls.Config{
|
||||
Rand: config.Rand,
|
||||
Time: config.Time,
|
||||
RootCAs: config.RootCAs,
|
||||
NextProtos: append([]string(nil), config.NextProtos...),
|
||||
ServerName: config.ServerName,
|
||||
ClientAuth: tls.ClientAuthType(config.ClientAuth),
|
||||
ClientCAs: config.ClientCAs,
|
||||
InsecureSkipVerify: config.InsecureSkipVerify,
|
||||
CipherSuites: append([]uint16(nil), config.CipherSuites...),
|
||||
SessionTicketsDisabled: config.SessionTicketsDisabled,
|
||||
MinVersion: config.MinVersion,
|
||||
MaxVersion: config.MaxVersion,
|
||||
DynamicRecordSizingDisabled: config.DynamicRecordSizingDisabled,
|
||||
Renegotiation: tls.RenegotiationSupport(config.Renegotiation),
|
||||
KeyLogWriter: config.KeyLogWriter,
|
||||
VerifyPeerCertificate: config.VerifyPeerCertificate,
|
||||
}
|
||||
|
||||
cfg.Certificates = make([]tls.Certificate, 0, len(config.Certificates))
|
||||
for _, cert := range config.Certificates {
|
||||
cfg.Certificates = append(cfg.Certificates, toUTLSCertificate(cert))
|
||||
}
|
||||
|
||||
cfg.CurvePreferences = make([]tls.CurveID, 0, len(config.CurvePreferences))
|
||||
for _, curve := range config.CurvePreferences {
|
||||
cfg.CurvePreferences = append(cfg.CurvePreferences, tls.CurveID(curve))
|
||||
}
|
||||
|
||||
if config.VerifyConnection != nil {
|
||||
cfg.VerifyConnection = func(state tls.ConnectionState) error {
|
||||
return config.VerifyConnection(toStdTLSConnectionState(state))
|
||||
}
|
||||
}
|
||||
|
||||
return cfg
|
||||
}
|
||||
|
||||
func toUTLSCertificate(cert stdtls.Certificate) tls.Certificate {
|
||||
signatureAlgorithms := make([]tls.SignatureScheme, 0, len(cert.SupportedSignatureAlgorithms))
|
||||
for _, sig := range cert.SupportedSignatureAlgorithms {
|
||||
signatureAlgorithms = append(signatureAlgorithms, tls.SignatureScheme(sig))
|
||||
}
|
||||
|
||||
return tls.Certificate{
|
||||
Certificate: cert.Certificate,
|
||||
PrivateKey: cert.PrivateKey,
|
||||
SupportedSignatureAlgorithms: signatureAlgorithms,
|
||||
OCSPStaple: cert.OCSPStaple,
|
||||
SignedCertificateTimestamps: cert.SignedCertificateTimestamps,
|
||||
Leaf: cert.Leaf,
|
||||
}
|
||||
}
|
||||
|
||||
func toStdTLSConnectionState(state tls.ConnectionState) stdtls.ConnectionState {
|
||||
return stdtls.ConnectionState{
|
||||
Version: state.Version,
|
||||
HandshakeComplete: state.HandshakeComplete,
|
||||
DidResume: state.DidResume,
|
||||
CipherSuite: state.CipherSuite,
|
||||
NegotiatedProtocol: state.NegotiatedProtocol,
|
||||
NegotiatedProtocolIsMutual: state.NegotiatedProtocolIsMutual,
|
||||
ServerName: state.ServerName,
|
||||
PeerCertificates: state.PeerCertificates,
|
||||
VerifiedChains: state.VerifiedChains,
|
||||
SignedCertificateTimestamps: state.SignedCertificateTimestamps,
|
||||
OCSPResponse: state.OCSPResponse,
|
||||
TLSUnique: state.TLSUnique,
|
||||
ECHAccepted: state.ECHAccepted,
|
||||
}
|
||||
}
|
||||
|
||||
func NewWebSocketClientHelloSpec() (*tls.ClientHelloSpec, error) {
|
||||
fingerprinter := &tls.Fingerprinter{}
|
||||
rawCapturedClientHelloBytes, _ := hex.DecodeString("16030107b8010007b403036252c641762f4a4f55d93dcbe1a01e62839ba1b48e9082896737ca23dc6bd02120c719a58f491c56be1f21fcd68bd097f2ee5a370c576a7c512aba46b7ed73841a0020eaea130113021303c02bc02fc02cc030cca9cca8c013c014009c009d002f00350100074baaaa0000000a000c000a5a5a11ec001d00170018000d0012001004030804040105030805050108060601ff0100010000170000002b0007065a5a03040303001b00030200020010000b000908687474702f312e31000b00020100002d00020101fe0d00ba0000010001be0020a5fb3e1a8aa228913bdf2dc9c52670860d2032a04d71140d6dc08f4cb41b2a1c0090420727529f82e9a8b097215adb52126184c638a98f0a6f8121b04edb939a7ff2a9564b44a917160ceb0a41f7a2dc12b89c622bc6687a21759c813858c81fe2e9024b02c6501c4b4c9e87ed2bd1538a420b6045a0785a66b044e53260e19b6f09042b903fa6758ce571dff16b91e95a19dbe5da0a46cab946036f48ad1394534866ee874f8dc75b3e4efa444be660262f003304ef04ed5a5a00010011ec04c098b01e12861f0d1738ebdb83b5fb876e7a1382f50149279a62479352b110f9791d1710c99cd988348175a20884650682e5e6a48bd2b27bb52360882bd84c6f97b6bc142c300ba7ab420bbec1872c13ba6970501e2b473f5c325da09a1dda800060d3c14454a95e6a67ddf28be60140cc3b07d393826b8babaf9a074b97bfa6fb8a2da784e98c5a332c62d7a60b7ac5b85e6606d128a4e9d89056680c6ff3ad5c3a3018218006db0fd81843e4ccb3f3e42ec858554be50aaa5b9afb613a1c07a631551706c4840bdb11a41564db56cdd2e58d1c619fc34567b74a2c54755f4174a0d589bf5938388a0976456c2a2aca2c08b2af11a2b550da9b22e4a39e2a7759c887e884b362b0b0e8394a149b44e82b6f5e680cd280cd2167953c94b111c20c4d028b2fc0926ac1a0454977fdf4a6876a555048a0c8b23531215f8543b5af5a2a69fc4151a870f481204e3a1535ec35321697d0ea0a470239e7b40b99e9b7ee01205bf567c42479c137c346b8af4dd825d4324c6d34875dd50b1841a78c42b79075af259a0f45252f311997f39902337088ee6817565b2e63858924a2354d86b1ba77bfa324339ec4a72663134db7227e652aea43700f751f7f519c5a66a036e0274a48c58716a3eb8a6de4730d19d534dd52ae83b9a7f07ccbc8d63b073a5127e3170dc58031524b81b611c6c4cf2aaba01fc55747b643fb417c85c0a21aa823709a037e418fc8e9c16c51a0c105c07d7c2f56dbcd4516a17a10036531768fb04300f0cdcc7c5135517b1643c3204b47575b97d0a513287985a0353523d93d511b694d6bb5ecc0698823855a606b83c77723808dd6737c9129a5c50457e71a063f927299214e902861e89a81423715b60717e8551b29126f857499810a0328d7182b538276d15517aa21b828ba7afcc13c1cc4cd1574a1c97bcfe4490f766d0e95a0c2911bc0d7a47161bcf5c344558314e7a1ab6c4227950b76c3319acaa53a7776a91e3b62ecd38f0732504674247947855a95892cc43b7f56a09f0c4281976e2b73820ff48180f054f482638078b08cf01d2b221a35214002b5cc8e394a414230a4f562af739e70ec540c260c651009a0616c5830658e0cbb2d2a2f94dab9f539a9ef19750f4925f7106a3a9c0a68e4a7d442202678beeef25b92c8709d93133c08728603ac900719db6615d294ad1aa965546a0c655a6891ab8e9fea31b7262dbba5af94372262934a4feac79ad72a3277aa7e97ab48f9a06f6857d26a2d48306b80a4bb600785f5b26f00202d2eb6af0226774e93615a484fa42a91bfe6034e890f6642c95a979d8df8a39ab10977846b1dc1998b51a8dde62975dc54bf92ada8dc0d7c0a020f429d4ff0b11ba95b6e076ec0443d01f0cb963441ddca9244631b776a029f874b799534c9c14808863c756b2d8a0309a0fab26bc37db05601cd5741acf9acc4c84a9e6b76b1e8282908a8d2268562e72109c3c8cb93507e1accf48723a056bd4acab4317771d4e432216a0a39377792469757a8c186d8085078aa2a8bce201628bad8c7ce98bb41dc56d6715a1dc3cb1cab48cb5a8f18d04a651b9efdd751bd199c266aac08726177bb432ecba334b06e516c12449846dcf90a4080248e2b15cb435e021ee8c4a8c17c095babf8011d67e0a5f16e25dc7357fc1c78e5884fe32598695589294e9c431c94ac2f5d2053757f9a905df36b001d002022b1fc0ef89cbfbd8f197c3e230879a2e71c155a1a6cb1bc33136cf93380c41d00000011000f00000c7373682e6576616e2e72756e0012000000230000000500050100000000baba0001000029010b00e600e0d1ae9857a5731dd0d5ca9d726bf57748ff9f57a7bb61ee7cc84e0f06358291fd2694914ab4ab3abc95fec3a1b393f458cca49eddd4dc24c896429eba5464d54ed59c562be84fe5660c8f74263db3c5eefbd36f2c667b9bd8219e20d84162a545978844bce25f2dc0f1c56f032bcee7f0bcd9fc762cfa115f368d0fe3b2e03fce32673bfdafd419eb8bc36bcf9c4a0d49c8d750ea7373e41c2ac13eb97f8fdb6c619742e98951c6447b76b263e0040ae874c7bd486421af99541b3b40e5b6eb3c0ae555dc1dfa7448aee19f05e54f70eb6655a91eb03eb93015a45eb29adc73d82437927e002120022bd91157ac53b55c75c03be16f0bc0040f38fa0085949f0157f8e7db794cc5")
|
||||
spec, err := fingerprinter.FingerprintClientHello(rawCapturedClientHelloBytes)
|
||||
return spec, err
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
package util
|
||||
|
||||
import (
|
||||
stdtls "crypto/tls"
|
||||
"net"
|
||||
"testing"
|
||||
|
||||
tls "github.com/refraction-networking/utls"
|
||||
)
|
||||
|
||||
func TestNewUTLSConfigPreservesClientSettings(t *testing.T) {
|
||||
called := false
|
||||
cfg := NewUTLSConfig(&stdtls.Config{
|
||||
ServerName: "example.com",
|
||||
InsecureSkipVerify: true,
|
||||
NextProtos: []string{"h2", "http/1.1"},
|
||||
MinVersion: stdtls.VersionTLS12,
|
||||
MaxVersion: stdtls.VersionTLS13,
|
||||
CipherSuites: []uint16{stdtls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256},
|
||||
CurvePreferences: []stdtls.CurveID{stdtls.X25519, stdtls.CurveP256},
|
||||
DynamicRecordSizingDisabled: true,
|
||||
Renegotiation: stdtls.RenegotiateOnceAsClient,
|
||||
VerifyConnection: func(state stdtls.ConnectionState) error {
|
||||
called = true
|
||||
if state.ServerName != "example.com" {
|
||||
t.Fatalf("ServerName = %q, want example.com", state.ServerName)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
})
|
||||
|
||||
if cfg.ServerName != "example.com" {
|
||||
t.Fatalf("ServerName = %q, want example.com", cfg.ServerName)
|
||||
}
|
||||
if !cfg.InsecureSkipVerify {
|
||||
t.Fatal("InsecureSkipVerify was not preserved")
|
||||
}
|
||||
if len(cfg.NextProtos) != 2 || cfg.NextProtos[0] != "h2" || cfg.NextProtos[1] != "http/1.1" {
|
||||
t.Fatalf("NextProtos = %v", cfg.NextProtos)
|
||||
}
|
||||
if cfg.MinVersion != tls.VersionTLS12 || cfg.MaxVersion != tls.VersionTLS13 {
|
||||
t.Fatalf("version range = %x-%x", cfg.MinVersion, cfg.MaxVersion)
|
||||
}
|
||||
if len(cfg.CipherSuites) != 1 || cfg.CipherSuites[0] != tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 {
|
||||
t.Fatalf("CipherSuites = %v", cfg.CipherSuites)
|
||||
}
|
||||
if len(cfg.CurvePreferences) != 2 || cfg.CurvePreferences[0] != tls.X25519 || cfg.CurvePreferences[1] != tls.CurveP256 {
|
||||
t.Fatalf("CurvePreferences = %v", cfg.CurvePreferences)
|
||||
}
|
||||
if !cfg.DynamicRecordSizingDisabled {
|
||||
t.Fatal("DynamicRecordSizingDisabled was not preserved")
|
||||
}
|
||||
if cfg.Renegotiation != tls.RenegotiateOnceAsClient {
|
||||
t.Fatalf("Renegotiation = %v", cfg.Renegotiation)
|
||||
}
|
||||
if cfg.VerifyConnection == nil {
|
||||
t.Fatal("VerifyConnection was not preserved")
|
||||
}
|
||||
if err := cfg.VerifyConnection(tls.ConnectionState{ServerName: "example.com"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !called {
|
||||
t.Fatal("VerifyConnection wrapper was not called")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUTLSChromeClientBuildsChromeHello(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
client := NewUTLSChromeClient(c1, &stdtls.Config{ServerName: "example.com"})
|
||||
if client.ClientHelloID != tls.HelloChrome_Auto {
|
||||
t.Fatalf("ClientHelloID = %+v, want %+v", client.ClientHelloID, tls.HelloChrome_Auto)
|
||||
}
|
||||
if err := client.BuildHandshakeStateWithoutSession(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(client.Extensions) == 0 {
|
||||
t.Fatal("Chrome ClientHello has no extensions")
|
||||
}
|
||||
}
|
||||
|
||||
func TestNewUTLSWebSocketClientAppliesCustomSpec(t *testing.T) {
|
||||
c1, c2 := net.Pipe()
|
||||
defer c1.Close()
|
||||
defer c2.Close()
|
||||
|
||||
client, err := NewUTLSWebSocketClient(c1, &stdtls.Config{
|
||||
ServerName: "example.com",
|
||||
NextProtos: []string{"h2"},
|
||||
MinVersion: stdtls.VersionTLS13,
|
||||
}, false)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if client.ClientHelloID != tls.HelloCustom {
|
||||
t.Fatalf("ClientHelloID = %+v, want %+v", client.ClientHelloID, tls.HelloCustom)
|
||||
}
|
||||
if len(client.Extensions) == 0 {
|
||||
t.Fatal("custom WebSocket ClientHello preset has no extensions")
|
||||
}
|
||||
if err := client.BuildHandshakeStateWithoutSession(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(client.HandshakeState.Hello.AlpnProtocols) != 1 || client.HandshakeState.Hello.AlpnProtocols[0] != "http/1.1" {
|
||||
t.Fatalf("ALPN = %v, want [http/1.1]", client.HandshakeState.Hello.AlpnProtocols)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user