TLS 流程 读ngx

 

3.1使用SSL层接口函数安全通信

使用SSL层接口函数进行安全通信的方法由以下几个步骤组成:

1)初始化OpenSSL库

使用OpenSSL库之前,应用程序必须初始化库,初始化函数列出如下:

SSL_library_init(void); 
OpenSSL_add_ssl_algorithms(); 
SSLeay_add_ssl_algorithms();

 

初始化库时只需要调用上面三个函数中的一个,后面的两个函数是第一个函数的宏。

如果需要使用OpenSSL库的出错信息处理,就必须调用函数SSL_load_error_strings (void)进行初始化,以后,应用程序就可以调用函数void ERR_print_errors_fp(FILE *fp) 打印SSL的错误信息。

2)选择会话协议

客户端和服务器都必须选择相同的会话协议版本,目前,协议版本有TLSv1.0、SSLv2、SSLv3和SSLv2/v3。

客户端使用下面的函数选择会话协议:

SSL_METHOD* TLSv1_client_method(void);  //TLSv1.0 协议 
SSL_METHOD* SSLv2_client_method(void);   //SSLv2 协议 
SSL_METHOD* SSLv3_client_method(void);   //SSLv3 协议 
SSL_METHOD* SSLv23_client_method(void);  //SSLv2/v3 协议

 

服务器使用下面的函数选择会话协议:

SSL_METHOD *TLSv1_server_method(void);  
SSL_METHOD *SSLv2_server_method(void);  
SSL_METHOD *SSLv3_server_method(void);  
SSL_METHOD *SSLv23_server_method(void);

 

3)创建会话环境

SSL会话环境称为CTX(Context),不同的协议会话对应不同的会话环境,创建会话环境的函数列出如下:

//参数method是前面请的 SSL通信方法
SSL_CTX *SSL_CTX_new(SSL_METHOD * method);

 

创建了会话环境后,接着设置环境CTX的属性,设置证书验证方式的函数列出如下:

/*参数ctx为当前会话环境CTX的指针;参数mode为验证方式,需要验证时使用SSL_VERIFY_PEER,不需要时使用SSL_VERIFY_NONE;参数verify_callback为回调函数*/
int SSL_CTX_set_verify(SSL_CTX *ctx,int mode,int(*verify_callback),int(X509_STORE_CTX *));

 

给会话环境加载CA证书的函数列出如下:

//参数Cafile为证书文件名,参数Capath为证书路径
SSL_CTX_load_verify_location(SSL_CTX *ctx, const char *Cafile, const char *Capath);

 

给会话环境加载用户证书的函数列出如下:

//参数type为私钥文件的结构类型
SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file,int type);

 

给会话环境加载用户私钥的函数列出如下:

SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char* file,int type);

 

会话环境加载证书和私钥后,应用程序可以调用下面的函数验证私钥和证书是否相符:

int SSL_CTX_check_private_key(SSL_CTX *ctx);

 

4) 建立SSL套接字

SSL套接字建立在普通的TCP协议套接字基础上,应用程序在创建普通套接字、得到套接字描述符fd之后,再创建SSL套接字,并将fd绑定到SSL套接字上,与SSL套接字相关的函数说明如下:

//创建SSL套接字
SSL *SSl_new(SSL_CTX *ctx);  
//绑定读写套接字
int SSL_set_fd(SSL *ssl, int fd);)  
//绑定只读套接字
int SSL_set_rfd(SSL *ssl,int fd);  
//绑定只写套接字
int SSL_set_wfd(SSL *ssl,int fd);

5)完成SSL握手

与普通socket编程类似,创建SSL套接字后,客户端使用连接函数SSL_connect( )替代普通socket的函数connect( )来完成连接过程。服务器端以函数SSL_accept()替代函数accept()来完成连接接受过程。这两个函数列出如下:

int SSL_connect(SSL *ssl);
int SSL_accept(SSL *ssl);

握手会话完成后,应用程序接着询问通信双方的证书信息,询问函数部分列出如下:

//从SSL套接字中提取对方的证书信息
X509 *SSL_get_peer_certificate(SSL *ssl);
//得到证书所用者的名字
X509_NAME *X509_get_subject_name(X509 *a);

