Skip to content

Commit 8009d17

Browse files
committed
lfsapi: add custom ssl cert support
1 parent c6f56d7 commit 8009d17

File tree

9 files changed

+530
-0
lines changed

9 files changed

+530
-0
lines changed

lfsapi/certs.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package lfsapi
2+
3+
import (
4+
"crypto/x509"
5+
"fmt"
6+
"io/ioutil"
7+
"path/filepath"
8+
9+
"github.com/rubyist/tracerx"
10+
)
11+
12+
// isCertVerificationDisabledForHost returns whether SSL certificate verification
13+
// has been disabled for the given host, or globally
14+
func isCertVerificationDisabledForHost(c *Client, host string) bool {
15+
hostSslVerify, _ := c.gitEnv.Get(fmt.Sprintf("http.https://%v/.sslverify", host))
16+
if hostSslVerify == "false" {
17+
return true
18+
}
19+
20+
return c.SkipSSLVerify
21+
}
22+
23+
// getRootCAsForHost returns a certificate pool for that specific host (which may
24+
// be "host:port" loaded from either the gitconfig or from a platform-specific
25+
// source which is not included by default in the golang certificate search)
26+
// May return nil if it doesn't have anything to add, in which case the default
27+
// RootCAs will be used if passed to TLSClientConfig.RootCAs
28+
func getRootCAsForHost(c *Client, host string) *x509.CertPool {
29+
// don't init pool, want to return nil not empty if none found; init only on successful add cert
30+
var pool *x509.CertPool
31+
32+
// gitconfig first
33+
pool = appendRootCAsForHostFromGitconfig(c.osEnv, c.gitEnv, pool, host)
34+
// Platform specific
35+
return appendRootCAsForHostFromPlatform(pool, host)
36+
}
37+
38+
func appendRootCAsForHostFromGitconfig(osEnv env, gitEnv env, pool *x509.CertPool, host string) *x509.CertPool {
39+
// Accumulate certs from all these locations:
40+
41+
// GIT_SSL_CAINFO first
42+
if cafile, _ := osEnv.Get("GIT_SSL_CAINFO"); len(cafile) > 0 {
43+
return appendCertsFromFile(pool, cafile)
44+
}
45+
// http.<url>/.sslcainfo or http.<url>.sslcainfo
46+
// we know we have simply "host" or "host:port"
47+
hostKeyWithSlash := fmt.Sprintf("http.https://%v/.sslcainfo", host)
48+
if cafile, ok := gitEnv.Get(hostKeyWithSlash); ok {
49+
return appendCertsFromFile(pool, cafile)
50+
}
51+
hostKeyWithoutSlash := fmt.Sprintf("http.https://%v.sslcainfo", host)
52+
if cafile, ok := gitEnv.Get(hostKeyWithoutSlash); ok {
53+
return appendCertsFromFile(pool, cafile)
54+
}
55+
// http.sslcainfo
56+
if cafile, ok := gitEnv.Get("http.sslcainfo"); ok {
57+
return appendCertsFromFile(pool, cafile)
58+
}
59+
// GIT_SSL_CAPATH
60+
if cadir, _ := osEnv.Get("GIT_SSL_CAPATH"); len(cadir) > 0 {
61+
return appendCertsFromFilesInDir(pool, cadir)
62+
}
63+
// http.sslcapath
64+
if cadir, ok := gitEnv.Get("http.sslcapath"); ok {
65+
return appendCertsFromFilesInDir(pool, cadir)
66+
}
67+
68+
return pool
69+
}
70+
71+
func appendCertsFromFilesInDir(pool *x509.CertPool, dir string) *x509.CertPool {
72+
files, err := ioutil.ReadDir(dir)
73+
if err != nil {
74+
tracerx.Printf("Error reading cert dir %q: %v", dir, err)
75+
return pool
76+
}
77+
for _, f := range files {
78+
pool = appendCertsFromFile(pool, filepath.Join(dir, f.Name()))
79+
}
80+
return pool
81+
}
82+
83+
func appendCertsFromFile(pool *x509.CertPool, filename string) *x509.CertPool {
84+
data, err := ioutil.ReadFile(filename)
85+
if err != nil {
86+
tracerx.Printf("Error reading cert file %q: %v", filename, err)
87+
return pool
88+
}
89+
// Firstly, try parsing as binary certificate
90+
if certs, err := x509.ParseCertificates(data); err == nil {
91+
return appendCerts(pool, certs)
92+
}
93+
// If not binary certs, try PEM data
94+
return appendCertsFromPEMData(pool, data)
95+
}
96+
97+
func appendCerts(pool *x509.CertPool, certs []*x509.Certificate) *x509.CertPool {
98+
if len(certs) == 0 {
99+
// important to return unmodified (may be nil)
100+
return pool
101+
}
102+
103+
if pool == nil {
104+
pool = x509.NewCertPool()
105+
}
106+
107+
for _, cert := range certs {
108+
pool.AddCert(cert)
109+
}
110+
111+
return pool
112+
}
113+
114+
func appendCertsFromPEMData(pool *x509.CertPool, data []byte) *x509.CertPool {
115+
if len(data) == 0 {
116+
return pool
117+
}
118+
119+
// Bit of a dance, need to ensure if AppendCertsFromPEM fails we still return
120+
// nil and not an empty pool, so system roots still get used
121+
var ret *x509.CertPool
122+
if pool == nil {
123+
ret = x509.NewCertPool()
124+
} else {
125+
ret = pool
126+
}
127+
if !ret.AppendCertsFromPEM(data) {
128+
// Return unmodified input pool (may be nil, do not replace with empty)
129+
return pool
130+
}
131+
return ret
132+
}

