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 }