mirror of
https://github.com/nginx/nginx.git
synced 2024-12-20 14:13:33 -06:00
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.
This commit is contained in:
parent
78e1a8ed7f
commit
faec547771
@ -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;
|
||||
};
|
||||
|
||||
|
||||
|
@ -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_commands[] = {
|
||||
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_mail_session_t *s, ngx_pool_t *pool,
|
||||
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_mail_session_t *s, ngx_pool_t *pool,
|
||||
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_mail_session_t *s, ngx_pool_t *pool,
|
||||
+ 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_mail_session_t *s, ngx_pool_t *pool,
|
||||
|
||||
#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_t *cf)
|
||||
}
|
||||
|
||||
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 *cf, void *parent, void *child)
|
||||
|
||||
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;
|
||||
|
@ -16,6 +16,8 @@ static void ngx_mail_init_session(ngx_connection_t *c);
|
||||
#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_connection_t *c)
|
||||
|
||||
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_connection_t *c)
|
||||
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
|
||||
|
||||
|
||||
|
@ -52,7 +52,9 @@ static ngx_mail_protocol_t ngx_mail_imap_protocol = {
|
||||
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)
|
||||
};
|
||||
|
||||
|
||||
|
@ -58,7 +58,9 @@ static ngx_mail_protocol_t ngx_mail_pop3_protocol = {
|
||||
ngx_mail_pop3_parse_command,
|
||||
ngx_mail_pop3_auth_state,
|
||||
|
||||
ngx_string("-ERR internal server error" CRLF)
|
||||
ngx_string("-ERR internal server error" CRLF),
|
||||
ngx_string("-ERR SSL certificate error" CRLF),
|
||||
ngx_string("-ERR No required SSL certificate" CRLF)
|
||||
};
|
||||
|
||||
|
||||
|
@ -45,7 +45,9 @@ static ngx_mail_protocol_t ngx_mail_smtp_protocol = {
|
||||
ngx_mail_smtp_parse_command,
|
||||
ngx_mail_smtp_auth_state,
|
||||
|
||||
ngx_string("451 4.3.2 Internal server error" CRLF)
|
||||
ngx_string("451 4.3.2 Internal server error" CRLF),
|
||||
ngx_string("421 4.7.1 SSL certificate error" CRLF),
|
||||
ngx_string("421 4.7.1 No required SSL certificate" CRLF)
|
||||
};
|
||||
|
||||
|
||||
|
@ -46,6 +46,15 @@ static ngx_conf_bitmask_t ngx_mail_ssl_protocols[] = {
|
||||
};
|
||||
|
||||
|
||||
static ngx_conf_enum_t ngx_mail_ssl_verify[] = {
|
||||
{ ngx_string("off"), 0 },
|
||||
{ ngx_string("on"), 1 },
|
||||
{ ngx_string("optional"), 2 },
|
||||
{ ngx_string("optional_no_ca"), 3 },
|
||||
{ ngx_null_string, 0 }
|
||||
};
|
||||
|
||||
|
||||
static ngx_command_t ngx_mail_ssl_commands[] = {
|
||||
|
||||
{ ngx_string("ssl"),
|
||||
@ -146,6 +155,41 @@ static ngx_command_t ngx_mail_ssl_commands[] = {
|
||||
offsetof(ngx_mail_ssl_conf_t, session_timeout),
|
||||
NULL },
|
||||
|
||||
{ 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_TAKE1,
|
||||
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_string("ssl_crl"),
|
||||
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, crl),
|
||||
NULL },
|
||||
|
||||
ngx_null_command
|
||||
};
|
||||
|
||||
@ -198,6 +242,9 @@ ngx_mail_ssl_create_conf(ngx_conf_t *cf)
|
||||
* scf->certificate_key = { 0, NULL };
|
||||
* scf->dhparam = { 0, NULL };
|
||||
* scf->ecdh_curve = { 0, NULL };
|
||||
* scf->client_certificate = { 0, NULL };
|
||||
* scf->trusted_certificate = { 0, NULL };
|
||||
* scf->crl = { 0, NULL };
|
||||
* scf->ciphers = { 0, NULL };
|
||||
* scf->shm_zone = NULL;
|
||||
*/
|
||||
@ -206,6 +253,8 @@ ngx_mail_ssl_create_conf(ngx_conf_t *cf)
|
||||
scf->starttls = NGX_CONF_UNSET_UINT;
|
||||
scf->passwords = NGX_CONF_UNSET_PTR;
|
||||
scf->prefer_server_ciphers = NGX_CONF_UNSET;
|
||||
scf->verify = NGX_CONF_UNSET_UINT;
|
||||
scf->verify_depth = NGX_CONF_UNSET_UINT;
|
||||
scf->builtin_session_cache = NGX_CONF_UNSET;
|
||||
scf->session_timeout = NGX_CONF_UNSET;
|
||||
scf->session_tickets = NGX_CONF_UNSET;
|
||||
@ -238,6 +287,9 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
(NGX_CONF_BITMASK_SET|NGX_SSL_SSLv3|NGX_SSL_TLSv1
|
||||
|NGX_SSL_TLSv1_1|NGX_SSL_TLSv1_2));
|
||||
|
||||
ngx_conf_merge_uint_value(conf->verify, prev->verify, 0);
|
||||
ngx_conf_merge_uint_value(conf->verify_depth, prev->verify_depth, 1);
|
||||
|
||||
ngx_conf_merge_str_value(conf->certificate, prev->certificate, "");
|
||||
ngx_conf_merge_str_value(conf->certificate_key, prev->certificate_key, "");
|
||||
|
||||
@ -248,6 +300,12 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
ngx_conf_merge_str_value(conf->ecdh_curve, prev->ecdh_curve,
|
||||
NGX_DEFAULT_ECDH_CURVE);
|
||||
|
||||
ngx_conf_merge_str_value(conf->client_certificate,
|
||||
prev->client_certificate, "");
|
||||
ngx_conf_merge_str_value(conf->trusted_certificate,
|
||||
prev->trusted_certificate, "");
|
||||
ngx_conf_merge_str_value(conf->crl, prev->crl, "");
|
||||
|
||||
ngx_conf_merge_str_value(conf->ciphers, prev->ciphers, NGX_DEFAULT_CIPHERS);
|
||||
|
||||
|
||||
@ -320,6 +378,35 @@ ngx_mail_ssl_merge_conf(ngx_conf_t *cf, void *parent, void *child)
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (conf->verify) {
|
||||
|
||||
if (conf->client_certificate.len == 0 && conf->verify != 3) {
|
||||
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 (ngx_ssl_trusted_certificate(cf, &conf->ssl,
|
||||
&conf->trusted_certificate,
|
||||
conf->verify_depth)
|
||||
!= NGX_OK)
|
||||
{
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
|
||||
if (ngx_ssl_crl(cf, &conf->ssl, &conf->crl) != NGX_OK) {
|
||||
return NGX_CONF_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
if (SSL_CTX_set_cipher_list(conf->ssl.ctx,
|
||||
(const char *) conf->ciphers.data)
|
||||
== 0)
|
||||
|
@ -28,6 +28,9 @@ typedef struct {
|
||||
ngx_uint_t starttls;
|
||||
ngx_uint_t protocols;
|
||||
|
||||
ngx_uint_t verify;
|
||||
ngx_uint_t verify_depth;
|
||||
|
||||
ssize_t builtin_session_cache;
|
||||
|
||||
time_t session_timeout;
|
||||
@ -36,6 +39,9 @@ typedef struct {
|
||||
ngx_str_t certificate_key;
|
||||
ngx_str_t dhparam;
|
||||
ngx_str_t ecdh_curve;
|
||||
ngx_str_t client_certificate;
|
||||
ngx_str_t trusted_certificate;
|
||||
ngx_str_t crl;
|
||||
|
||||
ngx_str_t ciphers;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user