blob: eec489c0b084345941791b2be68f34bf3f5c34cf [file] [log] [blame]
/*
*
* Copyright 2019 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 advancedtls is a utility library containing functions to construct
// credentials.TransportCredentials that can perform credential reloading and custom
// server authorization.
package advancedtls
import (
"context"
"crypto/tls"
"crypto/x509"
"fmt"
"net"
"syscall"
"time"
"google.golang.org/grpc/credentials"
)
// VerificationFuncParams contains the parameters available to users when implementing CustomVerificationFunc.
type VerificationFuncParams struct {
ServerName string
RawCerts [][]byte
VerifiedChains [][]*x509.Certificate
}
// VerificationResults contains the information about results of CustomVerificationFunc.
// VerificationResults is an empty struct for now. It may be extended in the future to include more information.
type VerificationResults struct{}
// CustomVerificationFunc is the function defined by users to perform custom server authorization.
// CustomVerificationFunc returns nil if the authorization fails; otherwise returns an empty struct.
type CustomVerificationFunc func(params *VerificationFuncParams) (*VerificationResults, error)
// GetRootCAsParams contains the parameters available to users when implementing GetRootCAs.
type GetRootCAsParams struct {
RawConn net.Conn
RawCerts [][]byte
}
// GetRootCAsResults contains the results of GetRootCAs.
// If users want to reload the root trust certificate, it is required to return the proper TrustCerts in GetRootCAs.
type GetRootCAsResults struct {
TrustCerts *x509.CertPool
}
// RootCertificateOptions contains a field and a function for obtaining root trust certificates.
// It is used by both ClientOptions and ServerOptions. Note that RootCertificateOptions is required
// to be correctly set on client side; on server side, it is only required when mutual TLS is
// enabled(RequireClientCert in ServerOptions is true).
type RootCertificateOptions struct {
// If field RootCACerts is set, field GetRootCAs will be ignored. RootCACerts will be used
// every time when verifying the peer certificates, without performing root certificate reloading.
RootCACerts *x509.CertPool
// If GetRootCAs is set and RootCACerts is nil, GetRootCAs will be invoked every time
// asked to check certificates sent from the server when a new connection is established.
// This is known as root CA certificate reloading.
GetRootCAs func(params *GetRootCAsParams) (*GetRootCAsResults, error)
}
// ClientOptions contains all the fields and functions needed to be filled by the client.
// General rules for certificate setting on client side:
// Certificates or GetClientCertificate indicates the certificates sent from the client to the
// server to prove client's identities. The rules for setting these two fields are:
// If requiring mutual authentication on server side:
// Either Certificates or GetClientCertificate must be set; the other will be ignored
// Otherwise:
// Nothing needed(the two fields will be ignored)
type ClientOptions struct {
// If field Certificates is set, field GetClientCertificate will be ignored. The client will use
// Certificates every time when asked for a certificate, without performing certificate reloading.
Certificates []tls.Certificate
// If GetClientCertificate is set and Certificates is nil, the client will invoke this
// function every time asked to present certificates to the server when a new connection is
// established. This is known as peer certificate reloading.
GetClientCertificate func(*tls.CertificateRequestInfo) (*tls.Certificate, error)
// VerifyPeer is a custom server authorization checking after certificate signature check.
// If this is set, we will replace the hostname check with this customized authorization check.
// If this is nil, we fall back to typical hostname check.
VerifyPeer CustomVerificationFunc
// 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.
ServerNameOverride string
RootCertificateOptions
}
// ServerOptions contains all the fields and functions needed to be filled by the client.
// General rules for certificate setting on server side:
// Certificates or GetClientCertificate indicates the certificates sent from the server to
// the client to prove server's identities. The rules for setting these two fields are:
// Either Certificates or GetCertificate must be set; the other will be ignored
type ServerOptions struct {
// If field Certificates is set, field GetClientCertificate will be ignored. The server will use
// Certificates every time when asked for a certificate, without performing certificate reloading.
Certificates []tls.Certificate
// If GetClientCertificate is set and Certificates is nil, the server will invoke this
// function every time asked to present certificates to the client when a new connection is
// established. This is known as peer certificate reloading.
GetCertificate func(*tls.ClientHelloInfo) (*tls.Certificate, error)
RootCertificateOptions
// If the server want the client to send certificates.
RequireClientCert bool
}
func (o *ClientOptions) config() (*tls.Config, error) {
if o.RootCACerts == nil && o.GetRootCAs == nil && o.VerifyPeer == nil {
return nil, fmt.Errorf(
"client needs to provide root CA certs, or a custom verification function")
}
// We have to set InsecureSkipVerify to true to skip the default checks and use the
// verification function we built from buildVerifyFunc.
config := &tls.Config{
ServerName: o.ServerNameOverride,
Certificates: o.Certificates,
GetClientCertificate: o.GetClientCertificate,
RootCAs: o.RootCACerts,
InsecureSkipVerify: true,
}
return config, nil
}
func (o *ServerOptions) config() (*tls.Config, error) {
if o.Certificates == nil && o.GetCertificate == nil {
return nil, fmt.Errorf("either Certificates or GetCertificate must be specified")
}
if o.RequireClientCert && o.GetRootCAs == nil && o.RootCACerts == nil {
return nil, fmt.Errorf("server needs to provide root CA certs if requiring client cert")
}
clientAuth := tls.NoClientCert
if o.RequireClientCert {
// We fall back to normal config settings if users don't need to reload root certificates.
// If using RequireAndVerifyClientCert, the underlying stack would use the default
// checking and ignore the verification function we built from buildVerifyFunc.
// If using RequireAnyClientCert, the code would skip all the checks and use the
// function from buildVerifyFunc.
if o.RootCACerts != nil {
clientAuth = tls.RequireAndVerifyClientCert
} else {
clientAuth = tls.RequireAnyClientCert
}
}
config := &tls.Config{
ClientAuth: clientAuth,
Certificates: o.Certificates,
GetCertificate: o.GetCertificate,
}
if o.RootCACerts != nil {
config.ClientCAs = o.RootCACerts
}
return config, nil
}
// advancedTLSCreds is the credentials required for authenticating a connection using TLS.
type advancedTLSCreds struct {
config *tls.Config
verifyFunc CustomVerificationFunc
getRootCAs func(params *GetRootCAsParams) (*GetRootCAsResults, error)
isClient bool
}
func (c advancedTLSCreds) Info() credentials.ProtocolInfo {
return credentials.ProtocolInfo{
SecurityProtocol: "tls",
SecurityVersion: "1.2",
ServerName: c.config.ServerName,
}
}
func (c *advancedTLSCreds) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
// Use local cfg to avoid clobbering ServerName if using multiple endpoints.
cfg := cloneTLSConfig(c.config)
// We return the full authority name to users if ServerName is empty without
// stripping the trailing port.
if cfg.ServerName == "" {
cfg.ServerName = authority
}
cfg.VerifyPeerCertificate = buildVerifyFunc(c, cfg.ServerName, rawConn)
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()
}
info := credentials.TLSInfo{
State: conn.ConnectionState(),
CommonAuthInfo: credentials.CommonAuthInfo{
SecurityLevel: credentials.PrivacyAndIntegrity,
},
}
return WrapSyscallConn(rawConn, conn), info, nil
}
func (c *advancedTLSCreds) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.AuthInfo, error) {
cfg := cloneTLSConfig(c.config)
// We build server side verification function only when root cert reloading is needed.
if c.getRootCAs != nil {
cfg.VerifyPeerCertificate = buildVerifyFunc(c, "", rawConn)
}
conn := tls.Server(rawConn, cfg)
if err := conn.Handshake(); err != nil {
conn.Close()
return nil, nil, err
}
info := credentials.TLSInfo{
State: conn.ConnectionState(),
CommonAuthInfo: credentials.CommonAuthInfo{
SecurityLevel: credentials.PrivacyAndIntegrity,
},
}
return WrapSyscallConn(rawConn, conn), info, nil
}
func (c *advancedTLSCreds) Clone() credentials.TransportCredentials {
return &advancedTLSCreds{
config: cloneTLSConfig(c.config),
verifyFunc: c.verifyFunc,
getRootCAs: c.getRootCAs,
isClient: c.isClient,
}
}
func (c *advancedTLSCreds) OverrideServerName(serverNameOverride string) error {
c.config.ServerName = serverNameOverride
return nil
}
// The function buildVerifyFunc is used when users want root cert reloading, and possibly custom
// server authorization check.
// We have to build our own verification function here because current tls module:
// 1. does not have a good support on root cert reloading
// 2. will ignore basic certificate check when setting InsecureSkipVerify to true
func buildVerifyFunc(c *advancedTLSCreds,
serverName string,
rawConn net.Conn) func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
return func(rawCerts [][]byte, verifiedChains [][]*x509.Certificate) error {
// If users didn't specify either rootCAs or getRootCAs on client side,
// as we see some use cases such as https://github.com/grpc/grpc/pull/20530,
// instead of failing, we just don't validate the server cert and let
// application decide via VerifyPeer
if c.isClient && c.config.RootCAs == nil && c.getRootCAs == nil {
if c.verifyFunc != nil {
_, err := c.verifyFunc(&VerificationFuncParams{
ServerName: serverName,
RawCerts: rawCerts,
VerifiedChains: verifiedChains,
})
return err
}
}
var rootCAs *x509.CertPool
if c.isClient {
rootCAs = c.config.RootCAs
} else {
rootCAs = c.config.ClientCAs
}
// reload root CA certs
if rootCAs == nil && c.getRootCAs != nil {
results, err := c.getRootCAs(&GetRootCAsParams{
RawConn: rawConn,
RawCerts: rawCerts,
})
if err != nil {
return err
}
rootCAs = results.TrustCerts
}
// verify peers' certificates against RootCAs and get verifiedChains
certs := make([]*x509.Certificate, len(rawCerts))
for i, asn1Data := range rawCerts {
cert, err := x509.ParseCertificate(asn1Data)
if err != nil {
return err
}
certs[i] = cert
}
opts := x509.VerifyOptions{
Roots: rootCAs,
CurrentTime: time.Now(),
Intermediates: x509.NewCertPool(),
}
if !c.isClient {
opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
} else {
opts.KeyUsages = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}
}
for _, cert := range certs[1:] {
opts.Intermediates.AddCert(cert)
}
// We use default hostname check if users don't specify verifyFunc function
if c.isClient && c.verifyFunc == nil && serverName != "" {
opts.DNSName = serverName
}
verifiedChains, err := certs[0].Verify(opts)
if err != nil {
return err
}
if c.isClient && c.verifyFunc != nil {
if c.verifyFunc != nil {
_, err := c.verifyFunc(&VerificationFuncParams{
ServerName: serverName,
RawCerts: rawCerts,
VerifiedChains: verifiedChains,
})
return err
}
}
return nil
}
}
// NewClientCreds uses ClientOptions to construct a TransportCredentials based on TLS.
func NewClientCreds(o *ClientOptions) (credentials.TransportCredentials, error) {
conf, err := o.config()
if err != nil {
return nil, err
}
tc := &advancedTLSCreds{
config: conf,
isClient: true,
getRootCAs: o.GetRootCAs,
verifyFunc: o.VerifyPeer,
}
tc.config.NextProtos = appendH2ToNextProtos(tc.config.NextProtos)
return tc, nil
}
// NewServerCreds uses ServerOptions to construct a TransportCredentials based on TLS.
func NewServerCreds(o *ServerOptions) (credentials.TransportCredentials, error) {
conf, err := o.config()
if err != nil {
return nil, err
}
tc := &advancedTLSCreds{
config: conf,
isClient: false,
getRootCAs: o.GetRootCAs,
}
tc.config.NextProtos = appendH2ToNextProtos(tc.config.NextProtos)
return tc, nil
}
// TODO(ZhenLian): The code below are duplicates with gRPC-Go under
// credentials/internal. Consider refactoring in the future.
const alpnProtoStrH2 = "h2"
func appendH2ToNextProtos(ps []string) []string {
for _, p := range ps {
if p == alpnProtoStrH2 {
return ps
}
}
ret := make([]string, 0, len(ps)+1)
ret = append(ret, ps...)
return append(ret, alpnProtoStrH2)
}
// We give syscall.Conn a new name here since syscall.Conn and net.Conn used
// below have the same names.
type sysConn = syscall.Conn
// syscallConn keeps reference of rawConn to support syscall.Conn for channelz.
// SyscallConn() (the method in interface syscall.Conn) is explicitly
// implemented on this type,
//
// Interface syscall.Conn is implemented by most net.Conn implementations (e.g.
// TCPConn, UnixConn), but is not part of net.Conn interface. So wrapper conns
// that embed net.Conn don't implement syscall.Conn. (Side note: tls.Conn
// doesn't embed net.Conn, so even if syscall.Conn is part of net.Conn, it won't
// help here).
type syscallConn struct {
net.Conn
// sysConn is a type alias of syscall.Conn. It's necessary because the name
// `Conn` collides with `net.Conn`.
sysConn
}
// WrapSyscallConn tries to wrap rawConn and newConn into a net.Conn that
// implements syscall.Conn. rawConn will be used to support syscall, and newConn
// will be used for read/write.
//
// This function returns newConn if rawConn doesn't implement syscall.Conn.
func WrapSyscallConn(rawConn, newConn net.Conn) net.Conn {
sysConn, ok := rawConn.(syscall.Conn)
if !ok {
return newConn
}
return &syscallConn{
Conn: newConn,
sysConn: sysConn,
}
}
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return cfg.Clone()
}