mirror of https://github.com/grpc/grpc-go.git
250 lines
8.7 KiB
Go
250 lines
8.7 KiB
Go
/*
|
|
*
|
|
* Copyright 2025 gRPC authors.
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*
|
|
*/
|
|
|
|
// Package credentials provides experimental TLS credentials.
|
|
// The use of this package is strongly discouraged. These credentials exist
|
|
// solely to maintain compatibility for users interacting with clients that
|
|
// violate the HTTP/2 specification by not advertising support for "h2" in ALPN.
|
|
// This package is slated for removal in upcoming grpc-go releases. Users must
|
|
// not rely on this package directly. Instead, they should either vendor a
|
|
// specific version of gRPC or copy the relevant credentials into their own
|
|
// codebase if absolutely necessary.
|
|
package credentials
|
|
|
|
import (
|
|
"context"
|
|
"crypto/tls"
|
|
"crypto/x509"
|
|
"fmt"
|
|
"net"
|
|
"os"
|
|
|
|
"golang.org/x/net/http2"
|
|
"google.golang.org/grpc/credentials"
|
|
"google.golang.org/grpc/experimental/credentials/internal"
|
|
)
|
|
|
|
// tlsCreds is the credentials required for authenticating a connection using TLS.
|
|
type tlsCreds struct {
|
|
// TLS configuration
|
|
config *tls.Config
|
|
}
|
|
|
|
func (c tlsCreds) Info() credentials.ProtocolInfo {
|
|
return credentials.ProtocolInfo{
|
|
SecurityProtocol: "tls",
|
|
SecurityVersion: "1.2",
|
|
ServerName: c.config.ServerName,
|
|
}
|
|
}
|
|
|
|
func (c *tlsCreds) 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 := cloneTLSConfig(c.config)
|
|
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 := tls.Client(rawConn, cfg)
|
|
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 := credentials.TLSInfo{
|
|
State: conn.ConnectionState(),
|
|
CommonAuthInfo: credentials.CommonAuthInfo{
|
|
SecurityLevel: credentials.PrivacyAndIntegrity,
|
|
},
|
|
}
|
|
id := internal.SPIFFEIDFromState(conn.ConnectionState())
|
|
if id != nil {
|
|
tlsInfo.SPIFFEID = id
|
|
}
|
|
return internal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
|
|
}
|
|
|
|
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
|
|
conn := tls.Server(rawConn, c.config)
|
|
if err := conn.Handshake(); err != nil {
|
|
conn.Close()
|
|
return nil, nil, err
|
|
}
|
|
cs := conn.ConnectionState()
|
|
tlsInfo := credentials.TLSInfo{
|
|
State: cs,
|
|
CommonAuthInfo: credentials.CommonAuthInfo{
|
|
SecurityLevel: credentials.PrivacyAndIntegrity,
|
|
},
|
|
}
|
|
id := internal.SPIFFEIDFromState(conn.ConnectionState())
|
|
if id != nil {
|
|
tlsInfo.SPIFFEID = id
|
|
}
|
|
return internal.WrapSyscallConn(rawConn, conn), tlsInfo, nil
|
|
}
|
|
|
|
func (c *tlsCreds) Clone() credentials.TransportCredentials {
|
|
return NewTLSWithALPNDisabled(c.config)
|
|
}
|
|
|
|
func (c *tlsCreds) OverrideServerName(serverNameOverride string) error {
|
|
c.config.ServerName = serverNameOverride
|
|
return nil
|
|
}
|
|
|
|
// The following cipher suites are forbidden for use with HTTP/2 by
|
|
// https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
|
|
var tls12ForbiddenCipherSuites = map[uint16]struct{}{
|
|
tls.TLS_RSA_WITH_AES_128_CBC_SHA: {},
|
|
tls.TLS_RSA_WITH_AES_256_CBC_SHA: {},
|
|
tls.TLS_RSA_WITH_AES_128_GCM_SHA256: {},
|
|
tls.TLS_RSA_WITH_AES_256_GCM_SHA384: {},
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: {},
|
|
tls.TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: {},
|
|
tls.TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: {},
|
|
tls.TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: {},
|
|
}
|
|
|
|
// NewTLSWithALPNDisabled uses c to construct a TransportCredentials based on
|
|
// TLS. ALPN verification is disabled.
|
|
func NewTLSWithALPNDisabled(c *tls.Config) credentials.TransportCredentials {
|
|
config := applyDefaults(c)
|
|
if config.GetConfigForClient != nil {
|
|
oldFn := config.GetConfigForClient
|
|
config.GetConfigForClient = func(hello *tls.ClientHelloInfo) (*tls.Config, error) {
|
|
cfgForClient, err := oldFn(hello)
|
|
if err != nil || cfgForClient == nil {
|
|
return cfgForClient, err
|
|
}
|
|
return applyDefaults(cfgForClient), nil
|
|
}
|
|
}
|
|
return &tlsCreds{config: config}
|
|
}
|
|
|
|
func applyDefaults(c *tls.Config) *tls.Config {
|
|
config := cloneTLSConfig(c)
|
|
config.NextProtos = appendH2ToNextProtos(config.NextProtos)
|
|
// If the user did not configure a MinVersion and did not configure a
|
|
// MaxVersion < 1.2, use MinVersion=1.2, which is required by
|
|
// https://datatracker.ietf.org/doc/html/rfc7540#section-9.2
|
|
if config.MinVersion == 0 && (config.MaxVersion == 0 || config.MaxVersion >= tls.VersionTLS12) {
|
|
config.MinVersion = tls.VersionTLS12
|
|
}
|
|
// If the user did not configure CipherSuites, use all "secure" cipher
|
|
// suites reported by the TLS package, but remove some explicitly forbidden
|
|
// by https://datatracker.ietf.org/doc/html/rfc7540#appendix-A
|
|
if config.CipherSuites == nil {
|
|
for _, cs := range tls.CipherSuites() {
|
|
if _, ok := tls12ForbiddenCipherSuites[cs.ID]; !ok {
|
|
config.CipherSuites = append(config.CipherSuites, cs.ID)
|
|
}
|
|
}
|
|
}
|
|
return config
|
|
}
|
|
|
|
// NewClientTLSFromCertWithALPNDisabled constructs TLS credentials from the
|
|
// provided root certificate authority certificate(s) to validate server
|
|
// connections. If certificates to establish the identity of the client need to
|
|
// be included in the credentials (eg: for mTLS), use NewTLS instead, where a
|
|
// complete tls.Config can be specified.
|
|
// serverNameOverride is for testing only. If set to a non empty string,
|
|
// it will override the virtual host name of authority (e.g. :authority header
|
|
// field) in requests. ALPN verification is disabled.
|
|
func NewClientTLSFromCertWithALPNDisabled(cp *x509.CertPool, serverNameOverride string) credentials.TransportCredentials {
|
|
return NewTLSWithALPNDisabled(&tls.Config{ServerName: serverNameOverride, RootCAs: cp})
|
|
}
|
|
|
|
// NewClientTLSFromFileWithALPNDisabled constructs TLS credentials from the
|
|
// provided root certificate authority certificate file(s) to validate server
|
|
// connections. If certificates to establish the identity of the client need to
|
|
// be included in the credentials (eg: for mTLS), use NewTLS instead, where a
|
|
// complete tls.Config can be specified.
|
|
// serverNameOverride is for testing only. If set to a non empty string,
|
|
// it will override the virtual host name of authority (e.g. :authority header
|
|
// field) in requests. ALPN verification is disabled.
|
|
func NewClientTLSFromFileWithALPNDisabled(certFile, serverNameOverride string) (credentials.TransportCredentials, error) {
|
|
b, err := os.ReadFile(certFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
cp := x509.NewCertPool()
|
|
if !cp.AppendCertsFromPEM(b) {
|
|
return nil, fmt.Errorf("credentials: failed to append certificates")
|
|
}
|
|
return NewTLSWithALPNDisabled(&tls.Config{ServerName: serverNameOverride, RootCAs: cp}), nil
|
|
}
|
|
|
|
// NewServerTLSFromCertWithALPNDisabled constructs TLS credentials from the
|
|
// input certificate for server. ALPN verification is disabled.
|
|
func NewServerTLSFromCertWithALPNDisabled(cert *tls.Certificate) credentials.TransportCredentials {
|
|
return NewTLSWithALPNDisabled(&tls.Config{Certificates: []tls.Certificate{*cert}})
|
|
}
|
|
|
|
// NewServerTLSFromFileWithALPNDisabled constructs TLS credentials from the
|
|
// input certificate file and key file for server. ALPN verification is disabled.
|
|
func NewServerTLSFromFileWithALPNDisabled(certFile, keyFile string) (credentials.TransportCredentials, error) {
|
|
cert, err := tls.LoadX509KeyPair(certFile, keyFile)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return NewTLSWithALPNDisabled(&tls.Config{Certificates: []tls.Certificate{cert}}), nil
|
|
}
|
|
|
|
// cloneTLSConfig returns a shallow clone of the exported
|
|
// fields of cfg, ignoring the unexported sync.Once, which
|
|
// contains a mutex and must not be copied.
|
|
//
|
|
// If cfg is nil, a new zero tls.Config is returned.
|
|
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
|
|
if cfg == nil {
|
|
return &tls.Config{}
|
|
}
|
|
|
|
return cfg.Clone()
|
|
}
|
|
|
|
// appendH2ToNextProtos appends h2 to next protos.
|
|
func appendH2ToNextProtos(ps []string) []string {
|
|
for _, p := range ps {
|
|
if p == http2.NextProtoTLS {
|
|
return ps
|
|
}
|
|
}
|
|
ret := make([]string, 0, len(ps)+1)
|
|
ret = append(ret, ps...)
|
|
return append(ret, http2.NextProtoTLS)
|
|
}
|