grpc-go/experimental/credentials/tls.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)
}