...

Source file src/google.golang.org/api/internal/cert/secureconnect_cert.go

Documentation: google.golang.org/api/internal/cert

     1  // Copyright 2022 Google LLC.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // Package cert contains certificate tools for Google API clients.
     6  // This package is intended to be used with crypto/tls.Config.GetClientCertificate.
     7  //
     8  // The certificates can be used to satisfy Google's Endpoint Validation.
     9  // See https://cloud.google.com/endpoint-verification/docs/overview
    10  //
    11  // This package is not intended for use by end developers. Use the
    12  // google.golang.org/api/option package to configure API clients.
    13  package cert
    14  
    15  import (
    16  	"crypto/tls"
    17  	"crypto/x509"
    18  	"encoding/json"
    19  	"errors"
    20  	"fmt"
    21  	"os"
    22  	"os/exec"
    23  	"os/user"
    24  	"path/filepath"
    25  	"sync"
    26  	"time"
    27  )
    28  
    29  const (
    30  	metadataPath = ".secureConnect"
    31  	metadataFile = "context_aware_metadata.json"
    32  )
    33  
    34  type secureConnectSource struct {
    35  	metadata secureConnectMetadata
    36  
    37  	// Cache the cert to avoid executing helper command repeatedly.
    38  	cachedCertMutex sync.Mutex
    39  	cachedCert      *tls.Certificate
    40  }
    41  
    42  type secureConnectMetadata struct {
    43  	Cmd []string `json:"cert_provider_command"`
    44  }
    45  
    46  // NewSecureConnectSource creates a certificate source using
    47  // the Secure Connect Helper and its associated metadata file.
    48  //
    49  // The configFilePath points to the location of the context aware metadata file.
    50  // If configFilePath is empty, use the default context aware metadata location.
    51  func NewSecureConnectSource(configFilePath string) (Source, error) {
    52  	if configFilePath == "" {
    53  		user, err := user.Current()
    54  		if err != nil {
    55  			// Error locating the default config means Secure Connect is not supported.
    56  			return nil, errSourceUnavailable
    57  		}
    58  		configFilePath = filepath.Join(user.HomeDir, metadataPath, metadataFile)
    59  	}
    60  
    61  	file, err := os.ReadFile(configFilePath)
    62  	if err != nil {
    63  		if errors.Is(err, os.ErrNotExist) {
    64  			// Config file missing means Secure Connect is not supported.
    65  			return nil, errSourceUnavailable
    66  		}
    67  		return nil, err
    68  	}
    69  
    70  	var metadata secureConnectMetadata
    71  	if err := json.Unmarshal(file, &metadata); err != nil {
    72  		return nil, fmt.Errorf("cert: could not parse JSON in %q: %w", configFilePath, err)
    73  	}
    74  	if err := validateMetadata(metadata); err != nil {
    75  		return nil, fmt.Errorf("cert: invalid config in %q: %w", configFilePath, err)
    76  	}
    77  	return (&secureConnectSource{
    78  		metadata: metadata,
    79  	}).getClientCertificate, nil
    80  }
    81  
    82  func validateMetadata(metadata secureConnectMetadata) error {
    83  	if len(metadata.Cmd) == 0 {
    84  		return errors.New("empty cert_provider_command")
    85  	}
    86  	return nil
    87  }
    88  
    89  func (s *secureConnectSource) getClientCertificate(info *tls.CertificateRequestInfo) (*tls.Certificate, error) {
    90  	s.cachedCertMutex.Lock()
    91  	defer s.cachedCertMutex.Unlock()
    92  	if s.cachedCert != nil && !isCertificateExpired(s.cachedCert) {
    93  		return s.cachedCert, nil
    94  	}
    95  	// Expand OS environment variables in the cert provider command such as "$HOME".
    96  	for i := 0; i < len(s.metadata.Cmd); i++ {
    97  		s.metadata.Cmd[i] = os.ExpandEnv(s.metadata.Cmd[i])
    98  	}
    99  	command := s.metadata.Cmd
   100  	data, err := exec.Command(command[0], command[1:]...).Output()
   101  	if err != nil {
   102  		return nil, err
   103  	}
   104  	cert, err := tls.X509KeyPair(data, data)
   105  	if err != nil {
   106  		return nil, err
   107  	}
   108  	s.cachedCert = &cert
   109  	return &cert, nil
   110  }
   111  
   112  // isCertificateExpired returns true if the given cert is expired or invalid.
   113  func isCertificateExpired(cert *tls.Certificate) bool {
   114  	if len(cert.Certificate) == 0 {
   115  		return true
   116  	}
   117  	parsed, err := x509.ParseCertificate(cert.Certificate[0])
   118  	if err != nil {
   119  		return true
   120  	}
   121  	return time.Now().After(parsed.NotAfter)
   122  }
   123  

View as plain text