6)数据传输

握手会话完成后,安全的连接已建立起来,接着是对数据的安全传输。数据的安全传输用到了加密/解密、压缩/解压缩。OpenSSL库使用函数SSL_read( )和SSL_write( )来替代普通函数read( )和write( ),通过对SSL套接字的读写操作来完成数据的传输,这两个函数列出如下:

int SSL_read(SSL *ssl,void *buf,int num);
int SSL_write(SSL *ssl,const void *buf,int num);

7)SSL通信结束

安全通信结束时,应用程序关闭SSL套接字,释放会话环境,OpenSSL库结束通信的函数列出如下:

//关闭SSL套接字
int SSL_shutdown(SSL *ssl);  
//释放SSL套接字
void SSl_free(SSL *ssl); 
//释放SSL会话环境
void SSL_CTX_free(SSL_CTX *ctx); 

 流程分析:

1、openssl库初始化以及设置exdata

 

ngx_ssl_init(ngx_log_t *log)
{
#ifndef OPENSSL_IS_BORINGSSL
    OPENSSL_config(NULL);
#endif

    SSL_library_init();
    SSL_load_error_strings();

    OpenSSL_add_all_algorithms();


    ngx_ssl_connection_index = SSL_get_ex_new_index(0, NULL, NULL, NULL, NULL);

    if (ngx_ssl_connection_index == -1) {
        ngx_ssl_error(NGX_LOG_ALERT, log, 0, "SSL_get_ex_new_index() failed");
        return NGX_ERROR;
    }

    ngx_ssl_server_conf_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
                                                         NULL);
    if (ngx_ssl_server_conf_index == -1) {
        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
                      "SSL_CTX_get_ex_new_index() failed");
        return NGX_ERROR;
    }

    ngx_ssl_session_cache_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
                                                           NULL);
    if (ngx_ssl_session_cache_index == -1) {
        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
                      "SSL_CTX_get_ex_new_index() failed");
        return NGX_ERROR;
    }

    ngx_ssl_session_ticket_keys_index = SSL_CTX_get_ex_new_index(0, NULL, NULL,
                                                                 NULL, NULL);
    if (ngx_ssl_session_ticket_keys_index == -1) {
        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
                      "SSL_CTX_get_ex_new_index() failed");
        return NGX_ERROR;
    }

    ngx_ssl_certificate_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
                                                         NULL);
    if (ngx_ssl_certificate_index == -1) {
        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
                      "SSL_CTX_get_ex_new_index() failed");
        return NGX_ERROR;
    }

    ngx_ssl_stapling_index = SSL_CTX_get_ex_new_index(0, NULL, NULL, NULL,
                                                      NULL);
    if (ngx_ssl_stapling_index == -1) {
        ngx_ssl_error(NGX_LOG_ALERT, log, 0,
                      "SSL_CTX_get_ex_new_index() failed");
        return NGX_ERROR;
    }

    return NGX_OK;
}

2、根据证书-key初始化 openssl的ctx环境

 

   if (ngx_ssl_create(&conf->ssl, conf->protocols, conf) != NGX_OK) {
        return NGX_CONF_ERROR;
    }

    if (ngx_ssl_certificate(cf, &conf->ssl, &conf->certificate,
                            &conf->certificate_key, conf->passwords)!= NGX_OK)
    {
        return NGX_CONF_ERROR;
    }
      //设置密码链表,具体看密码    ciphers(1)指令吧 
    if (SSL_CTX_set_cipher_list(conf->ssl.ctx,
                                (const char *) conf->ciphers.data) == 0)
   
---------------------------------------
    conf->ssl.buffer_size = conf->buffer_size;

    if (conf->verify) {

        if (conf->client_certificate.len == 0 && conf->verify != 3) {
       ---------------------
        }
        //双向认证
        if (ngx_ssl_client_certificate(cf, &conf->ssl,&conf->client_certificate,
                                       conf->verify_depth)!= NGX_OK)
        {
           ------------------
        }
    }
