package lfshttp
import (
"crypto/tls"
"crypto/x509"
"encoding/pem"
"fmt"
"net/url"
"os"
"path/filepath"
"github.com/git-lfs/git-lfs/v3/config"
"github.com/git-lfs/git-lfs/v3/errors"
"github.com/git-lfs/git-lfs/v3/tools"
"github.com/git-lfs/git-lfs/v3/tr"
"github.com/rubyist/tracerx"
)
func isCertVerificationDisabledForHost(c *Client, host string) bool {
hostSslVerify, _ := c.uc.Get("http", fmt.Sprintf("https://%v", host), "sslverify")
if hostSslVerify == "false" {
return true
}
return c.SkipSSLVerify
}
func isClientCertEnabledForHost(c *Client, host string) bool {
_, hostSslKeyOk := c.uc.Get("http", fmt.Sprintf("https://%v/", host), "sslKey")
_, hostSslCertOk := c.uc.Get("http", fmt.Sprintf("https://%v/", host), "sslCert")
return hostSslKeyOk && hostSslCertOk
}
func decryptPEMBlock(c *Client, block *pem.Block, path string, key []byte) ([]byte, error) {
fileurl := fmt.Sprintf("cert:///%s", filepath.ToSlash(path))
url, err := url.Parse(fileurl)
if err != nil {
return nil, err
}
credWrapper := c.credHelperContext.GetCredentialHelper(nil, url)
credWrapper.Input["username"] = []string{""}
creds, err := credWrapper.CredentialHelper.Fill(credWrapper.Input)
if err != nil {
tracerx.Printf("Error filling credentials for %q: %v", fileurl, err)
return nil, err
}
pass := creds["password"][0]
decrypted, err := x509.DecryptPEMBlock(block, []byte(pass))
if err != nil {
credWrapper.CredentialHelper.Reject(creds)
return nil, err
}
credWrapper.CredentialHelper.Approve(creds)
toEncode := &pem.Block{Type: block.Type, Headers: nil, Bytes: decrypted}
buf := pem.EncodeToMemory(toEncode)
return buf, nil
}
func getClientCertForHost(c *Client, host string) (*tls.Certificate, error) {
hostSslKey, _ := c.uc.Get("http", fmt.Sprintf("https://%v/", host), "sslKey")
hostSslCert, _ := c.uc.Get("http", fmt.Sprintf("https://%v/", host), "sslCert")
hostSslKey, err := tools.ExpandPath(hostSslKey, false)
if err != nil {
return nil, errors.Wrapf(err, tr.Tr.Get("Error resolving key path %q", hostSslKey))
}
hostSslCert, err = tools.ExpandPath(hostSslCert, false)
if err != nil {
return nil, errors.Wrapf(err, tr.Tr.Get("Error resolving cert path %q", hostSslCert))
}
cert, err := os.ReadFile(hostSslCert)
if err != nil {
tracerx.Printf("Error reading client cert file %q: %v", hostSslCert, err)
return nil, errors.Wrapf(err, tr.Tr.Get("Error reading client cert file %q", hostSslCert))
}
key, err := os.ReadFile(hostSslKey)
if err != nil {
tracerx.Printf("Error reading client key file %q: %v", hostSslKey, err)
return nil, errors.Wrapf(err, tr.Tr.Get("Error reading client key file %q", hostSslKey))
}
block, _ := pem.Decode(key)
if block == nil {
return nil, errors.New(tr.Tr.Get("Error decoding PEM block from %q", hostSslKey))
}
if x509.IsEncryptedPEMBlock(block) {
key, err = decryptPEMBlock(c, block, hostSslKey, key)
if err != nil {
tracerx.Printf("Unable to decrypt client key file %q: %v", hostSslKey, err)
return nil, errors.Wrapf(err, tr.Tr.Get("Error reading client key file %q (not a PKCS#1 file?)", hostSslKey))
}
}
certobj, err := tls.X509KeyPair(cert, key)
if err != nil {
tracerx.Printf("Error reading client cert/key %v", err)
return nil, errors.Wrapf(err, tr.Tr.Get("Error reading client cert/key"))
}
return &certobj, nil
}
func getRootCAsForHost(c *Client, host string) *x509.CertPool {
var pool *x509.CertPool
pool = appendRootCAsForHostFromGitconfig(c.osEnv, c.gitEnv, pool, host)
return appendRootCAsForHostFromPlatform(pool, host)
}
func appendRootCAsForHostFromGitconfig(osEnv, gitEnv config.Environment, pool *x509.CertPool, host string) *x509.CertPool {
url := fmt.Sprintf("https://%v/", host)
uc := config.NewURLConfig(gitEnv)
backend, _ := uc.Get("http", url, "sslbackend")
schannelUseSslCaInfoStrValue, _ := uc.Get("http", url, "schannelusesslcainfo")
schannelUseSslCaInfo := config.Bool(schannelUseSslCaInfoStrValue, false)
if backend == "schannel" && !schannelUseSslCaInfo {
return pool
}
if cafile, _ := osEnv.Get("GIT_SSL_CAINFO"); len(cafile) > 0 {
return appendCertsFromFile(pool, cafile)
}
if cafile, ok := uc.Get("http", url, "sslcainfo"); ok {
return appendCertsFromFile(pool, cafile)
}
if cadir, _ := osEnv.Get("GIT_SSL_CAPATH"); len(cadir) > 0 {
return appendCertsFromFilesInDir(pool, cadir)
}
if cadir, ok := gitEnv.Get("http.sslcapath"); ok {
return appendCertsFromFilesInDir(pool, cadir)
}
return pool
}
func appendCertsFromFilesInDir(pool *x509.CertPool, dir string) *x509.CertPool {
dirpath, errpath := tools.TranslateCygwinPath(dir)
if errpath != nil {
tracerx.Printf("Error reading cert dir %q: %v", dirpath, errpath)
}
files, err := os.ReadDir(dirpath)
if err != nil {
tracerx.Printf("Error reading cert dir %q: %v", dir, err)
return pool
}
for _, f := range files {
pool = appendCertsFromFile(pool, filepath.Join(dir, f.Name()))
}
return pool
}
func appendCertsFromFile(pool *x509.CertPool, filename string) *x509.CertPool {
filenamepath, errfile := tools.TranslateCygwinPath(filename)
if errfile != nil {
tracerx.Printf("Error reading cert dir %q: %v", filenamepath, errfile)
}
data, err := os.ReadFile(filenamepath)
if err != nil {
tracerx.Printf("Error reading cert file %q: %v", filename, err)
return pool
}
if certs, err := x509.ParseCertificates(data); err == nil {
return appendCerts(pool, certs)
}
return appendCertsFromPEMData(pool, data)
}
func appendCerts(pool *x509.CertPool, certs []*x509.Certificate) *x509.CertPool {
if len(certs) == 0 {
return pool
}
if pool == nil {
pool = x509.NewCertPool()
}
for _, cert := range certs {
pool.AddCert(cert)
}
return pool
}
func appendCertsFromPEMData(pool *x509.CertPool, data []byte) *x509.CertPool {
if len(data) == 0 {
return pool
}
var ret *x509.CertPool
if pool == nil {
ret = x509.NewCertPool()
} else {
ret = pool
}
if !ret.AppendCertsFromPEM(data) {
return pool
}
return ret
}