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.
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
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));
}