[PATCH] mail_{ssl, auth_http}_module: add support for SSL client certificates

Sven Peter sven at ha.cki.ng
Mon Jan 13 10:29:26 UTC 2014


# HG changeset patch
# User Sven Peter <sven at ha.cki.ng>
# Date 1389607375 -3600
#      Mon Jan 13 11:02:55 2014 +0100
# Node ID 8744640301ae0f7d4c16108e68c9ae6eb60f2213
# Parent  4aa64f6950313311e0d322a2af1788edeb7f036c
mail_{ssl,auth_http}_module: add support for SSL client certificates

This patch adds support for SSL client certificates to the mail proxy
capabilities of nginx both for STARTTLS and SSL mode.
Just like the HTTP SSL module a root CA is defined in the mail section
of the configuration file. Verification can be optional or mandatory.
Additionally, the result of the verification is exposed to the
auth http backend via the SSL-Verify, SSL-Subject-DN and SSL-Issuer-DN
HTTP headers.

diff -r 4aa64f695031 -r 8744640301ae src/mail/ngx_mail_auth_http_module.c
--- a/src/mail/ngx_mail_auth_http_module.c	Sat Jan 04 03:32:22 2014 +0400
+++ b/src/mail/ngx_mail_auth_http_module.c	Mon Jan 13 11:02:55 2014 +0100
@@ -1144,6 +1144,11 @@
     ngx_buf_t                 *b;
     ngx_str_t                  login, passwd;
     ngx_mail_core_srv_conf_t  *cscf;
+    ngx_str_t ssl_client_verify = {0, NULL};
+    ngx_str_t ssl_client_raw_s_dn = {0, NULL};
+    ngx_str_t ssl_client_raw_i_dn = {0, NULL};
+    ngx_str_t ssl_client_s_dn = {0, NULL};
+    ngx_str_t ssl_client_i_dn = {0, NULL};
 
     if (ngx_mail_auth_http_escape(pool, &s->login, &login) != NGX_OK) {
         return NULL;
@@ -1153,6 +1158,29 @@
         return NULL;
     }
 
+    // ssl_client_verify doesn't need to be escaped since it comes from nginx itself
+#if (NGX_MAIL_SSL)
+    ngx_ssl_get_client_verify(s->connection, pool, &ssl_client_verify);
+    ngx_ssl_get_subject_dn(s->connection, pool, &ssl_client_s_dn);
+    ngx_ssl_get_subject_dn(s->connection, pool, &ssl_client_i_dn);
+
+    if (ssl_client_raw_s_dn.len != 0) {
+        if (ngx_mail_auth_http_escape(pool, &ssl_client_raw_s_dn, &ssl_client_s_dn) != NGX_OK) {
+            return NULL;
+        }
+    }
+
+    if (ssl_client_raw_i_dn.len != 0) {
+        if (ngx_mail_auth_http_escape(pool, &ssl_client_raw_i_dn, &ssl_client_i_dn) != NGX_OK) {
+            return NULL;
+        }
+    }
+#else
+    // avoid -Wunused-variable
+    (void)ssl_client_raw_i_dn;
+    (void)ssl_client_raw_s_dn;
+#endif
+
     cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 
     len = sizeof("GET ") - 1 + ahcf->uri.len + sizeof(" HTTP/1.0" CRLF) - 1
@@ -1173,6 +1201,9 @@
           + sizeof("Auth-SMTP-Helo: ") - 1 + s->smtp_helo.len
           + sizeof("Auth-SMTP-From: ") - 1 + s->smtp_from.len
           + sizeof("Auth-SMTP-To: ") - 1 + s->smtp_to.len
+          + sizeof("SSL-Verify: ") - 1 + ssl_client_verify.len + sizeof(CRLF) - 1
+          + sizeof("SSL-Subject-DN: ") - 1 + ssl_client_s_dn.len + sizeof(CRLF) - 1
+          + sizeof("SSL-Issuer-DN: ") - 1 + ssl_client_i_dn.len + sizeof(CRLF) - 1
           + ahcf->header.len
           + sizeof(CRLF) - 1;
 
