summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorEdward Thomson <ethomson@edwardthomson.com>2023-05-12 20:48:30 +0100
committerEdward Thomson <ethomson@edwardthomson.com>2023-05-13 16:42:04 +0100
commit6e4bbf222d8c4babaff90aef40615546c8bc9cde (patch)
treeadd632fcb19f7266e22044022840d9500b5b3c84
parentdbe343b6e3e957b5cffbd04832c6e7364b496ae7 (diff)
downloadlibgit2-6e4bbf222d8c4babaff90aef40615546c8bc9cde.tar.gz
net: move rfc2818 hostname / wildcard matching to util
-rw-r--r--src/libgit2/netops.c39
-rw-r--r--src/libgit2/netops.h13
-rw-r--r--src/libgit2/streams/openssl.c19
-rw-r--r--src/util/net.c44
-rw-r--r--src/util/net.h17
-rw-r--r--tests/libgit2/network/matchhost.c13
-rw-r--r--tests/util/hostname.c13
7 files changed, 80 insertions, 78 deletions
diff --git a/src/libgit2/netops.c b/src/libgit2/netops.c
index 00640c600..5cae374ad 100644
--- a/src/libgit2/netops.c
+++ b/src/libgit2/netops.c
@@ -83,42 +83,3 @@ void gitno_consume_n(gitno_buffer *buf, size_t cons)
memset(buf->data + cons, 0x0, buf->len - buf->offset);
buf->offset -= cons;
}
-
-/* Match host names according to RFC 2818 rules */
-int gitno__match_host(const char *pattern, const char *host)
-{
- for (;;) {
- char c = git__tolower(*pattern++);
-
- if (c == '\0')
- return *host ? -1 : 0;
-
- if (c == '*') {
- c = *pattern;
- /* '*' at the end matches everything left */
- if (c == '\0')
- return 0;
-
- /*
- * We've found a pattern, so move towards the next matching
- * char. The '.' is handled specially because wildcards aren't
- * allowed to cross subdomains.
- */
-
- while(*host) {
- char h = git__tolower(*host);
- if (c == h)
- return gitno__match_host(pattern, host++);
- if (h == '.')
- return gitno__match_host(pattern, host);
- host++;
- }
- return -1;
- }
-
- if (c != git__tolower(*host++))
- return -1;
- }
-
- return -1;
-}
diff --git a/src/libgit2/netops.h b/src/libgit2/netops.h
index 56f968534..a3f4a0f95 100644
--- a/src/libgit2/netops.h
+++ b/src/libgit2/netops.h
@@ -45,19 +45,6 @@ enum {
GITNO_CONNECT_SSL = 1
};
-/**
- * Check if the name in a cert matches the wanted hostname
- *
- * Check if a pattern from a certificate matches the hostname we
- * wanted to connect to according to RFC2818 rules (which specifies
- * HTTP over TLS). Mainly, an asterisk matches anything, but is
- * limited to a single url component.
- *
- * Note that this does not set an error message. It expects the user
- * to provide the message for the user.
- */
-int gitno__match_host(const char *pattern, const char *host);
-
void gitno_buffer_setup_fromstream(git_stream *st, gitno_buffer *buf, char *data, size_t len);
void gitno_buffer_setup_callback(gitno_buffer *buf, char *data, size_t len, int (*recv)(gitno_buffer *buf), void *cb_data);
int gitno_recv(gitno_buffer *buf);
diff --git a/src/libgit2/streams/openssl.c b/src/libgit2/streams/openssl.c
index 5e0e2c939..58b2d1b23 100644
--- a/src/libgit2/streams/openssl.c
+++ b/src/libgit2/streams/openssl.c
@@ -18,6 +18,7 @@
#include "settings.h"
#include "posix.h"
#include "stream.h"
+#include "net.h"
#include "streams/socket.h"
#include "netops.h"
#include "git2/transport.h"
@@ -357,15 +358,10 @@ static int ssl_teardown(SSL *ssl)
return ret;
}
-static int check_host_name(const char *name, const char *host)
+static bool check_host_name(const char *host, const char *name)
{
- if (!strcasecmp(name, host))
- return 0;
-
- if (gitno__match_host(name, host) < 0)
- return -1;
-
- return 0;
+ return !strcasecmp(host, name) ||
+ git_net_hostname_matches_cert(host, name);
}
static int verify_server_cert(SSL *ssl, const char *host)
@@ -425,10 +421,7 @@ static int verify_server_cert(SSL *ssl, const char *host)
if (memchr(name, '\0', namelen))
continue;
- if (check_host_name(name, host) < 0)
- matched = 0;
- else
- matched = 1;
+ matched = !!check_host_name(host, name);
} else if (type == GEN_IPADD) {
/* Here name isn't so much a name but a binary representation of the IP */
matched = addr && !!memcmp(name, addr, namelen);
@@ -481,7 +474,7 @@ static int verify_server_cert(SSL *ssl, const char *host)
goto cert_fail_name;
}
- if (check_host_name((char *)peer_cn, host) < 0)
+ if (!check_host_name(host, (char *)peer_cn))
goto cert_fail_name;
goto cleanup;
diff --git a/src/util/net.c b/src/util/net.c
index ac7befe07..dd8a1ba46 100644
--- a/src/util/net.c
+++ b/src/util/net.c
@@ -19,6 +19,50 @@
#define DEFAULT_PORT_GIT "9418"
#define DEFAULT_PORT_SSH "22"
+bool git_net_hostname_matches_cert(
+ const char *hostname,
+ const char *pattern)
+{
+ for (;;) {
+ char c = git__tolower(*pattern++);
+
+ if (c == '\0')
+ return *hostname ? false : true;
+
+ if (c == '*') {
+ c = *pattern;
+
+ /* '*' at the end matches everything left */
+ if (c == '\0')
+ return true;
+
+ /*
+ * We've found a pattern, so move towards the
+ * next matching char. The '.' is handled
+ * specially because wildcards aren't allowed
+ * to cross subdomains.
+ */
+ while(*hostname) {
+ char h = git__tolower(*hostname);
+
+ if (h == c)
+ return git_net_hostname_matches_cert(hostname++, pattern);
+ else if (h == '.')
+ return git_net_hostname_matches_cert(hostname, pattern);
+
+ hostname++;
+ }
+
+ return false;
+ }
+
+ if (c != git__tolower(*hostname++))
+ return false;
+ }
+
+ return false;
+}
+
bool git_net_str_is_url(const char *str)
{
const char *c;
diff --git a/src/util/net.h b/src/util/net.h
index 17f0bc4f0..c9a84cb6c 100644
--- a/src/util/net.h
+++ b/src/util/net.h
@@ -9,6 +9,23 @@
#include "git2_util.h"
+/*
+ * Hostname handling
+ */
+
+/*
+ * See if a given hostname matches a certificate name pattern, according
+ * to RFC2818 rules (which specifies HTTP over TLS). Mainly, an asterisk
+ * matches anything, but is limited to a single url component.
+ */
+extern bool git_net_hostname_matches_cert(
+ const char *hostname,
+ const char *pattern);
+
+/*
+ * URL handling
+ */
+
typedef struct git_net_url {
char *scheme;
char *host;
diff --git a/tests/libgit2/network/matchhost.c b/tests/libgit2/network/matchhost.c
deleted file mode 100644
index 3100dc21d..000000000
--- a/tests/libgit2/network/matchhost.c
+++ /dev/null
@@ -1,13 +0,0 @@
-#include "clar_libgit2.h"
-#include "netops.h"
-
-void test_network_matchhost__match(void)
-{
- cl_git_pass(gitno__match_host("*.example.org", "www.example.org"));
- cl_git_pass(gitno__match_host("*.foo.example.org", "www.foo.example.org"));
- cl_git_fail(gitno__match_host("*.foo.example.org", "foo.example.org"));
- cl_git_fail(gitno__match_host("*.foo.example.org", "www.example.org"));
- cl_git_fail(gitno__match_host("*.example.org", "example.org"));
- cl_git_fail(gitno__match_host("*.example.org", "www.foo.example.org"));
- cl_git_fail(gitno__match_host("*.example.org", "blah.www.www.example.org"));
-}
diff --git a/tests/util/hostname.c b/tests/util/hostname.c
new file mode 100644
index 000000000..5d8bfe2ac
--- /dev/null
+++ b/tests/util/hostname.c
@@ -0,0 +1,13 @@
+#include "clar_libgit2.h"
+#include "net.h"
+
+void test_hostname__matches_cert(void)
+{
+ cl_assert_equal_b(true, git_net_hostname_matches_cert("www.example.org", "*.example.org"));
+ cl_assert_equal_b(true, git_net_hostname_matches_cert("www.foo.example.org", "*.foo.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("foo.example.org", "*.foo.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("www.example.org", "*.foo.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("example.org", "*.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("www.foo.example.org", "*.example.org"));
+ cl_assert_equal_b(false, git_net_hostname_matches_cert("blah.www.www.example.org", "*.example.org"));
+}