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 }