Skip to content

Commit daba49a

Browse files
committed
integration test added for clone with client certificate
1 parent 2b01f7f commit daba49a

File tree

5 files changed

+235
-9
lines changed

5 files changed

+235
-9
lines changed

test/cmd/lfstest-gitserver.go

Lines changed: 130 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,22 @@ import (
66
"bufio"
77
"bytes"
88
"crypto/rand"
9+
"crypto/rsa"
910
"crypto/sha256"
11+
"crypto/tls"
12+
"crypto/x509"
13+
"crypto/x509/pkix"
1014
"encoding/base64"
1115
"encoding/hex"
1216
"encoding/json"
1317
"encoding/pem"
18+
"errors"
1419
"fmt"
1520
"io"
1621
"io/ioutil"
1722
"log"
1823
"math"
24+
"math/big"
1925
"net/http"
2026
"net/http/httptest"
2127
"net/textproto"
@@ -32,10 +38,11 @@ import (
3238
)
3339

3440
var (
35-
repoDir string
36-
largeObjects = newLfsStorage()
37-
server *httptest.Server
38-
serverTLS *httptest.Server
41+
repoDir string
42+
largeObjects = newLfsStorage()
43+
server *httptest.Server
44+
serverTLS *httptest.Server
45+
serverClientCert *httptest.Server
3946

4047
// maps OIDs to content strings. Both the LFS and Storage test servers below
4148
// see OIDs.
@@ -61,6 +68,22 @@ func main() {
6168
mux := http.NewServeMux()
6269
server = httptest.NewServer(mux)
6370
serverTLS = httptest.NewTLSServer(mux)
71+
serverClientCert = httptest.NewUnstartedServer(mux)
72+
73+
//setup Client Cert server
74+
rootKey, rootCert := generateCARootCertificates()
75+
_, clientCertPEM, clientKeyPEM := generateClientCertificates(rootCert, rootKey)
76+
77+
certPool := x509.NewCertPool()
78+
certPool.AddCert(rootCert)
79+
80+
serverClientCert.TLS = &tls.Config{
81+
Certificates: []tls.Certificate{serverTLS.TLS.Certificates[0]},
82+
ClientAuth: tls.RequireAndVerifyClientCert,
83+
ClientCAs: certPool,
84+
}
85+
serverClientCert.StartTLS()
86+
6487
ntlmSession, err := ntlm.CreateServerSession(ntlm.Version2, ntlm.ConnectionOrientedMode)
6588
if err != nil {
6689
fmt.Println("Error creating ntlm session:", err)
@@ -108,15 +131,26 @@ func main() {
108131
sslurlname := writeTestStateFile([]byte(serverTLS.URL), "LFSTEST_SSL_URL", "lfstest-gitserver-ssl")
109132
defer os.RemoveAll(sslurlname)
110133

134+
clientCertUrlname := writeTestStateFile([]byte(serverClientCert.URL), "LFSTEST_CLIENT_CERT_URL", "lfstest-gitserver-ssl")
135+
defer os.RemoveAll(clientCertUrlname)
136+
111137
block := &pem.Block{}
112138
block.Type = "CERTIFICATE"
113139
block.Bytes = serverTLS.TLS.Certificates[0].Certificate[0]
114140
pembytes := pem.EncodeToMemory(block)
141+
115142
certname := writeTestStateFile(pembytes, "LFSTEST_CERT", "lfstest-gitserver-cert")
116143
defer os.RemoveAll(certname)
117144

145+
cccertname := writeTestStateFile(clientCertPEM, "LFSTEST_CLIENT_CERT", "lfstest-gitserver-client-cert")
146+
defer os.RemoveAll(cccertname)
147+
148+
ckcertname := writeTestStateFile(clientKeyPEM, "LFSTEST_CLIENT_KEY", "lfstest-gitserver-client-key")
149+
defer os.RemoveAll(ckcertname)
150+
118151
debug("init", "server url: %s", server.URL)
119152
debug("init", "server tls url: %s", serverTLS.URL)
153+
debug("init", "server client cert url: %s", serverClientCert.URL)
120154

121155
<-stopch
122156
debug("init", "git server done")
@@ -1212,3 +1246,95 @@ func reqId(w http.ResponseWriter) (string, bool) {
12121246
}
12131247
return fmt.Sprintf("%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:]), true
12141248
}
1249+
1250+
// https://ericchiang.github.io/post/go-tls/
1251+
func generateCARootCertificates() (rootKey *rsa.PrivateKey, rootCert *x509.Certificate) {
1252+
1253+
// generate a new key-pair
1254+
rootKey, err := rsa.GenerateKey(rand.Reader, 2048)
1255+
if err != nil {
1256+
log.Fatalf("generating random key: %v", err)
1257+
}
1258+
1259+
rootCertTmpl, err := CertTemplate()
1260+
if err != nil {
1261+
log.Fatalf("creating cert template: %v", err)
1262+
}
1263+
// describe what the certificate will be used for
1264+
rootCertTmpl.IsCA = true
1265+
rootCertTmpl.KeyUsage = x509.KeyUsageCertSign | x509.KeyUsageDigitalSignature
1266+
rootCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}
1267+
// rootCertTmpl.IPAddresses = []net.IP{net.ParseIP("127.0.0.1")}
1268+
1269+
rootCert, _, err = CreateCert(rootCertTmpl, rootCertTmpl, &rootKey.PublicKey, rootKey)
1270+
1271+
return
1272+
}
1273+
1274+
func generateClientCertificates(rootCert *x509.Certificate, rootKey interface{}) (clientKey *rsa.PrivateKey, clientCertPEM []byte, clientKeyPEM []byte) {
1275+
1276+
// create a key-pair for the client
1277+
clientKey, err := rsa.GenerateKey(rand.Reader, 2048)
1278+
if err != nil {
1279+
log.Fatalf("generating random key: %v", err)
1280+
}
1281+
1282+
// create a template for the client
1283+
clientCertTmpl, err1 := CertTemplate()
1284+
if err1 != nil {
1285+
log.Fatalf("creating cert template: %v", err1)
1286+
}
1287+
clientCertTmpl.KeyUsage = x509.KeyUsageDigitalSignature
1288+
clientCertTmpl.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}
1289+
1290+
// the root cert signs the cert by again providing its private key
1291+
_, clientCertPEM, err2 := CreateCert(clientCertTmpl, rootCert, &clientKey.PublicKey, rootKey)
1292+
if err2 != nil {
1293+
log.Fatalf("error creating cert: %v", err2)
1294+
}
1295+
1296+
// encode and load the cert and private key for the client
1297+
clientKeyPEM = pem.EncodeToMemory(&pem.Block{
1298+
Type: "RSA PRIVATE KEY", Bytes: x509.MarshalPKCS1PrivateKey(clientKey),
1299+
})
1300+
1301+
return
1302+
}
1303+
1304+
// helper function to create a cert template with a serial number and other required fields
1305+
func CertTemplate() (*x509.Certificate, error) {
1306+
// generate a random serial number (a real cert authority would have some logic behind this)
1307+
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128)
1308+
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit)
1309+
if err != nil {
1310+
return nil, errors.New("failed to generate serial number: " + err.Error())
1311+
}
1312+
1313+
tmpl := x509.Certificate{
1314+
SerialNumber: serialNumber,
1315+
Subject: pkix.Name{Organization: []string{"Yhat, Inc."}},
1316+
SignatureAlgorithm: x509.SHA256WithRSA,
1317+
NotBefore: time.Now(),
1318+
NotAfter: time.Now().Add(time.Hour), // valid for an hour
1319+
BasicConstraintsValid: true,
1320+
}
1321+
return &tmpl, nil
1322+
}
1323+
1324+
func CreateCert(template, parent *x509.Certificate, pub interface{}, parentPriv interface{}) (
1325+
cert *x509.Certificate, certPEM []byte, err error) {
1326+
1327+
certDER, err := x509.CreateCertificate(rand.Reader, template, parent, pub, parentPriv)
1328+
if err != nil {
1329+
return
1330+
}
1331+
// parse the resulting certificate so we can use it again
1332+
cert, err = x509.ParseCertificate(certDER)
1333+
if err != nil {
1334+
return
1335+
}
1336+
// PEM encode the certificate (this is a standard TLS encoding)
1337+
b := pem.Block{Type: "CERTIFICATE", Bytes: certDER}
1338+
certPEM = pem.EncodeToMemory(&b)
1339+
return
1340+
}

test/test-clone.sh

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,66 @@ begin_test "cloneSSL"
140140
)
141141
end_test
142142

