From a5669298f9c87fed00235aefea02d8b84b455570 Mon Sep 17 00:00:00 2001 From: Michael Steinert Date: Tue, 4 Mar 2014 10:10:15 -0700 Subject: [openssl] Support wildcard hostname verification Most of this code comes from version Curl 7.35. --- .gitignore | 7 ++ Makefile.am | 12 ++- librabbitmq/CMakeLists.txt | 6 +- librabbitmq/amqp_hostcheck.c | 201 +++++++++++++++++++++++++++++++++++++++++++ librabbitmq/amqp_hostcheck.h | 36 ++++++++ librabbitmq/amqp_openssl.c | 9 +- tests/CMakeLists.txt | 5 ++ tests/test_hostcheck.c | 78 +++++++++++++++++ 8 files changed, 344 insertions(+), 10 deletions(-) create mode 100644 librabbitmq/amqp_hostcheck.c create mode 100644 librabbitmq/amqp_hostcheck.h create mode 100644 tests/test_hostcheck.c diff --git a/.gitignore b/.gitignore index 86595df..6bca1bb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ .libs /aclocal.m4 /autom4te.cache +/compile /config.guess /config.h /config.h.in @@ -21,10 +22,12 @@ /ltmain.sh /missing /stamp-h1 +/test-suite.log INSTALL Makefile Makefile.in examples/amqp_bind +examples/amqp_connect_timeout examples/amqp_consumer examples/amqp_exchange_declare examples/amqp_listen @@ -34,6 +37,7 @@ examples/amqp_rpc_sendstring_client examples/amqp_sendstring examples/amqp_unbind examples/amqps_bind +examples/amqps_connect_timeout examples/amqps_consumer examples/amqps_exchange_declare examples/amqps_listen @@ -43,6 +47,9 @@ examples/amqps_sendstring examples/amqps_unbind librabbitmq.pc test-driver +tests/*.log +tests/*.trs +tests/test_hostcheck tests/test_parse_url tests/test_tables tools/amqp-consume diff --git a/Makefile.am b/Makefile.am index 84bd21a..8cd767a 100644 --- a/Makefile.am +++ b/Makefile.am @@ -49,7 +49,10 @@ librabbitmq_librabbitmq_la_SOURCES += librabbitmq/amqp_gnutls.c endif if SSL_OPENSSL -librabbitmq_librabbitmq_la_SOURCES += librabbitmq/amqp_openssl.c +librabbitmq_librabbitmq_la_SOURCES += \ + librabbitmq/amqp_hostcheck.c \ + librabbitmq/amqp_hostcheck.h \ + librabbitmq/amqp_openssl.c endif if SSL_POLARSSL @@ -71,7 +74,8 @@ endif check_PROGRAMS = \ tests/test_tables \ - tests/test_parse_url + tests/test_parse_url \ + tests/test_hostcheck TESTS = $(check_PROGRAMS) @@ -81,6 +85,10 @@ tests_test_tables_LDADD = librabbitmq/librabbitmq.la tests_test_parse_url_SOURCES = tests/test_parse_url.c tests_test_parse_url_LDADD = librabbitmq/librabbitmq.la +tests_test_hostcheck_SOURCES = \ + tests/test_hostcheck.c \ + librabbitmq/amqp_hostcheck.c + noinst_LTLIBRARIES = if EXAMPLES diff --git a/librabbitmq/CMakeLists.txt b/librabbitmq/CMakeLists.txt index 4568781..d3623b3 100644 --- a/librabbitmq/CMakeLists.txt +++ b/librabbitmq/CMakeLists.txt @@ -81,7 +81,11 @@ if (ENABLE_SSL_SUPPORT) set(AMQP_SSL_SOCKET_H_PATH amqp_ssl_socket.h) if (SSL_ENGINE STREQUAL "OpenSSL") - set(AMQP_SSL_SRCS ${AMQP_SSL_SOCKET_H_PATH} amqp_openssl.c) + set(AMQP_SSL_SRCS ${AMQP_SSL_SOCKET_H_PATH} + amqp_openssl.c + amqp_hostcheck.c + amqp_hostcheck.h + ) include_directories(${OPENSSL_INCLUDE_DIR}) set(AMQP_SSL_LIBS ${OPENSSL_LIBRARIES}) diff --git a/librabbitmq/amqp_hostcheck.c b/librabbitmq/amqp_hostcheck.c new file mode 100644 index 0000000..8ad2cf7 --- /dev/null +++ b/librabbitmq/amqp_hostcheck.c @@ -0,0 +1,201 @@ +/* vim:set ft=c ts=2 sw=2 sts=2 et cindent: */ +/* + * Copyright 1996-2014 Daniel Stenberg . + * Copyright 2014 Michael Steinert + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + */ + +#include "amqp_private.h" + +#include + +/* Portable, consistent toupper (remember EBCDIC). Do not use toupper() + * because its behavior is altered by the current locale. + */ + +static char +amqp_raw_toupper(char in) +{ + switch (in) { + case 'a': + return 'A'; + case 'b': + return 'B'; + case 'c': + return 'C'; + case 'd': + return 'D'; + case 'e': + return 'E'; + case 'f': + return 'F'; + case 'g': + return 'G'; + case 'h': + return 'H'; + case 'i': + return 'I'; + case 'j': + return 'J'; + case 'k': + return 'K'; + case 'l': + return 'L'; + case 'm': + return 'M'; + case 'n': + return 'N'; + case 'o': + return 'O'; + case 'p': + return 'P'; + case 'q': + return 'Q'; + case 'r': + return 'R'; + case 's': + return 'S'; + case 't': + return 'T'; + case 'u': + return 'U'; + case 'v': + return 'V'; + case 'w': + return 'W'; + case 'x': + return 'X'; + case 'y': + return 'Y'; + case 'z': + return 'Z'; + } + return in; +} + +/* + * amqp_raw_equal() is for doing "raw" case insensitive strings. This is meant + * to be locale independent and only compare strings we know are safe for + * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for + * some further explanation to why this function is necessary. + * + * The function is capable of comparing a-z case insensitively even for + * non-ascii. + */ + +static int +amqp_raw_equal(const char *first, const char *second) +{ + while (*first && *second) { + if (amqp_raw_toupper(*first) != amqp_raw_toupper(*second)) { + /* get out of the loop as soon as they don't match */ + break; + } + first++; + second++; + } + /* we do the comparison here (possibly again), just to make sure that if + * the loop above is skipped because one of the strings reached zero, we + * must not return this as a successful match + */ + return (amqp_raw_toupper(*first) == amqp_raw_toupper(*second)); +} + +static int +amqp_raw_nequal(const char *first, const char *second, size_t max) +{ + while (*first && *second && max) { + if (amqp_raw_toupper(*first) != amqp_raw_toupper(*second)) { + break; + } + max--; + first++; + second++; + } + if (0 == max) { + return 1; /* they are equal this far */ + } + return amqp_raw_toupper(*first) == amqp_raw_toupper(*second); +} + +/* + * Match a hostname against a wildcard pattern. + * E.g. + * "foo.host.com" matches "*.host.com". + * + * We use the matching rule described in RFC6125, section 6.4.3. + * http://tools.ietf.org/html/rfc6125#section-6.4.3 + */ + +static int +amqp_hostmatch(const char *hostname, const char *pattern) +{ + const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; + int wildcard_enabled; + size_t prefixlen, suffixlen; + pattern_wildcard = strchr(pattern, '*'); + if (pattern_wildcard == NULL) { + return amqp_raw_equal(pattern, hostname) ? 1 : 0; + } + /* We require at least 2 dots in pattern to avoid too wide wildcard match. */ + wildcard_enabled = 1; + pattern_label_end = strchr(pattern, '.'); + if (pattern_label_end == NULL || + strchr(pattern_label_end + 1, '.') == NULL || + pattern_wildcard > pattern_label_end || + amqp_raw_nequal(pattern, "xn--", 4)) { + wildcard_enabled = 0; + } + if (!wildcard_enabled) { + return amqp_raw_equal(pattern, hostname) ? 1 : 0; + } + hostname_label_end = strchr(hostname, '.'); + if (hostname_label_end == NULL || + !amqp_raw_equal(pattern_label_end, hostname_label_end)) { + return 0; + } + /* The wildcard must match at least one character, so the left-most + * label of the hostname is at least as large as the left-most label + * of the pattern. + */ + if (hostname_label_end - hostname < pattern_label_end - pattern) { + return 0; + } + prefixlen = pattern_wildcard - pattern; + suffixlen = pattern_label_end - (pattern_wildcard + 1); + return amqp_raw_nequal(pattern, hostname, prefixlen) && + amqp_raw_nequal(pattern_wildcard + 1, hostname_label_end - suffixlen, + suffixlen) ? 1 : 0; +} + +int +amqp_hostcheck(const char *match_pattern, const char *hostname) +{ + /* sanity check */ + if (!match_pattern || !*match_pattern || !hostname || !*hostname) { + return 0; + } + /* trivial case */ + if (amqp_raw_equal(hostname, match_pattern)) { + return 1; + } + return amqp_hostmatch(hostname, match_pattern); +} diff --git a/librabbitmq/amqp_hostcheck.h b/librabbitmq/amqp_hostcheck.h new file mode 100644 index 0000000..2832933 --- /dev/null +++ b/librabbitmq/amqp_hostcheck.h @@ -0,0 +1,36 @@ +/* vim:set ft=c ts=2 sw=2 sts=2 et cindent: */ +#ifndef librabbitmq_amqp_hostcheck_h +#define librabbitmq_amqp_hostcheck_h + +/* + * Copyright 1996-2014 Daniel Stenberg . + * Copyright 2014 Michael Steinert + * + * All rights reserved. + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, + * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE + * USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * Except as contained in this notice, the name of a copyright holder shall + * not be used in advertising or otherwise to promote the sale, use or other + * dealings in this Software without prior written authorization of the + * copyright holder. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +int +amqp_hostcheck(const char *match_pattern, const char *hostname); + +#endif diff --git a/librabbitmq/amqp_openssl.c b/librabbitmq/amqp_openssl.c index 32595ae..ab8a94e 100644 --- a/librabbitmq/amqp_openssl.c +++ b/librabbitmq/amqp_openssl.c @@ -31,6 +31,7 @@ #include "amqp_ssl_socket.h" #include "amqp_socket.h" +#include "amqp_hostcheck.h" #include "amqp_private.h" #include "threads.h" @@ -214,15 +215,9 @@ amqp_ssl_socket_verify_hostname(void *base, const char *host) goto error; } } -#ifdef _MSC_VER -#define strcasecmp _stricmp -#endif - if (strcasecmp(host, (char *)utf8_value)) { + if (!amqp_hostcheck((char *)utf8_value, host)) { goto error; } -#ifdef _MSC_VER -#undef strcasecmp -#endif exit: OPENSSL_free(utf8_value); return status; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 7ebf545..7554794 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -16,3 +16,8 @@ add_executable(test_tables test_tables.c) target_link_libraries(test_tables ${RMQ_LIBRARY_TARGET}) add_test(tables test_tables) configure_file(test_tables.expected ${CMAKE_CURRENT_BINARY_DIR}/tests/test_tables.expected COPY_ONLY) + +add_executable(test_hostcheck + test_hostcheck.c + ../librabbitmq/amqp_hostcheck.c) +add_test(hostcheck test_hostcheck) diff --git a/tests/test_hostcheck.c b/tests/test_hostcheck.c new file mode 100644 index 0000000..8d562e6 --- /dev/null +++ b/tests/test_hostcheck.c @@ -0,0 +1,78 @@ +/* vim:set ft=c ts=2 sw=2 sts=2 et cindent: */ +/* + * Copyright 2014 Michael Steinert + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "amqp_hostcheck.h" + +#include +#include + +static void +hostcheck_success(const char *match_pattern, const char *url) +{ + int ok; + + ok = amqp_hostcheck(match_pattern, url); + if (! ok) { + fprintf(stderr, "Expected hostname check to pass, but didn't: %s (%s)\n", + url, match_pattern); + abort(); + } + + fprintf(stdout, "ok: [success] %s, %s\n", url, match_pattern); +} + +static void +hostcheck_fail(const char *match_pattern, const char *url) +{ + int ok; + + ok = amqp_hostcheck(match_pattern, url); + if (ok) { + fprintf(stderr, "Expected hostname check to fail, but didn't: %s (%s)\n", + url, match_pattern); + abort(); + } + + fprintf(stdout, "ok: [fail] %s, %s\n", url, match_pattern); +} + +int +main(void) +{ + hostcheck_success("www.rabbitmq.com", "www.rabbitmq.com"); + hostcheck_success("www.rabbitmq.com", "wWw.RaBbItMq.CoM"); + hostcheck_success("*.rabbitmq.com", "wWw.RaBbItMq.CoM"); + hostcheck_fail("rabbitmq.com", "www.rabbitmq.com"); + hostcheck_success("*.rabbitmq.com", "www.rabbitmq.com"); + hostcheck_fail("*.com", "www.rabbitmq.com"); + hostcheck_fail("*.rabbitmq.com", "long.url.rabbitmq.com"); + hostcheck_success("*.url.rabbitmq.com", "long.url.rabbitmq.com"); + + return 0; +} -- cgit v1.2.1