github.com/minio/minio@v0.0.0-20240328213742-3f72439b8a27/internal/config/certs.go (about) 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 18 package config 19 20 import ( 21 "bytes" 22 "crypto/tls" 23 "crypto/x509" 24 "encoding/pem" 25 "errors" 26 "os" 27 28 "github.com/minio/pkg/v2/env" 29 ) 30 31 // EnvCertPassword is the environment variable which contains the password used 32 // to decrypt the TLS private key. It must be set if the TLS private key is 33 // password protected. 34 const EnvCertPassword = "MINIO_CERT_PASSWD" 35 36 // ParsePublicCertFile - parses public cert into its *x509.Certificate equivalent. 37 func ParsePublicCertFile(certFile string) (x509Certs []*x509.Certificate, err error) { 38 // Read certificate file. 39 var data []byte 40 if data, err = os.ReadFile(certFile); err != nil { 41 return nil, err 42 } 43 44 // Trimming leading and tailing white spaces. 45 data = bytes.TrimSpace(data) 46 47 // Parse all certs in the chain. 48 current := data 49 for len(current) > 0 { 50 var pemBlock *pem.Block 51 if pemBlock, current = pem.Decode(current); pemBlock == nil { 52 return nil, ErrTLSUnexpectedData(nil).Msg("Could not read PEM block from file %s", certFile) 53 } 54 55 var x509Cert *x509.Certificate 56 if x509Cert, err = x509.ParseCertificate(pemBlock.Bytes); err != nil { 57 return nil, ErrTLSUnexpectedData(nil).Msg("Failed to parse `%s`: %s", certFile, err.Error()) 58 } 59 60 x509Certs = append(x509Certs, x509Cert) 61 } 62 63 if len(x509Certs) == 0 { 64 return nil, ErrTLSUnexpectedData(nil).Msg("Empty public certificate file %s", certFile) 65 } 66 67 return x509Certs, nil 68 } 69 70 // LoadX509KeyPair - load an X509 key pair (private key , certificate) 71 // from the provided paths. The private key may be encrypted and is 72 // decrypted using the ENV_VAR: MINIO_CERT_PASSWD. 73 func LoadX509KeyPair(certFile, keyFile string) (tls.Certificate, error) { 74 certPEMBlock, err := os.ReadFile(certFile) 75 if err != nil { 76 return tls.Certificate{}, ErrTLSReadError(nil).Msg("Unable to read the public key: %s", err) 77 } 78 keyPEMBlock, err := os.ReadFile(keyFile) 79 if err != nil { 80 return tls.Certificate{}, ErrTLSReadError(nil).Msg("Unable to read the private key: %s", err) 81 } 82 key, rest := pem.Decode(keyPEMBlock) 83 if len(rest) > 0 { 84 return tls.Certificate{}, ErrTLSUnexpectedData(nil).Msg("The private key contains additional data") 85 } 86 if key == nil { 87 return tls.Certificate{}, ErrTLSUnexpectedData(nil).Msg("The private key is not readable") 88 } 89 if x509.IsEncryptedPEMBlock(key) { 90 password := env.Get(EnvCertPassword, "") 91 if len(password) == 0 { 92 return tls.Certificate{}, ErrTLSNoPassword(nil) 93 } 94 decryptedKey, decErr := x509.DecryptPEMBlock(key, []byte(password)) 95 if decErr != nil { 96 return tls.Certificate{}, ErrTLSWrongPassword(decErr) 97 } 98 keyPEMBlock = pem.EncodeToMemory(&pem.Block{Type: key.Type, Bytes: decryptedKey}) 99 } 100 cert, err := tls.X509KeyPair(certPEMBlock, keyPEMBlock) 101 if err != nil { 102 return tls.Certificate{}, ErrTLSUnexpectedData(nil).Msg(err.Error()) 103 } 104 return cert, nil 105 } 106 107 // EnsureCertAndKey checks if both client certificate and key paths are provided 108 func EnsureCertAndKey(clientCert, clientKey string) error { 109 if (clientCert != "" && clientKey == "") || 110 (clientCert == "" && clientKey != "") { 111 return errors.New("cert and key must be specified as a pair") 112 } 113 return nil 114 }