Example #1
0
/*! \brief
* creates a FILE * from the fd passed by the accept thread.
* This operation is potentially expensive (certificate verification),
* so we do it in the child thread context.
*
* \note must decrement ref count before returning NULL on error
*/
static void *handle_tcptls_connection(void *data)
{
	struct ast_tcptls_session_instance *tcptls_session = data;
#ifdef DO_SSL
	int (*ssl_setup)(SSL *) = (tcptls_session->client) ? SSL_connect : SSL_accept;
	int ret;
	char err[256];
#endif

	/* TCP/TLS connections are associated with external protocols, and
	 * should not be allowed to execute 'dangerous' functions. This may
	 * need to be pushed down into the individual protocol handlers, but
	 * this seems like a good general policy.
	 */
	if (ast_thread_inhibit_escalations()) {
		ast_log(LOG_ERROR, "Failed to inhibit privilege escalations; killing connection\n");
		ast_tcptls_close_session_file(tcptls_session);
		ao2_ref(tcptls_session, -1);
		return NULL;
	}

	tcptls_session->stream_cookie = tcptls_stream_alloc();
	if (!tcptls_session->stream_cookie) {
		ast_tcptls_close_session_file(tcptls_session);
		ao2_ref(tcptls_session, -1);
		return NULL;
	}

	/*
	* open a FILE * as appropriate.
	*/
	if (!tcptls_session->parent->tls_cfg) {
		tcptls_session->f = tcptls_stream_fopen(tcptls_session->stream_cookie, NULL,
			tcptls_session->fd, -1);
		if (tcptls_session->f) {
			if (setvbuf(tcptls_session->f, NULL, _IONBF, 0)) {
				ast_tcptls_close_session_file(tcptls_session);
			}
		}
	}
#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_log(LOG_ERROR, "Problem setting up ssl connection: %s\n", ERR_error_string(ERR_get_error(), err));
		} else if ((tcptls_session->f = tcptls_stream_fopen(tcptls_session->stream_cookie,
			tcptls_session->ssl, tcptls_session->fd, -1))) {
			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));
						ret = ASN1_STRING_to_UTF8(&str2, str);
						if (ret < 0) {
							continue;
						}

						if (str2) {
							if (strlen((char *) str2) != ret) {
								ast_log(LOG_WARNING, "Invalid certificate common name length (contains NULL bytes?)\n");
							} else 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);
						X509_free(peer);
						ast_tcptls_close_session_file(tcptls_session);
						ao2_ref(tcptls_session, -1);
						return NULL;
					}
				}
				X509_free(peer);
			}
		}
		if (!tcptls_session->f) {	/* no success opening descriptor stacking */
			SSL_free(tcptls_session->ssl);
		}
	}
#endif /* DO_SSL */

	if (!tcptls_session->f) {
		ast_tcptls_close_session_file(tcptls_session);
		ast_log(LOG_WARNING, "FILE * open failed!\n");
#ifndef DO_SSL
		if (tcptls_session->parent->tls_cfg) {
			ast_log(LOG_ERROR, "Attempted a TLS connection without OpenSSL support. This will not work!\n");
		}
#endif
		ao2_ref(tcptls_session, -1);
		return NULL;
	}

	if (tcptls_session->parent->worker_fn) {
		return tcptls_session->parent->worker_fn(tcptls_session);
	} else {
		return tcptls_session;
	}
}
Example #2
0
/*! \brief
* creates a FILE * from the fd passed by the accept thread.
* This operation is potentially expensive (certificate verification),
* so we do it in the child thread context.
*
* \note must decrement ref count before returning NULL on error
*/
static void *handle_tcptls_connection(void *data)
{
    struct ast_tcptls_session_instance *tcptls_session = data;
#ifdef DO_SSL
    SSL *ssl;
#endif

    /* TCP/TLS connections are associated with external protocols, and
     * should not be allowed to execute 'dangerous' functions. This may
     * need to be pushed down into the individual protocol handlers, but
     * this seems like a good general policy.
     */
    if (ast_thread_inhibit_escalations()) {
        ast_log(LOG_ERROR, "Failed to inhibit privilege escalations; killing connection\n");
        ast_tcptls_close_session_file(tcptls_session);
        ao2_ref(tcptls_session, -1);
        return NULL;
    }

    if (tcptls_session->parent->tls_cfg) {
#ifdef DO_SSL
        if (ast_iostream_start_tls(&tcptls_session->stream, tcptls_session->parent->tls_cfg->ssl_ctx, tcptls_session->client) < 0) {
            ast_tcptls_close_session_file(tcptls_session);
            ao2_ref(tcptls_session, -1);
            return NULL;
        }

        ssl = ast_iostream_get_ssl(tcptls_session->stream);
        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(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(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;
                X509_NAME *name = X509_get_subject_name(peer);
                STACK_OF(GENERAL_NAME) *alt_names;
                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));
                    if (!check_tcptls_cert_name(str, tcptls_session->parent->hostname, "common name")) {
                        found = 1;
                        break;
                    }
                }

                if (!found) {
                    alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, NULL, NULL);
                    if (alt_names != NULL) {
                        int alt_names_count = sk_GENERAL_NAME_num(alt_names);

                        for (pos = 0; pos < alt_names_count; pos++) {
                            const GENERAL_NAME *alt_name = sk_GENERAL_NAME_value(alt_names, pos);

                            if (alt_name->type != GEN_DNS) {
                                continue;
                            }

                            if (!check_tcptls_cert_name(alt_name->d.dNSName, tcptls_session->parent->hostname, "alt name")) {
                                found = 1;
                                break;
                            }
                        }

                        sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free);
                    }
                }

                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);
        }
#else
        ast_log(LOG_ERROR, "Attempted a TLS connection without OpenSSL support. This will not work!\n");
        ast_tcptls_close_session_file(tcptls_session);
        ao2_ref(tcptls_session, -1);
        return NULL;
#endif /* DO_SSL */
    }

    if (tcptls_session->parent->worker_fn) {
        return tcptls_session->parent->worker_fn(tcptls_session);
    } else {
        return tcptls_session;
    }
}