[PATCH] SSL: add "{proxy, uwsgi}_ssl_verify" and supporting directives

Piotr Sikora piotr at cloudflare.com
Wed Feb 5 06:54:47 UTC 2014


# HG changeset patch
# User Piotr Sikora <piotr at cloudflare.com>
# Date 1391582213 28800
#      Tue Feb 04 22:36:53 2014 -0800
# Node ID e7704dcea76c83708cd8bf01709e15dc658871ae
# Parent  f0129ac05ced1ee418fa97dbbae35f3c0b831992
SSL: add "{proxy,uwsgi}_ssl_verify" and supporting directives.

Verify SSL certificate when connecting to an SSL upstream.

"{proxy,uwsgi}_ssl_verify" directives support 3 modes:
- off - don't verify upstream's SSL certificate (default),
- on - verify validity and trust of upstream's SSL certificate,
- server_name - same as above, but when SNI is used, also verify
  that it matches one of the hostnames in the certificate. This
  mode requires OpenSSL-1.0.2+.

Supporting directives:
- "{proxy,uwsgi}_ssl_verify_depth",
- "{proxy,uwsgi}_ssl_trusted_certificate",
- "{proxy,uwsgi}_ssl_crl".

Signed-off-by: Piotr Sikora <piotr at cloudflare.com>

diff -r f0129ac05ced -r e7704dcea76c src/event/ngx_event_connect.h
--- a/src/event/ngx_event_connect.h	Tue Feb 04 22:36:41 2014 -0800
+++ b/src/event/ngx_event_connect.h	Tue Feb 04 22:36:53 2014 -0800
@@ -68,6 +68,10 @@ struct ngx_peer_connection_s {
 
                                      /* ngx_connection_log_error_e */
     unsigned                         log_error:2;
+
+#if (NGX_SSL)
+    unsigned                         verify:2;
+#endif
 };
 
 
diff -r f0129ac05ced -r e7704dcea76c src/http/modules/ngx_http_proxy_module.c
--- a/src/http/modules/ngx_http_proxy_module.c	Tue Feb 04 22:36:41 2014 -0800
+++ b/src/http/modules/ngx_http_proxy_module.c	Tue Feb 04 22:36:53 2014 -0800
@@ -81,6 +81,9 @@ typedef struct {
     ngx_uint_t                     ssl;
     ngx_uint_t                     ssl_protocols;
     ngx_str_t                      ssl_ciphers;
+    ngx_uint_t                     ssl_verify_depth;
+    ngx_str_t                      ssl_trusted_certificate;
+    ngx_str_t                      ssl_crl;
 #endif
 } ngx_http_proxy_loc_conf_t;
 
@@ -203,6 +206,16 @@ static ngx_conf_bitmask_t  ngx_http_prox
     { ngx_null_string, 0 }
 };
 
+
+static ngx_conf_enum_t  ngx_http_proxy_ssl_verify[] = {
+    { ngx_string("off"), 0 },
+    { ngx_string("on"), 1 },
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+    { ngx_string("server_name"), 2 },
+#endif
+    { ngx_null_string, 0 }
+};
+
 #endif
 
 
@@ -560,6 +573,34 @@ static ngx_command_t  ngx_http_proxy_com
       offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_server_name),
       NULL },
 
+    { ngx_string("proxy_ssl_verify"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.ssl_verify),
+      &ngx_http_proxy_ssl_verify },
+
+    { 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
 
       ngx_null_command
@@ -2411,6 +2452,8 @@ ngx_http_proxy_create_loc_conf(ngx_conf_
      *     conf->ssl = 0;
      *     conf->ssl_protocols = 0;
      *     conf->ssl_ciphers = { 0, NULL };
+     *     conf->ssl_trusted_certificate = { 0, NULL };
+     *     conf->ssl_crl = { 0, NULL };
      */
 
     conf->upstream.store = NGX_CONF_UNSET;
@@ -2449,8 +2492,10 @@ ngx_http_proxy_create_loc_conf(ngx_conf_
     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_UINT;
 #endif
 
     /* "proxy_cyclic_temp_file" is disabled */
@@ -2467,6 +2512,10 @@ ngx_http_proxy_create_loc_conf(ngx_conf_
     conf->headers_hash_max_size = NGX_CONF_UNSET_UINT;
     conf->headers_hash_bucket_size = NGX_CONF_UNSET_UINT;
 
+#if (NGX_HTTP_SSL)
+    conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+#endif
+
     ngx_str_set(&conf->upstream.module, "proxy");
 
     return conf;
@@ -2737,8 +2786,50 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t
         conf->upstream.ssl_server_name = prev->upstream.ssl_server_name;
     }
 
-    if (conf->ssl && ngx_http_proxy_set_ssl(cf, conf) != NGX_OK) {
-        return NGX_CONF_ERROR;
+    ngx_conf_merge_uint_value(conf->upstream.ssl_verify,
+                              prev->upstream.ssl_verify, 0);
+
+    ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+                              prev->ssl_verify_depth, 9);
+
+    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->ssl) {
+
+        if (ngx_http_proxy_set_ssl(cf, conf) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (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;
+            }
+        }
+    }
+
+    if (conf->upstream.ssl == NULL) {
+        conf->upstream.ssl = prev->upstream.ssl;
     }
 #endif
 
@@ -2797,12 +2888,6 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t
 
     ngx_conf_merge_ptr_value(conf->cookie_paths, prev->cookie_paths, NULL);
 
-#if (NGX_HTTP_SSL)
-    if (conf->upstream.ssl == NULL) {
-        conf->upstream.ssl = prev->upstream.ssl;
-    }
-#endif
-
     ngx_conf_merge_uint_value(conf->http_version, prev->http_version,
                               NGX_HTTP_VERSION_10);
 
diff -r f0129ac05ced -r e7704dcea76c src/http/modules/ngx_http_upstream_keepalive_module.c
--- a/src/http/modules/ngx_http_upstream_keepalive_module.c	Tue Feb 04 22:36:41 2014 -0800
+++ b/src/http/modules/ngx_http_upstream_keepalive_module.c	Tue Feb 04 22:36:53 2014 -0800
@@ -51,6 +51,7 @@ typedef struct {
 
 #if (NGX_HTTP_SSL)
     ngx_str_t                          server_name;
+    unsigned                           verify:2;
 #endif
 
 } ngx_http_upstream_keepalive_cache_t;
@@ -250,6 +251,7 @@ ngx_http_upstream_get_keepalive_peer(ngx
                 || ngx_strncmp(pc->server_name.data, item->server_name.data,
                                pc->server_name.len)
                    == 0)
+            && (pc->verify <= item->verify)
 #endif
             )
         {
@@ -372,6 +374,8 @@ ngx_http_upstream_free_keepalive_peer(ng
                    pc->server_name.len);
     }
 
+    item->verify = pc->verify;
+
 #endif
 
     if (c->read->ready) {
diff -r f0129ac05ced -r e7704dcea76c src/http/modules/ngx_http_uwsgi_module.c
--- a/src/http/modules/ngx_http_uwsgi_module.c	Tue Feb 04 22:36:41 2014 -0800
+++ b/src/http/modules/ngx_http_uwsgi_module.c	Tue Feb 04 22:36:53 2014 -0800
@@ -39,6 +39,9 @@ typedef struct {
     ngx_uint_t                 ssl;
     ngx_uint_t                 ssl_protocols;
     ngx_str_t                  ssl_ciphers;
+    ngx_uint_t                 ssl_verify_depth;
+    ngx_str_t                  ssl_trusted_certificate;
+    ngx_str_t                  ssl_crl;
 #endif
 } ngx_http_uwsgi_loc_conf_t;
 
@@ -108,6 +111,16 @@ static ngx_conf_bitmask_t  ngx_http_uwsg
     { ngx_null_string, 0 }
 };
 
+
+static ngx_conf_enum_t  ngx_http_uwsgi_ssl_verify[] = {
+    { ngx_string("off"), 0 },
+    { ngx_string("on"), 1 },
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+    { ngx_string("server_name"), 2 },
+#endif
+    { ngx_null_string, 0 }
+};
+
 #endif
 
 
@@ -416,6 +429,34 @@ static ngx_command_t ngx_http_uwsgi_comm
       offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_server_name),
       NULL },
 
+    { ngx_string("uwsgi_ssl_verify"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      offsetof(ngx_http_uwsgi_loc_conf_t, upstream.ssl_verify),
+      &ngx_http_uwsgi_ssl_verify },
+
+    { ngx_string("uwsgi_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_uwsgi_loc_conf_t, ssl_verify_depth),
+      NULL },
+
+    { ngx_string("uwsgi_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_uwsgi_loc_conf_t, ssl_trusted_certificate),
+      NULL },
+
+    { ngx_string("uwsgi_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_uwsgi_loc_conf_t, ssl_crl),
+      NULL },
+
 #endif
 
       ngx_null_command
@@ -1250,8 +1291,10 @@ ngx_http_uwsgi_create_loc_conf(ngx_conf_
     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_UINT;
 #endif
 
     /* "uwsgi_cyclic_temp_file" is disabled */
@@ -1259,6 +1302,10 @@ ngx_http_uwsgi_create_loc_conf(ngx_conf_
 
     conf->upstream.change_buffering = 1;
 
+#if (NGX_HTTP_SSL)
+    conf->ssl_verify_depth = NGX_CONF_UNSET_UINT;
+#endif
+
     ngx_str_set(&conf->upstream.module, "uwsgi");
 
     return conf;
@@ -1516,8 +1563,46 @@ ngx_http_uwsgi_merge_loc_conf(ngx_conf_t
         conf->upstream.ssl_server_name = prev->upstream.ssl_server_name;
     }
 
-    if (conf->ssl && ngx_http_uwsgi_set_ssl(cf, conf) != NGX_OK) {
-        return NGX_CONF_ERROR;
+    ngx_conf_merge_uint_value(conf->upstream.ssl_verify,
+                              prev->upstream.ssl_verify, 0);
+
+    ngx_conf_merge_uint_value(conf->ssl_verify_depth,
+                              prev->ssl_verify_depth, 9);
+
+    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->ssl) {
+
+        if (ngx_http_uwsgi_set_ssl(cf, conf) != NGX_OK) {
+            return NGX_CONF_ERROR;
+        }
+
+        if (conf->upstream.ssl_verify) {
+
+            if (conf->ssl_trusted_certificate.len == 0) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "no \"uwsgi_ssl_trusted_certificate\" "
+                                   "is defined for the \"uwsgi_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;
+            }
+        }
     }
 
     if (conf->upstream.ssl == NULL) {
diff -r f0129ac05ced -r e7704dcea76c src/http/ngx_http_upstream.c
--- a/src/http/ngx_http_upstream.c	Tue Feb 04 22:36:41 2014 -0800
+++ b/src/http/ngx_http_upstream.c	Tue Feb 04 22:36:53 2014 -0800
@@ -541,27 +541,33 @@ ngx_http_upstream_init_request(ngx_http_
 
 #if (NGX_HTTP_SSL)
 
-    if (u->ssl && u->conf->ssl_server_name) {
-
-        if (ngx_http_complex_value(r, u->conf->ssl_server_name, &name)
-            != NGX_OK)
-        {
-            ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
-            return;
-        }
-
-        u->peer.server_name.len = name.len;
-
-        if (u->peer.server_name.len) {
-            u->peer.server_name.data = ngx_pnalloc(r->pool, name.len + 1);
-            if (u->peer.server_name.data == NULL) {
+    if (u->ssl) {
+
+        if (u->conf->ssl_server_name) {
+
+            if (ngx_http_complex_value(r, u->conf->ssl_server_name, &name)
+                != NGX_OK)
+            {
                 ngx_http_finalize_request(r, NGX_HTTP_INTERNAL_SERVER_ERROR);
                 return;
             }
 
-            ngx_memcpy(u->peer.server_name.data, name.data, name.len);
-            u->peer.server_name.data[name.len] = '\0';
+            u->peer.server_name.len = name.len;
+
+            if (u->peer.server_name.len) {
+                u->peer.server_name.data = ngx_pnalloc(r->pool, name.len + 1);
+                if (u->peer.server_name.data == NULL) {
+                    ngx_http_finalize_request(r,
+                                              NGX_HTTP_INTERNAL_SERVER_ERROR);
+                    return;
+                }
+
+                ngx_memcpy(u->peer.server_name.data, name.data, name.len);
+                u->peer.server_name.data[name.len] = '\0';
+            }
         }
+
+        u->peer.verify = u->conf->ssl_verify;
     }
 
 #endif
@@ -1421,6 +1427,8 @@ ngx_http_upstream_ssl_init_connection(ng
 static void
 ngx_http_upstream_ssl_handshake(ngx_connection_t *c)
 {
+    long                  rc;
+    X509                 *cert;
     ngx_http_request_t   *r;
     ngx_http_upstream_t  *u;
 
@@ -1429,6 +1437,49 @@ ngx_http_upstream_ssl_handshake(ngx_conn
 
     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 failed;
+            }
+
+            cert = SSL_get_peer_certificate(c->ssl->connection);
+
+            if (cert == NULL) {
+                ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                              "upstream sent no required SSL certificate");
+
+                goto failed;
+            }
+
+#if OPENSSL_VERSION_NUMBER >= 0x10002000L
+
+            if (u->conf->ssl_verify == 2 && u->peer.server_name.len) {
+
+                if (X509_check_host(cert, u->peer.server_name.data,
+                                    u->peer.server_name.len, 0)
+                    != 1)
+                {
+                    ngx_log_error(NGX_LOG_ERR, c->log, 0,
+                                  "upstream SSL certificate doesn't match "
+                                  "\"%V\"", &u->peer.server_name);
+
+                    X509_free(cert);
+                    goto failed;
+                }
+            }
+
+#endif
+
+            X509_free(cert);
+        }
+
         if (u->conf->ssl_session_reuse) {
             u->peer.save_session(&u->peer, u->peer.data);
         }
@@ -1444,6 +1495,8 @@ ngx_http_upstream_ssl_handshake(ngx_conn
         return;
     }
 
+failed:
+
     c = r->connection;
 
     ngx_http_upstream_next(r, u, NGX_HTTP_UPSTREAM_FT_ERROR);
diff -r f0129ac05ced -r e7704dcea76c src/http/ngx_http_upstream.h
--- a/src/http/ngx_http_upstream.h	Tue Feb 04 22:36:41 2014 -0800
+++ b/src/http/ngx_http_upstream.h	Tue Feb 04 22:36:53 2014 -0800
@@ -196,6 +196,7 @@ typedef struct {
     ngx_ssl_t                       *ssl;
     ngx_flag_t                       ssl_session_reuse;
     ngx_http_complex_value_t        *ssl_server_name;
+    ngx_uint_t                       ssl_verify;
 #endif
 
     ngx_str_t                        module;



More information about the nginx-devel mailing list