github.com/letsencrypt/boulder@v0.20251208.0/grpc/creds/creds.go (about)

     1  package creds
     2  
     3  import (
     4  	"context"
     5  	"crypto/tls"
     6  	"crypto/x509"
     7  	"errors"
     8  	"fmt"
     9  	"net"
    10  
    11  	"google.golang.org/grpc/credentials"
    12  )
    13  
    14  var (
    15  	ErrClientHandshakeNop = errors.New(
    16  		"boulder/grpc/creds: Client-side handshakes are not implemented with " +
    17  			"serverTransportCredentials")
    18  	ErrServerHandshakeNop = errors.New(
    19  		"boulder/grpc/creds: Server-side handshakes are not implemented with " +
    20  			"clientTransportCredentials")
    21  	ErrOverrideServerNameNop = errors.New(
    22  		"boulder/grpc/creds: OverrideServerName() is not implemented")
    23  	ErrNilServerConfig = errors.New(
    24  		"boulder/grpc/creds: `serverConfig` must not be nil")
    25  	ErrEmptyPeerCerts = errors.New(
    26  		"boulder/grpc/creds: validateClient given state with empty PeerCertificates")
    27  )
    28  
    29  type ErrSANNotAccepted struct {
    30  	got, expected []string
    31  }
    32  
    33  func (e ErrSANNotAccepted) Error() string {
    34  	return fmt.Sprintf("boulder/grpc/creds: client certificate SAN was invalid. "+
    35  		"Got %q, expected one of %q.", e.got, e.expected)
    36  }
    37  
    38  // clientTransportCredentials is a grpc/credentials.TransportCredentials which supports
    39  // connecting to, and verifying multiple DNS names
    40  type clientTransportCredentials struct {
    41  	roots   *x509.CertPool
    42  	clients []tls.Certificate
    43  	// If set, this is used as the hostname to validate on certificates, instead
    44  	// of the value passed to ClientHandshake by grpc.
    45  	hostOverride string
    46  }
    47  
    48  // NewClientCredentials returns a new initialized grpc/credentials.TransportCredentials for client usage
    49  func NewClientCredentials(rootCAs *x509.CertPool, clientCerts []tls.Certificate, hostOverride string) credentials.TransportCredentials {
    50  	return &clientTransportCredentials{rootCAs, clientCerts, hostOverride}
    51  }
    52  
    53  // ClientHandshake does the authentication handshake specified by the corresponding
    54  // authentication protocol on rawConn for clients. It returns the authenticated
    55  // connection and the corresponding auth information about the connection.
    56  // Implementations must use the provided context to implement timely cancellation.
    57  func (tc *clientTransportCredentials) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
    58  	var err error
    59  	host := tc.hostOverride
    60  	if host == "" {
    61  		// IMPORTANT: Don't wrap the errors returned from this method. gRPC expects to be
    62  		// able to check err.Temporary to spot temporary errors and reconnect when they happen.
    63  		host, _, err = net.SplitHostPort(addr)
    64  		if err != nil {
    65  			return nil, nil, err
    66  		}
    67  	}
    68  	conn := tls.Client(rawConn, &tls.Config{
    69  		ServerName:   host,
    70  		RootCAs:      tc.roots,
    71  		Certificates: tc.clients,
    72  	})
    73  	err = conn.HandshakeContext(ctx)
    74  	if err != nil {
    75  		_ = rawConn.Close()
    76  		return nil, nil, err
    77  	}
    78  	return conn, nil, nil
    79  }
    80  
    81  // ServerHandshake is not implemented for a `clientTransportCredentials`, use
    82  // a `serverTransportCredentials` if you require `ServerHandshake`.
    83  func (tc *clientTransportCredentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
    84  	return nil, nil, ErrServerHandshakeNop
    85  }
    86  
    87  // Info returns information about the transport protocol used
    88  func (tc *clientTransportCredentials) Info() credentials.ProtocolInfo {
    89  	return credentials.ProtocolInfo{SecurityProtocol: "tls"}
    90  }
    91  
    92  // GetRequestMetadata returns nil, nil since TLS credentials do not have metadata.
    93  func (tc *clientTransportCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
    94  	return nil, nil
    95  }
    96  
    97  // RequireTransportSecurity always returns true because TLS is transport security
    98  func (tc *clientTransportCredentials) RequireTransportSecurity() bool {
    99  	return true
   100  }
   101  
   102  // Clone returns a copy of the clientTransportCredentials
   103  func (tc *clientTransportCredentials) Clone() credentials.TransportCredentials {
   104  	return NewClientCredentials(tc.roots, tc.clients, tc.hostOverride)
   105  }
   106  
   107  // OverrideServerName is not implemented and here only to satisfy the interface
   108  func (tc *clientTransportCredentials) OverrideServerName(serverNameOverride string) error {
   109  	return ErrOverrideServerNameNop
   110  }
   111  
   112  // serverTransportCredentials is a grpc/credentials.TransportCredentials which supports
   113  // filtering acceptable peer connections by a list of accepted client certificate SANs
   114  type serverTransportCredentials struct {
   115  	serverConfig *tls.Config
   116  	acceptedSANs map[string]struct{}
   117  }
   118  
   119  // NewServerCredentials returns a new initialized grpc/credentials.TransportCredentials for server usage
   120  func NewServerCredentials(serverConfig *tls.Config, acceptedSANs map[string]struct{}) (credentials.TransportCredentials, error) {
   121  	if serverConfig == nil {
   122  		return nil, ErrNilServerConfig
   123  	}
   124  
   125  	return &serverTransportCredentials{serverConfig, acceptedSANs}, nil
   126  }
   127  
   128  // validateClient checks a peer's client certificate's SAN entries against
   129  // a list of accepted SANs. If the client certificate does not have a SAN on the
   130  // list it is rejected.
   131  //
   132  // Note 1: This function *only* verifies the SAN entries! Callers are expected to
   133  // have provided the `tls.ConnectionState` returned from a validate (e.g.
   134  // non-error producing) `conn.Handshake()`.
   135  //
   136  // Note 2: We do *not* consider the client certificate subject common name. The
   137  // CN field is deprecated and should be present as a DNS SAN!
   138  func (tc *serverTransportCredentials) validateClient(peerState tls.ConnectionState) error {
   139  	/*
   140  	 * If there's no list of accepted SANs, all clients are OK
   141  	 *
   142  	 * TODO(@cpu): This should be converted to a hard error at initialization time
   143  	 * once we have deployed & updated all gRPC configurations to have an accepted
   144  	 * SAN list configured
   145  	 */
   146  	if len(tc.acceptedSANs) == 0 {
   147  		return nil
   148  	}
   149  
   150  	// If `conn.Handshake()` is called before `validateClient` this should not
   151  	// occur. We return an error in this event primarily for unit tests that may
   152  	// call `validateClient` with manufactured & artificial connection states.
   153  	if len(peerState.PeerCertificates) < 1 {
   154  		return ErrEmptyPeerCerts
   155  	}
   156  
   157  	// Since we call `conn.Handshake()` before `validateClient` and ensure
   158  	// a non-error response we don't need to validate anything except the presence
   159  	// of an acceptable SAN in the leaf entry of `PeerCertificates`. The tls
   160  	// package's `serverHandshake` and in particular, `processCertsFromClient`
   161  	// will address everything else as an error returned from `Handshake()`.
   162  	leaf := peerState.PeerCertificates[0]
   163  
   164  	// Combine both the DNS and IP address subjectAlternativeNames into a single
   165  	// list for checking.
   166  	var receivedSANs []string
   167  	receivedSANs = append(receivedSANs, leaf.DNSNames...)
   168  	for _, ip := range leaf.IPAddresses {
   169  		receivedSANs = append(receivedSANs, ip.String())
   170  	}
   171  
   172  	for _, name := range receivedSANs {
   173  		if _, ok := tc.acceptedSANs[name]; ok {
   174  			return nil
   175  		}
   176  	}
   177  
   178  	// If none of the DNS or IP SANs on the leaf certificate matched the
   179  	// acceptable list, the client isn't valid and we error
   180  	var acceptableSANs []string
   181  	for k := range tc.acceptedSANs {
   182  		acceptableSANs = append(acceptableSANs, k)
   183  	}
   184  	return ErrSANNotAccepted{receivedSANs, acceptableSANs}
   185  }
   186  
   187  // ServerHandshake does the authentication handshake for servers. It returns
   188  // the authenticated connection and the corresponding auth information about
   189  // the connection.
   190  func (tc *serverTransportCredentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
   191  	// Perform the server <- client TLS handshake. This will validate the peer's
   192  	// client certificate.
   193  	conn := tls.Server(rawConn, tc.serverConfig)
   194  	err := conn.Handshake()
   195  	if err != nil {
   196  		return nil, nil, err
   197  	}
   198  
   199  	// In addition to the validation from `conn.Handshake()` we apply further
   200  	// constraints on what constitutes a valid peer
   201  	err = tc.validateClient(conn.ConnectionState())
   202  	if err != nil {
   203  		return nil, nil, err
   204  	}
   205  
   206  	return conn, credentials.TLSInfo{State: conn.ConnectionState()}, nil
   207  }
   208  
   209  // ClientHandshake is not implemented for a `serverTransportCredentials`, use
   210  // a `clientTransportCredentials` if you require `ClientHandshake`.
   211  func (tc *serverTransportCredentials) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
   212  	return nil, nil, ErrClientHandshakeNop
   213  }
   214  
   215  // Info provides the ProtocolInfo of this TransportCredentials.
   216  func (tc *serverTransportCredentials) Info() credentials.ProtocolInfo {
   217  	return credentials.ProtocolInfo{SecurityProtocol: "tls"}
   218  }
   219  
   220  // GetRequestMetadata returns nil, nil since TLS credentials do not have metadata.
   221  func (tc *serverTransportCredentials) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) {
   222  	return nil, nil
   223  }
   224  
   225  // RequireTransportSecurity always returns true because TLS is transport security
   226  func (tc *serverTransportCredentials) RequireTransportSecurity() bool {
   227  	return true
   228  }
   229  
   230  // Clone returns a copy of the serverTransportCredentials
   231  func (tc *serverTransportCredentials) Clone() credentials.TransportCredentials {
   232  	clone, _ := NewServerCredentials(tc.serverConfig, tc.acceptedSANs)
   233  	return clone
   234  }
   235  
   236  // OverrideServerName is not implemented and here only to satisfy the interface
   237  func (tc *serverTransportCredentials) OverrideServerName(serverNameOverride string) error {
   238  	return ErrOverrideServerNameNop
   239  }