//记载 授信任的CA
    if (ngx_ssl_trusted_certificate(cf, &conf->ssl, &conf->trusted_certificate,
                                    conf->verify_depth)!= NGX_OK)
  -------------------

    if (ngx_ssl_session_cache(&conf->ssl, &ngx_http_ssl_sess_id_ctx, conf->builtin_session_cache,
                              conf->shm_zone, conf->session_timeout) != NGX_OK)
    {
        --------------
    }

 

 ngx_ssl_create  的简要分析:

//其中 protocols 就是配置文件中对应的配置项 
/*
#define NGX_SSL_SSLv2    0x0002
#define NGX_SSL_SSLv3    0x0004
#define NGX_SSL_TLSv1    0x0008
#define NGX_SSL_TLSv1_1  0x0010
#define NGX_SSL_TLSv1_2  0x0020
每种协议占一位,protocols 的取值就是各个协议做或运算得到的值。比如上文中第一个配置,
只有TLSv1.2,那protocols =0x0020【十进制32】,如果是TLSv1 TLSv1.1 TLSv1.2,
那就是 protocols = 0x0020 | 0x0010 | 0x0008 = 0x0038


*/
ngx_int_t
ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data)
{
    ssl->ctx = SSL_CTX_new(SSLv23_method());
    //通过SSL所用方法新建SSL_CTX上下文信息 
    if (ssl->ctx == NULL) {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "SSL_CTX_new() failed");
        return NGX_ERROR;
    }
    //设置上下文信息的数据,保存扩展数据    根据需求确定
    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_server_conf_index, data) == 0) {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "SSL_CTX_set_ex_data() failed");
        return NGX_ERROR;
    }

    ssl->buffer_size = NGX_SSL_BUFSIZE;

    /* client side options */
    //客户端服务器选项的设定,具体查看具体参数吧,可以看一下英文  
#ifdef SSL_OP_MICROSOFT_SESS_ID_BUG
    SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_SESS_ID_BUG);
#endif

#ifdef SSL_OP_NETSCAPE_CHALLENGE_BUG
    SSL_CTX_set_options(ssl->ctx, SSL_OP_NETSCAPE_CHALLENGE_BUG);
#endif

    /* server side options */

#ifdef SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG
    SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLREF2_REUSE_CERT_TYPE_BUG);
#endif

#ifdef SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER
    SSL_CTX_set_options(ssl->ctx, SSL_OP_MICROSOFT_BIG_SSLV3_BUFFER);
#endif

#ifdef SSL_OP_MSIE_SSLV2_RSA_PADDING
    /* this option allow a potential SSL 2.0 rollback (CAN-2005-2969) */
    SSL_CTX_set_options(ssl->ctx, SSL_OP_MSIE_SSLV2_RSA_PADDING);
#endif

#ifdef SSL_OP_SSLEAY_080_CLIENT_DH_BUG
    SSL_CTX_set_options(ssl->ctx, SSL_OP_SSLEAY_080_CLIENT_DH_BUG);
#endif

#ifdef SSL_OP_TLS_D5_BUG
    SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_D5_BUG);
#endif

#ifdef SSL_OP_TLS_BLOCK_PADDING_BUG
    SSL_CTX_set_options(ssl->ctx, SSL_OP_TLS_BLOCK_PADDING_BUG);
#endif

#ifdef SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS
    SSL_CTX_set_options(ssl->ctx, SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS);
#endif

    SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_DH_USE);

#ifdef SSL_CTRL_CLEAR_OPTIONS
    /* only in 0.9.8m+ */
    SSL_CTX_clear_options(ssl->ctx,
                          SSL_OP_NO_SSLv2|SSL_OP_NO_SSLv3|SSL_OP_NO_TLSv1);
#endif

    if (!(protocols & NGX_SSL_SSLv2)) {
        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv2);
    }
    if (!(protocols & NGX_SSL_SSLv3)) {
        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_SSLv3);
    }
    if (!(protocols & NGX_SSL_TLSv1)) {
        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1);
    }
#ifdef SSL_OP_NO_TLSv1_1
    SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
    if (!(protocols & NGX_SSL_TLSv1_1)) {
        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_1);
    }
#endif
#ifdef SSL_OP_NO_TLSv1_2
    SSL_CTX_clear_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
    if (!(protocols & NGX_SSL_TLSv1_2)) {
        SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_TLSv1_2);
    }
