Skip to content

Instantly share code, notes, and snippets.

@aviramc
Created October 9, 2013 16:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save aviramc/6903821 to your computer and use it in GitHub Desktop.
Save aviramc/6903821 to your computer and use it in GitHub Desktop.
# HG changeset patch
# User Aviram Cohen <aviram@adallom.com>
# Date 1381334949 -7200
# Branch stable-1.4
# Node ID 9a6e20bf72f8cf4d17653e4fdfcbac48c4de03aa
# Parent 0702de638a4c51123d7b97801d393e8e25eb48de
Added remote end SSL certificate verification in the proxy module.
This patch adds the following directives to the proxy module:
- proxy_ssl_verify - whether or not to verify the remote end's certificate. Default is off.
- proxy_ssl_verify_name - the remote end's name to verify. Default is $host, can be set to "" in order to avoid name verification.
- proxy_ssl_verify_depth - how deep the ssl verification should be done. Default is 1.
- proxy_ssl_trusted_certificate - the path of the certificate file that is used for verification. This must be provided when proxy_ssl_verify is on.
- proxy_ssl_crl - the path a file that contains the CRLs of the hosts to which we proxy. Default is empty, and CRL verification is not done.
diff -r 0702de638a4c -r 9a6e20bf72f8 src/event/ngx_event_openssl.c
--- a/src/event/ngx_event_openssl.c Mon May 06 14:20:27 2013 +0400
+++ b/src/event/ngx_event_openssl.c Wed Oct 09 18:09:09 2013 +0200
@@ -42,6 +42,11 @@
static char *ngx_openssl_engine(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void ngx_openssl_exit(ngx_cycle_t *cycle);
+static int ngx_openssl_host_wildcard_match(ASN1_STRING *match_pattern,
+ ngx_str_t *hostname);
+static int ngx_openssl_host_exact_match(ASN1_STRING *match_pattern,
+ ngx_str_t *hostname);
+
static ngx_command_t ngx_openssl_commands[] = {
@@ -2562,3 +2567,163 @@
EVP_cleanup();
ENGINE_cleanup();
}
+
+
+static int
+ngx_openssl_host_wildcard_match(ASN1_STRING *match_pattern,
+ ngx_str_t *hostname)
+{
+ int host_top_domain_length;
+ u_char *host_top_domain;
+ u_char *wildcard_pattern;
+
+ /* sanity check */
+ if (!match_pattern
+ || match_pattern->length <= 0
+ || !hostname
+ || !hostname->len)
+ {
+ return 0;
+ }
+
+ /* trivial case */
+ if (ngx_strncasecmp((u_char *) match_pattern->data,
+ hostname->data,
+ hostname->len)
+ == 0)
+ {
+ return 1;
+ }
+
+ /* simple wildcard matching - only in the beginning of the string. */
+ if (match_pattern->length > 2
+ && match_pattern->data[0] == '*'
+ && match_pattern->data[1] == '.')
+ {
+
+ wildcard_pattern = (u_char *) (match_pattern->data + 1);
+
+ host_top_domain = ngx_strlchr(hostname->data,
+ hostname->data + hostname->len,
+ '.');
+
+ /*
+ * If the pattern begings with "*." and the hostname consists of
+ * a top level domain, compare the pattern to the top level domain.
+ */
+ if (host_top_domain != NULL) {
+ host_top_domain_length =
+ hostname->len - (int) (host_top_domain - hostname->data);
+
+ if (host_top_domain_length == match_pattern->length - 1
+ && ngx_strncasecmp(wildcard_pattern,
+ host_top_domain,
+ match_pattern->length - 1)
+ == 0)
+ {
+ return 1;
+ }
+ }
+ }
+
+ return 0;
+}
+
+
+static int
+ngx_openssl_host_exact_match(ASN1_STRING *match_pattern,
+ ngx_str_t *hostname)
+{
+ /* sanity check */
+ if (!match_pattern
+ || match_pattern->length <= 0
+ || !hostname
+ || !hostname->len
+ || match_pattern->length != (int) hostname->len)
+ {
+ return 0;
+ }
+
+ if (ngx_strncmp((u_char *) match_pattern->data,
+ hostname->data,
+ hostname->len)
+ == 0)
+ {
+ return 1;
+ }
+
+ return 0;
+}
+
+
+int
+ngx_openssl_verify_name(X509 *cert, ngx_str_t *expected_name)
+{
+ GENERAL_NAMES *gens = NULL;
+ GENERAL_NAME *gen;
+ X509_NAME *name = NULL;
+ ASN1_STRING *cstr = NULL;
+ X509_NAME_ENTRY *ne;
+ int i;
+ int rc = 0;
+
+ /* based on OpenSSL's do_x509_check */
+
+ gens = X509_get_ext_d2i(cert, NID_subject_alt_name, NULL, NULL);
+
+ if (gens) {
+
+ rc = 0;
+
+ for (i = 0; i < sk_GENERAL_NAME_num(gens); i++) {
+
+ gen = sk_GENERAL_NAME_value(gens, i);
+
+ /* we only check for either name or IP */
+ switch (gen->type) {
+
+ case GEN_DNS:
+ cstr = gen->d.dNSName;
+ rc = ngx_openssl_host_wildcard_match(cstr,
+ expected_name);
+ break;
+
+ case GEN_IPADD:
+ cstr = gen->d.iPAddress;
+ rc = ngx_openssl_host_exact_match(cstr,
+ expected_name);
+ break;
+
+ default:
+ cstr = NULL;
+ rc = 0;
+ }
+
+ if (rc) {
+ break;
+ }
+
+ }
+
+ GENERAL_NAMES_free(gens);
+
+ if (rc) {
+ return 1;
+ }
+ }
+
+ name = X509_get_subject_name(cert);
+ i = X509_NAME_get_index_by_NID(name, NID_commonName, -1);
+ while (i >= 0) {
+ ne = X509_NAME_get_entry(name, i);
+ cstr = X509_NAME_ENTRY_get_data(ne);
+
+ if (ngx_openssl_host_exact_match(cstr, expected_name)) {
+ return 1;
+ }
+
+ i = X509_NAME_get_index_by_NID(name, NID_commonName, i);
+ }
+
+ return 0;
+}
diff -r 0702de638a4c -r 9a6e20bf72f8 src/event/ngx_event_openssl.h
--- a/src/event/ngx_event_openssl.h Mon May 06 14:20:27 2013 +0400
+++ b/src/event/ngx_event_openssl.h Wed Oct 09 18:09:09 2013 +0200
@@ -167,6 +167,8 @@
char *fmt, ...);
void ngx_ssl_cleanup_ctx(void *data);
+int
+ngx_openssl_verify_name(X509 *cert, ngx_str_t *expected_name);
extern int ngx_ssl_connection_index;
extern int ngx_ssl_server_conf_index;
diff -r 0702de638a4c -r 9a6e20bf72f8 src/http/modules/ngx_http_proxy_module.c
--- a/src/http/modules/ngx_http_proxy_module.c Mon May 06 14:20:27 2013 +0400
+++ b/src/http/modules/ngx_http_proxy_module.c Wed Oct 09 18:09:09 2013 +0200
@@ -74,6 +74,15 @@
ngx_uint_t http_version;
+#if (NGX_HTTP_SSL)
+ ngx_uint_t ssl_verify_depth;
+ ngx_str_t ssl_trusted_certificate;
+ ngx_str_t ssl_crl;
+ ngx_str_t ssl_verify_name_source;
+ ngx_array_t *ssl_verify_name_lengths;
+ ngx_array_t *ssl_verify_name_values;
+#endif
+
ngx_uint_t headers_hash_max_size;
ngx_uint_t headers_hash_bucket_size;
} ngx_http_proxy_loc_conf_t;
@@ -510,6 +519,41 @@
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_session_reuse),
NULL },
+
+ { ngx_string("proxy_ssl_verify"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_flag_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_verify),
+ NULL },
+
+ { ngx_string("proxy_ssl_verify_name"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_verify_name_source),
+ NULL },
+
+ { ngx_string("proxy_ssl_verify_depth"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_num_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_verify_depth),
+ NULL },
+
+ { ngx_string("proxy_ssl_trusted_certificate"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_trusted_certificate),
+ NULL },
+
+ { ngx_string("proxy_ssl_crl"),
+ NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+ ngx_conf_set_str_slot,
+ NGX_HTTP_LOC_CONF_OFFSET,
+ offsetof(ngx_http_proxy_loc_conf_t, ssl_crl),
+ NULL },
#endif
@@ -633,6 +677,7 @@
ngx_http_upstream_t *u;
ngx_http_proxy_ctx_t *ctx;
ngx_http_proxy_loc_conf_t *plcf;
+ ngx_str_t host;
if (ngx_http_upstream_create(r) != NGX_OK) {
return NGX_HTTP_INTERNAL_SERVER_ERROR;
@@ -662,6 +707,24 @@
}
}
+#if (NGX_HTTP_SSL)
+
+ if (plcf->ssl_verify_name_lengths == NULL) {
+ u->ssl_verify_name.data = plcf->ssl_verify_name_source.data;
+ u->ssl_verify_name.len = plcf->ssl_verify_name_source.len;
+ } else {
+ if (ngx_http_script_run(r, &host, plcf->ssl_verify_name_lengths->elts,
+ 0, plcf->ssl_verify_name_values->elts)
+ == NULL)
+ {
+ return NGX_HTTP_INTERNAL_SERVER_ERROR;
+ }
+ u->ssl_verify_name.data = host.data;
+ u->ssl_verify_name.len = host.len;
+ }
+
+#endif
+
u->output.tag = (ngx_buf_tag_t) &ngx_http_proxy_module;
u->conf = &plcf->upstream;
@@ -2382,6 +2445,11 @@
* conf->body_set = NULL;
* conf->body_source = { 0, NULL };
* conf->redirects = NULL;
+ * conf->ssl_trusted_certificate = { 0, NULL };
+ * conf->ssl_crl = { 0, NULL };
+ * conf->ssl_verify_name_source = { 0, NULL };
+ * conf->ssl_verify_name_lengths = NULL;
+ * conf->ssl_verify_name_values = NULL;
*/
conf->upstream.store = NGX_CONF_UNSET;
@@ -2419,8 +2487,11 @@
conf->upstream.pass_headers = NGX_CONF_UNSET_PTR;
conf->upstream.intercept_errors = NGX_CONF_UNSET;
+
#if (NGX_HTTP_SSL)
conf->upstream.ssl_session_reuse = NGX_CONF_UNSET;
+ conf->upstream.ssl_verify = NGX_CONF_UNSET;
+ conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
#endif
/* "proxy_cyclic_temp_file" is disabled */
@@ -2455,6 +2526,7 @@
ngx_http_core_loc_conf_t *clcf;
ngx_http_proxy_rewrite_t *pr;
ngx_http_script_compile_t sc;
+ ngx_int_t n;
if (conf->upstream.store != 0) {
ngx_conf_merge_value(conf->upstream.store,
@@ -2695,8 +2767,62 @@
prev->upstream.intercept_errors, 0);
#if (NGX_HTTP_SSL)
+
ngx_conf_merge_value(conf->upstream.ssl_session_reuse,
prev->upstream.ssl_session_reuse, 1);
+ ngx_conf_merge_value(conf->upstream.ssl_verify,
+ prev->upstream.ssl_verify, 0);
+ ngx_conf_merge_str_value(conf->ssl_verify_name_source,
+ prev->ssl_verify_name_source, "$host");
+ ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+ prev->ssl_verify_depth, 1);
+ ngx_conf_merge_str_value(conf->ssl_trusted_certificate,
+ prev->ssl_trusted_certificate, "");
+ ngx_conf_merge_str_value(conf->ssl_crl,
+ prev->ssl_crl, "");
+
+ if (conf->upstream.ssl && conf->upstream.ssl_verify) {
+ if (conf->ssl_trusted_certificate.len == 0) {
+ ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+ "no \"proxy_ssl_trusted_certificate\" is "
+ " defined for the \"proxy_ssl_verify\" "
+ "directive");
+
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_trusted_certificate(cf, conf->upstream.ssl,
+ &conf->ssl_trusted_certificate,
+ conf->ssl_verify_depth)
+ != NGX_OK)
+ {
+ return NGX_CONF_ERROR;
+ }
+
+ if (ngx_ssl_crl(cf, conf->upstream.ssl, &conf->ssl_crl) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ n = ngx_http_script_variables_count(&conf->ssl_verify_name_source);
+
+ if (n) {
+ ngx_memzero(&sc, sizeof(ngx_http_script_compile_t));
+
+ sc.cf = cf;
+ sc.source = &conf->ssl_verify_name_source;
+ sc.variables = n;
+ sc.lengths = &conf->ssl_verify_name_lengths;
+ sc.values = &conf->ssl_verify_name_values;
+ sc.complete_lengths = 1;
+ sc.complete_values = 1;
+
+ if (ngx_http_script_compile(&sc) != NGX_OK) {
+ return NGX_CONF_ERROR;
+ }
+
+ }
+ }
+
#endif
ngx_conf_merge_value(conf->redirect, prev->redirect, 1);
diff -r 0702de638a4c -r 9a6e20bf72f8 src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c Mon May 06 14:20:27 2013 +0400
+++ b/src/http/ngx_http_upstream.c Wed Oct 09 18:09:09 2013 +0200
@@ -1319,12 +1319,43 @@
{
ngx_http_request_t *r;
ngx_http_upstream_t *u;
-
+ X509 *cert;
+ long rc;
+
r = c->data;
u = r->upstream;
if (c->ssl->handshaked) {
-
+ if (u->conf->ssl_verify) {
+ rc = SSL_get_verify_result(c->ssl->connection);
+ if (rc != X509_V_OK) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "upstream SSL certificate verify error: (%l:%s)",
+ rc, X509_verify_cert_error_string(rc));
+ goto fail;
+ }
+
+ cert = SSL_get_peer_certificate(c->ssl->connection);
+
+ if (cert == NULL) {
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "upstream sent no SSL certificate");
+ goto fail;
+ }
+
+ if (u->ssl_verify_name.len
+ && ngx_openssl_verify_name(cert, &u->ssl_verify_name)
+ == 0)
+ {
+ X509_free(cert);
+ ngx_log_error(NGX_LOG_ERR, c->log, 0,
+ "upstream SSL certificate name validation error");
+ goto fail;
+ }
+
+ X509_free(cert);
+ }
+
if (u->conf->ssl_session_reuse) {
u->peer.save_session(&u->peer, u->peer.data);
}
@@ -1332,13 +1363,22 @@
c->write->handler = ngx_http_upstream_handler;
c->read->handler = ngx_http_upstream_handler;
+ c = r->connection;
+
ngx_http_upstream_send_request(r, u);
+ ngx_http_run_posted_requests(c);
+
return;
}
+fail:
+
+ c = r->connection;
+
ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
+ ngx_http_run_posted_requests(c);
}
#endif
diff -r 0702de638a4c -r 9a6e20bf72f8 src/http/ngx_http_upstream.h
--- a/src/http/ngx_http_upstream.h Mon May 06 14:20:27 2013 +0400
+++ b/src/http/ngx_http_upstream.h Wed Oct 09 18:09:09 2013 +0200
@@ -191,6 +191,7 @@
#if (NGX_HTTP_SSL)
ngx_ssl_t *ssl;
ngx_flag_t ssl_session_reuse;
+ ngx_flag_t ssl_verify;
#endif
ngx_str_t module;
@@ -297,6 +298,10 @@
ngx_int_t (*input_filter)(void *data, ssize_t bytes);
void *input_filter_ctx;
+#if (NGX_HTTP_SSL)
+ ngx_str_t ssl_verify_name;
+#endif
+
#if (NGX_HTTP_CACHE)
ngx_int_t (*create_key)(ngx_http_request_t *r);
#endif
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment