Commit 5b2cd75587826148c2c2d189371fe49847f06ec9

Ian Hattendorf 2020-02-03T16:24:02

winhttp: support optional client cert

diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index ee8cb15..9d3f05d 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -864,42 +864,68 @@ 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, cert_valid, client_cert_requested, 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;
-		}
 
-		request_failed = 1;
-		cert_valid = 0;
-	}
+	while (request_failed && attempts++ < 3) {
+		request_failed = 0;
+		cert_valid = 1;
+		client_cert_requested = 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;
+				}
+			}
+		}
 
-	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");
+		if (!request_failed || send_request_error == ERROR_WINHTTP_SECURE_FAILURE) {
+			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;
 }