diff --git a/http.go b/http.go index 0777c566..398d3116 100644 --- a/http.go +++ b/http.go @@ -1,6 +1,8 @@ package git import ( + "crypto/tls" + "crypto/x509" "errors" "fmt" "io" @@ -190,8 +192,19 @@ func (self *httpSmartSubtransportStream) sendRequest() error { var resp *http.Response var err error - var userName string - var password string + + // Obtain the credentials and use them. + cred, err := self.owner.transport.SmartCredentials("", CredentialTypeUserpassPlaintext) + if err != nil { + return err + } + defer cred.Free() + + userName, password, err := cred.GetUserpassPlaintext() + if err != nil { + return err + } + for { req := &http.Request{ Method: self.req.Method, @@ -204,30 +217,38 @@ func (self *httpSmartSubtransportStream) sendRequest() error { } req.SetBasicAuth(userName, password) - resp, err = http.DefaultClient.Do(req) - if err != nil { - return err - } - if resp.StatusCode == http.StatusOK { - break - } + c := http.Client{} - if resp.StatusCode == http.StatusUnauthorized { - resp.Body.Close() + cap := x509.NewCertPool() - cred, err := self.owner.transport.SmartCredentials("", CredentialTypeUserpassPlaintext) - if err != nil { - return err - } - defer cred.Free() + // NOTE: self.req.URL.Host returns only host without port. To be + // able to fetch the correct certs from the global certs, parse again + // and get host+port with url.Host. + u, err := url.Parse(self.req.URL.String()) + if err != nil { + return fmt.Errorf("failed to parse URL: %v", err) + } - userName, password, err = cred.GetUserpassPlaintext() - if err != nil { - return err + // Use CA cert if found. + if cert, found := globalCACertPool.certPool[u.Host]; found { + if ok := cap.AppendCertsFromPEM(cert); !ok { + return fmt.Errorf("failed to parse CA cert") } + c.Transport = &http.Transport{ + TLSClientConfig: &tls.Config{ + RootCAs: cap, + }, + } + } - continue + resp, err = c.Do(req) + if err != nil { + return err + } + + if resp.StatusCode == http.StatusOK { + break } // Any other error we treat as a hard error and punt back to the caller diff --git a/transport.go b/transport.go index 23514b47..daa8c957 100644 --- a/transport.go +++ b/transport.go @@ -24,6 +24,7 @@ import "C" import ( "fmt" "io" + "net/url" "reflect" "runtime" "sync" @@ -39,8 +40,38 @@ var ( }{ transports: make(map[string]*RegisteredSmartTransport), } + // globalCACertPool is a mapping of global CA certs used by git2go-managed + // transports. The map's key is hostname+port and the value is a + // corresponding CA cert. + // Since the git2go-managed transports aren't public, this can be used to + // provide certs to the subtransports that can be looked up for a given + // host. + globalCACertPool = struct { + sync.Mutex + certPool map[string][]byte + }{ + certPool: make(map[string][]byte), + } ) +// RegisterCACerts registers CA cert associated with an address in the +// globalCACertPool. +func RegisterCACerts(address string, caBundle []byte) error { + globalCACertPool.Lock() + defer globalCACertPool.Unlock() + // Ignore empty CA bundles. + if len(caBundle) == 0 { + return nil + } + u, err := url.Parse(address) + if err != nil { + return err + } + // Store the certificate based on host+port, e.g.: 127.0.0.1:42107. + globalCACertPool.certPool[u.Host] = caBundle + return nil +} + // unregisterManagedTransports unregisters all git2go-managed transports. func unregisterManagedTransports() error { globalRegisteredSmartTransports.Lock()