Commit 86a1cdd38ee1cad0c578373a3888b92dbcb30106

Edward Thomson 2020-12-13T13:44:56

Merge pull request #5384 from ianhattendorf/fix/winhttp-client-cert winhttp: support optional client cert

diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index 2b77ec9..c82c788 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -874,42 +874,65 @@ static int do_send_request(winhttp_stream *s, size_t len, bool chunked)
 
 static int send_request(winhttp_stream *s, size_t len, bool chunked)
 {
-	int request_failed = 0, cert_valid = 1, error = 0;
-	DWORD ignore_flags;
+	int request_failed = 1, error, attempts = 0;
+	DWORD ignore_flags, send_request_error;
 
 	git_error_clear();
-	if ((error = do_send_request(s, len, chunked)) < 0) {
-		if (GetLastError() != ERROR_WINHTTP_SECURE_FAILURE) {
-			git_error_set(GIT_ERROR_OS, "failed to send request");
-			return -1;
+
+	while (request_failed && attempts++ < 3) {
+		int cert_valid = 1;
+		int client_cert_requested = 0;
+		request_failed = 0;
+		if ((error = do_send_request(s, len, chunked)) < 0) {
+			send_request_error = GetLastError();
+			request_failed = 1;
+			switch (send_request_error) {
+				case ERROR_WINHTTP_SECURE_FAILURE:
+					cert_valid = 0;
+					break;
+				case ERROR_WINHTTP_CLIENT_AUTH_CERT_NEEDED:
+					client_cert_requested = 1;
+					break;
+				default:
+					git_error_set(GIT_ERROR_OS, "failed to send request");
+					return -1;
+			}
 		}
 
-		request_failed = 1;
-		cert_valid = 0;
-	}
+		if (!request_failed || !cert_valid) {
+			git_error_clear();
+			if ((error = certificate_check(s, cert_valid)) < 0) {
+				if (!git_error_last())
+					git_error_set(GIT_ERROR_OS, "user cancelled certificate check");
 
-	git_error_clear();
-	if ((error = certificate_check(s, cert_valid)) < 0) {
-		if (!git_error_last())
-			git_error_set(GIT_ERROR_OS, "user cancelled certificate check");
-
-		return error;
-	}
+				return error;
+			}
+		}
 
-	/* if neither the request nor the certificate check returned errors, we're done */
-	if (!request_failed)
-		return 0;
+		/* if neither the request nor the certificate check returned errors, we're done */
+		if (!request_failed)
+			return 0;
 
-	ignore_flags = no_check_cert_flags;
+		if (!cert_valid) {
+			ignore_flags = no_check_cert_flags;
+			if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) {
+				git_error_set(GIT_ERROR_OS, "failed to set security options");
+				return -1;
+			}
+		}
 
-	if (!WinHttpSetOption(s->request, WINHTTP_OPTION_SECURITY_FLAGS, &ignore_flags, sizeof(ignore_flags))) {
-		git_error_set(GIT_ERROR_OS, "failed to set security options");
-		return -1;
+		if (client_cert_requested) {
+			/*
+			 * Client certificates are not supported, explicitly tell the server that
+			 * (it's possible a client certificate was requested but is not required)
+			 */
+			if (!WinHttpSetOption(s->request, WINHTTP_OPTION_CLIENT_CERT_CONTEXT, WINHTTP_NO_CLIENT_CERT_CONTEXT, 0)) {
+				git_error_set(GIT_ERROR_OS, "failed to set client cert context");
+				return -1;
+			}
+		}
 	}
 
-	if ((error = do_send_request(s, len, chunked)) < 0)
-		git_error_set(GIT_ERROR_OS, "failed to send request with unchecked certificate");
-
 	return error;
 }