#endif

#ifdef SSL_OP_NO_COMPRESSION
    SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_COMPRESSION);
#endif

#ifdef SSL_MODE_RELEASE_BUFFERS
    SSL_CTX_set_mode(ssl->ctx, SSL_MODE_RELEASE_BUFFERS);
#endif

#ifdef SSL_MODE_NO_AUTO_CHAIN
    SSL_CTX_set_mode(ssl->ctx, SSL_MODE_NO_AUTO_CHAIN);
#endif
   /*/ 这里一定需要,设置读取头一个字节,作为判断协议,如果不设定那么将使得
   n = SSL_read(c, buffer, 1024);少读前一个字节,因为读取第一个字节作为判断字节  
*/
    SSL_CTX_set_read_ahead(ssl->ctx, 1);

    SSL_CTX_set_info_callback(ssl->ctx, ngx_ssl_info_callback);

    return NGX_OK;
}
View Code

ngx_ssl_certificate 简要分析

ngx_int_t
ngx_ssl_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
    ngx_str_t *key, ngx_array_t *passwords)
{
    BIO         *bio;
    X509        *x509;
    u_long       n;
    ngx_str_t   *pwd;
    ngx_uint_t   tries;

    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
        return NGX_ERROR;
    }

    /*
     * we can't use SSL_CTX_use_certificate_chain_file() as it doesn't
     * allow to access certificate later from SSL_CTX, so we reimplement
     * it here
     //设置加载服务器的证书和私钥    没有使用 SSL_CTX_use_certificate_chain_file
     */

    bio = BIO_new_file((char *) cert->data, "r");
    if (bio == NULL) {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "BIO_new_file(\"%s\") failed", cert->data);
        return NGX_ERROR;
    }

    x509 = PEM_read_bio_X509_AUX(bio, NULL, NULL, NULL);
    if (x509 == NULL) {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "PEM_read_bio_X509_AUX(\"%s\") failed", cert->data);
        BIO_free(bio);
        return NGX_ERROR;
    }

    if (SSL_CTX_use_certificate(ssl->ctx, x509) == 0) {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "SSL_CTX_use_certificate(\"%s\") failed", cert->data);
        X509_free(x509);
        BIO_free(bio);
        return NGX_ERROR;
    }

    if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_certificate_index, x509)
        == 0)
    {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "SSL_CTX_set_ex_data() failed");
        X509_free(x509);
        BIO_free(bio);
        return NGX_ERROR;
    }

    X509_free(x509);

    /* read rest of the chain */

    for ( ;; ) {

        x509 = PEM_read_bio_X509(bio, NULL, NULL, NULL);
        if (x509 == NULL) {
            n = ERR_peek_last_error();

            if (ERR_GET_LIB(n) == ERR_LIB_PEM
                && ERR_GET_REASON(n) == PEM_R_NO_START_LINE)
            {
                /* end of file */
                ERR_clear_error();
                break;
            }

            /* some real error */

            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                          "PEM_read_bio_X509(\"%s\") failed", cert->data);
            BIO_free(bio);
            return NGX_ERROR;
        }

        if (SSL_CTX_add_extra_chain_cert(ssl->ctx, x509) == 0) {
            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                          "SSL_CTX_add_extra_chain_cert(\"%s\") failed",
                          cert->data);
            X509_free(x509);
            BIO_free(bio);
            return NGX_ERROR;
        }
    }

    BIO_free(bio);

    if (ngx_strncmp(key->data, "engine:", sizeof("engine:") - 1) == 0) {

#ifndef OPENSSL_NO_ENGINE

        u_char      *p, *last;
        ENGINE      *engine;
        EVP_PKEY    *pkey;

        p = key->data + sizeof("engine:") - 1;
        last = (u_char *) ngx_strchr(p, ':');

        if (last == NULL) {
            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid syntax in \"%V\"", key);
            return NGX_ERROR;
        }

        *last = '\0';

        engine = ENGINE_by_id((char *) p);

        if (engine == NULL) {
            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                          "ENGINE_by_id(\"%s\") failed", p);
            return NGX_ERROR;
        }

        *last++ = ':';

        pkey = ENGINE_load_private_key(engine, (char *) last, 0, 0);

        if (pkey == NULL) {
            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                          "ENGINE_load_private_key(\"%s\") failed", last);
            ENGINE_free(engine);
            return NGX_ERROR;
        }

        ENGINE_free(engine);

        if (SSL_CTX_use_PrivateKey(ssl->ctx, pkey) == 0) {
            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                          "SSL_CTX_use_PrivateKey(\"%s\") failed", last);
            EVP_PKEY_free(pkey);
            return NGX_ERROR;
        }

        EVP_PKEY_free(pkey);

        return NGX_OK;