lfsapi/certs_darwin.go

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package lfsapi
2+
3+
import (
4+
"crypto/x509"
5+
"regexp"
6+
"strings"
7+
8+
"github.com/git-lfs/git-lfs/subprocess"
9+
"github.com/rubyist/tracerx"
10+
)
11+
12+
func appendRootCAsForHostFromPlatform(pool *x509.CertPool, host string) *x509.CertPool {
13+
// Go loads only the system root certificates by default
14+
// see https://github.com/golang/go/blob/master/src/crypto/x509/root_darwin.go
15+
// We want to load certs configured in the System keychain too, this is separate
16+
// from the system root certificates. It's also where other tools such as
17+
// browsers (e.g. Chrome) will load custom trusted certs from. They often
18+
// don't load certs from the login keychain so that's not included here
19+
// either, for consistency.
20+
21+
// find system.keychain for user-added certs (don't assume location)
22+
cmd := subprocess.ExecCommand("/usr/bin/security", "list-keychains")
23+
kcout, err := cmd.Output()
24+
if err != nil {
25+
tracerx.Printf("Error listing keychains: %v", err)
26+
return nil
27+
}
28+
29+
var systemKeychain string
30+
keychains := strings.Split(string(kcout), "\n")
31+
for _, keychain := range keychains {
32+
lc := strings.ToLower(keychain)
33+
if !strings.Contains(lc, "/system.keychain") {
34+
continue
35+
}
36+
systemKeychain = strings.Trim(keychain, " \t\"")
37+
break
38+
}
39+
40+
if len(systemKeychain) == 0 {
41+
return nil
42+
}
43+
44+
pool = appendRootCAsFromKeychain(pool, host, systemKeychain)
45+
46+
// Also check host without port
47+
portreg := regexp.MustCompile(`([^:]+):\d+`)
48+
if match := portreg.FindStringSubmatch(host); match != nil {
49+
hostwithoutport := match[1]
50+
pool = appendRootCAsFromKeychain(pool, hostwithoutport, systemKeychain)
51+
}
52+
53+
return pool
54+
}
55+
56+
func appendRootCAsFromKeychain(pool *x509.CertPool, name, keychain string) *x509.CertPool {
57+
cmd := subprocess.ExecCommand("/usr/bin/security", "find-certificate", "-a", "-p", "-c", name, keychain)
58+
data, err := cmd.Output()
59+
if err != nil {
60+
tracerx.Printf("Error reading keychain %q: %v", keychain, err)
61+
return pool
62+
}
63+
return appendCertsFromPEMData(pool, data)
64+
}

lfsapi/certs_freebsd.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package lfsapi
2+
3+
import "crypto/x509"
4+
5+
func appendRootCAsForHostFromPlatform(pool *x509.CertPool, host string) *x509.CertPool {
6+
// Do nothing, use golang default
7+
return pool
8+
}

lfsapi/certs_linux.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package lfsapi
2+
3+
import "crypto/x509"
4+
5+
func appendRootCAsForHostFromPlatform(pool *x509.CertPool, host string) *x509.CertPool {
6+
// Do nothing, use golang default
7+
return pool
8+
}

lfsapi/certs_openbsd.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package lfsapi
2+
3+
import "crypto/x509"
4+
5+
func appendRootCAsForHostFromPlatform(pool *x509.CertPool, host string) *x509.CertPool {
6+
// Do nothing, use golang default
7+
return pool
8+
}

0 commit comments

Comments
 (0)