Index: channels/chan_sip.c =================================================================== --- channels/chan_sip.c (revision 391547) +++ channels/chan_sip.c (working copy) @@ -2334,6 +2334,8 @@ .worker_fn = sip_tcp_worker_fn, }; +static void sip_tcptls_pressl(void* data); + /*! \brief The TCP/TLS server definition */ static struct ast_tcptls_session_args sip_tls_desc = { .accept_fd = -1, @@ -2343,6 +2345,7 @@ .name = "SIP TLS server", .accept_fn = ast_tcptls_server_root, .worker_fn = sip_tcp_worker_fn, + .pressl_fn = sip_tcptls_pressl, }; /*! \brief Append to SIP dialog history @@ -30944,6 +30947,77 @@ } while(0)); } +/*! + * \brief Find peer name and set hostname for SSL/TLS + */ +static void sip_tcptls_pressl(void* data) +{ + struct sip_peer *peer; + struct ao2_iterator *it_peers; + int k; + int total_peers = 0; + struct sip_peer **peerarray; + struct ast_tcptls_session_instance *tcptls_session = data; + char *tmp_hostname = NULL; + + ast_debug(3, "Remote address is '%s'\n", + ast_sockaddr_stringify_host_remote(&tcptls_session->remote_address)); + /* + * TODO: for some reason next line does not work as expected. Here we need "peers" list for unregistered peers + * TODO: peer = sip_find_peer_full(NULL, &tcptls_session->remote_address, NULL, FALSE, FINDALLDEVICES, FALSE, 0); + */ + ao2_lock(peers); + if (!(it_peers = ao2_callback(peers, OBJ_MULTIPLE, NULL, NULL))) { + ast_log(AST_LOG_ERROR, "Unable to create iterator for peers container for sip_tcptls_pressl\n"); + ao2_unlock(peers); + return; + } + if (!(peerarray = ast_calloc(sizeof(struct sip_peer *), ao2_container_count(peers)))) { + ast_log(AST_LOG_ERROR, "Unable to allocate peer array for sip_tcptls_pressl\n"); + ao2_iterator_destroy(it_peers); + ao2_unlock(peers); + return; + } + ao2_unlock(peers); + + while ((peer = ao2_t_iterator_next(it_peers, "iterate thru peers table"))) { + ao2_lock(peer); + + if (!(peer->type & SIP_TYPE_PEER)) { + ao2_unlock(peer); + sip_unref_peer(peer, "unref peer because it's actually a user"); + continue; + } + + peerarray[total_peers++] = peer; + ao2_unlock(peer); + } + ao2_iterator_destroy(it_peers); + + for (k = 0; k < total_peers; k++) { + peer = peerarray[k]; + + ao2_lock(peer); + + ast_debug(3, "Testing '%s'\n", ast_sockaddr_stringify_host_remote(&peer->addr)); + if (0 == ast_sockaddr_cmp_addr(&tcptls_session->remote_address, &peer->addr)) { + ast_debug(3, "match found! (%s)\n", peer->tohost); + tmp_hostname = ast_strdup(peer->tohost); + } + + ao2_unlock(peer); + peer = peerarray[k] = sip_unref_peer(peer, "toss iterator peer ptr"); + } + + if (NULL == tmp_hostname) + ast_log(LOG_WARNING, "Peer is not found\n"); + else { + ast_verb(2, "tohost is '%s'\n", tmp_hostname); + strcpy(tcptls_session->parent->hostname, tmp_hostname); + ast_free(tmp_hostname); + } +} + /*! \brief Re-read SIP.conf config file \note This function reloads all config data, except for active peers (with registrations). They will only Index: include/asterisk/tcptls-wildcard.h =================================================================== --- include/asterisk/tcptls-wildcard.h (revision 0) +++ include/asterisk/tcptls-wildcard.h (working copy) @@ -0,0 +1,52 @@ +/* + * ************************************************************************* + * This file is produced by Serhij Stasyuk + * mostly as diff between OpenSSL 1.0.1c and 1.0.2 git + * (crypto/x509v3/v3_util.c and crypto/x509v3/x509v3.h) for use in Asterisk + * SIP TLS session initiation hostname check (with wildcard certificates). + * + * After Asterisk OpenSSL requirement will be >= 1.0.2 this file can be + * safely removed. + * + * For appropriate copyright and license information please see Asterisk and + * OpenSSL LICENSE files. + * ************************************************************************* + */ + +#ifndef _TCPTLS_WILDCARD_ +#define _TCPTLS_WILDCARD_ + +#if defined(__cplusplus) || defined(c_plusplus) +extern "C" { +#endif + +#if (OPENSSL_VERSION_NUMBER < 0x1000200fL) + +/* // size_t, memchr, memcmp */ +#include +/* // ASN1_STRING */ +#include +/* // X509v3 related */ +#include + +/* // In new openssl/x509v3.h */ +/* Always check subject name for host match even if subject alt names present */ +#define X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT 0x1 +/* Disable wild-card matching for dnsName fields and common name. */ +#define X509_CHECK_FLAG_NO_WILDCARDS 0x2 + +int X509_check_host(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags); +int X509_check_email(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags); +int X509_check_ip(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags); +int X509_check_ip_asc(X509 *x, const char *ipasc, unsigned int flags); + +#endif /* (OPENSSL_VERSION_NUMBER < 0x1000200fL) */ + +#if defined(__cplusplus) || defined(c_plusplus) +} +#endif + +#endif /* _TCPTLS_WILDCARD_ */ Index: include/asterisk/tcptls.h =================================================================== --- include/asterisk/tcptls.h (revision 391547) +++ include/asterisk/tcptls.h (working copy) @@ -65,6 +65,9 @@ #ifdef DO_SSL #include #include +#if (OPENSSL_VERSION_NUMBER < 0x1000200fL) +#include "asterisk/tcptls-wildcard.h" +#endif /* (OPENSSL_VERSION_NUMBER < 0x1000200fL) */ #else /* declare dummy types so we can define a pointer to them */ typedef struct {} SSL; @@ -89,6 +92,8 @@ AST_SSL_TLSV1_CLIENT = (1 << 5) }; +#define SSL_VERIFY_DEPTH 10 + struct ast_tls_config { int enabled; char *certfile; @@ -141,6 +146,7 @@ void *(*accept_fn)(void *); /*!< the function in charge of doing the accept */ void (*periodic_fn)(void *);/*!< something we may want to run before after select on the accept socket */ void *(*worker_fn)(void *); /*!< the function in charge of doing the actual work */ + void (*pressl_fn)(void *);/*!< something we may want to run before SSL_new on the accept socket */ const char *name; }; Index: main/tcptls-wildcard.c =================================================================== --- main/tcptls-wildcard.c (revision 0) +++ main/tcptls-wildcard.c (working copy) @@ -0,0 +1,319 @@ +/* + * ************************************************************************* + * This file is produced by Serhij Stasyuk + * mostly as diff between OpenSSL 1.0.1c and 1.0.2 git + * (crypto/x509v3/v3_util.c and crypto/x509v3/x509v3.h) for use in Asterisk + * SIP TLS session initiation hostname check (with wildcard certificates). + * + * After Asterisk OpenSSL requirement will be >= 1.0.2 this file can be + * safely removed. + * + * For appropriate copyright and license information please see Asterisk and + * OpenSSL LICENSE files. + * ************************************************************************* + */ + +#if (OPENSSL_VERSION_NUMBER < 0x1000200fL) + +#include "asterisk/tcptls-wildcard.h" + +typedef int (*equal_fn)(const unsigned char *pattern, size_t pattern_len, + const unsigned char *subject, size_t subject_len); + +/* Compare while ASCII ignoring case. */ +static int equal_nocase(const unsigned char *pattern, size_t pattern_len, + const unsigned char *subject, size_t subject_len) + { + if (pattern_len != subject_len) + return 0; + while (pattern_len) + { + unsigned char l = *pattern; + unsigned char r = *subject; + /* The pattern must not contain NUL characters. */ + if (l == 0) + return 0; + if (l != r) + { + if ('A' <= l && l <= 'Z') + l = (l - 'A') + 'a'; + if ('A' <= r && r <= 'Z') + r = (r - 'A') + 'a'; + if (l != r) + return 0; + } + ++pattern; + ++subject; + --pattern_len; + } + return 1; + } + +/* Compare using memcmp. */ +static int equal_case(const unsigned char *pattern, size_t pattern_len, + const unsigned char *subject, size_t subject_len) +{ + /* The pattern must not contain NUL characters. */ + if (memchr(pattern, '\0', pattern_len) != NULL) + return 0; + if (pattern_len != subject_len) + return 0; + return !memcmp(pattern, subject, pattern_len); +} + +/* RFC 5280, section 7.5, requires that only the domain is compared in + a case-insensitive manner. */ +static int equal_email(const unsigned char *a, size_t a_len, + const unsigned char *b, size_t b_len) + { + size_t i = a_len; + if (a_len != b_len) + return 0; + /* We search backwards for the '@' character, so that we do + not have to deal with quoted local-parts. The domain part + is compared in a case-insensitive manner. */ + while (i > 0) + { + --i; + if (a[i] == '@' || b[i] == '@') + { + if (!equal_nocase(a + i, a_len - i, + b + i, a_len - i)) + return 0; + break; + } + } + if (i == 0) + i = a_len; + return equal_case(a, i, b, i); + } + +/* Compare the prefix and suffix with the subject, and check that the + characters in-between are valid. */ +static int wildcard_match(const unsigned char *prefix, size_t prefix_len, + const unsigned char *suffix, size_t suffix_len, + const unsigned char *subject, size_t subject_len) + { + const unsigned char *wildcard_start; + const unsigned char *wildcard_end; + const unsigned char *p; + if (subject_len < prefix_len + suffix_len) + return 0; + if (!equal_nocase(prefix, prefix_len, subject, prefix_len)) + return 0; + wildcard_start = subject + prefix_len; + wildcard_end = subject + (subject_len - suffix_len); + if (!equal_nocase(wildcard_end, suffix_len, suffix, suffix_len)) + return 0; + /* The wildcard must match at least one character. */ + if (wildcard_start == wildcard_end) + return 0; + /* Check that the part matched by the wildcard contains only + permitted characters and only matches a single label. */ + for (p = wildcard_start; p != wildcard_end; ++p) + if (!(('0' <= *p && *p <= '9') || + ('A' <= *p && *p <= 'Z') || + ('a' <= *p && *p <= 'z') || + *p == '-')) + return 0; + return 1; + } + +/* Checks if the memory region consistens of [0-9A-Za-z.-]. */ +static int valid_domain_characters(const unsigned char *p, size_t len) + { + while (len) + { + if (!(('0' <= *p && *p <= '9') || + ('A' <= *p && *p <= 'Z') || + ('a' <= *p && *p <= 'z') || + *p == '-' || *p == '.')) + return 0; + ++p; + --len; + } + return 1; + } + +/* Find the '*' in a wildcard pattern. If no such character is found + or the pattern is otherwise invalid, returns NULL. */ +static const unsigned char *wildcard_find_star(const unsigned char *pattern, + size_t pattern_len) + { + const unsigned char *star = memchr(pattern, '*', pattern_len); + size_t dot_count = 0; + const unsigned char *suffix_start; + size_t suffix_length; + if (star == NULL) + return NULL; + suffix_start = star + 1; + suffix_length = (pattern + pattern_len) - (star + 1); + if (!(valid_domain_characters(pattern, star - pattern) && + valid_domain_characters(suffix_start, suffix_length))) + return NULL; + /* Check that the suffix matches at least two labels. */ + while (suffix_length) + { + if (*suffix_start == '.') + ++dot_count; + ++suffix_start; + --suffix_length; + } + if (dot_count < 2) + return NULL; + return star; + } + +/* Compare using wildcards. */ +static int equal_wildcard(const unsigned char *pattern, size_t pattern_len, + const unsigned char *subject, size_t subject_len) + { + const unsigned char *star = wildcard_find_star(pattern, pattern_len); + if (star == NULL) + return equal_nocase(pattern, pattern_len, + subject, subject_len); + return wildcard_match(pattern, star - pattern, + star + 1, (pattern + pattern_len) - star - 1, + subject, subject_len); + } + +/* Compare an ASN1_STRING to a supplied string. If they match + * return 1. If cmp_type > 0 only compare if string matches the + * type, otherwise convert it to UTF8. + */ + +static int do_check_string(ASN1_STRING *a, int cmp_type, equal_fn equal, + const unsigned char *b, size_t blen) + { + if (!a->data || !a->length) + return 0; + if (cmp_type > 0) + { + if (cmp_type != a->type) + return 0; + if (cmp_type == V_ASN1_IA5STRING) + return equal(a->data, a->length, b, blen); + if (a->length == (int)blen && !memcmp(a->data, b, blen)) + return 1; + else + return 0; + } + else + { + int astrlen, rv; + unsigned char *astr; + astrlen = ASN1_STRING_to_UTF8(&astr, a); + if (astrlen < 0) + return -1; + rv = equal(astr, astrlen, b, blen); + OPENSSL_free(astr); + return rv; + } + } + +static int do_x509_check(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags, int check_type) + { + GENERAL_NAMES *gens = NULL; + X509_NAME *name = NULL; + int i; + int cnid; + int alt_type; + equal_fn equal; + if (check_type == GEN_EMAIL) + { + cnid = NID_pkcs9_emailAddress; + alt_type = V_ASN1_IA5STRING; + equal = equal_email; + } + else if (check_type == GEN_DNS) + { + cnid = NID_commonName; + alt_type = V_ASN1_IA5STRING; + if (flags & X509_CHECK_FLAG_NO_WILDCARDS) + equal = equal_nocase; + else + equal = equal_wildcard; + } + else + { + cnid = 0; + alt_type = V_ASN1_OCTET_STRING; + equal = equal_case; + } + + if (chklen == 0) + chklen = strlen((const char *)chk); + + gens = X509_get_ext_d2i(x, NID_subject_alt_name, NULL, NULL); + if (gens) + { + int rv = 0; + for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) + { + GENERAL_NAME *gen; + ASN1_STRING *cstr; + gen = sk_GENERAL_NAME_value(gens, i); + if(gen->type != check_type) + continue; + if (check_type == GEN_EMAIL) + cstr = gen->d.rfc822Name; + else if (check_type == GEN_DNS) + cstr = gen->d.dNSName; + else + cstr = gen->d.iPAddress; + if (do_check_string(cstr, alt_type, equal, chk, chklen)) + { + rv = 1; + break; + } + } + GENERAL_NAMES_free(gens); + if (rv) + return 1; + if (!(flags & X509_CHECK_FLAG_ALWAYS_CHECK_SUBJECT) || !cnid) + return 0; + } + i = -1; + name = X509_get_subject_name(x); + while((i = X509_NAME_get_index_by_NID(name, cnid, i)) >= 0) + { + X509_NAME_ENTRY *ne; + ASN1_STRING *str; + ne = X509_NAME_get_entry(name, i); + str = X509_NAME_ENTRY_get_data(ne); + if (do_check_string(str, -1, equal, chk, chklen)) + return 1; + } + return 0; + } + +int X509_check_host(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags) + { + return do_x509_check(x, chk, chklen, flags, GEN_DNS); + } + +int X509_check_email(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags) + { + return do_x509_check(x, chk, chklen, flags, GEN_EMAIL); + } + +int X509_check_ip(X509 *x, const unsigned char *chk, size_t chklen, + unsigned int flags) + { + return do_x509_check(x, chk, chklen, flags, GEN_IPADD); + } + +int X509_check_ip_asc(X509 *x, const char *ipasc, unsigned int flags) + { + unsigned char ipout[16]; + int iplen; + iplen = a2i_ipadd(ipout, ipasc); + if (iplen == 0) + return -2; + return do_x509_check(x, ipout, (size_t)iplen, flags, GEN_IPADD); + } + +#endif Index: main/tcptls.c =================================================================== --- main/tcptls.c (revision 391547) +++ main/tcptls.c (working copy) @@ -175,84 +175,79 @@ } } #ifdef DO_SSL - else if ( (tcptls_session->ssl = SSL_new(tcptls_session->parent->tls_cfg->ssl_ctx)) ) { - SSL_set_fd(tcptls_session->ssl, tcptls_session->fd); - if ((ret = ssl_setup(tcptls_session->ssl)) <= 0) { - ast_verb(2, "Problem setting up ssl connection: %s\n", ERR_error_string(ERR_get_error(), err)); - } else { + else { + ast_debug(3, "calling pressl_fn (%p)\n", tcptls_session->parent->pressl_fn); + if (NULL != tcptls_session->parent->pressl_fn) { + tcptls_session->parent->pressl_fn(tcptls_session); + } + + if ( (tcptls_session->ssl = SSL_new(tcptls_session->parent->tls_cfg->ssl_ctx)) ) { + SSL_set_fd(tcptls_session->ssl, tcptls_session->fd); + if ((ret = ssl_setup(tcptls_session->ssl)) <= 0) { + ast_verb(2, "Problem setting up ssl connection: %s\n", ERR_error_string(ERR_get_error(), err)); + } else { #if defined(HAVE_FUNOPEN) /* the BSD interface */ - tcptls_session->f = funopen(tcptls_session->ssl, ssl_read, ssl_write, NULL, ssl_close); + tcptls_session->f = funopen(tcptls_session->ssl, ssl_read, ssl_write, NULL, ssl_close); #elif defined(HAVE_FOPENCOOKIE) /* the glibc/linux interface */ - static const cookie_io_functions_t cookie_funcs = { - ssl_read, ssl_write, NULL, ssl_close - }; - tcptls_session->f = fopencookie(tcptls_session->ssl, "w+", cookie_funcs); + static const cookie_io_functions_t cookie_funcs = { + ssl_read, ssl_write, NULL, ssl_close + }; + tcptls_session->f = fopencookie(tcptls_session->ssl, "w+", cookie_funcs); #else - /* could add other methods here */ - ast_debug(2, "no tcptls_session->f methods attempted!\n"); + /* could add other methods here */ + ast_debug(2, "no tcptls_session->f methods attempted!\n"); #endif - if ((tcptls_session->client && !ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_DONT_VERIFY_SERVER)) - || (!tcptls_session->client && ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_VERIFY_CLIENT))) { - X509 *peer; - long res; - peer = SSL_get_peer_certificate(tcptls_session->ssl); - if (!peer) { - ast_log(LOG_ERROR, "No peer SSL certificate to verify\n"); - ast_tcptls_close_session_file(tcptls_session); - ao2_ref(tcptls_session, -1); - return NULL; - } + if ((tcptls_session->client && !ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_DONT_VERIFY_SERVER)) + || (!tcptls_session->client && ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_VERIFY_CLIENT))) { + X509 *peer; + long res; + peer = SSL_get_peer_certificate(tcptls_session->ssl); + if (!peer) { + ast_log(LOG_ERROR, "No peer SSL certificate to verify\n"); + ast_tcptls_close_session_file(tcptls_session); + ao2_ref(tcptls_session, -1); + return NULL; + } - res = SSL_get_verify_result(tcptls_session->ssl); - if (res != X509_V_OK) { - ast_log(LOG_ERROR, "Certificate did not verify: %s\n", X509_verify_cert_error_string(res)); - X509_free(peer); - ast_tcptls_close_session_file(tcptls_session); - ao2_ref(tcptls_session, -1); - return NULL; - } - if (!ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_IGNORE_COMMON_NAME)) { - ASN1_STRING *str; - unsigned char *str2; - X509_NAME *name = X509_get_subject_name(peer); - int pos = -1; - int found = 0; - - for (;;) { - /* Walk the certificate to check all available "Common Name" */ - /* XXX Probably should do a gethostbyname on the hostname and compare that as well */ - pos = X509_NAME_get_index_by_NID(name, NID_commonName, pos); - if (pos < 0) { - break; - } - str = X509_NAME_ENTRY_get_data(X509_NAME_get_entry(name, pos)); - ASN1_STRING_to_UTF8(&str2, str); - if (str2) { - if (!strcasecmp(tcptls_session->parent->hostname, (char *) str2)) { - found = 1; - } - ast_debug(3, "SSL Common Name compare s1='%s' s2='%s'\n", tcptls_session->parent->hostname, str2); - OPENSSL_free(str2); - } - if (found) { - break; - } - } - if (!found) { - ast_log(LOG_ERROR, "Certificate common name did not match (%s)\n", tcptls_session->parent->hostname); + res = SSL_get_verify_result(tcptls_session->ssl); + if (res != X509_V_OK) { + ast_log(LOG_ERROR, "Certificate did not verify: %s\n", X509_verify_cert_error_string(res)); X509_free(peer); ast_tcptls_close_session_file(tcptls_session); ao2_ref(tcptls_session, -1); return NULL; } + if (!ast_test_flag(&tcptls_session->parent->tls_cfg->flags, AST_SSL_IGNORE_COMMON_NAME)) { + int ret; + int found = 0; + + ret = X509_check_host(peer, (const unsigned char *)tcptls_session->parent->hostname, 0, 0); + if (ret < 0) { + ast_log(LOG_ERROR, "internal error in X509_check_host\n"); + } else if (ret == 0) { + ast_log(LOG_WARNING, "Certificate name(s) did not match\n"); + } else if (ret == 1) { + ast_verb(2, "Certificate name(s) matched\n"); + found = 1; + } else { + ast_log(LOG_ERROR, "unknown return code from X509_check_host\n"); + } + if (!found) { + ast_log(LOG_ERROR, "Certificate common name did not match (%s)\n", tcptls_session->parent->hostname); + X509_free(peer); + ast_tcptls_close_session_file(tcptls_session); + ao2_ref(tcptls_session, -1); + return NULL; + } + } + X509_free(peer); } - X509_free(peer); } + if (!tcptls_session->f) { /* no success opening descriptor stacking */ + SSL_free(tcptls_session->ssl); + } } - if (!tcptls_session->f) { /* no success opening descriptor stacking */ - SSL_free(tcptls_session->ssl); - } } #endif /* DO_SSL */ @@ -377,6 +372,7 @@ SSL_CTX_set_verify(cfg->ssl_ctx, ast_test_flag(&cfg->flags, AST_SSL_VERIFY_CLIENT) ? SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT : SSL_VERIFY_NONE, NULL); + SSL_CTX_set_verify_depth(cfg->ssl_ctx, SSL_VERIFY_DEPTH); if (!ast_strlen_zero(cfg->certfile)) { char *tmpprivate = ast_strlen_zero(cfg->pvtfile) ? cfg->certfile : cfg->pvtfile;