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  }