[nginx] Mail: client SSL certificates support.
Maxim Dounin
mdounin at mdounin.ru
Wed Feb 25 14:58:34 UTC 2015
details: http://hg.nginx.org/nginx/rev/ec01b1d1fff1
branches:
changeset: 5989:ec01b1d1fff1
user: Maxim Dounin <mdounin at mdounin.ru>
date: Wed Feb 25 17:48:05 2015 +0300
description:
Mail: client SSL certificates support.
The "ssl_verify_client", "ssl_verify_depth", "ssl_client_certificate",
"ssl_trusted_certificate", and "ssl_crl" directives introduced to control
SSL client certificate verification in mail proxy module.
If there is a certificate, detail of the certificate are passed to
the auth_http script configured via Auth-SSL-Verify, Auth-SSL-Subject,
Auth-SSL-Issuer, Auth-SSL-Serial, Auth-SSL-Fingerprint headers. If
the auth_http_pass_client_cert directive is set, client certificate
in PEM format will be passed in the Auth-SSL-Cert header (urlencoded).
If there is no required certificate provided during an SSL handshake
or certificate verification fails then a protocol-specific error is
returned after the SSL handshake and the connection is closed.
Based on previous work by Sven Peter, Franck Levionnois and Filipe Da Silva.
diffstat:
src/mail/ngx_mail.h | 2 +
src/mail/ngx_mail_auth_http_module.c | 120 ++++++++++++++++++++++++++++++++++-
src/mail/ngx_mail_handler.c | 71 ++++++++++++++++++++
src/mail/ngx_mail_imap_module.c | 4 +-
src/mail/ngx_mail_pop3_module.c | 4 +-
src/mail/ngx_mail_smtp_module.c | 4 +-
src/mail/ngx_mail_ssl_module.c | 87 +++++++++++++++++++++++++
src/mail/ngx_mail_ssl_module.h | 6 +
8 files changed, 294 insertions(+), 4 deletions(-)
diffs (truncated from 492 to 300 lines):
diff --git a/src/mail/ngx_mail.h b/src/mail/ngx_mail.h
--- a/src/mail/ngx_mail.h
+++ b/src/mail/ngx_mail.h
@@ -336,6 +336,8 @@ struct ngx_mail_protocol_s {
ngx_mail_auth_state_pt auth_state;
ngx_str_t internal_server_error;
+ ngx_str_t cert_error;
+ ngx_str_t no_cert;
};
diff --git a/src/mail/ngx_mail_auth_http_module.c b/src/mail/ngx_mail_auth_http_module.c
--- a/src/mail/ngx_mail_auth_http_module.c
+++ b/src/mail/ngx_mail_auth_http_module.c
@@ -16,6 +16,7 @@ typedef struct {
ngx_addr_t *peer;
ngx_msec_t timeout;
+ ngx_flag_t pass_client_cert;
ngx_str_t host_header;
ngx_str_t uri;
@@ -106,6 +107,13 @@ static ngx_command_t ngx_mail_auth_http
0,
NULL },
+ { ngx_string("auth_http_pass_client_cert"),
+ NGX_MAIL_MAIN_CONF|NGX_MAIL_SRV_CONF|NGX_CONF_FLAG,
+ ngx_conf_set_flag_slot,
+ NGX_MAIL_SRV_CONF_OFFSET,
+ offsetof(ngx_mail_auth_http_conf_t, pass_client_cert),
+ NULL },
+
ngx_null_command
};
@@ -1143,6 +1151,11 @@ ngx_mail_auth_http_create_request(ngx_ma
size_t len;
ngx_buf_t *b;
ngx_str_t login, passwd;
+#if (NGX_MAIL_SSL)
+ ngx_str_t verify, subject, issuer, serial, fingerprint,
+ raw_cert, cert;
+ ngx_connection_t *c;
+#endif
ngx_mail_core_srv_conf_t *cscf;
if (ngx_mail_auth_http_escape(pool, &s->login, &login) != NGX_OK) {
@@ -1153,6 +1166,61 @@ ngx_mail_auth_http_create_request(ngx_ma
return NULL;
}
+#if (NGX_MAIL_SSL)
+
+ c = s->connection;
+
+ if (c->ssl) {
+
+ /* certificate details */
+
+ if (ngx_ssl_get_client_verify(c, pool, &verify) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ngx_ssl_get_subject_dn(c, pool, &subject) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ngx_ssl_get_issuer_dn(c, pool, &issuer) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ngx_ssl_get_serial_number(c, pool, &serial) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ngx_ssl_get_fingerprint(c, pool, &fingerprint) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ahcf->pass_client_cert) {
+
+ /* certificate itself, if configured */
+
+ if (ngx_ssl_get_raw_certificate(c, pool, &raw_cert) != NGX_OK) {
+ return NULL;
+ }
+
+ if (ngx_mail_auth_http_escape(pool, &raw_cert, &cert) != NGX_OK) {
+ return NULL;
+ }
+
+ } else {
+ ngx_str_null(&cert);
+ }
+
+ } else {
+ ngx_str_null(&verify);
+ ngx_str_null(&subject);
+ ngx_str_null(&issuer);
+ ngx_str_null(&serial);
+ ngx_str_null(&fingerprint);
+ ngx_str_null(&cert);
+ }
+
+#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
@@ -1175,6 +1243,13 @@ ngx_mail_auth_http_create_request(ngx_ma
+ sizeof("Auth-SMTP-To: ") - 1 + s->smtp_to.len + sizeof(CRLF) - 1
#if (NGX_MAIL_SSL)
+ sizeof("Auth-SSL: on" CRLF) - 1
+ + sizeof("Auth-SSL-Verify: ") - 1 + verify.len + sizeof(CRLF) - 1
+ + sizeof("Auth-SSL-Subject: ") - 1 + subject.len + sizeof(CRLF) - 1
+ + sizeof("Auth-SSL-Issuer: ") - 1 + issuer.len + sizeof(CRLF) - 1
+ + sizeof("Auth-SSL-Serial: ") - 1 + serial.len + sizeof(CRLF) - 1
+ + sizeof("Auth-SSL-Fingerprint: ") - 1 + fingerprint.len
+ + sizeof(CRLF) - 1
+ + sizeof("Auth-SSL-Cert: ") - 1 + cert.len + sizeof(CRLF) - 1
#endif
+ ahcf->header.len
+ sizeof(CRLF) - 1;
@@ -1260,9 +1335,49 @@ ngx_mail_auth_http_create_request(ngx_ma
#if (NGX_MAIL_SSL)
- if (s->connection->ssl) {
+ if (c->ssl) {
b->last = ngx_cpymem(b->last, "Auth-SSL: on" CRLF,
sizeof("Auth-SSL: on" CRLF) - 1);
+
+ b->last = ngx_cpymem(b->last, "Auth-SSL-Verify: ",
+ sizeof("Auth-SSL-Verify: ") - 1);
+ b->last = ngx_copy(b->last, verify.data, verify.len);
+ *b->last++ = CR; *b->last++ = LF;
+
+ if (subject.len) {
+ b->last = ngx_cpymem(b->last, "Auth-SSL-Subject: ",
+ sizeof("Auth-SSL-Subject: ") - 1);
+ b->last = ngx_copy(b->last, subject.data, subject.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (issuer.len) {
+ b->last = ngx_cpymem(b->last, "Auth-SSL-Issuer: ",
+ sizeof("Auth-SSL-Issuer: ") - 1);
+ b->last = ngx_copy(b->last, issuer.data, issuer.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (serial.len) {
+ b->last = ngx_cpymem(b->last, "Auth-SSL-Serial: ",
+ sizeof("Auth-SSL-Serial: ") - 1);
+ b->last = ngx_copy(b->last, serial.data, serial.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (fingerprint.len) {
+ b->last = ngx_cpymem(b->last, "Auth-SSL-Fingerprint: ",
+ sizeof("Auth-SSL-Fingerprint: ") - 1);
+ b->last = ngx_copy(b->last, fingerprint.data, fingerprint.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
+
+ if (cert.len) {
+ b->last = ngx_cpymem(b->last, "Auth-SSL-Cert: ",
+ sizeof("Auth-SSL-Cert: ") - 1);
+ b->last = ngx_copy(b->last, cert.data, cert.len);
+ *b->last++ = CR; *b->last++ = LF;
+ }
}
#endif
@@ -1328,6 +1443,7 @@ ngx_mail_auth_http_create_conf(ngx_conf_
}
ahcf->timeout = NGX_CONF_UNSET_MSEC;
+ ahcf->pass_client_cert = NGX_CONF_UNSET;
ahcf->file = cf->conf_file->file.name.data;
ahcf->line = cf->conf_file->line;
@@ -1363,6 +1479,8 @@ ngx_mail_auth_http_merge_conf(ngx_conf_t
ngx_conf_merge_msec_value(conf->timeout, prev->timeout, 60000);
+ ngx_conf_merge_value(conf->pass_client_cert, prev->pass_client_cert, 0);
+
if (conf->headers == NULL) {
conf->headers = prev->headers;
conf->header = prev->header;
diff --git a/src/mail/ngx_mail_handler.c b/src/mail/ngx_mail_handler.c
--- a/src/mail/ngx_mail_handler.c
+++ b/src/mail/ngx_mail_handler.c
@@ -16,6 +16,8 @@ static void ngx_mail_init_session(ngx_co
#if (NGX_MAIL_SSL)
static void ngx_mail_ssl_init_connection(ngx_ssl_t *ssl, ngx_connection_t *c);
static void ngx_mail_ssl_handshake_handler(ngx_connection_t *c);
+static ngx_int_t ngx_mail_verify_cert(ngx_mail_session_t *s,
+ ngx_connection_t *c);
#endif
@@ -247,6 +249,10 @@ ngx_mail_ssl_handshake_handler(ngx_conne
s = c->data;
+ if (ngx_mail_verify_cert(s, c) != NGX_OK) {
+ return;
+ }
+
if (s->starttls) {
cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
@@ -267,6 +273,71 @@ ngx_mail_ssl_handshake_handler(ngx_conne
ngx_mail_close_connection(c);
}
+
+static ngx_int_t
+ngx_mail_verify_cert(ngx_mail_session_t *s, ngx_connection_t *c)
+{
+ long rc;
+ X509 *cert;
+ ngx_mail_ssl_conf_t *sslcf;
+ ngx_mail_core_srv_conf_t *cscf;
+
+ sslcf = ngx_mail_get_module_srv_conf(s, ngx_mail_ssl_module);
+
+ if (!sslcf->verify) {
+ return NGX_OK;
+ }
+
+ rc = SSL_get_verify_result(c->ssl->connection);
+
+ if (rc != X509_V_OK
+ && (sslcf->verify != 3 || !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_ssl_remove_cached_session(sslcf->ssl.ctx,
+ (SSL_get0_session(c->ssl->connection)));
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ s->out = cscf->protocol->cert_error;
+ s->quit = 1;
+
+ c->write->handler = ngx_mail_send;
+
+ ngx_mail_send(s->connection->write);
+ return NGX_ERROR;
+ }
+
+ if (sslcf->verify == 1) {
+ 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_ssl_remove_cached_session(sslcf->ssl.ctx,
+ (SSL_get0_session(c->ssl->connection)));
+
+ cscf = ngx_mail_get_module_srv_conf(s, ngx_mail_core_module);
+
+ s->out = cscf->protocol->no_cert;
+ s->quit = 1;
+
+ c->write->handler = ngx_mail_send;
+
+ ngx_mail_send(s->connection->write);
+ return NGX_ERROR;
+ }
+
+ X509_free(cert);
+ }
+
+ return NGX_OK;
+}
+
#endif
diff --git a/src/mail/ngx_mail_imap_module.c b/src/mail/ngx_mail_imap_module.c
--- a/src/mail/ngx_mail_imap_module.c
+++ b/src/mail/ngx_mail_imap_module.c
@@ -52,7 +52,9 @@ static ngx_mail_protocol_t ngx_mail_ima
ngx_mail_imap_parse_command,
ngx_mail_imap_auth_state,
- ngx_string("* BAD internal server error" CRLF)
+ ngx_string("* BAD internal server error" CRLF),
+ ngx_string("* BYE SSL certificate error" CRLF),
+ ngx_string("* BYE No required SSL certificate" CRLF)
};
More information about the nginx-devel
mailing list