IT博客汇
  • 首页
  • 精华
  • 技术
  • 设计
  • 资讯
  • 扯淡
  • 权利声明
  • 登录 注册

    nginx模块开发(54)—SSL模块研究之session_cache(上)

    cjhust发表于 2016-05-24 17:49:42
    love 0

    1、知识百科

    互联网的通信安全,建立在SSL/TLS协议之上,SSL/TLS协议的基本思路是采用公钥加密法(非对称加密如RSA2048),即客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。

    由于SSL每次握手都会涉及RSA加解密,因此对服务器的CPU消耗极大,在SSLv3及更早版本就实现了基于session cache的方案来解决该问题。

    Session cache方案的基本原理就是每次会话有个session id,每个session id对应一个session(ngx_ssl_session_t),得到该session就不用传证书和RSA解密了。

    SSL new

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力

    SSL reuse

     
    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力

    2、数据结构

    ngx_http_ssl_srv_conf_t

    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;

    ngx_ssl_t(ssl)

    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;

    ngx_ssl_connection_t(sc)

    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;

    3、相关配置

    nginx.conf

    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;
        }
    }
    ngx_http_ssl_module模块支持下列内建变量:

    (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。

    ssl

    syntax:ssl [on|off]

    default:ssl off

    context:http/server

    指令功能:为指定的server开启HTTPS,建议直接在listen字段后加上ssl开启。

    ssl_certificate

    syntax:ssl_certificate file

    default:ssl_certificate cert.pem

    context:http/server

    指令功能:为这个虚拟主机指定PEM格式的证书文件。

    ssl_certificate_key

    syntax:ssl_certificate_key file

    default:ssl_certificate_key cert.pem

    context:http/server

    指令功能:为这个虚拟主机指定PEM格式的密钥。

    ssl_protocols

    syntax:ssl_protocols [SSLv2] [SSLv3] [TLSv1]

    default:ssl_protocols SSLv2 SSLv3 TLSv1

    context:http/server

    指令功能:开启指定的SSL协议,TLSv1.1和TLSv1.2在openssl-1.0.1或更高版本才支持。

    ssl_ciphers

    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

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力

    ssl_prefer_server_ciphers

    syntax:ssl_prefer_server_ciphers [on|off]

    default:ssl_prefer_server_ciphers off

    context:http/server

    指令功能:服务器密码将优先于客户端密码。

    ssl_session_cache

    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个会话,每个共享的缓存必须有自己的名称,相同名称的缓存可以使用在不同的虚拟主机中;

    ssl_session_timeout

    syntax:ssl_session_timeout time

    default:ssl_session_timeout 5m

    context: http/server

    指令功能:设置客户端能够反复使用储缓存中的会话超时时间,这个模块支持一些非标准的HTTP错误代码,可以借助error_page指令用于调试。

    (1)495:检查客户端证书时发生错误;

    (2)496:客户端无法提供必须的证书;

    (3)497:传送到HTTPS的普通请求;

    4、源码分析(nginx部分)

    ngx_http_init_connection(ngx_connection_t *c)

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力

    备注:在解析完配置后,对listener添加回调函数nginx_http_init_connection。

     

    如果访问的是http协议,ngx_http_wait_request_handler是入口函数:

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力


    如果访问的是https协议,ngx_http_ssl_handshark是入口函数,即先进行SSL握手,再处理HTTP请求:

    nginx模块开发(54)—SSL模块研究之session_cache - cjhust - 我一直在努力

    ngx_http_ssl_handshake(ngx_event_t *rev)

    函数功能: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);
    }
    

    ngx_ssl_create_connection(ngx_ssl_t *ssl, ngx_connection_t *c, ngx_uint_t flags)

    函数功能:为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;
    }

    ngx_ssl_handshake(ngx_connection_t *c)

    函数功能: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_handler(ngx_event_t *ev)

    函数功能: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
    }

    ngx_http_ssl_handshake_handler(ngx_connection_t *c)

    函数功能: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);
    }

    ngx_ssl_recv(ngx_connection_t *c, u_char *buf, size_t size)

    函数功能:接收对称加密算法的数据,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);
    
            。。。。
       }
    }

    ngx_ssl_write(ngx_connection_t *c, u_char *data, size_t 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);
    
       。。。
    }


沪ICP备19023445号-2号
友情链接