Commit 08545d366b24186631b08e3c232f35222d9de5ea

Carlos Martín Nieto 2014-08-30T00:40:37

winhttp: credential check on successful connect On successful connection, still ask the user whether they accept the server's certificate, indicating that WinHTTP would let it though.

diff --git a/src/transports/winhttp.c b/src/transports/winhttp.c
index 6523086..3ddf49d 100644
--- a/src/transports/winhttp.c
+++ b/src/transports/winhttp.c
@@ -16,6 +16,8 @@
 #include "remote.h"
 #include "repository.h"
 
+#include <wincrypt.h>
+#pragma comment(lib, "crypt32")
 #include <winhttp.h>
 #pragma comment(lib, "winhttp")
 
@@ -203,6 +205,31 @@ static int fallback_cred_acquire_cb(
 	return error;
 }
 
+static int certificate_check(winhttp_stream *s, int valid)
+{
+	int error;
+	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+	PCERT_CONTEXT cert_ctx;
+	DWORD cert_ctx_size = sizeof(cert_ctx);
+
+	if (t->owner->certificate_check_cb == NULL)
+		return 0;
+
+	if (!WinHttpQueryOption(s->request, WINHTTP_OPTION_SERVER_CERT_CONTEXT, &cert_ctx, &cert_ctx_size)) {
+		giterr_set(GITERR_OS, "failed to get server certificate");
+		return -1;
+	}
+
+	giterr_clear();
+	error = t->owner->certificate_check_cb(GIT_CERT_X509, cert_ctx->pbCertEncoded, cert_ctx->cbCertEncoded, valid, t->owner->cred_acquire_payload);
+	CertFreeCertificateContext(cert_ctx);
+
+	if (error < 0 && !giterr_last())
+		giterr_set(GITERR_NET, "user cancelled certificate check");
+
+	return error;
+}
+
 static int winhttp_stream_connect(winhttp_stream *s)
 {
 	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
@@ -384,6 +411,8 @@ static int winhttp_stream_connect(winhttp_stream *s)
 			goto on_error;
 	}
 
+	/* set up the certificate failure callback */
+
 	/* We've done everything up to calling WinHttpSendRequest. */
 
 	error = 0;
@@ -537,6 +566,7 @@ static int winhttp_stream_read(
 	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
 	DWORD dw_bytes_read;
 	char replay_count = 0;
+	int error;
 
 replay:
 	/* Enforce a reasonable cap on the number of replays */
@@ -566,6 +596,9 @@ replay:
 			s->sent_request = 1;
 		}
 
+		if ((error = certificate_check(s, 1)) < 0)
+			return error;
+
 		if (s->chunked) {
 			assert(s->verb == post_verb);
 
@@ -815,6 +848,7 @@ static int winhttp_stream_write_single(
 	winhttp_stream *s = (winhttp_stream *)stream;
 	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
 	DWORD bytes_written;
+	int error;
 
 	if (!s->request && winhttp_stream_connect(s) < 0)
 		return -1;
@@ -835,6 +869,9 @@ static int winhttp_stream_write_single(
 
 	s->sent_request = 1;
 
+	if ((error = certificate_check(s, 1)) < 0)
+		return error;
+
 	if (!WinHttpWriteData(s->request,
 			(LPCVOID)buffer,
 			(DWORD)len,
@@ -954,6 +991,7 @@ static int winhttp_stream_write_chunked(
 {
 	winhttp_stream *s = (winhttp_stream *)stream;
 	winhttp_subtransport *t = OWNING_SUBTRANSPORT(s);
+	int error;
 
 	if (!s->request && winhttp_stream_connect(s) < 0)
 		return -1;
@@ -978,6 +1016,9 @@ static int winhttp_stream_write_chunked(
 		s->sent_request = 1;
 	}
 
+	if ((error = certificate_check(s, 1)) < 0)
+		return error;
+
 	if (len > CACHED_POST_BODY_BUF_SIZE) {
 		/* Flush, if necessary */
 		if (s->chunk_buffer_len > 0) {
diff --git a/tests/online/clone.c b/tests/online/clone.c
index a880d47..cebe3b2 100644
--- a/tests/online/clone.c
+++ b/tests/online/clone.c
@@ -485,11 +485,13 @@ void test_online_clone__certificate_invalid(void)
 {
 	g_options.remote_callbacks.certificate_check = fail_certificate_check;
 
-	cl_git_fail_with(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options),
+	cl_git_fail_with(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options),
 		GIT_ECERTIFICATE);
 
+#ifdef GIT_SSH
 	cl_git_fail_with(git_clone(&g_repo, "ssh://github.com/libgit2/TestGitRepository", "./foo", &g_options),
 		GIT_ECERTIFICATE);
+#endif
 }
 
 static int succeed_certificate_check(git_cert_t type, void *data, size_t len, int valid, void *payload)
@@ -507,5 +509,5 @@ void test_online_clone__certificate_valid(void)
 {
 	g_options.remote_callbacks.certificate_check = succeed_certificate_check;
 
-	cl_git_pass(git_clone(&g_repo, "http://github.com/libgit2/TestGitRepository", "./foo", &g_options));
+	cl_git_pass(git_clone(&g_repo, "https://github.com/libgit2/TestGitRepository", "./foo", &g_options));
 }