From da0b13cca0517f213c7949ac44afeb4dad8f4a81 Mon Sep 17 00:00:00 2001 From: Hirbod Behnam Date: Sat, 22 Oct 2022 04:36:36 +0330 Subject: [PATCH] Added uTLS to gRPC (#1264) * Added uTLS to gRPC * Use base 16 of ciphers as StandardName --- transport/internet/grpc/dial.go | 8 ++- transport/internet/tls/grpc.go | 107 ++++++++++++++++++++++++++++++++ 2 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 transport/internet/tls/grpc.go diff --git a/transport/internet/grpc/dial.go b/transport/internet/grpc/dial.go index 07fe40e8..9836d93a 100644 --- a/transport/internet/grpc/dial.go +++ b/transport/internet/grpc/dial.go @@ -121,7 +121,13 @@ func getGrpcClient(ctx context.Context, dest net.Destination, streamSettings *in } if tlsConfig != nil { - dialOptions = append(dialOptions, grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig.GetTLSConfig()))) + var transportCredential credentials.TransportCredentials + if fingerprint, exists := tls.Fingerprints[tlsConfig.Fingerprint]; exists { + transportCredential = tls.NewGrpcUtls(tlsConfig.GetTLSConfig(), fingerprint) + } else { // Fallback to normal gRPC TLS + transportCredential = credentials.NewTLS(tlsConfig.GetTLSConfig()) + } + dialOptions = append(dialOptions, grpc.WithTransportCredentials(transportCredential)) } else { dialOptions = append(dialOptions, grpc.WithInsecure()) } diff --git a/transport/internet/tls/grpc.go b/transport/internet/tls/grpc.go new file mode 100644 index 00000000..ede921b7 --- /dev/null +++ b/transport/internet/tls/grpc.go @@ -0,0 +1,107 @@ +package tls + +import ( + "context" + gotls "crypto/tls" + utls "github.com/refraction-networking/utls" + "google.golang.org/grpc/credentials" + "net" + "net/url" + "strconv" +) + +// grpcUtlsInfo contains the auth information for a TLS authenticated connection. +// It implements the AuthInfo interface. +type grpcUtlsInfo struct { + State utls.ConnectionState + credentials.CommonAuthInfo + // This API is experimental. + SPIFFEID *url.URL +} + +// AuthType returns the type of TLSInfo as a string. +func (t grpcUtlsInfo) AuthType() string { + return "utls" +} + +// GetSecurityValue returns security info requested by channelz. +func (t grpcUtlsInfo) GetSecurityValue() credentials.ChannelzSecurityValue { + v := &credentials.TLSChannelzSecurityValue{ + StandardName: "0x" + strconv.FormatUint(uint64(t.State.CipherSuite), 16), + } + // Currently there's no way to get LocalCertificate info from tls package. + if len(t.State.PeerCertificates) > 0 { + v.RemoteCertificate = t.State.PeerCertificates[0].Raw + } + return v +} + +// grpcUtls is the credentials required for authenticating a connection using TLS. +type grpcUtls struct { + config *gotls.Config + fingerprint *utls.ClientHelloID +} + +func (c grpcUtls) Info() credentials.ProtocolInfo { + return credentials.ProtocolInfo{ + SecurityProtocol: "tls", + SecurityVersion: "1.2", + ServerName: c.config.ServerName, + } +} + +func (c *grpcUtls) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (_ net.Conn, _ credentials.AuthInfo, err error) { + // use local cfg to avoid clobbering ServerName if using multiple endpoints + cfg := c.config.Clone() + if cfg.ServerName == "" { + serverName, _, err := net.SplitHostPort(authority) + if err != nil { + // If the authority had no host port or if the authority cannot be parsed, use it as-is. + serverName = authority + } + cfg.ServerName = serverName + } + conn := UClient(rawConn, cfg, c.fingerprint).(*UConn) + errChannel := make(chan error, 1) + go func() { + errChannel <- conn.Handshake() + close(errChannel) + }() + select { + case err := <-errChannel: + if err != nil { + conn.Close() + return nil, nil, err + } + case <-ctx.Done(): + conn.Close() + return nil, nil, ctx.Err() + } + tlsInfo := grpcUtlsInfo{ + State: conn.ConnectionState(), + CommonAuthInfo: credentials.CommonAuthInfo{ + SecurityLevel: credentials.PrivacyAndIntegrity, + }, + } + return conn, tlsInfo, nil +} + +// ServerHandshake will always panic. We don't support running uTLS as server. +func (c *grpcUtls) ServerHandshake(net.Conn) (net.Conn, credentials.AuthInfo, error) { + panic("not available!") +} + +func (c *grpcUtls) Clone() credentials.TransportCredentials { + return NewGrpcUtls(c.config, c.fingerprint) +} + +func (c *grpcUtls) OverrideServerName(serverNameOverride string) error { + c.config.ServerName = serverNameOverride + return nil +} + +// NewGrpcUtls uses c to construct a TransportCredentials based on uTLS. +func NewGrpcUtls(c *gotls.Config, fingerprint *utls.ClientHelloID) credentials.TransportCredentials { + tc := &grpcUtls{c.Clone(), fingerprint} + return tc +}