I'm running into an issue with using GIT to connect to a GIT server that is protected by client certificates. I'm trying to use the Azure Pipeline Agent to run jobs on a brand new Azure VM.
I'm connecting to an on-premise TFS instance that is secured to the internet via client certificates.
With some trial and error, I got the certificates int the right shape to connect the agent up to the server. Using OpenSSL like this
openssl pkcs12 -in cert.pfx -cacerts -nokeys -out ca.pem -passin pass:
openssl pkcs12 -in cert.pfx -clcerts -nokeys -out clientcert.pem -passin pass:
openssl pkcs12 -in cert.pfx -clcerts -nocerts -out clientcert-key-pass.pem -passin pass: -passout pass:$pfxpassword
openssl pkcs12 -in cert.pfx -out temp.pem -nodes -passin pass:
openssl pkcs12 -export -out cert-secure.pfx -in temp.pem -passout pass:$pfxpassword
Thats where the fun ends....
For the background, I'm running on Ubuntu 18.04 and have tried GIT 2.17 and 2.22.
When I run curl on the machine with the same set of certificates it correctly gets past the client certificate security on the server and lets me access the underlying service which returns a 401 which is expected.
However, when using the same certificates from GIT I consistently get a 403 error and the server rejects the connection due to issues with the client certs.
The command I am running for GIT is
GIT_CURL_VERBOSE=1 GIT_TRACE=1 git -c http.sslcainfo="/home/scott/ca.pem" -c http.sslcert="/home/scott/clientcert.pem" -c http.sslkey="/home/scott/clientcert-key-pass.pem" -c http.sslCertPasswordProtected=true fetch --force --tags --prune --progress --no-recurse-submodules origin
The result of this is (redacted urls)
07:04:43.587108 git.c:439 trace: built-in: git fetch --force --tags --prune --progress --no-recurse-submodules origin
07:04:43.587518 run-command.c:663 trace: run_command: GIT_DIR=.git git-remote-https origin https://*****
Password for 'cert:////home/scott/clientcert.pem':
* Couldn't find host **** in the .netrc file; using defaults
* Trying 3.248.79.34...
* TCP_NODELAY set
* Connected to **** (3.248.79.34) port 443 (#0)
* found 3 certificates in /home/scott/ca.crt
* found 402 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ECDHE_RSA_AES_256_GCM_SHA384
* server certificate verification SKIPPED
* server certificate status verification SKIPPED
* common name: *** (matched)
* server certificate expiration date OK
* server certificate activation date OK
* certificate public key: RSA
* certificate version: #3
* subject: **** - redacted
* start date: Mon, 08 Apr 2019 00:00:00 GMT
* expire date: Tue, 07 Apr 2020 12:00:00 GMT
* issuer: C=US,O=DigiCert Inc,CN=DigiCert SHA2 Secure Server CA
* compression: NULL
* ALPN, server accepted to use http/1.1
> GET /****/info/refs?service=git-upload-pack HTTP/1.1
Host: ****.com
User-Agent: git/2.22.0
Accept: */*
Accept-Encoding: deflate, gzip
Accept-Language: C, *;q=0.9
Pragma: no-cache
< HTTP/1.1 403 Forbidden
< Content-Type: text/html
< Server: Microsoft-IIS/10.0
< Date: Tue, 13 Aug 2019 07:04:47 GMT
< Content-Length: 1300
<
* Connection #0 to host ****.com left intact
fatal: unable to access 'https://*****': The requested URL returned error: 403
It seems like GIT is skipping the client auth for some reason
The equivalent curl command is
curl -v --http1.0 --cacert /home/scott/ca.pem --key /home/scott/clientcert-key-pass.pem --cert /home/scott/clientcert.pem https://****.com/tfs/****/refs?service=git-upload-pack
and this results in success and can get past the client auth on the webserver and connect to the underlying service
* Trying 3.248.79.34...
* TCP_NODELAY set
* Connected to tfsemea1.ta.philips.com (3.248.79.34) port 443 (#0)
* ALPN, offering http/1.1
Enter PEM pass phrase:
* successfully set certificate verify locations:
* CAfile: /home/scott/ca.pem
CApath: /etc/ssl/certs
* (304) (OUT), TLS handshake, Client hello (1):
* (304) (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
* SSL connection using TLSv1.2 / ECDHE-RSA-AES256-GCM-SHA384
* ALPN, server accepted to use http/1.1
* Server certificate:
* subject: ******
* start date: Apr 8 00:00:00 2019 GMT
* expire date: Apr 7 12:00:00 2020 GMT
* subjectAltName: host "***.com" matched cert's "***.com"
* issuer: C=US; O=DigiCert Inc; CN=DigiCert SHA2 Secure Server CA
* SSL certificate verify ok.
> GET /***/info/refs?service=git-upload-pack HTTP/1.0
> Host: ***.com
> User-Agent: curl/7.58.0
> Accept: */*
>
* TLSv1.2 (IN), TLS handshake, Hello request (0):
* TLSv1.2 (OUT), TLS handshake, Client hello (1):
* TLSv1.2 (IN), TLS handshake, Server hello (2):
* TLSv1.2 (IN), TLS handshake, Certificate (11):
* TLSv1.2 (IN), TLS handshake, Server key exchange (12):
* TLSv1.2 (IN), TLS handshake, Request CERT (13):
* TLSv1.2 (IN), TLS handshake, Server finished (14):
* TLSv1.2 (OUT), TLS handshake, Certificate (11):
* TLSv1.2 (OUT), TLS handshake, Client key exchange (16):
* TLSv1.2 (OUT), TLS handshake, CERT verify (15):
* TLSv1.2 (OUT), TLS change cipher, Client hello (1):
* TLSv1.2 (OUT), TLS handshake, Finished (20):
* TLSv1.2 (IN), TLS handshake, Finished (20):
< HTTP/1.1 401 Unauthorized
< Content-Type: text/html; charset=utf-8
< Server: Microsoft-IIS/10.0
< P3P: CP="CAO DSP COR ADMa DEV CONo TELo CUR PSA PSD TAI IVDo OUR SAMi BUS DEM NAV STA UNI COM INT PHY ONL FIN PUR LOC CNT"
< WWW-Authenticate: Bearer
< WWW-Authenticate: Basic realm="https://***.com/tfs"
< WWW-Authenticate: Negotiate
< WWW-Authenticate: NTLM
< X-TFS-ProcessId: d2b304b0-a0ef-48b9-89b8-3281cb42c26d
< ActivityId: 07d4f17f-3940-4006-aab5-5c7afa57e84a
< X-TFS-Session: 07d4f17f-3940-4006-aab5-5c7afa57e84a
< X-VSS-E2EID: 07d4f17f-3940-4006-aab5-5c7afa57e84a
< X-FRAME-OPTIONS: SAMEORIGIN
< X-TFS-SoapException: %3C%3Fxml%20version%3D%221.0%22%20encoding%3D%22utf-8%22%3F%3E%3Csoap%3AEnvelope%20xmlns%3Asoap%3D%22http%3A%2F%2Fwww.w3.org%2F2003%2F05%2Fsoap-envelope%22%3E%3Csoap%3ABody%3E%3Csoap%3AFault%3E%3Csoap%3ACode%3E%3Csoap%3AValue%3Esoap%3AReceiver%3C%2Fsoap%3AValue%3E%3Csoap%3ASubcode%3E%3Csoap%3AValue%3EUnauthorizedRequestException%3C%2Fsoap%3AValue%3E%3C%2Fsoap%3ASubcode%3E%3C%2Fsoap%3ACode%3E%3Csoap%3AReason%3E%3Csoap%3AText%20xml%3Alang%3D%22en%22%3ETF400813%3A%20Resource%20not%20available%20for%20anonymous%20access.%20Client%20authentication%20required.%3C%2Fsoap%3AText%3E%3C%2Fsoap%3AReason%3E%3C%2Fsoap%3AFault%3E%3C%2Fsoap%3ABody%3E%3C%2Fsoap%3AEnvelope%3E
< X-TFS-ServiceError: TF400813%3A%20Resource%20not%20available%20for%20anonymous%20access.%20Client%20authentication%20required.
< X-Powered-By: ASP.NET
< Lfs-Authenticate: NTLM
< X-Content-Type-Options: nosniff
< X-Powered-By: ARR/3.0
< Date: Tue, 13 Aug 2019 07:58:27 GMT
< Connection: close
< Content-Length: 20158
At @VonC suggestion I tried without using the same key, without a password, using the following command
GIT_CURL_VERBOSE=1 GIT_TRACE=1 git -c http.sslcainfo="/home/scott/ca.pem" -c http.sslcert="/home/scott/clientcert.pem" -c http.sslkey="/home/scott/nopass.pem" fetch --force --tags --prune --progress --no-recurse-submodules origin
I removed the password using openssl rsa -in ~/clientcert-key-pass.pem -out ~/nopass.pem
It generated a very similar output
15:22:57.671901 git.c:440 trace: built-in: git fetch --force --tags --prune --progress --no-recurse-submodules origin
15:22:57.672253 run-command.c:663 trace: run_command: GIT_DIR=.git git-remote-https origin https://****
* Couldn't find host **** in the .netrc file; using defaults
* Trying 52.18.21.126...
* TCP_NODELAY set
* Connected to **** (52.18.21.126) port 443 (#0)
* found 3 certificates in /home/scott/ca.pem
* found 402 certificates in /etc/ssl/certs
* ALPN, offering http/1.1
* SSL connection using TLS1.2 / ECDHE_RSA_AES_256_GCM_SHA384
* server certificate verification SKIPPED
* server certificate status verification SKIPPED
* common name: **** (matched)
* server certificate expiration date OK
* server certificate activation date OK
* certificate public key: RSA
* certificate version: #3
* subject: ****
* start date: Thu, 29 Aug 2019 00:00:00 GMT
* expire date: Tue, 07 Apr 2020 12:00:00 GMT
* issuer: C=US,O=DigiCert Inc,CN=DigiCert SHA2 Secure Server CA
* compression: NULL
* ALPN, server accepted to use http/1.1
> GET /tfs/***/info/refs?service=git-upload-pack HTTP/1.1
Host: ****
User-Agent: git/2.23.0
Accept: */*
Accept-Encoding: deflate, gzip
Accept-Language: C, *;q=0.9
Pragma: no-cache
< HTTP/1.1 403 Forbidden
< Content-Type: text/html
< Server: Microsoft-IIS/10.0
< Date: Sun, 22 Sep 2019 13:22:56 GMT
< Content-Length: 1300
<
* Connection #0 to host **** left intact
fatal: unable to access 'https://****/': The requested URL returned error: 403
The only difference in the output is the prompt for the password
Password for 'cert:////home/scott/clientcert.pem':
Running git config -l
produces the following effective config from within the folder I am using
core.repositoryformatversion=0
core.filemode=true
core.bare=false
core.logallrefupdates=true
remote.origin.url=https://****
remote.origin.fetch=+refs/heads/*:refs/remotes/origin/*
gc.auto=0
Banging my head on this for some time now and am starting to think that something is broken somewhere and that GIT is not starting the client certificate handshake process you can see in curl and that it is just not presenting it and therefore getting the 403.
Am I missing something?
update:
403 - means the webserver is rejecting the request due to client cert issues 401 - means the underlying service (TFS) is rejecting the request due to authentication issues (which is a separate issue)