github.com/ydb-platform/ydb-go-sdk/v3@v3.57.0/internal/certificates/certificates.go (about)

     1  package certificates
     2  
     3  import (
     4  	"crypto/x509"
     5  	"encoding/pem"
     6  	"fmt"
     7  	"os"
     8  	"sync"
     9  
    10  	"github.com/ydb-platform/ydb-go-sdk/v3/internal/xerrors"
    11  )
    12  
    13  var (
    14  	// fileCache stores certificates by file name
    15  	fileCache sync.Map
    16  	// pemCache stores certificates by pem cache
    17  	pemCache sync.Map
    18  )
    19  
    20  type (
    21  	fromFileOptions struct {
    22  		onHit   func()
    23  		onMiss  func()
    24  		noCache bool
    25  	}
    26  	FromFileOption func(opts *fromFileOptions)
    27  )
    28  
    29  func FromFileOnMiss(onMiss func()) FromFileOption {
    30  	return func(opts *fromFileOptions) {
    31  		opts.onMiss = onMiss
    32  	}
    33  }
    34  
    35  func FromFileOnHit(onHit func()) FromFileOption {
    36  	return func(opts *fromFileOptions) {
    37  		opts.onHit = onHit
    38  	}
    39  }
    40  
    41  func loadFromFileCache(key string) (_ []*x509.Certificate, exists bool) {
    42  	value, exists := fileCache.Load(key)
    43  	if !exists {
    44  		return nil, false
    45  	}
    46  	certs, ok := value.([]*x509.Certificate)
    47  	if !ok {
    48  		panic(fmt.Sprintf("unexpected value type '%T'", value))
    49  	}
    50  
    51  	return certs, true
    52  }
    53  
    54  // FromFile reads and parses pem-encoded certificate(s) from file.
    55  func FromFile(file string, opts ...FromFileOption) ([]*x509.Certificate, error) {
    56  	options := fromFileOptions{}
    57  	for _, opt := range opts {
    58  		if opt != nil {
    59  			opt(&options)
    60  		}
    61  	}
    62  
    63  	if !options.noCache {
    64  		certs, exists := loadFromFileCache(file)
    65  		if exists {
    66  			if options.onHit != nil {
    67  				options.onHit()
    68  			}
    69  
    70  			return certs, nil
    71  		}
    72  	}
    73  
    74  	bytes, err := os.ReadFile(file)
    75  	if err != nil {
    76  		return nil, xerrors.WithStackTrace(err)
    77  	}
    78  
    79  	certs, err := FromPem(bytes,
    80  		FromPemNoCache(true), // no use pem cache - certs stored in file cache only
    81  	)
    82  	if err != nil {
    83  		return nil, xerrors.WithStackTrace(err)
    84  	}
    85  
    86  	if !options.noCache {
    87  		fileCache.Store(file, certs)
    88  		if options.onMiss != nil {
    89  			options.onMiss()
    90  		}
    91  	}
    92  
    93  	return certs, nil
    94  }
    95  
    96  func loadFromPemCache(key string) (_ *x509.Certificate, exists bool) {
    97  	value, exists := pemCache.Load(key)
    98  	if !exists {
    99  		return nil, false
   100  	}
   101  	cert, ok := value.(*x509.Certificate)
   102  	if !ok {
   103  		panic(fmt.Sprintf("unexpected value type '%T'", value))
   104  	}
   105  
   106  	return cert, true
   107  }
   108  
   109  // parseCertificate is a cached version of x509.ParseCertificate. Cache key is string(der)
   110  func parseCertificate(der []byte, opts ...FromPemOption) (*x509.Certificate, error) {
   111  	options := fromPemOptions{}
   112  	for _, opt := range opts {
   113  		if opt != nil {
   114  			opt(&options)
   115  		}
   116  	}
   117  
   118  	key := string(der)
   119  
   120  	if !options.noCache {
   121  		cert, exists := loadFromPemCache(key)
   122  		if exists {
   123  			if options.onHit != nil {
   124  				options.onHit()
   125  			}
   126  
   127  			return cert, nil
   128  		}
   129  	}
   130  
   131  	cert, err := x509.ParseCertificate(der)
   132  	if err != nil {
   133  		return nil, xerrors.WithStackTrace(err)
   134  	}
   135  
   136  	if !options.noCache {
   137  		pemCache.Store(key, cert)
   138  		if options.onMiss != nil {
   139  			options.onMiss()
   140  		}
   141  	}
   142  
   143  	return cert, nil
   144  }
   145  
   146  type (
   147  	fromPemOptions struct {
   148  		onHit   func()
   149  		onMiss  func()
   150  		noCache bool
   151  	}
   152  	FromPemOption func(opts *fromPemOptions)
   153  )
   154  
   155  func FromPemMiss(onMiss func()) FromPemOption {
   156  	return func(opts *fromPemOptions) {
   157  		opts.onMiss = onMiss
   158  	}
   159  }
   160  
   161  func FromPemOnHit(onHit func()) FromPemOption {
   162  	return func(opts *fromPemOptions) {
   163  		opts.onHit = onHit
   164  	}
   165  }
   166  
   167  func FromPemNoCache(noCache bool) FromPemOption {
   168  	return func(opts *fromPemOptions) {
   169  		opts.noCache = noCache
   170  	}
   171  }
   172  
   173  // FromPem parses one or more certificate from pem blocks in bytes.
   174  // It returns nil error if at least one certificate was successfully parsed.
   175  // This function uses cached parseCertificate.
   176  func FromPem(bytes []byte, opts ...FromPemOption) (certs []*x509.Certificate, err error) {
   177  	var block *pem.Block
   178  
   179  	for len(bytes) > 0 {
   180  		block, bytes = pem.Decode(bytes)
   181  		if block == nil {
   182  			break
   183  		}
   184  		if block.Type != "CERTIFICATE" || len(block.Headers) != 0 {
   185  			continue
   186  		}
   187  		var cert *x509.Certificate
   188  		cert, err = parseCertificate(block.Bytes, opts...)
   189  		if err != nil {
   190  			continue
   191  		}
   192  		certs = append(certs, cert)
   193  	}
   194  
   195  	if len(certs) == 0 {
   196  		return nil, xerrors.WithStackTrace(err)
   197  	}
   198  
   199  	return certs, nil
   200  }