zoukankan      html  css  js  c++  java
  • 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 = '';
    
            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以后,解密后就不必重新生成对话密钥了。

    http代理服务器(3-4-7层代理)-网络事件库公共组件、内核kernel驱动 摄像头驱动 tcpip网络协议栈、netfilter、bridge 好像看过!!!! 但行好事 莫问前程 --身高体重180的胖子
  • 相关阅读:
    ScheduledExecutorService 定时任务运行原理(三)
    ThreadPoolExecutor详解(二)
    Future模式详细讲解及实例分析
    @ApiParam @PathVariable @RequestParam三者区别
    协议及首次使用必读
    关于webmagic的依赖问题:ava.lang.NoClassDefFoundError: org/jsoup/helper/StringUtil
    空指针错误悼念与分析
    原创:idea启动项目的是否报错:java.lang.ClassNotFoundException: javax.servlet.ServletContext
    聚合工程中 context:component-scan 与数据源的思考,的使用说明
    关于component-scan中base-package包含通配符的问题探究
  • 原文地址:https://www.cnblogs.com/codestack/p/14812562.html
Copyright © 2011-2022 走看看