#else

        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                           "loading \"engine:...\" certificate keys "
                           "is not supported");
        return NGX_ERROR;

#endif
    }

    if (ngx_conf_full_name(cf->cycle, key, 1) != NGX_OK) {
        return NGX_ERROR;
    }

    if (passwords) {
        tries = passwords->nelts;
        pwd = passwords->elts;

        SSL_CTX_set_default_passwd_cb(ssl->ctx, ngx_ssl_password_callback);
        SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, pwd);

    } else {
        tries = 1;
#if (NGX_SUPPRESS_WARN)
        pwd = NULL;
#endif
    }

    for ( ;; ) {//设置加载服务器的私钥  

        if (SSL_CTX_use_PrivateKey_file(ssl->ctx, (char *) key->data,
                                        SSL_FILETYPE_PEM)
            != 0)
        {
            break;
        }

        if (--tries) {
            ERR_clear_error();
            SSL_CTX_set_default_passwd_cb_userdata(ssl->ctx, ++pwd);
            continue;
        }

        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "SSL_CTX_use_PrivateKey_file(\"%s\") failed", key->data);
        return NGX_ERROR;
    }

    SSL_CTX_set_default_passwd_cb(ssl->ctx, NULL);

    return NGX_OK;
}
View Code

ngx_ssl_client_certificate 简要分析

//如果是双向认证还得做下面这几件事情  
//     SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_http_ssl_verify_callback);  
// SSL_CTX_set_verify_depth(ssl->ctx, depth);  
//     SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL);  
//     SSL_load_client_CA_file((char *) cert->data);  
//     ERR_clear_error();  
//     SSL_CTX_set_client_CA_list(ssl->ctx, list);  
//     SSL_CTX_get_cert_store(ssl->ctx);  
// X509_STORE_add_lookup(store, X509_LOOKUP_file();  
// X509_LOOKUP_load_file(lookup, (char *) crl->data, X509_FILETYPE_PEM);  
// X509_STORE_set_flags(store,X509_V_FLAG_CRL_CHECK|X509_V_FLAG_CRL_CHECK_ALL);

ngx_int_t
ngx_ssl_client_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
    ngx_int_t depth)
{
    STACK_OF(X509_NAME)  *list;

    SSL_CTX_set_verify(ssl->ctx, SSL_VERIFY_PEER, ngx_ssl_verify_callback);

    SSL_CTX_set_verify_depth(ssl->ctx, depth);

    if (cert->len == 0) {
        return NGX_OK;
    }

    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
        return NGX_ERROR;
    }

    if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
        == 0)
    {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "SSL_CTX_load_verify_locations(\"%s\") failed",
                      cert->data);
        return NGX_ERROR;
    }

    /*
     * SSL_CTX_load_verify_locations() may leave errors in the error queue
     * while returning success
     */

    ERR_clear_error();

    list = SSL_load_client_CA_file((char *) cert->data);

    if (list == NULL) {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "SSL_load_client_CA_file(\"%s\") failed", cert->data);
        return NGX_ERROR;
    }

    /*
     * before 0.9.7h and 0.9.8 SSL_load_client_CA_file()
     * always leaved an error in the error queue
     */

    ERR_clear_error();

    SSL_CTX_set_client_CA_list(ssl->ctx, list);

    return NGX_OK;
}
View Code

ngx_ssl_trusted_certificate 简要分析

