Add "pass_only" option to ssl_verify_client to enable app-only validation

Hello,

Proposed patch enables use-case scenario when Nginx asks Client for TLS
certificate but does not make any attempt to validate it, passing it to
the application instead. Application itself if then free to decide
whether provided certificate is valid and is able to reject it with the
same http status codes as Nginx does.

Same use-case is also referenced in
Issue with SSL client certificate” and is critical for
implementing protocols like WebID (WebID - W3C Wiki), which
relies on custom TLS certificates, which are signed with keys which are
unknown in advance.
With the patch, that can be accomplished by specifying
“ssl_verify_client pass_only;” (“ssl_client_certificate” is not used
in this case) and using “uwsgi_param X_CLIENT_CERT
$ssl_client_raw_cert;” (or similar options for different backends).

Currently, Nginx supports “off”, “on” and “optional” parameters to
“ssl_verify_client” option, latter of which (“on” and “optional”)
require CA certificate (specified with “ssl_client_certificate” option)
and perform mandatory check against it if client provides certificate.
“optional” parameter seem to allow client to skip providing the
certificate, but still requires CA certificate and performs the check
(if client provides the cert), returning http status 495 if validation
against that CA fails.
So there is currently no way to require client certificate but perform
it’s validation in application (or on whatever backend) only, hence the
patch.

Please consider merging the patch into nginx codebase, enabling
aforementioned use-case in some other way, or at least commenting on why
it might be wrong or unsuitable approach/feature (if only to block
further proposals in the same vein).

Patch is made on top of current (as of 07.2012) svn trunk.
In case forum interface mangles the inline attachment, it can also be
found on the following URL: https://raw.github.com/gist/3146701/

From 0ade221a2dbaeedfa5255875a89485166221a6f0 Mon Sep 17 00:00:00 2001
From: Mike K. [email protected]
Date: Fri, 20 Jul 2012 02:45:10 +0600
Subject: [PATCH] Add “pass_only” option to ssl_verify_client to enable
app-only validation


src/http/modules/ngx_http_ssl_module.c | 3 +±
src/http/ngx_http_request.c | 2 ±
2 files changed, 3 insertions(+), 2 deletions(-)

diff --git a/src/http/modules/ngx_http_ssl_module.c
b/src/http/modules/ngx_http_ssl_module.c
index d759489…4435435 100644
— a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -48,6 +48,7 @@ static ngx_conf_enum_t ngx_http_ssl_verify[] = {
{ ngx_string(“off”), 0 },
{ ngx_string(“on”), 1 },
{ ngx_string(“optional”), 2 },

  • { ngx_string(“pass_only”), 3 },
    { ngx_null_string, 0 }
    };

