互联网的通信安全,建立在SSL/TLS协议之上,SSL/TLS协议的基本思路是采用公钥加密法(非对称加密如RSA2048),即客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。
由于SSL每次握手都会涉及RSA加解密,因此对服务器的CPU消耗极大,在SSLv3及更早版本就实现了基于session cache的方案来解决该问题。
Session cache方案的基本原理就是每次会话有个session id,每个session id对应一个session(ngx_ssl_session_t),得到该session就不用传证书和RSA解密了。
typedef struct {
ngx_flag_t enable;
ngx_ssl_t ssl;
ngx_flag_t prefer_server_ciphers;
ngx_uint_t protocols;
ngx_uint_t verify;
ngx_uint_t verify_depth;
size_t buffer_size;
ssize_t builtin_session_cache;
time_t session_timeout; //与指令 ssl_session_timeout有关
ngx_str_t certificate; //与指令 ssl_certificate有关
ngx_str_t certificate_key; //与指令 ssl_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;
ngx_array_t *passwords;
ngx_shm_zone_t *shm_zone; //与指令 ssl_session_cache有关
ngx_flag_t session_tickets;
ngx_array_t *session_ticket_keys;
ngx_flag_t stapling;
ngx_flag_t stapling_verify;
ngx_str_t stapling_file;
ngx_str_t stapling_responder;
u_char *file;
ngx_uint_t line;
} ngx_http_ssl_srv_conf_t;
typedef struct {
SSL_CTX *ctx; //SSL_CTX_new(SSLv23_method())创建,merge server的时候创建
ngx_log_t *log;
size_t buffer_size; //与指令ssl_buffer_size有关
} ngx_ssl_t;
typedef struct {
ngx_ssl_conn_t *connection; //SSL_new(),openssl库连接,每次请求都会创建一个
SSL_CTX *session_ctx; //ssl->ctx
ngx_int_t last;
ngx_buf_t *buf;
size_t buffer_size;
ngx_connection_handler_pt handler;
ngx_event_handler_pt saved_read_handler;
ngx_event_handler_pt saved_write_handler;
unsigned handshaked:1;
unsigned renegotiation:1;
unsigned buffer:1;
unsigned no_wait_shutdown:1;
unsigned no_send_shutdown:1;
unsigned handshake_buffer_set:1;
} ngx_ssl_connection_t;
server {
listen 80;
listen 443 ssl;
ssl_certificate cert/upyun.pem;
ssl_certificate_key cert/upyun.key;
ssl_protocols SSLv3 TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers AES128-SHA:AES256-SHA:AES128-SHA256:AES256-SHA256:AES128-GCM-SHA256:AES256-GCM-SHA384:ECDHE-RSA-AES128-SHA256:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:DES-CBC3-SHA:RC4-SHA;
ssl_prefer_server_ciphers on;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 30m;
location /{
index index.html;
}
}
(1)$ssl_cipher:返回建立的SSL连接中那些使用的密码字段,如TLSv1;
(2)$ssl_client_serial:返回建立的SSL连接中客户端证书的序列号;
(3)$ssl_client_s_dn:返回建立的SSL连接中客户端证书的DN主题字段;
(4)$ssl_client_i_dn:返回建立的SSL连接中客户端证书的DN发行者字段;
(5)$ssl_protocol:返回建立的SSL连接中使用的协议,如AES128-SHA;
(6)$ssl_session_id:session ID(64字节长度);
(7)$ssl_client_cert:
(8)$ssl_client_raw_cert:
(9)$ssl_verify:如果客户端证书审核通过,这个变量为SUCCESS;
备注:可以通过access_log来观察session new/reused。
syntax:ssl [on|off]
default:ssl off
context:http/server
指令功能:为指定的server开启HTTPS,建议直接在listen字段后加上ssl开启。
syntax:ssl_certificate file
default:ssl_certificate cert.pem
context:http/server
指令功能:为这个虚拟主机指定PEM格式的证书文件。
syntax:ssl_certificate_key file
default:ssl_certificate_key cert.pem
context:http/server
指令功能:为这个虚拟主机指定PEM格式的密钥。
syntax:ssl_protocols [SSLv2] [SSLv3] [TLSv1]
default:ssl_protocols SSLv2 SSLv3 TLSv1
context:http/server
指令功能:开启指定的SSL协议,TLSv1.1和TLSv1.2在openssl-1.0.1或更高版本才支持。
syntax:ssl_ciphers file
default:ssl_ciphers ALL:!ADH:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP
context:http/server
指令功能:指出允许的密码,密码指定为OpenSSL支持的格式,如:
ssl_ciphers AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
备注:可以使用openssl下列命令查看完整格式列表:
#openssl ciphers
syntax:ssl_prefer_server_ciphers [on|off]
default:ssl_prefer_server_ciphers off
context:http/server
指令功能:服务器密码将优先于客户端密码。
syntax:ssl_session_cache off|none|builtin:size and/or shared:name:size
default:ssl_session_cache off
context:http/server
指令功能:设置储存SSL会话的缓存类型和大小,线上一般这样设置ssl_session_cache shared:SSL:10m;
缓存类型有以下几种:
(1)off:强制关闭,告诉客户端这个会话已经不能被再次使用;
(2)none:非强制关闭,告诉客户端这个会话可以被再次使用,但是nginx实际上并不使用它们,这是为某些使用ssl_session_cache的邮件客户端提供的一种变通方案,可以使用在邮件代理和HTTP服务器中;
(3)builtin:内建OpenSSL缓存,仅能用在一个工作进程中,缓存大小在会话总数中指定,如果要使用这个类型可能会引起内存碎片问题;
(4)shared:缓存在所有的工作进程中共享,单位为字节,1MB缓存大概保存4000个会话,每个共享的缓存必须有自己的名称,相同名称的缓存可以使用在不同的虚拟主机中;
syntax:ssl_session_timeout time
default:ssl_session_timeout 5m
context: http/server
指令功能:设置客户端能够反复使用储缓存中的会话超时时间,这个模块支持一些非标准的HTTP错误代码,可以借助error_page指令用于调试。
(1)495:检查客户端证书时发生错误;
(2)496:客户端无法提供必须的证书;
(3)497:传送到HTTPS的普通请求;
备注:在解析完配置后,对listener添加回调函数nginx_http_init_connection。
如果访问的是http协议,ngx_http_wait_request_handler是入口函数:
如果访问的是https协议,ngx_http_ssl_handshark是入口函数,即先进行SSL握手,再处理HTTP请求:
函数功能:https协议的入口处理函数。
static void
ngx_http_ssl_handshake(ngx_event_t *rev)
{
。。。
c = rev->data;
hc = c->data;
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"http check ssl handshake");
//超时关闭连接
if (rev->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
ngx_http_close_connection(c);
return;
}
if (c->close) {
ngx_http_close_connection(c);
return;
}
size = hc->proxy_protocol ? sizeof(buf) : 1; // 1
//首字节预读,不会从缓存中实际读出数据
n = recv(c->fd, (char *) buf, size, MSG_PEEK);
err = ngx_socket_errno;
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0, "http recv(): %z", n);
if (n == -1) {
//数据还未准备好
if (err == NGX_EAGAIN) {
rev->ready = 0;
if (!rev->timer_set) {
ngx_add_timer(rev, c->listening->post_accept_timeout);
ngx_reusable_connection(c, 1);
}
if (ngx_handle_read_event(rev, 0) != NGX_OK) {
ngx_http_close_connection(c);
}
return;
}
ngx_connection_error(c, err, "recv() failed");
ngx_http_close_connection(c);
return;
}
if (hc->proxy_protocol) {
…
}
//首字节探测,判断是否是HTTPS请求
if (n == 1) {
if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */) {
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, rev->log, 0,
"https ssl handshake: 0x%02Xd", buf[0]);
sscf = ngx_http_get_module_srv_conf(hc->conf_ctx,
ngx_http_ssl_module);
//创建SSL连接,会调用SSL_new()
if (ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
!= NGX_OK)
{
ngx_http_close_connection(c);
return;
}
//SSL握手,连接双方会在SSL握手时交换相关数据(版本、加密算法、server端的公钥等)
rc = ngx_ssl_handshake(c);
//可能需要多次交互,才能完成SSL握手
if (rc == NGX_AGAIN) {
if (!rev->timer_set) {
ngx_add_timer(rev, c->listening->post_accept_timeout);
}
ngx_reusable_connection(c, 0);
c->ssl->handler = ngx_http_ssl_handshake_handler;
return;
}
ngx_http_ssl_handshake_handler(c);
return;
}
//普通的HTTP请求,会返回400
ngx_log_debug0(NGX_LOG_DEBUG_HTTP, rev->log, 0, "plain http");
c->log->action = "waiting for request";
rev->handler = ngx_http_wait_request_handler;
ngx_http_wait_request_handler(rev);
return;
}
ngx_log_error(NGX_LOG_INFO, c->log, 0, "client closed connection");
ngx_http_close_connection(c);
}
函数功能:为SSL建立连接做准备,每次请求都会进入,即使长连接状态下c->fd。
ngx_int_t
ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags)
{
ngx_ssl_connection_t *sc;
//nginx对ssl连接的描述结构,记录了ssl连接的信息和状态
sc = ngx_pcalloc(c->pool, sizeof(ngx_ssl_connection_t));
if (sc == NULL) {
return NGX_ERROR;
}
sc->buffer = ((flags & NGX_SSL_BUFFER) != 0);
sc->buffer_size = ssl->buffer_size;
sc->session_ctx = ssl->ctx;
//创建openssl库中国对ssl连接的描述结构
sc->connection = SSL_new(ssl->ctx);
if (sc->connection == NULL) {
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_new() failed");
return NGX_ERROR;
}
//关联(openssl库)ssl连接到socket
if (SSL_set_fd(sc->connection, c->fd) == 0) {
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_fd() failed");
return NGX_ERROR;
}
if (flags & NGX_SSL_CLIENT) {
//发起对后端的ssl连接
SSL_set_connect_state(sc->connection);
} else {
//nginx是服务端
SSL_set_accept_state(sc->connection);
}
//关联(openssl库)ssl连接到用户数据(当前连接)
if (SSL_set_ex_data(sc->connection, ngx_ssl_connection_index, c) == 0) {
ngx_ssl_error(NGX_LOG_ALERT, c->log, 0, "SSL_set_ex_data() failed");
return NGX_ERROR;
}
c->ssl = sc;
return NGX_OK;
}
函数功能:SSL握手,如果SSL握手完成,该函数会替换连接的读写接口,后续就可以对数据进行加解密。
ngx_int_t
ngx_ssl_handshake(ngx_connection_t *c)
{
int n, sslerr;
ngx_err_t err;
ngx_ssl_clear_error(c->log);
n = SSL_do_handshake(c->ssl->connection);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_do_handshake: %d", n);
if (n == 1) { //握手成功
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
return NGX_ERROR;
}
SSL_CIPHER *cipher;
cipher = SSL_get_current_cipher(c->ssl->connection);
if (cipher) {
SSL_CIPHER_description(cipher, &buf[1], 128);
...
if (SSL_session_reused(c->ssl->connection)) {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"SSL reused session");
}
} else {
ngx_log_debug0(NGX_LOG_DEBUG_EVENT, c->log, 0,
"SSL no shared ciphers");
}
}
#endif
c->ssl->handshaked = 1;
//修改读写接口,HTTP的是ngx_unix_recv和ngx_unix_send
c->recv = ngx_ssl_recv;
c->send = ngx_ssl_write;
c->recv_chain = ngx_ssl_recv_chain;
c->send_chain = ngx_ssl_send_chain;
#if OPENSSL_VERSION_NUMBER < 0x10100000L
#ifdef SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS
/* initial handshake done, disable renegotiation (CVE-2009-3555) */
if (c->ssl->connection->s3) {
c->ssl->connection->s3->flags |= SSL3_FLAGS_NO_RENEGOTIATE_CIPHERS;
}
#endif
#endif
return NGX_OK; //接着由ngx_http_ssl_handshake_handler来处理
}
sslerr = SSL_get_error(c->ssl->connection, n);
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
if (sslerr == SSL_ERROR_WANT_READ) {
c->read->ready = 0;
c->read->handler = ngx_ssl_handshake_handler;
c->write->handler = ngx_ssl_handshake_handler;
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
return NGX_ERROR;
}
return NGX_AGAIN;
}
if (sslerr == SSL_ERROR_WANT_WRITE) {
c->write->ready = 0;
c->read->handler = ngx_ssl_handshake_handler;
c->write->handler = ngx_ssl_handshake_handler;
if (ngx_handle_read_event(c->read, 0) != NGX_OK) {
return NGX_ERROR;
}
if (ngx_handle_write_event(c->write, 0) != NGX_OK) {
return NGX_ERROR;
}
return NGX_AGAIN;
}
err = (sslerr == SSL_ERROR_SYSCALL) ? ngx_errno : 0;
c->ssl->no_wait_shutdown = 1;
c->ssl->no_send_shutdown = 1;
c->read->eof = 1;
if (sslerr == SSL_ERROR_ZERO_RETURN || ERR_peek_error() == 0) {
ngx_connection_error(c, err,
"peer closed connection in SSL handshake");
return NGX_ERROR;
}
c->read->error = 1;
ngx_ssl_connection_error(c, sslerr, err, "SSL_do_handshake() failed");
return NGX_ERROR;
}
函数功能:ngx_ssl_handshake返回AGAIN时的回调函数,处理正常或超时等处理是由ngx_http_ssl_handshake_handler来处理。
static void
ngx_ssl_handshake_handler(ngx_event_t *ev)
{
ngx_connection_t *c;
c = ev->data;
ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0,
"SSL handshake handler: %d", ev->write);
if (ev->timedout) {
c->ssl->handler(c); //ngx_http_ssl_handshake_handler
return;
}
if (ngx_ssl_handshake(c) == NGX_AGAIN) {
return;
}
c->ssl->handler(c); //ngx_http_ssl_handshake_handler
}
函数功能:SSL握手成功或失败后的处理函数。
static void
ngx_http_ssl_handshake_handler(ngx_connection_t *c)
{
if (c->ssl->handshaked) { //成功标记
c->ssl->no_wait_shutdown = 1;
。。。
c->log->action = "waiting for request";
c->read->handler = ngx_http_wait_request_handler;
c->write->handler = ngx_http_empty_handler;
ngx_reusable_connection(c, 1);
ngx_http_wait_request_handler(c->read); //处理经过对称加密的数据
return;
}
//SSL握手失败或超时
if (c->read->timedout) {
ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
}
ngx_http_close_connection(c);
}
函数功能:接收对称加密算法的数据,SSL_read,HTTP的recv函数是ngx_unix_recv。
ssize_t
ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)
{
。。。
for ( ;; ) {
//HTTP的是n = recv(c->fd, buf, size, 0)
//buf里的数据是已经解密后的数据
n = SSL_read(c->ssl->connection, buf, size);
。。。。
}
}
函数功能:发送对称加密算法的数据,SSL_write,HTTP的write函数是ngx_unix_send。
ssize_t
ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t size)
{
。。。
// HTTP的是n = send(c->fd, buf, size, 0);
n = SSL_write(c->ssl->connection, data, size);
。。。
}