ngx_ssl_trusted_certificate(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *cert,
    ngx_int_t depth)
{ //设置最大的验证用户证书的上级数。
    SSL_CTX_set_verify_depth(ssl->ctx, depth);

    if (cert->len == 0) {
        return NGX_OK;
    }

    if (ngx_conf_full_name(cf->cycle, cert, 1) != NGX_OK) {
        return NGX_ERROR;
    }
    //用于加载受信任的CA证书
    if (SSL_CTX_load_verify_locations(ssl->ctx, (char *) cert->data, NULL)
        == 0)
    {
        ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                      "SSL_CTX_load_verify_locations(\"%s\") failed",
                      cert->data);
        return NGX_ERROR;
    }

    /*
     * SSL_CTX_load_verify_locations() may leave errors in the error queue
     * while returning success
     */

    ERR_clear_error();

    return NGX_OK;
}
View Code

ngx_ssl_session_cache 简要分析

ngx_ssl_session_cache(ngx_ssl_t *ssl, ngx_str_t *sess_ctx,
    ssize_t builtin_session_cache, ngx_shm_zone_t *shm_zone, time_t timeout)
{
    long  cache_mode;
//设置会话
    SSL_CTX_set_timeout(ssl->ctx, (long) timeout);

    if (ngx_ssl_session_id_context(ssl, sess_ctx) != NGX_OK) {
        return NGX_ERROR;
    }

    if (builtin_session_cache == NGX_SSL_NO_SCACHE) {
        SSL_CTX_set_session_cache_mode(ssl->ctx, SSL_SESS_CACHE_OFF);
        return NGX_OK;
    }

    if (builtin_session_cache == NGX_SSL_NONE_SCACHE) {

        /*
         * If the server explicitly says that it does not support
         * session reuse (see SSL_SESS_CACHE_OFF above), then
         * Outlook Express fails to upload a sent email to
         * the Sent Items folder on the IMAP server via a separate IMAP
         * connection in the background. Therefore we have a special
         * mode (SSL_SESS_CACHE_SERVER|SSL_SESS_CACHE_NO_INTERNAL_STORE)
         * where the server pretends that it supports session reuse,
         * but it does not actually store any session.
         */

        SSL_CTX_set_session_cache_mode(ssl->ctx,
                                       SSL_SESS_CACHE_SERVER
                                       |SSL_SESS_CACHE_NO_AUTO_CLEAR
                                       |SSL_SESS_CACHE_NO_INTERNAL_STORE);
        /*/设置cache的大小,默认的为1024*20=20000,这个也就是可以存多少个session_id,
        一般都不需要更改的。假如为0的话将是无限  
*/
        SSL_CTX_sess_set_cache_size(ssl->ctx, 1);

        return NGX_OK;
    }

    cache_mode = SSL_SESS_CACHE_SERVER;

    if (shm_zone && builtin_session_cache == NGX_SSL_NO_BUILTIN_SCACHE) {
        cache_mode |= SSL_SESS_CACHE_NO_INTERNAL;
    }

    SSL_CTX_set_session_cache_mode(ssl->ctx, cache_mode);

    if (builtin_session_cache != NGX_SSL_NO_BUILTIN_SCACHE) {

        if (builtin_session_cache != NGX_SSL_DFLT_BUILTIN_SCACHE) {
            SSL_CTX_sess_set_cache_size(ssl->ctx, builtin_session_cache);
        }
    }

    if (shm_zone) {
        SSL_CTX_sess_set_new_cb(ssl->ctx, ngx_ssl_new_session);
        SSL_CTX_sess_set_get_cb(ssl->ctx, ngx_ssl_get_cached_session);
        SSL_CTX_sess_set_remove_cb(ssl->ctx, ngx_ssl_remove_session);

        if (SSL_CTX_set_ex_data(ssl->ctx, ngx_ssl_session_cache_index, shm_zone)
            == 0)
        {
            ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
                          "SSL_CTX_set_ex_data() failed");
            return NGX_ERROR;
        }
    }

    return NGX_OK;
}
View Code

 

 3、网络编程

tcp建立后进入ssl 握手

  • recv fd data  n = recv(c->fd, (char *) buf, size, MSG_PEEK); 
  • if (buf[0] & 0x80 /* SSLv2 */ || buf[0] == 0x16 /* SSLv3/TLSv1 */)
  • ngx_ssl_create_connection(&sscf->ssl, c, NGX_SSL_BUFFER)
    • sc->connection = SSL_new(ssl->ctx);
    • SSL_set_fd(sc->connection, c->fd)
    •  SSL_set_connect_state(sc->connection); /SSL_set_accept_state(sc->connection);
  • ngx_ssl_handshake
    •  n = SSL_do_handshake(c->ssl->connection); //改函数内部会调用ngx_http_ssl_alpn_select执行  尝试握手
    • 握手完成 ngx_handle_read_event(c->read) ngx_handle_write_event(c->write)  由于 c->write 已经ready 所以不会insert 到 epoll 里面去
    • c->recv = ngx_ssl_recv;
      c->send = ngx_ssl_write;

    • 握手未完成  sslerr = SSL_get_error(c->ssl->connection, n);  if (sslerr == SSL_ERROR_WANT_READ)
      • 设置 read write handle 时间回调 等待握手结束后的处理  

        c->read->handler = ngx_ssl_handshake_handler;
        c->write->handler = ngx_ssl_handshake_handler;

其代码实现如下:

//tls单向认证四次握手过程,都会调用该函数处理,返回NGX_AGAIN表示握手还没有完成,需要再次进行后续握手过程
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_http_ssl_alpn_select执行

    //0x80:SSLv2  0x16:SSLv3/TLSv1 
    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_FUNC_LINE) != NGX_OK) {
            return NGX_ERROR; ngx_epoll_add_event
        }

        if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != NGX_OK) {
            return NGX_ERROR;
        }


        c->ssl->handshaked = 1;

        c->recv = ngx_ssl_recv;
        c->send = ngx_ssl_write;
        c->recv_chain = ngx_ssl_recv_chain;
        c->send_chain = ngx_ssl_send_chain;

#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

        return NGX_OK;//握手完成
    }

    sslerr = SSL_get_error(c->ssl->connection, n);
    
    ngx_log_debug1(NGX_LOG_DEBUG_EVENT, c->log, 0, "SSL_get_error: %d", sslerr);
    //这里应该再重新接收一次和NGINX一样,等待下一次循环(epoll)再进行,同时设置读写句柄,以便下次读取的时候直接进行握手
    //单向认证四次握手过程还没有完成,需要继续握手
    if (sslerr == SSL_ERROR_WANT_READ) {  //# define SSL_ERROR_WANT_READ             2
        c->read->ready = 0;// 由于 tcp fd 返回的时候 是writeable  所以 c->write->ready = 1  在初始化的时候会赋值
        c->read->handler = ngx_ssl_handshake_handler;
        c->write->handler = ngx_ssl_handshake_handler;

        if (ngx_handle_read_event(c->read, 0, NGX_FUNC_LINE) != NGX_OK) {
            return NGX_ERROR;
        }
            // 由于 tcp fd 返回的时候 是writeable  所以 c->write->ready = 1  在初始化的时候会赋值;所以 此时 不会讲 write-ev add 到epoll
        if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != 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_FUNC_LINE) != NGX_OK) {
            return NGX_ERROR;
        }

        if (ngx_handle_write_event(c->write, 0, NGX_FUNC_LINE) != 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; //握手失败
}