@@ -466,7 +467,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void
*parent, void *child)

 if (conf->verify) {
  •    if (conf->client_certificate.len == 0) {
    
  •    if (conf->verify != 3 && conf->client_certificate.len == 0) {
           ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                         "no ssl_client_certificate for
    

ssl_client_verify");
return NGX_CONF_ERROR;
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
index 26420b1…11373dc 100644
— a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -1631,7 +1631,7 @@ ngx_http_process_request(ngx_http_request_t *r)

     sscf = ngx_http_get_module_srv_conf(r, ngx_http_ssl_module);
  •    if (sscf->verify) {
    
  •    if (sscf->verify && sscf->verify != 3) {
           rc = SSL_get_verify_result(c->ssl->connection);
    
           if (rc != X509_V_OK) {
    


1.7.10.4

Posted at Nginx Forum:

Hello!

On Thu, Jul 19, 2012 at 05:13:01PM -0400, mk.fg wrote:

implementing protocols like WebID (WebID - W3C Wiki), which
and perform mandatory check against it if client provides certificate.
it might be wrong or unsuitable approach/feature (if only to block
further proposals in the same vein).

I don’t think that the patch is a right way to go. If client
certificates without CA being known in advance is indeed something
required - it probably needs something like optional_no_ca instead
as done in Apache’s mod_ssl, i.e. with usual expiration checks and so
on,
but without verification against CAs.

Maxim D.

Thank you for the analysis and suggestions, I’ll take a look at mod_ssl
and will try to come up with a more appropriate patch.

Posted at Nginx Forum:

ahoy! any progress on this one? IMHO WebID can become a very hot
technology soon :slight_smile:

WebID ACL & SPARQL -
http://lists.w3.org/Archives/Public/public-rww/2012Jun/0072.html

Posted at Nginx Forum:

Maxim D. Wrote:

the application instead. Application itself if
required - it probably needs something like
optional_no_ca instead
as done in Apache’s mod_ssl, i.e. with usual
expiration checks and so on,
but without verification against CAs.

New version of the patch, with these requirements taken into account.

Option is now called “optional_no_ca”, as suggested, and allows to check
all certificate parameters except for a trust chain.
I’ve used ssl_verify_error_is_optional macro (listing trust-chain
related errors) directly from apache 2.4.2 codebase.

Note that since ngx_ssl_get_client_verify now has to access
configuration, which is accessible from ngx_http_request_t, it wasn’t
enough to pass ngx_connection_t to it, plus it was only used from
ngx_http_ssl_module.c, so I’ve moved the modified version of it into
ngx_http_ssl_module.c, to avoid having to include http-only stuff into
ngx_event_openssl.c.
If that was a bad idea, and there’s a need to keep that function generic
(non-http-only), please suggest whether generic copy should just be kept
in ngx_event_openssl.c, it’s signature should be extended to have
http-specific options or maybe there should be conditional includes for
http stuff.

URL for the patch: https://raw.github.com/gist/3319062/

diff --git a/src/event/ngx_event_openssl.c
b/src/event/ngx_event_openssl.c
index 4356a05…f2c3511 100644
— a/src/event/ngx_event_openssl.c
+++ b/src/event/ngx_event_openssl.c
@@ -2309,31 +2309,6 @@ ngx_ssl_get_serial_number(ngx_connection_t *c,
ngx_pool_t *pool, ngx_str_t *s)
}

-ngx_int_t
-ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *s)
-{

  • X509 *cert;
  • if (SSL_get_verify_result(c->ssl->connection) != X509_V_OK) {
  •    ngx_str_set(s, "FAILED");
    
  •    return NGX_OK;
    
  • }
  • cert = SSL_get_peer_certificate(c->ssl->connection);
  • if (cert) {
  •    ngx_str_set(s, "SUCCESS");
    
  • } else {
  •    ngx_str_set(s, "NONE");
    
  • }
  • X509_free(cert);
  • return NGX_OK;
    -}

static void *
ngx_openssl_create_conf(ngx_cycle_t *cycle)
{
diff --git a/src/event/ngx_event_openssl.h
b/src/event/ngx_event_openssl.h
index cd6d885…97da051 100644
— a/src/event/ngx_event_openssl.h
+++ b/src/event/ngx_event_openssl.h
@@ -141,6 +141,14 @@ ngx_int_t
ngx_ssl_get_client_verify(ngx_connection_t *c, ngx_pool_t *pool,
ngx_str_t *s);

+#define ngx_ssl_verify_error_is_optional(errnum) \

  • ((errnum == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) \
  • || (errnum == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) \
  • || (errnum == X509_V_ERR_UNABLE_TO_GET_ISSUER_CERT_LOCALLY) \
  • || (errnum == X509_V_ERR_CERT_UNTRUSTED) \
  • || (errnum == X509_V_ERR_UNABLE_TO_VERIFY_LEAF_SIGNATURE))

ngx_int_t ngx_ssl_handshake(ngx_connection_t *c);
ssize_t ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size);
ssize_t ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size);
diff --git a/src/http/modules/ngx_http_ssl_module.c
b/src/http/modules/ngx_http_ssl_module.c
index d759489…8f29f3c 100644
— a/src/http/modules/ngx_http_ssl_module.c
+++ b/src/http/modules/ngx_http_ssl_module.c
@@ -22,6 +22,8 @@ static ngx_int_t
ngx_http_ssl_static_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
static ngx_int_t ngx_http_ssl_variable(ngx_http_request_t *r,
ngx_http_variable_value_t *v, uintptr_t data);
+static ngx_int_t
ngx_http_ssl_variable_get_client_verify(ngx_http_request_t *r,

  • ngx_http_variable_value_t *v, uintptr_t data);

static ngx_int_t ngx_http_ssl_add_variables(ngx_conf_t *cf);
static void *ngx_http_ssl_create_srv_conf(ngx_conf_t *cf);
@@ -48,6 +50,7 @@ static ngx_conf_enum_t ngx_http_ssl_verify[] = {
{ ngx_string(“off”), 0 },
{ ngx_string(“on”), 1 },
{ ngx_string(“optional”), 2 },

  • { ngx_string(“optional_no_ca”), 3 },
    { ngx_null_string, 0 }
    };

@@ -214,8 +217,9 @@ static ngx_http_variable_t ngx_http_ssl_vars[] = {
{ ngx_string(“ssl_client_serial”), NULL, ngx_http_ssl_variable,
(uintptr_t) ngx_ssl_get_serial_number, NGX_HTTP_VAR_CHANGEABLE, 0
},

  • { ngx_string(“ssl_client_verify”), NULL, ngx_http_ssl_variable,
  •  (uintptr_t) ngx_ssl_get_client_verify, NGX_HTTP_VAR_CHANGEABLE, 0
    

},

  • { ngx_string(“ssl_client_verify”), NULL,

  •  ngx_http_ssl_variable_get_client_verify,
    
  •  0, NGX_HTTP_VAR_CHANGEABLE, 0 },
    

    { ngx_null_string, NULL, NULL, 0, 0, 0 }
    };
    @@ -306,6 +310,60 @@ ngx_http_ssl_add_variables(ngx_conf_t *cf)
    }

+static ngx_int_t
+ngx_http_ssl_variable_get_client_verify(ngx_http_request_t *r,

  • ngx_http_variable_value_t *v, uintptr_t data)
    +{
  • ngx_str_t s;
  • ngx_connection_t *c;
  • long rc;
  • X509 *cert;
  • ngx_http_ssl_srv_conf_t *sscf;
  • c = r->connection;
  • if (c->ssl) {
  •    rc = SSL_get_verify_result(c->ssl->connection);
    
  •    if (rc != X509_V_OK) {
    
  •        sscf = ngx_http_get_module_srv_conf(r,
    

ngx_http_ssl_module);
+

  •        if (sscf->verify != 3  ||
    

ngx_ssl_verify_error_is_optional(rc)) {

  •          ngx_str_set(&s, "FAILED");
    
  •          return NGX_OK;
    
  •        }
    
  •    }
    
  •    cert = SSL_get_peer_certificate(c->ssl->connection);
    
  •    if (cert) {
    
  •        ngx_str_set(&s, "SUCCESS");
    
  •    } else {
    
  •        ngx_str_set(&s, "NONE");
    
  •    }
    
  •    X509_free(cert);
    
  •    v->len = s.len;
    
  •    v->data = s.data;
    
  •    if (v->len) {
    
  •        v->valid = 1;
    
  •        v->no_cacheable = 0;
    
  •        v->not_found = 0;
    
  •        return NGX_OK;
    
  •    }
    
  • }
  • v->not_found = 1;
  • return NGX_OK;
    +}

static void *
ngx_http_ssl_create_srv_conf(ngx_conf_t *cf)
{
@@ -466,7 +524,7 @@ ngx_http_ssl_merge_srv_conf(ngx_conf_t *cf, void
*parent, void *child)

 if (conf->verify) {
  •    if (conf->client_certificate.len == 0) {
    
  •    if (conf->verify != 3 && conf->client_certificate.len == 0) {
           ngx_log_error(NGX_LOG_EMERG, cf->log, 0,
                         "no ssl_client_certificate for
    

ssl_client_verify");
return NGX_CONF_ERROR;
diff --git a/src/http/ngx_http_request.c b/src/http/ngx_http_request.c
index cb970c5…96cec55 100644
— a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -1642,7 +1642,9 @@ ngx_http_process_request(ngx_http_request_t *r)
if (sscf->verify) {
rc = SSL_get_verify_result(c->ssl->connection);

  •        if (rc != X509_V_OK) {
    
  •        if ((sscf->verify != 3 && rc != X509_V_OK)
    
  •            || !(sscf->verify == 3 &&
    

ngx_ssl_verify_error_is_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));

Posted at Nginx Forum: