storj.io/minio@v0.0.0-20230509071714-0cbc90f649b1/pkg/certs/certs.go (about) 1 /* 2 * MinIO Cloud Storage, (C) 2018 MinIO, Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package certs 18 19 import ( 20 "context" 21 "crypto/tls" 22 "crypto/x509" 23 "errors" 24 "fmt" 25 "os" 26 "path/filepath" 27 "sync" 28 "time" 29 30 "github.com/rjeczalik/notify" 31 ) 32 33 // LoadX509KeyPairFunc is a function that parses a private key and 34 // certificate file and returns a TLS certificate on success. 35 type LoadX509KeyPairFunc func(certFile, keyFile string) (tls.Certificate, error) 36 37 // GetCertificateFunc is a callback that allows a TLS stack deliver different 38 // certificates based on the client trying to establish a TLS connection. 39 // 40 // For example, a GetCertificateFunc can return different TLS certificates depending 41 // upon the TLS SNI sent by the client. 42 type GetCertificateFunc func(hello *tls.ClientHelloInfo) (*tls.Certificate, error) 43 44 // Manager is a TLS certificate manager that can handle multiple certificates. 45 // When a client tries to establish a TLS connection, Manager will try to 46 // pick a certificate that can be validated by the client. 47 // 48 // For instance, if the client specifies a TLS SNI then Manager will try to 49 // find the corresponding certificate. If there is no such certificate it 50 // will fallback to the certificate named public.crt. 51 // 52 // Manager will automatically reload certificates if the corresponding file changes. 53 type Manager struct { 54 lock sync.RWMutex 55 certificates map[pair]*tls.Certificate // Mapping: certificate file name => TLS certificates 56 defaultCert pair 57 58 loadX509KeyPair LoadX509KeyPairFunc 59 events chan notify.EventInfo 60 ctx context.Context 61 } 62 63 // pair represents a certificate and private key file tuple. 64 type pair struct { 65 KeyFile string 66 CertFile string 67 } 68 69 // NewManager returns a new Manager that handles one certificate specified via 70 // the certFile and keyFile. It will use the loadX509KeyPair function to (re)load 71 // certificates. 72 // 73 // The certificate loaded from certFile is considered the default certificate. 74 // If a client does not send the TLS SNI extension then Manager will return 75 // this certificate. 76 func NewManager(ctx context.Context, certFile, keyFile string, loadX509KeyPair LoadX509KeyPairFunc) (manager *Manager, err error) { 77 certFile, err = filepath.Abs(certFile) 78 if err != nil { 79 return nil, err 80 } 81 keyFile, err = filepath.Abs(keyFile) 82 if err != nil { 83 return nil, err 84 } 85 86 manager = &Manager{ 87 certificates: map[pair]*tls.Certificate{}, 88 defaultCert: pair{ 89 KeyFile: keyFile, 90 CertFile: certFile, 91 }, 92 loadX509KeyPair: loadX509KeyPair, 93 events: make(chan notify.EventInfo, 1), 94 ctx: ctx, 95 } 96 if err := manager.AddCertificate(certFile, keyFile); err != nil { 97 return nil, err 98 } 99 go manager.watchFileEvents() 100 return manager, nil 101 } 102 103 // AddCertificate adds the TLS certificate in certFile resp. keyFile 104 // to the Manager. 105 // 106 // If there is already a certificate with the same base name it will be 107 // replaced by the newly added one. 108 func (m *Manager) AddCertificate(certFile, keyFile string) (err error) { 109 certFile, err = filepath.Abs(certFile) 110 if err != nil { 111 return err 112 } 113 keyFile, err = filepath.Abs(keyFile) 114 if err != nil { 115 return err 116 } 117 certFileIsLink, err := isSymlink(certFile) 118 if err != nil { 119 return err 120 } 121 keyFileIsLink, err := isSymlink(keyFile) 122 if err != nil { 123 return err 124 } 125 if certFileIsLink && !keyFileIsLink { 126 return fmt.Errorf("certs: '%s' is a symlink but '%s' is a regular file", certFile, keyFile) 127 } 128 if keyFileIsLink && !certFileIsLink { 129 return fmt.Errorf("certs: '%s' is a symlink but '%s' is a regular file", keyFile, certFile) 130 } 131 132 certificate, err := m.loadX509KeyPair(certFile, keyFile) 133 if err != nil { 134 return err 135 } 136 // We set the certificate leaf to the actual certificate such that 137 // we don't have to do the parsing (multiple times) when matching the 138 // certificate to the client hello. This a performance optimisation. 139 if certificate.Leaf == nil { 140 certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0]) 141 if err != nil { 142 return err 143 } 144 } 145 146 p := pair{ 147 CertFile: certFile, 148 KeyFile: keyFile, 149 } 150 m.lock.Lock() 151 defer m.lock.Unlock() 152 153 // We don't allow IP SANs in certificates - except for the "default" certificate 154 // which is, by convention, the first certificate added to the manager. The problem 155 // with allowing IP SANs in more than one certificate is that the manager usually can't 156 // match the client SNI to a SAN since the SNI is meant to communicate the destination 157 // host name and clients will not set the SNI to an IP address. 158 // Allowing multiple certificates with IP SANs lead to errors that confuses users - like: 159 // "It works for `https://instance.minio.local` but not for `https://10.0.2.1`" 160 if len(m.certificates) > 0 && len(certificate.Leaf.IPAddresses) > 0 { 161 return errors.New("cert: certificate must not contain any IP SANs: only the default certificate may contain IP SANs") 162 } 163 m.certificates[p] = &certificate 164 165 if certFileIsLink && keyFileIsLink { 166 go m.watchSymlinks(certFile, keyFile) 167 } else { 168 // Windows doesn't allow for watching file changes but instead allows 169 // for directory changes only, while we can still watch for changes 170 // on files on other platforms. Watch parent directory on all platforms 171 // for simplicity. 172 if err = notify.Watch(filepath.Dir(certFile), m.events, eventWrite...); err != nil { 173 return err 174 } 175 if err = notify.Watch(filepath.Dir(keyFile), m.events, eventWrite...); err != nil { 176 return err 177 } 178 } 179 return nil 180 } 181 182 // watchSymlinks starts an endless loop reloading the 183 // certFile and keyFile periodically. 184 func (m *Manager) watchSymlinks(certFile, keyFile string) { 185 for { 186 select { 187 case <-m.ctx.Done(): 188 return // Once stopped exits this routine. 189 case <-time.After(24 * time.Hour): 190 certificate, err := m.loadX509KeyPair(certFile, keyFile) 191 if err != nil { 192 continue 193 } 194 if certificate.Leaf == nil { // This is a performance optimisation 195 certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0]) 196 if err != nil { 197 continue 198 } 199 } 200 201 p := pair{ 202 CertFile: certFile, 203 KeyFile: keyFile, 204 } 205 m.lock.Lock() 206 m.certificates[p] = &certificate 207 m.lock.Unlock() 208 } 209 } 210 } 211 212 // watchFileEvents starts an endless loop waiting for file systems events. 213 // Once an event occurs it reloads the private key and certificate that 214 // has changed, if any. 215 func (m *Manager) watchFileEvents() { 216 for { 217 select { 218 case <-m.ctx.Done(): 219 return 220 case event := <-m.events: 221 if !isWriteEvent(event.Event()) { 222 continue 223 } 224 225 for pair := range m.certificates { 226 if p := event.Path(); pair.KeyFile == p || pair.CertFile == p { 227 certificate, err := m.loadX509KeyPair(pair.CertFile, pair.KeyFile) 228 if err != nil { 229 continue 230 } 231 if certificate.Leaf == nil { // This is performance optimisation 232 certificate.Leaf, err = x509.ParseCertificate(certificate.Certificate[0]) 233 if err != nil { 234 continue 235 } 236 } 237 m.lock.Lock() 238 m.certificates[pair] = &certificate 239 m.lock.Unlock() 240 } 241 } 242 } 243 } 244 } 245 246 // GetCertificate returns a TLS certificate based on the client hello. 247 // 248 // It tries to find a certificate that would be accepted by the client 249 // according to the client hello. However, if no certificate can be 250 // found GetCertificate returns the certificate loaded from the 251 // Public file. 252 func (m *Manager) GetCertificate(hello *tls.ClientHelloInfo) (*tls.Certificate, error) { 253 m.lock.RLock() 254 defer m.lock.RUnlock() 255 256 // If the client does not send a SNI we return the "default" 257 // certificate. A client may not send a SNI - e.g. when trying 258 // to connect to an IP directly (https://<ip>:<port>). 259 // 260 // In this case we don't know which the certificate the client 261 // asks for. It may be a public-facing certificate issued by a 262 // public CA or an internal certificate containing internal domain 263 // names. 264 // Now, we should not serve "the first" certificate that would be 265 // accepted by the client based on the Client Hello. Otherwise, we 266 // may expose an internal certificate to the client that contains 267 // internal domain names. That way we would disclose internal 268 // infrastructure details. 269 // 270 // Therefore, we serve the "default" certificate - which by convention 271 // is the first certificate added to the Manager. It's the calling code's 272 // responsibility to ensure that the "public-facing" certificate is used 273 // when creating a Manager instance. 274 if hello.ServerName == "" { 275 certificate := m.certificates[m.defaultCert] 276 return certificate, nil 277 } 278 279 // Optimization: If there is just one certificate, always serve that one. 280 if len(m.certificates) == 1 { 281 for _, certificate := range m.certificates { 282 return certificate, nil 283 } 284 } 285 286 // Iterate over all certificates and return the first one that would 287 // be accepted by the peer (TLS client) based on the client hello. 288 // In particular, the client usually specifies the requested host/domain 289 // via SNI. 290 // 291 // Note: The certificate.Leaf should be non-nil and contain the actual 292 // client certificate of MinIO that should be presented to the peer (TLS client). 293 // Otherwise, the leaf certificate has to be parsed again - which is kind of 294 // expensive and may cause a performance issue. For more information, check the 295 // docs of tls.ClientHelloInfo.SupportsCertificate. 296 for _, certificate := range m.certificates { 297 if err := hello.SupportsCertificate(certificate); err == nil { 298 return certificate, nil 299 } 300 } 301 return nil, errors.New("certs: no server certificate is supported by peer") 302 } 303 304 // GetClientCertificate returns a TLS certificate for mTLS based on the 305 // certificate request. 306 // 307 // It tries to find a certificate that would be accepted by the server 308 // according to the certificate request. However, if no certificate can be 309 // found GetClientCertificate returns the certificate loaded from the 310 // Public file. 311 func (m *Manager) GetClientCertificate(reqInfo *tls.CertificateRequestInfo) (*tls.Certificate, error) { 312 m.lock.RLock() 313 defer m.lock.RUnlock() 314 315 // Optimization: If there is just one certificate, always serve that one. 316 if len(m.certificates) == 1 { 317 for _, certificate := range m.certificates { 318 return certificate, nil 319 } 320 } 321 322 // Iterate over all certificates and return the first one that would 323 // be accepted by the peer (TLS server) based on reqInfo. 324 // 325 // Note: The certificate.Leaf should be non-nil and contain the actual 326 // client certificate of MinIO that should be presented to the peer (TLS server). 327 // Otherwise, the leaf certificate has to be parsed again - which is kind of 328 // expensive and may cause a performance issue. For more information, check the 329 // docs of tls.CertificateRequestInfo.SupportsCertificate. 330 for _, certificate := range m.certificates { 331 if err := reqInfo.SupportsCertificate(certificate); err == nil { 332 return certificate, nil 333 } 334 } 335 return nil, errors.New("certs: no client certificate is supported by peer") 336 } 337 338 // isSymlink returns true if the given file 339 // is a symbolic link. 340 func isSymlink(file string) (bool, error) { 341 st, err := os.Lstat(file) 342 if err != nil { 343 return false, err 344 } 345 return st.Mode()&os.ModeSymlink == os.ModeSymlink, nil 346 }