/* tls握手第一步,接收客户端发送过来的ClientHello请求,
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);
        return;
    }

    if (ngx_ssl_handshake(c) == NGX_AGAIN) {
        return;
    }

    c->ssl->handler(c);
}

参考文章:

阮一峰:http://www.ruanyifeng.com/blog/2014/02/ssl_tls.html

极客:https://www.geek-share.com/detail/2613479783.html

 

转载自:http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html

SSL/TLS协议是为了解决这三大风险而设计的,希望达到:

(1) 所有信息都是加密传播,第三方无法窃听。

(2) 具有校验机制,一旦被篡改,通信双方会立刻发现。

(3) 配备身份证书,防止身份被冒充。

SSL/TLS协议的基本思路是采用公钥加密法,也就是说,客户端先向服务器端索要公钥,然后用公钥加密信息,服务器收到密文后,用自己的私钥解密。

但是,这里有两个问题。

(1)如何保证公钥不被篡改?

解决方法:将公钥放在数字证书中。只要证书是可信的,公钥就是可信的。

(2)公钥加密计算量太大,如何减少耗用的时间?

解决方法:每一次对话(session),客户端和服务器端都生成一个"对话密钥"(session key),用它来加密信息。由于"对话密钥"是对称加密,所以运算速度非常快,而服务器公钥只用于加密"对话密钥"本身,这样就减少了加密运算的消耗时间。

因此,SSL/TLS协议的基本过程是这样的:

(1) 客户端向服务器端索要并验证公钥。

(2) 双方协商生成"对话密钥"。

(3) 双方采用"对话密钥"进行加密通信。

SSL协议的握手过程

开始加密通信之前,客户端和服务器首先必须建立连接和交换参数,这个过程叫做握手(handshake)

第一步,爱丽丝给出协议版本号、一个客户端生成的随机数(Client random),以及客户端支持的加密方法。

第二步,鲍勃确认双方使用的加密方法,并给出数字证书、以及一个服务器生成的随机数(Server random)。

第三步,爱丽丝确认数字证书有效,然后生成一个新的随机数(Premaster secret),并使用数字证书中的公钥,加密这个随机数,发给鲍勃。

第四步,鲍勃使用自己的私钥,获取爱丽丝发来的随机数(即Premaster secret)。

第五步,爱丽丝和鲍勃根据约定的加密方法,使用前面的三个随机数,生成"对话密钥"(session key),用来加密接下来的整个对话过程。

 

 

二、私钥的作用

握手阶段有三点需要注意。

(1)生成对话密钥一共需要三个随机数。

(2)握手之后的对话使用"对话密钥"加密(对称加密),服务器的公钥和私钥只用于加密和解密"对话密钥"(非对称加密),无其他作用。

(3)服务器公钥放在服务器的数字证书之中。

整个对话过程中(握手阶段和其后的对话),服务器的公钥和私钥只需要用到一次

DH算法的握手阶段

  整个握手阶段都不加密(也没法加密),都是明文的。因此,如果有人窃听通信,他可以知道双方选择的加密方法,以及三个随机数中的两个。

整个通话的安全,只取决于第三个随机数(Premaster secret)能不能被破解。

  理论上,只要服务器的公钥足够长(比如2048位),那么Premaster secret可以保证不被破解。但是为了足够安全,我们可以考虑把握手阶段的算法从默认的RSA算法,改为 Diffie-Hellman算法(简称DH算法)。

采用DH算法后,Premaster secret不需要传递,双方只要交换各自的参数,就可以算出这个随机数。

so:严格来说密钥的传递不叫非对称加密 而是D-H密钥交换

其数学原理简述就是:将a和b相乘得出乘积c很容易,但要是想要通过乘积c推导出a和b极难。即对一个大数进行因式分解极难

session的恢复

握手阶段用来建立SSL连接。如果出于某种原因,对话中断,就需要重新握手。

这时有两种方法可以恢复原来的session:一种叫做session ID,另一种叫做session ticket。

session ID的思想很简单,就是每一次对话都有一个编号(session ID)。如果对话中断,下次重连的时候,只要客户端给出这个编号,且服务器有这个编号的记录,双方就可以重新使用已有的"对话密钥",而不必重新生成一把。

上图中,客户端给出session ID,服务器确认该编号存在,双方就不再进行握手阶段剩余的步骤,而直接用已有的对话密钥进行加密通信。

session ID是目前所有浏览器都支持的方法,但是它的缺点在于session ID往往只保留在一台服务器上。所以,如果客户端的请求发到另一台服务器,就无法恢复对话。session ticket就是为了解决这个问题而诞生的,目前只有Firefox和Chrome浏览器支持。

上图中,客户端不再发送session ID,而是发送一个服务器在上一次对话中发送过来的session ticket。这个session ticket是加密的,只有服务器才能解密,其中包括本次对话的主要信息,比如对话密钥和加密方法。当服务器收到session ticket以后,解密后就不必重新生成对话密钥了。

 

posted @ 2021-05-26 23:40  codestacklinuxer  阅读(639)  评论(0编辑  收藏  举报