github.com/minio/mc@v0.0.0-20240503112107-b471de8d1882/cmd/tofu.go (about)

     1  // Copyright (c) 2015-2022 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 cmd
    19  
    20  import (
    21  	"bufio"
    22  	"bytes"
    23  	"context"
    24  	"crypto/ecdsa"
    25  	"crypto/ed25519"
    26  	"crypto/rsa"
    27  	"crypto/sha1"
    28  	"crypto/sha256"
    29  	"crypto/tls"
    30  	"crypto/x509"
    31  	"encoding/asn1"
    32  	"encoding/hex"
    33  	"encoding/pem"
    34  	"fmt"
    35  	"math/big"
    36  	"net/http"
    37  	"os"
    38  	"path/filepath"
    39  	"strings"
    40  
    41  	"github.com/fatih/color"
    42  	"github.com/minio/mc/pkg/probe"
    43  )
    44  
    45  func marshalPublicKey(pub interface{}) (publicKeyBytes []byte, e error) {
    46  	// pkcs1PublicKey reflects the ASN.1 structure of a PKCS #1 public key.
    47  	type pkcs1PublicKey struct {
    48  		N *big.Int
    49  		E int
    50  	}
    51  
    52  	switch pub := pub.(type) {
    53  	case *rsa.PublicKey:
    54  		publicKeyBytes, e = asn1.Marshal(pkcs1PublicKey{
    55  			N: pub.N,
    56  			E: pub.E,
    57  		})
    58  		if e != nil {
    59  			return nil, e
    60  		}
    61  	case *ecdsa.PublicKey:
    62  		pubKey, e := pub.ECDH()
    63  		if e != nil {
    64  			return nil, e
    65  		}
    66  		publicKeyBytes = pubKey.Bytes()
    67  	case ed25519.PublicKey:
    68  		publicKeyBytes = pub
    69  	default:
    70  		return nil, fmt.Errorf("x509: unsupported public key type: %T", pub)
    71  	}
    72  
    73  	return publicKeyBytes, nil
    74  }
    75  
    76  // promptTrustSelfSignedCert connects to the given endpoint and
    77  // checks whether the peer certificate can be verified.
    78  // If not, it computes a fingerprint of the peer certificate
    79  // public key, asks the user to confirm the fingerprint and
    80  // adds the peer certificate to the local trust store in the
    81  // CAs directory.
    82  func promptTrustSelfSignedCert(ctx context.Context, endpoint, alias string) (*x509.Certificate, *probe.Error) {
    83  	req, e := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
    84  	if e != nil {
    85  		return nil, probe.NewError(e)
    86  	}
    87  
    88  	// no need to probe certs for http endpoints.
    89  	if req.URL.Scheme == "http" {
    90  		return nil, nil
    91  	}
    92  
    93  	client := http.Client{
    94  		Transport: &http.Transport{
    95  			Proxy: http.ProxyFromEnvironment,
    96  			TLSClientConfig: &tls.Config{
    97  				RootCAs: globalRootCAs, // make sure to use loaded certs before probing
    98  			},
    99  		},
   100  	}
   101  
   102  	_, te := client.Do(req)
   103  	if te == nil {
   104  		// certs are already trusted system wide, nothing to do.
   105  		return nil, nil
   106  	}
   107  
   108  	if !strings.Contains(te.Error(), "certificate signed by unknown authority") &&
   109  		!strings.Contains(te.Error(), "certificate is not trusted") /* darwin specific error message */ {
   110  		return nil, probe.NewError(te)
   111  	}
   112  
   113  	// Now, we fetch the peer certificate, compute the SHA-256 of
   114  	// public key and let the user confirm the fingerprint.
   115  	// If the user confirms, we store the peer certificate in the CAs
   116  	// directory and retry.
   117  	peerCert, e := fetchPeerCertificate(ctx, endpoint)
   118  	if e != nil {
   119  		return nil, probe.NewError(e)
   120  	}
   121  
   122  	if peerCert.IsCA && len(peerCert.AuthorityKeyId) == 0 {
   123  		// If peerCert is its own CA then AuthorityKeyId will be empty
   124  		// which means the SubjeyKeyId is the sha1.Sum(publicKeyBytes)
   125  		// Refer - SubjectKeyId generated using method 1 in RFC 5280, Section 4.2.1.2:
   126  		publicKeyBytes, e := marshalPublicKey(peerCert.PublicKey)
   127  		if e != nil {
   128  			return nil, probe.NewError(e)
   129  		}
   130  		h := sha1.Sum(publicKeyBytes)
   131  		if !bytes.Equal(h[:], peerCert.SubjectKeyId) {
   132  			return nil, probe.NewError(te)
   133  		}
   134  	} else {
   135  		// Check that the subject key id is equal to the authority key id.
   136  		// If true, the certificate is its own issuer, and therefore, a
   137  		// self-signed certificate. Otherwise, the certificate has been
   138  		// issued by some other certificate that is just not trusted.
   139  		if !bytes.Equal(peerCert.SubjectKeyId, peerCert.AuthorityKeyId) {
   140  			return nil, probe.NewError(te)
   141  		}
   142  	}
   143  
   144  	fingerprint := sha256.Sum256(peerCert.RawSubjectPublicKeyInfo)
   145  	fmt.Printf("Fingerprint of %s public key: %s\nConfirm public key y/N: ", color.GreenString(alias), color.YellowString(hex.EncodeToString(fingerprint[:])))
   146  	answer, e := bufio.NewReader(os.Stdin).ReadString('\n')
   147  	if e != nil {
   148  		return nil, probe.NewError(e)
   149  	}
   150  	if answer = strings.ToLower(answer); answer != "y\n" && answer != "yes\n" {
   151  		return nil, probe.NewError(te)
   152  	}
   153  
   154  	certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: peerCert.Raw})
   155  	if e = os.WriteFile(filepath.Join(mustGetCAsDir(), alias+".crt"), certPEM, 0o644); e != nil {
   156  		return nil, probe.NewError(e)
   157  	}
   158  	return peerCert, nil
   159  }
   160  
   161  // fetchPeerCertificate uses the given transport to fetch the peer
   162  // certificate from the given endpoint.
   163  func fetchPeerCertificate(ctx context.Context, endpoint string) (*x509.Certificate, error) {
   164  	req, e := http.NewRequestWithContext(ctx, http.MethodGet, endpoint, nil)
   165  	if e != nil {
   166  		return nil, e
   167  	}
   168  	client := http.Client{
   169  		Transport: &http.Transport{
   170  			TLSClientConfig: &tls.Config{
   171  				InsecureSkipVerify: true,
   172  			},
   173  		},
   174  	}
   175  	resp, e := client.Do(req)
   176  	if e != nil {
   177  		return nil, e
   178  	}
   179  	if resp.TLS == nil || len(resp.TLS.PeerCertificates) == 0 {
   180  		return nil, fmt.Errorf("Unable to read remote TLS certificate")
   181  	}
   182  	return resp.TLS.PeerCertificates[0], nil
   183  }