github.com/ConsenSys/Quorum@v20.10.0+incompatible/plugin/central.go (about) 1 package plugin 2 3 import ( 4 "bytes" 5 "crypto/tls" 6 "fmt" 7 "io" 8 "io/ioutil" 9 "net" 10 "net/http" 11 "net/url" 12 "os" 13 "strings" 14 15 "github.com/ethereum/go-ethereum/log" 16 ) 17 18 // Central https centralClient communicating with Plugin Central 19 type CentralClient struct { 20 config *PluginCentralConfiguration 21 httpClient *http.Client 22 } 23 24 // Create New Central Client 25 func NewPluginCentralClient(config *PluginCentralConfiguration) *CentralClient { 26 c := &CentralClient{ 27 config: config, 28 } 29 c.httpClient = &http.Client{} 30 c.httpClient.Transport = &http.Transport{ 31 DialTLS: c.getNewSecureDialer(), 32 } 33 return c 34 } 35 36 // Builds a Dialer that supports CA Verification & Certificate Pinning. 37 func (cc *CentralClient) getNewSecureDialer() Dialer { 38 return func(network, addr string) (net.Conn, error) { 39 c, err := tls.Dial(network, addr, &tls.Config{InsecureSkipVerify: cc.config.InsecureSkipTLSVerify}) 40 if err != nil { 41 return c, err 42 } 43 // support certificate pinning? 44 if cc.config.CertFingerprint != "" { 45 conState := c.ConnectionState() 46 for _, peercert := range conState.PeerCertificates { 47 if bytes.Equal(peercert.Signature[0:], []byte(cc.config.CertFingerprint)) { 48 return c, nil 49 } 50 } 51 return nil, fmt.Errorf("certificate pinning failed") 52 } 53 return c, nil 54 } 55 } 56 57 // Get the public key from central 58 func (cc *CentralClient) PublicKey() ([]byte, error) { 59 target := fmt.Sprintf("%s/%s", cc.config.BaseURL, cc.config.PublicKeyURI) 60 log.Debug("downloading public key", "url", target) 61 readCloser, err := cc.get(target) 62 if err != nil { 63 return nil, err 64 } 65 defer func() { 66 _ = readCloser.Close() 67 }() 68 return ioutil.ReadAll(readCloser) 69 } 70 71 // retrieve plugin signature 72 func (cc *CentralClient) PluginSignature(definition *PluginDefinition) ([]byte, error) { 73 target := fmt.Sprintf("%s/%s/%s", cc.config.BaseURL, definition.RemotePath(), definition.SignatureFileName()) 74 log.Debug("downloading plugin signature file", "url", target) 75 readCloser, err := cc.get(target) 76 if err != nil { 77 return nil, err 78 } 79 defer func() { 80 _ = readCloser.Close() 81 }() 82 return ioutil.ReadAll(readCloser) 83 } 84 85 // retrieve plugin distribution file 86 func (cc *CentralClient) PluginDistribution(definition *PluginDefinition, outFilePath string) error { 87 target := fmt.Sprintf("%s/%s/%s", cc.config.BaseURL, definition.RemotePath(), definition.DistFileName()) 88 log.Debug("downloading plugin zip file", "url", target) 89 outFile, err := os.Create(outFilePath) 90 if err != nil { 91 return err 92 } 93 defer func() { 94 _ = outFile.Close() 95 }() 96 readCloser, err := cc.get(target) 97 if err != nil { 98 return err 99 } 100 defer func() { 101 _ = readCloser.Close() 102 }() 103 _, err = io.Copy(outFile, readCloser) 104 return err 105 } 106 107 // perform HTTP GET 108 // 109 // caller needs to close the reader 110 func (cc *CentralClient) get(target string) (io.ReadCloser, error) { 111 if err := isValidTargetURL(cc.config.BaseURL, target); err != nil { 112 return nil, err 113 } 114 res, err := cc.httpClient.Get(target) 115 if err != nil { 116 return nil, err 117 } 118 if res.StatusCode != http.StatusOK { 119 defer func() { 120 _ = res.Body.Close() 121 }() 122 data, _ := ioutil.ReadAll(res.Body) 123 return nil, fmt.Errorf("HTTP GET error: code=%d, status=%s, body=%s", res.StatusCode, res.Status, string(data)) 124 } 125 return res.Body, nil 126 } 127 128 // An adapter function for tls.Dial with CA verification & SSL Pinning support. 129 type Dialer func(network, addr string) (net.Conn, error) 130 131 // Validate the target url is well formed and match base. 132 func isValidTargetURL(base string, target string) error { 133 u, err := url.Parse(target) 134 if err != nil { 135 return err 136 } 137 t, err := url.Parse(base) 138 if err != nil { 139 return err 140 } 141 if strings.Compare(t.Host, u.Host) != 0 || strings.Compare(t.Scheme, u.Scheme) != 0 { 142 return fmt.Errorf("target host doesnt match base host") 143 } 144 return nil 145 }