Merge pull request #5814 from ianhattendorf/fix/winhttp-proxy-https winhttp: skip certificate check if unable to send request
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150
diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index a2a71be..3da625a 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -111,7 +111,8 @@ typedef struct {
DWORD post_body_len;
unsigned sent_request : 1,
received_response : 1,
- chunked : 1;
+ chunked : 1,
+ status_sending_request_reached: 1;
} winhttp_stream;
typedef struct {
@@ -713,30 +714,36 @@ static void CALLBACK winhttp_status(
DWORD status;
GIT_UNUSED(connection);
- GIT_UNUSED(ctx);
GIT_UNUSED(info_len);
- if (code != WINHTTP_CALLBACK_STATUS_SECURE_FAILURE)
- return;
-
- status = *((DWORD *)info);
-
- if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID))
- git_error_set(GIT_ERROR_HTTP, "SSL certificate issued for different common name");
- else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID))
- git_error_set(GIT_ERROR_HTTP, "SSL certificate has expired");
- else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA))
- git_error_set(GIT_ERROR_HTTP, "SSL certificate signed by unknown CA");
- else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT))
- git_error_set(GIT_ERROR_HTTP, "SSL certificate is invalid");
- else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED))
- git_error_set(GIT_ERROR_HTTP, "certificate revocation check failed");
- else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED))
- git_error_set(GIT_ERROR_HTTP, "SSL certificate was revoked");
- else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR))
- git_error_set(GIT_ERROR_HTTP, "security libraries could not be loaded");
- else
- git_error_set(GIT_ERROR_HTTP, "unknown security error %lu", status);
+ switch (code) {
+ case WINHTTP_CALLBACK_STATUS_SECURE_FAILURE:
+ status = *((DWORD *)info);
+
+ if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_CN_INVALID))
+ git_error_set(GIT_ERROR_HTTP, "SSL certificate issued for different common name");
+ else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_DATE_INVALID))
+ git_error_set(GIT_ERROR_HTTP, "SSL certificate has expired");
+ else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CA))
+ git_error_set(GIT_ERROR_HTTP, "SSL certificate signed by unknown CA");
+ else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_INVALID_CERT))
+ git_error_set(GIT_ERROR_HTTP, "SSL certificate is invalid");
+ else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REV_FAILED))
+ git_error_set(GIT_ERROR_HTTP, "certificate revocation check failed");
+ else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_CERT_REVOKED))
+ git_error_set(GIT_ERROR_HTTP, "SSL certificate was revoked");
+ else if ((status & WINHTTP_CALLBACK_STATUS_FLAG_SECURITY_CHANNEL_ERROR))
+ git_error_set(GIT_ERROR_HTTP, "security libraries could not be loaded");
+ else
+ git_error_set(GIT_ERROR_HTTP, "unknown security error %lu", status);
+
+ break;
+
+ case WINHTTP_CALLBACK_STATUS_SENDING_REQUEST:
+ ((winhttp_stream *) ctx)->status_sending_request_reached = 1;
+
+ break;
+ }
}
static int winhttp_connect(
@@ -836,7 +843,12 @@ static int winhttp_connect(
goto on_error;
}
- if (WinHttpSetStatusCallback(t->connection, winhttp_status, WINHTTP_CALLBACK_FLAG_SECURE_FAILURE, 0) == WINHTTP_INVALID_STATUS_CALLBACK) {
+ if (WinHttpSetStatusCallback(
+ t->connection,
+ winhttp_status,
+ WINHTTP_CALLBACK_FLAG_SECURE_FAILURE | WINHTTP_CALLBACK_FLAG_SEND_REQUEST,
+ 0
+ ) == WINHTTP_INVALID_STATUS_CALLBACK) {
git_error_set(GIT_ERROR_OS, "failed to set status callback");
goto on_error;
}
@@ -869,12 +881,12 @@ static int do_send_request(winhttp_stream *s, size_t len, bool chunked)
success = WinHttpSendRequest(s->request,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
- WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, 0);
+ WINHTTP_IGNORE_REQUEST_TOTAL_LENGTH, (DWORD_PTR)s);
} else {
success = WinHttpSendRequest(s->request,
WINHTTP_NO_ADDITIONAL_HEADERS, 0,
WINHTTP_NO_REQUEST_DATA, 0,
- (DWORD)len, 0);
+ (DWORD)len, (DWORD_PTR)s);
}
if (success || GetLastError() != (DWORD)SEC_E_BUFFER_TOO_SMALL)
@@ -911,7 +923,13 @@ static int send_request(winhttp_stream *s, size_t len, bool chunked)
}
}
- if (!request_failed || !cert_valid) {
+ /*
+ * Only check the certificate if we were able to reach the sending request phase, or
+ * received a secure failure error. Otherwise, the server certificate won't be available
+ * since the request wasn't able to complete (e.g. proxy auth required)
+ */
+ if (!cert_valid ||
+ (!request_failed && s->status_sending_request_reached)) {
git_error_clear();
if ((error = certificate_check(s, cert_valid)) < 0) {
if (!git_error_last())
diff --git a/tests/online/clone.c b/tests/online/clone.c
index c62baac..6de687b 100644
--- a/tests/online/clone.c
+++ b/tests/online/clone.c
@@ -869,6 +869,28 @@ void test_online_clone__proxy_credentials_in_environment(void)
git_buf_dispose(&url);
}
+void test_online_clone__proxy_credentials_in_url_https(void)
+{
+ git_buf url = GIT_BUF_INIT;
+
+ if (!_remote_proxy_host || !_remote_proxy_user || !_remote_proxy_pass)
+ cl_skip();
+
+ cl_git_pass(git_buf_printf(&url, "%s://%s:%s@%s/",
+ _remote_proxy_scheme ? _remote_proxy_scheme : "http",
+ _remote_proxy_user, _remote_proxy_pass, _remote_proxy_host));
+
+ g_options.fetch_opts.proxy_opts.type = GIT_PROXY_SPECIFIED;
+ g_options.fetch_opts.proxy_opts.url = url.ptr;
+ g_options.fetch_opts.proxy_opts.certificate_check = proxy_cert_cb;
+ g_options.fetch_opts.callbacks.certificate_check = ssl_cert;
+ called_proxy_creds = 0;
+ cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
+ cl_assert(called_proxy_creds == 0);
+
+ git_buf_dispose(&url);
+}
+
void test_online_clone__proxy_auto_not_detected(void)
{
g_options.fetch_opts.proxy_opts.type = GIT_PROXY_AUTO;