Opened 9 years ago
Closed 7 years ago
#857 closed enhancement (fixed)
Problem with multiline $ssl_client_cert HTTP header in proxy mode
Reported by: | Owned by: | ||
---|---|---|---|
Priority: | minor | Milestone: | |
Component: | nginx-core | Version: | 1.9.x |
Keywords: | ssl cerificate proxy header folding multiline | Cc: | |
uname -a: | Linux test1 2.6.32-573.7.1.el6.x86_64 #1 SMP Tue Sep 22 22:00:00 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux | ||
nginx -V: |
nginx version: nginx/1.9.6
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-16) (GCC) built with OpenSSL 1.0.1e-fips 11 Feb 2013 TLS SNI support enabled configure arguments: --prefix=/etc/nginx --sbin-path=/usr/sbin/nginx --conf-path=/etc/nginx/nginx.conf --error-log-path=/var/log/nginx/error.log --http-log-path=/var/log/nginx/access.log --pid-path=/var/run/nginx.pid --lock-path=/var/run/nginx.lock --http-client-body-temp-path=/var/cache/nginx/client_temp --http-proxy-temp-path=/var/cache/nginx/proxy_temp --http-fastcgi-temp-path=/var/cache/nginx/fastcgi_temp --http-uwsgi-temp-path=/var/cache/nginx/uwsgi_temp --http-scgi-temp-path=/var/cache/nginx/scgi_temp --user=nginx --group=nginx --with-http_ssl_module --with-http_realip_module --with-http_addition_module --with-http_sub_module --with-http_dav_module --with-http_flv_module --with-http_mp4_module --with-http_gunzip_module --with-http_gzip_static_module --with-http_random_index_module --with-http_secure_link_module --with-http_stub_status_module --with-http_auth_request_module --with-threads --with-stream --with-stream_ssl_module --with-mail --with-mail_ssl_module --with-file-aio --with-ipv6 --with-http_v2_module --with-cc-opt='-O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector --param=ssp-buffer-size=4 -m64 -mtune=generic' |
Description
$ssl_client_cert
variable is multiline while RFC7230 deprecated folding headers. I want to send certificate info to Jetty server which refuses it with HTTP/1.1 400 Bad Continuation
error. I thought it was Jetty error and filled bug report to them: https://bugs.eclipse.org/bugs/show_bug.cgi?id=483795
In nginx documentation there is info that this folding is on purpose
http://nginx.org/en/docs/http/ngx_http_ssl_module.html
:
$ssl_client_cert
returns the client certificate in the PEM format for an established SSL connection, with each line except the first prepended with the tab character; this is intended for the use in the proxy_set_header directive;
I think those headers should be sent in one line.
Change History (9)
comment:1 by , 9 years ago
comment:2 by , 9 years ago
Component: | nginx-module → nginx-core |
---|---|
Status: | new → accepted |
Type: | defect → enhancement |
This is certainly isn't a bug, as it works as intended and exactly as documented. Whether or not it will work with your backend is another question. But given the fact that header folding is indeed deprecated and not supported even by nginx itself, we certainly need a better way to send client certificates to backends.
comment:3 by , 9 years ago
This has been widely reported in the past, and a hacky solution has been provided: https://forum.nginx.org/read.php?29,249804,249848#msg-249848
Furthermore, a patch has also been submitted: https://forum.nginx.org/read.php?2,236546,236596
comment:4 by , 8 years ago
I've been bitten by this exact problem, here's my proposed fix, which I'll post to the mailing list as soon as I'm allowed to:
diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c index 5c7734d..48ddae0 100644 --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3434,6 +3434,42 @@ ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) return NGX_OK; } +ngx_int_t +ngx_ssl_get_bare_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) +{ + size_t len; + ngx_uint_t i; + ngx_str_t cert; + + if (ngx_ssl_get_raw_certificate(c, pool, &cert) != NGX_OK) { + return NGX_ERROR; + } + + if (cert.len == 0) { + s->len = 0; + return NGX_OK; + } + + // The stripped certificate is never going to be bigger than the input. + // but it's not going to be a whole lot longer, so we'll just allocate the same number of bytes. + s->data = ngx_pnalloc(pool, cert.len); + if (s->data == NULL) { + return NGX_ERROR; + } + + len = 0; + for (i = 0; i < cert.len - 1; i++) { + u_char ch = cert.data[i]; + if (ch != LF) { + s->data[len++] = ch; + } + } + + s->len = len; + + return NGX_OK; +} + ngx_int_t ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h index d233c02..62cc6e8 100644 --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -201,6 +201,8 @@ ngx_int_t ngx_ssl_get_raw_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_get_bare_certificate(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); ngx_int_t ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool, diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c index e75f5d8..889fd18 100644 --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -288,6 +288,9 @@ static ngx_http_variable_t ngx_http_ssl_vars[] = { { ngx_string("ssl_client_cert"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_bare_cert"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_get_bare_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_raw_cert"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_raw_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 },
comment:5 by , 7 years ago
We use apache httpd as a backend server.
Changes with Apache 2.4.24:
SECURITY: CVE-2016-8743 (cve.mitre.org)
Enforce HTTP request grammar corresponding to RFC7230 for request lines
and request headers, to prevent response splitting and cache pollution by
malicious clients or downstream proxies. [William Rowe, Stefan Fritsch]
$ssl_client_cert with proxy_set_header does not work anymore.
We will be happy with any solution from thread https://forum.nginx.org/read.php?29,271154,271154#msg-271154
New variable, encoding, plain spaces, anything other than "ignore 857"
comment:7 by , 7 years ago
The following patch introduces the $ssl_client_escaped_cert variable.
# HG changeset patch # User Maxim Dounin <mdounin@mdounin.ru> # Date 1502810183 -10800 # Tue Aug 15 18:16:23 2017 +0300 # Node ID 4354e33536c62c9cd1b911d0fbd69e4243da83e8 # Parent a2f5e25d6a283546f76435b9fc3e7e814b092bae SSL: the $ssl_client_escaped_cert variable (ticket #857). This variable contains URL-encoded client SSL certificate. In contrast to $ssl_client_cert, it doesn't depend on deprecated header continuation. The NGX_ESCAPE_URI_COMPONENT variant of encoding is used, so the resulting variable can be safely used not only in headers, but also as a request argument. The $ssl_client_cert variable should be considered deprecated now. The $ssl_client_raw_cert variable will be eventually renambed back to $ssl_client_cert. diff --git a/src/event/ngx_event_openssl.c b/src/event/ngx_event_openssl.c --- a/src/event/ngx_event_openssl.c +++ b/src/event/ngx_event_openssl.c @@ -3663,6 +3663,36 @@ ngx_ssl_get_certificate(ngx_connection_t ngx_int_t +ngx_ssl_get_escaped_certificate(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s) +{ + ngx_str_t cert; + uintptr_t n; + + if (ngx_ssl_get_raw_certificate(c, pool, &cert) != NGX_OK) { + return NGX_ERROR; + } + + if (cert.len == 0) { + s->len = 0; + return NGX_OK; + } + + n = ngx_escape_uri(NULL, cert.data, cert.len, NGX_ESCAPE_URI_COMPONENT); + + s->len = cert.len + n * 2; + s->data = ngx_pnalloc(pool, s->len); + if (s->data == NULL) { + return NGX_ERROR; + } + + ngx_escape_uri(s->data, cert.data, cert.len, NGX_ESCAPE_URI_COMPONENT); + + return NGX_OK; +} + + +ngx_int_t ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s) { BIO *bio; diff --git a/src/event/ngx_event_openssl.h b/src/event/ngx_event_openssl.h --- a/src/event/ngx_event_openssl.h +++ b/src/event/ngx_event_openssl.h @@ -212,6 +212,8 @@ ngx_int_t ngx_ssl_get_raw_certificate(ng ngx_str_t *s); ngx_int_t ngx_ssl_get_certificate(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); +ngx_int_t ngx_ssl_get_escaped_certificate(ngx_connection_t *c, ngx_pool_t *pool, + ngx_str_t *s); ngx_int_t ngx_ssl_get_subject_dn(ngx_connection_t *c, ngx_pool_t *pool, ngx_str_t *s); ngx_int_t ngx_ssl_get_issuer_dn(ngx_connection_t *c, ngx_pool_t *pool, diff --git a/src/http/modules/ngx_http_ssl_module.c b/src/http/modules/ngx_http_ssl_module.c --- a/src/http/modules/ngx_http_ssl_module.c +++ b/src/http/modules/ngx_http_ssl_module.c @@ -299,6 +299,10 @@ static ngx_http_variable_t ngx_http_ssl (uintptr_t) ngx_ssl_get_raw_certificate, NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_escaped_cert"), NULL, ngx_http_ssl_variable, + (uintptr_t) ngx_ssl_get_escaped_certificate, + NGX_HTTP_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_s_dn"), NULL, ngx_http_ssl_variable, (uintptr_t) ngx_ssl_get_subject_dn, NGX_HTTP_VAR_CHANGEABLE, 0 }, diff --git a/src/stream/ngx_stream_ssl_module.c b/src/stream/ngx_stream_ssl_module.c --- a/src/stream/ngx_stream_ssl_module.c +++ b/src/stream/ngx_stream_ssl_module.c @@ -249,6 +249,10 @@ static ngx_stream_variable_t ngx_stream (uintptr_t) ngx_ssl_get_raw_certificate, NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_escaped_cert"), NULL, ngx_stream_ssl_variable, + (uintptr_t) ngx_ssl_get_escaped_certificate, + NGX_STREAM_VAR_CHANGEABLE, 0 }, + { ngx_string("ssl_client_s_dn"), NULL, ngx_stream_ssl_variable, (uintptr_t) ngx_ssl_get_subject_dn, NGX_STREAM_VAR_CHANGEABLE, 0 },
comment:9 by , 7 years ago
Resolution: | → fixed |
---|---|
Status: | accepted → closed |
The $ssl_client_escaped_cert variable will be available in the next nginx release, 1.13.5. Thanks for prodding this.
Header folding has been deprecated in the latest RFC http://tools.ietf.org/html/rfc7230#section-3.2.4