@@ -1255,6 +1286,20 @@
 
     }
 
+    if (ssl_client_verify.len && ssl_client_s_dn.len && ssl_client_i_dn.len) {
+        b->last = ngx_cpymem(b->last, "SSL-Verify: ", sizeof("SSL-Verify: ") - 1);
+        b->last = ngx_copy(b->last, ssl_client_verify.data, ssl_client_verify.len);
+        *b->last++ = CR; *b->last++ = LF;
+
+        b->last = ngx_cpymem(b->last, "SSL-Subject-DN: ", sizeof("SSL-Subject-DN: ") - 1);
+        b->last = ngx_copy(b->last, ssl_client_s_dn.data, ssl_client_s_dn.len);
+        *b->last++ = CR; *b->last++ = LF;
+
+        b->last = ngx_cpymem(b->last, "SSL-Issuer-DN: ", sizeof("SSL-Issuer-DN: ") - 1);
+        b->last = ngx_copy(b->last, ssl_client_i_dn.data, ssl_client_i_dn.len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
     if (ahcf->header.len) {
         b->last = ngx_copy(b->last, ahcf->header.data, ahcf->header.len);
     }
diff -r 4aa64f695031 -r 8744640301ae src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c	Sat Jan 04 03:32:22 2014 +0400
+++ b/src/mail/ngx_mail_handler.c	Mon Jan 13 11:02:55 2014 +0100
@@ -236,11 +236,40 @@
 {
     ngx_mail_session_t        *s;
     ngx_mail_core_srv_conf_t  *cscf;
+    ngx_mail_ssl_conf_t *sslcf;
 
     if (c->ssl->handshaked) {
 
         s = c->data;
 
+        sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+        if (sslcf->verify != NGX_MAIL_SSL_VERIFY_OFF) {
+            long rc;
+            rc = SSL_get_verify_result(c->ssl->connection);
+
+            if (rc != X509_V_OK &&
+                (sslcf->verify != NGX_MAIL_SSL_VERIFY_OPTIONAL_NO_CA && ngx_ssl_verify_error_optional(rc))) {
+                ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                              "client SSL certificate verify error: (%l:%s)",
+                              rc, X509_verify_cert_error_string(rc));
+                ngx_mail_close_connection(c);
+                return;
+            }
+            
+            if (sslcf->verify == NGX_MAIL_SSL_VERIFY_ON) {
+                X509 *cert;
+                cert = SSL_get_peer_certificate(c->ssl->connection);
+                
+                if (cert == NULL) {
+                    ngx_log_error(NGX_LOG_INFO, c->log, 0,
+                                  "client sent no required SSL certificate");
+                    ngx_mail_close_connection(c); 
+                    return;          
+                }
+                X509_free(cert);
+            }
+        }
+
         if (s->starttls) {
             cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
 
diff -r 4aa64f695031 -r 8744640301ae src/mail/ngx_mail_ssl_module.c
--- a/src/mail/ngx_mail_ssl_module.c	Sat Jan 04 03:32:22 2014 +0400
+++ b/src/mail/ngx_mail_ssl_module.c	Mon Jan 13 11:02:55 2014 +0100
@@ -43,6 +43,13 @@
     { ngx_null_string, 0 }
 };
 
+static ngx_conf_enum_t ngx_mail_ssl_verify[] = {
+    { ngx_string("off"), NGX_MAIL_SSL_VERIFY_OFF },
+    { ngx_string("on"), NGX_MAIL_SSL_VERIFY_ON },
+    { ngx_string("optional"), NGX_MAIL_SSL_VERIFY_OPTIONAL },
+    { ngx_string("optional_no_ca"), NGX_MAIL_SSL_VERIFY_OPTIONAL_NO_CA },
+    { ngx_null_string, 0 }
+};
 
 static ngx_command_t  ngx_mail_ssl_commands[] = {
 
@@ -130,7 +137,40 @@
       offsetof(ngx_mail_ssl_conf_t, session_timeout),
       NULL },
 
-      ngx_null_command
+    {
+      ngx_string("ssl_verify_client"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_enum_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, verify),
+      &ngx_mail_ssl_verify
+    },
+    {
+      ngx_string("ssl_verify_depth"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_1MORE,
+      ngx_conf_set_num_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, verify_depth),
+      NULL
+    },
+    {
+      ngx_string("ssl_client_certificate"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, client_certificate),
+      NULL
+    },
+    {
+      ngx_string("ssl_trusted_certificate"),
+      NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_TAKE1,
+      ngx_conf_set_str_slot,
+      NGX_MAIL_SRV_CONF_OFFSET,
+      offsetof(ngx_mail_ssl_conf_t, trusted_certificate),
+      NULL
+    },
+
+    ngx_null_command
 };
 
 
@@ -184,6 +224,8 @@
      *     scf->ecdh_curve = { 0, NULL };
      *     scf->ciphers = { 0, NULL };
      *     scf->shm_zone = NULL;
+     *     scf->client_certificate = { 0, NULL };
+     *     scf->trusted_certificate = { 0, NULL };
      */
 
     scf->enable = NGX_CONF_UNSET;
@@ -192,6 +234,8 @@
     scf->builtin_session_cache = NGX_CONF_UNSET;
     scf->session_timeout = NGX_CONF_UNSET;
     scf->session_ticket_keys = NGX_CONF_UNSET_PTR;
+    scf->verify = NGX_CONF_UNSET_UINT;
+    scf->verify_depth = NGX_CONF_UNSET_UINT;
 
     return scf;
 }
@@ -230,6 +274,11 @@
 
     ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
 
+    ngx_conf_merge_uint_value(conf->verify, prev->verify, NGX_MAIL_SSL_VERIFY_OFF);
+    ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1);
+
+    ngx_conf_merge_str_value(conf->client_certificate, prev->client_certificate, "");
+    ngx_conf_merge_str_value(conf->trusted_certificate, prev->trusted_certificate, "");
 
     conf->ssl.log = cf->log;
 