143+
begin_test "clone ClientCert"
144+
(
145+
146+
set -e
147+
# if $TRAVIS; then
148+
# echo "Skipping SSL tests, Travis has weird behaviour in validating custom certs, test locally only"
149+
# exit 0
150+
# fi
151+
152+
reponame="test-cloneClientCert"
153+
setup_remote_repo "$reponame"
154+
clone_repo_clientcert "$reponame" "$reponame"
155+
156+
git lfs track "*.dat" 2>&1 | tee track.log
157+
grep "Tracking \*.dat" track.log
158+
159+
# generate some test data & commits with random LFS data
160+
echo "[
161+
{
162+
\"CommitDate\":\"$(get_date -5d)\",
163+
\"Files\":[
164+
{\"Filename\":\"file1.dat\",\"Size\":100},
165+
{\"Filename\":\"file2.dat\",\"Size\":75}]
166+
},
167+
{
168+
\"CommitDate\":\"$(get_date -1d)\",
169+
\"Files\":[
170+
{\"Filename\":\"file3.dat\",\"Size\":30}]
171+
}
172+
]" | lfstest-testutils addcommits
173+
174+
git push origin master
175+
176+
# Now clone again with 'git lfs clone', test specific clone dir
177+
cd "$TRASHDIR"
178+
179+
newclonedir="testcloneClietCert1"
180+
git lfs clone "$CLIENTCERTGITSERVER/$reponame" "$newclonedir" 2>&1 | tee lfsclone.log
181+
grep "Cloning into" lfsclone.log
182+
grep "Git LFS:" lfsclone.log
183+
# should be no filter errors
184+
[ ! $(grep "filter" lfsclone.log) ]
185+
[ ! $(grep "error" lfsclone.log) ]
186+
# should be cloned into location as per arg
187+
[ -d "$newclonedir" ]
188+
189+
# check a few file sizes to make sure pulled
190+
pushd "$newclonedir"
191+
[ $(wc -c < "file1.dat") -eq 100 ]
192+
[ $(wc -c < "file2.dat") -eq 75 ]
193+
[ $(wc -c < "file3.dat") -eq 30 ]
194+
popd
195+
196+
197+
# Now check SSL clone with standard 'git clone' and smudge download
198+
rm -rf "$reponame"
199+
git clone "$CLIENTCERTGITSERVER/$reponame"
200+
201+
)
202+
end_test
143203

144204
begin_test "clone with flags"
145205
(

test/testenv.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,9 +102,19 @@ LFS_URL_FILE="$REMOTEDIR/url"
102102
# section in test/README.md
103103
LFS_SSL_URL_FILE="$REMOTEDIR/sslurl"
104104

105+
# This file contains the client cert SSL URL of the test Git server. See the "Test Suite"
106+
# section in test/README.md
107+
LFS_CLIENT_CERT_URL_FILE="$REMOTEDIR/clientcerturl"
108+
105109
# This file contains the self-signed SSL cert of the TLS endpoint of the test Git server.
106110
LFS_CERT_FILE="$REMOTEDIR/cert"
107111

112+
# This file contains the client certificate of the client cert endpoint of the test Git server.
113+
LFS_CLIENT_CERT_FILE="$REMOTEDIR/client.crt"
114+
115+
# This file contains the client key of the client cert endpoint of the test Git server.
116+
LFS_CLIENT_KEY_FILE="$REMOTEDIR/client.key"
117+
108118
# the fake home dir used for the initial setup
109119
TESTHOME="$REMOTEDIR/home"
110120

test/testhelpers.sh

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -258,6 +258,25 @@ clone_repo_ssl() {
258258
echo "$out"
259259
}
260260

261+
# clone_repo_clientcert clones a repository from the test Git server to the subdirectory
262+
# $dir under $TRASHDIR, using the client cert endpoint.
263+
# setup_remote_repo() needs to be run first. Output is written to clone_client_cert.log.
264+
clone_repo_clientcert() {
265+
cd "$TRASHDIR"
266+
267+
local reponame="$1"
268+
local dir="$2"
269+
echo "clone $CLIENTCERTGITSERVER/$reponame to $dir"
270+
out=$(git clone "$CLIENTCERTGITSERVER/$reponame" "$dir" 2>&1)
271+
cd "$dir"
272+
273+
git config credential.helper lfstest
274+
#todo setup client cert...
275+
276+
echo "$out" > clone_client_cert.log
277+
echo "$out"
278+
}
279+
261280
# setup_remote_repo_with_file creates a remote repo, clones it locally, commits
262281
# a file tracked by LFS, and pushes it to the remote:
263282
#
@@ -330,7 +349,16 @@ setup() {
330349
fi
331350
fi
332351

333-
LFSTEST_URL="$LFS_URL_FILE" LFSTEST_SSL_URL="$LFS_SSL_URL_FILE" LFSTEST_DIR="$REMOTEDIR" LFSTEST_CERT="$LFS_CERT_FILE" lfstest-gitserver > "$REMOTEDIR/gitserver.log" 2>&1 &
352+
LFSTEST_URL="$LFS_URL_FILE" LFSTEST_SSL_URL="$LFS_SSL_URL_FILE" LFSTEST_CLIENT_CERT_URL="$LFS_CLIENT_CERT_URL_FILE" LFSTEST_DIR="$REMOTEDIR" LFSTEST_CERT="$LFS_CERT_FILE" LFSTEST_CLIENT_CERT="$LFS_CLIENT_CERT_FILE" LFSTEST_CLIENT_KEY="$LFS_CLIENT_KEY_FILE" lfstest-gitserver > "$REMOTEDIR/gitserver.log" 2>&1 &
353+
354+
wait_for_file "$LFS_URL_FILE"
355+
wait_for_file "$LFS_SSL_URL_FILE"
356+
wait_for_file "$LFS_CLIENT_CERT_URL_FILE"
357+
wait_for_file "$LFS_CERT_FILE"
358+
wait_for_file "$LFS_CLIENT_CERT_FILE"
359+
wait_for_file "$LFS_CLIENT_KEY_FILE"
360+
361+
LFS_CLIENT_CERT_URL=`cat $LFS_CLIENT_CERT_URL_FILE`
334362

335363
# Set up the initial git config and osx keychain if applicable
336364
HOME="$TESTHOME"
@@ -341,6 +369,8 @@ setup() {
341369
git config --global user.name "Git LFS Tests"
342370
git config --global user.email "[email protected]"
343371
git config --global http.sslcainfo "$LFS_CERT_FILE"
372+
git config --global http.$LFS_CLIENT_CERT_URL/.sslKey "$LFS_CLIENT_KEY_FILE"
373+
git config --global http.$LFS_CLIENT_CERT_URL/.sslCert "$LFS_CLIENT_CERT_FILE"
344374

345375
( grep "git-lfs clean" "$REMOTEDIR/home/.gitconfig" > /dev/null && grep "git-lfs filter-process" "$REMOTEDIR/home/.gitconfig" > /dev/null ) || {
346376
echo "global git config should be set in $REMOTEDIR/home"
@@ -359,15 +389,14 @@ setup() {
359389
echo "lfstest-gitserver:"
360390
echo " LFSTEST_URL=$LFS_URL_FILE"
361391
echo " LFSTEST_SSL_URL=$LFS_SSL_URL_FILE"
392+
echo " LFSTEST_CLIENT_CERT_URL=$LFS_CLIENT_CERT_URL_FILE ($LFS_CLIENT_CERT_URL)"
362393
echo " LFSTEST_CERT=$LFS_CERT_FILE"
394+
echo " LFSTEST_CLIENT_CERT=$LFS_CLIENT_CERT_FILE"
395+
echo " LFSTEST_CLIENT_KEY=$LFS_CLIENT_KEY_FILE"
363396
echo " LFSTEST_DIR=$REMOTEDIR"
364397
echo "GIT:"
365398
git config --global --get-regexp "lfs|credential|user"
366399

367-
wait_for_file "$LFS_URL_FILE"
368-
wait_for_file "$LFS_SSL_URL_FILE"
369-
wait_for_file "$LFS_CERT_FILE"
370-
371400
echo
372401
}
373402

test/testlib.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ fi
5959

6060
GITSERVER=$(cat "$LFS_URL_FILE")
6161
SSLGITSERVER=$(cat "$LFS_SSL_URL_FILE")
62+
CLIENTCERTGITSERVER=$(cat "$LFS_CLIENT_CERT_URL_FILE")
6263
cd "$TRASHDIR"
6364

6465
# Mark the beginning of a test. A subshell should immediately follow this

0 commit comments

Comments
 (0)