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