@@ -310,6 +359,21 @@
         return NGX_CONF_ERROR;
     }
 
+    if (conf->verify) {
+      if (conf->client_certificate.len == 0 && conf->verify != NGX_MAIL_SSL_VERIFY_OPTIONAL_NO_CA) {
+        ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
+                       "no ssl_client_certificate for ssl_client_verify");
+        return NGX_CONF_ERROR;
+      }
+
+      if (ngx_ssl_client_certificate(cf, &conf->ssl,
+                                     &conf->client_certificate,
+                                     conf->verify_depth)
+                != NGX_OK) {
+        return NGX_CONF_ERROR;
+      }
+    }
+
     if (conf->prefer_server_ciphers) {
         SSL_CTX_set_options(conf->ssl.ctx, SSL_OP_CIPHER_SERVER_PREFERENCE);
     }
diff -r 4aa64f695031 -r 8744640301ae src/mail/ngx_mail_ssl_module.h
--- a/src/mail/ngx_mail_ssl_module.h	Sat Jan 04 03:32:22 2014 +0400
+++ b/src/mail/ngx_mail_ssl_module.h	Mon Jan 13 11:02:55 2014 +0100
@@ -37,8 +37,14 @@
     ngx_str_t        dhparam;
     ngx_str_t        ecdh_curve;
 
+    ngx_str_t        client_certificate;
+    ngx_str_t        trusted_certificate;
+
     ngx_str_t        ciphers;
 
+    ngx_uint_t       verify;
+    ngx_uint_t       verify_depth;
+
     ngx_shm_zone_t  *shm_zone;
 
     ngx_array_t     *session_ticket_keys;
@@ -47,6 +53,13 @@
     ngx_uint_t       line;
 } ngx_mail_ssl_conf_t;
 
+enum ngx_mail_ssl_verify_enum {
+  NGX_MAIL_SSL_VERIFY_OFF = 0,
+  NGX_MAIL_SSL_VERIFY_ON,
+  NGX_MAIL_SSL_VERIFY_OPTIONAL,
+  NGX_MAIL_SSL_VERIFY_OPTIONAL_NO_CA,
+};
+
 
 extern ngx_module_t  ngx_mail_ssl_module;
 



More information about the nginx-devel mailing list