2024-05-24 14:08:09 -07:00
/ *
*
* Copyright 2014 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
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"net/url"
"os"
2024-08-27 10:04:05 -07:00
"google.golang.org/grpc/grpclog"
2024-05-24 14:08:09 -07:00
credinternal "google.golang.org/grpc/internal/credentials"
2024-08-27 10:04:05 -07:00
"google.golang.org/grpc/internal/envconfig"
2024-05-24 14:08:09 -07:00
)
2024-08-27 10:04:05 -07:00
var logger = grpclog . Component ( "credentials" )
2024-05-24 14:08:09 -07:00
// TLSInfo contains the auth information for a TLS authenticated connection.
// It implements the AuthInfo interface.
type TLSInfo struct {
State tls . ConnectionState
CommonAuthInfo
// This API is experimental.
SPIFFEID * url . URL
}
// AuthType returns the type of TLSInfo as a string.
func ( t TLSInfo ) AuthType ( ) string {
return "tls"
}
// cipherSuiteLookup returns the string version of a TLS cipher suite ID.
func cipherSuiteLookup ( cipherSuiteID uint16 ) string {
for _ , s := range tls . CipherSuites ( ) {
if s . ID == cipherSuiteID {
return s . Name
}
}
for _ , s := range tls . InsecureCipherSuites ( ) {
if s . ID == cipherSuiteID {
return s . Name
}
}
return fmt . Sprintf ( "unknown ID: %v" , cipherSuiteID )
}
// GetSecurityValue returns security info requested by channelz.
func ( t TLSInfo ) GetSecurityValue ( ) ChannelzSecurityValue {
v := & TLSChannelzSecurityValue {
StandardName : cipherSuiteLookup ( t . State . CipherSuite ) ,
}
// 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
}
// tlsCreds is the credentials required for authenticating a connection using TLS.
type tlsCreds struct {
// TLS configuration
config * tls . Config
}
func ( c tlsCreds ) Info ( ) ProtocolInfo {
return ProtocolInfo {
SecurityProtocol : "tls" ,
SecurityVersion : "1.2" ,
ServerName : c . config . ServerName ,
}
}
func ( c * tlsCreds ) ClientHandshake ( ctx context . Context , authority string , rawConn net . Conn ) ( _ net . Conn , _ AuthInfo , err error ) {
// use local cfg to avoid clobbering ServerName if using multiple endpoints
cfg := credinternal . 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 ( )
}
2024-08-27 10:04:05 -07:00
// The negotiated protocol can be either of the following:
// 1. h2: When the server supports ALPN. Only HTTP/2 can be negotiated since
// it is the only protocol advertised by the client during the handshake.
// The tls library ensures that the server chooses a protocol advertised
// by the client.
// 2. "" (empty string): If the server doesn't support ALPN. ALPN is a requirement
// for using HTTP/2 over TLS. We can terminate the connection immediately.
np := conn . ConnectionState ( ) . NegotiatedProtocol
if np == "" {
if envconfig . EnforceALPNEnabled {
conn . Close ( )
return nil , nil , fmt . Errorf ( "credentials: cannot check peer: missing selected ALPN property" )
}
logger . Warningf ( "Allowing TLS connection to server %q with ALPN disabled. TLS connections to servers with ALPN disabled will be disallowed in future grpc-go releases" , cfg . ServerName )
}
2024-05-24 14:08:09 -07:00
tlsInfo := TLSInfo {
State : conn . ConnectionState ( ) ,
CommonAuthInfo : CommonAuthInfo {
SecurityLevel : PrivacyAndIntegrity ,
} ,
}
id := credinternal . SPIFFEIDFromState ( conn . ConnectionState ( ) )
if id != nil {
tlsInfo . SPIFFEID = id
}
return credinternal . WrapSyscallConn ( rawConn , conn ) , tlsInfo , nil
}
func ( c * tlsCreds ) ServerHandshake ( rawConn net . Conn ) ( net . Conn , AuthInfo , error ) {
conn := tls . Server ( rawConn , c . config )
if err := conn . Handshake ( ) ; err != nil {
conn . Close ( )
return nil , nil , err
}
2024-08-27 10:04:05 -07:00
cs := conn . ConnectionState ( )
// The negotiated application protocol can be empty only if the client doesn't
// support ALPN. In such cases, we can close the connection since ALPN is required
// for using HTTP/2 over TLS.
if cs . NegotiatedProtocol == "" {
if envconfig . EnforceALPNEnabled {
conn . Close ( )
return nil , nil , fmt . Errorf ( "credentials: cannot check peer: missing selected ALPN property" )
} else if logger . V ( 2 ) {
logger . Info ( "Allowing TLS connection from client with ALPN disabled. TLS connections with ALPN disabled will be disallowed in future grpc-go releases" )
}
}
2024-05-24 14:08:09 -07:00
tlsInfo := TLSInfo {
2024-08-27 10:04:05 -07:00
State : cs ,
2024-05-24 14:08:09 -07:00
CommonAuthInfo : CommonAuthInfo {
SecurityLevel : PrivacyAndIntegrity ,
} ,
}
id := credinternal . SPIFFEIDFromState ( conn . ConnectionState ( ) )
if id != nil {
tlsInfo . SPIFFEID = id
}
return credinternal . WrapSyscallConn ( rawConn , conn ) , tlsInfo , nil
}
func ( c * tlsCreds ) Clone ( ) TransportCredentials {
return NewTLS ( 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 : { } ,
}
// NewTLS uses c to construct a TransportCredentials based on TLS.
func NewTLS ( c * tls . Config ) TransportCredentials {
tc := & tlsCreds { credinternal . CloneTLSConfig ( c ) }
tc . config . NextProtos = credinternal . AppendH2ToNextProtos ( tc . 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 tc . config . MinVersion == 0 && ( tc . config . MaxVersion == 0 || tc . config . MaxVersion >= tls . VersionTLS12 ) {
tc . 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 tc . config . CipherSuites == nil {
for _ , cs := range tls . CipherSuites ( ) {
if _ , ok := tls12ForbiddenCipherSuites [ cs . ID ] ; ! ok {
tc . config . CipherSuites = append ( tc . config . CipherSuites , cs . ID )
}
}
}
return tc
}
// NewClientTLSFromCert 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.
func NewClientTLSFromCert ( cp * x509 . CertPool , serverNameOverride string ) TransportCredentials {
return NewTLS ( & tls . Config { ServerName : serverNameOverride , RootCAs : cp } )
}
// NewClientTLSFromFile 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.
func NewClientTLSFromFile ( certFile , serverNameOverride string ) ( 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 NewTLS ( & tls . Config { ServerName : serverNameOverride , RootCAs : cp } ) , nil
}
// NewServerTLSFromCert constructs TLS credentials from the input certificate for server.
func NewServerTLSFromCert ( cert * tls . Certificate ) TransportCredentials {
return NewTLS ( & tls . Config { Certificates : [ ] tls . Certificate { * cert } } )
}
// NewServerTLSFromFile constructs TLS credentials from the input certificate file and key
// file for server.
func NewServerTLSFromFile ( certFile , keyFile string ) ( TransportCredentials , error ) {
cert , err := tls . LoadX509KeyPair ( certFile , keyFile )
if err != nil {
return nil , err
}
return NewTLS ( & tls . Config { Certificates : [ ] tls . Certificate { cert } } ) , nil
}
// TLSChannelzSecurityValue defines the struct that TLS protocol should return
// from GetSecurityValue(), containing security info like cipher and certificate used.
//
// # Experimental
//
// Notice: This type is EXPERIMENTAL and may be changed or removed in a
// later release.
type TLSChannelzSecurityValue struct {
ChannelzSecurityValue
StandardName string
LocalCertificate [ ] byte
RemoteCertificate [ ] byte
}