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; }
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; }
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; }
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; }
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; }
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;
- 设置 read write handle 时间回调 等待握手结束后的处理
其代码实现如下:
//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以后,解密后就不必重新生成对话密钥了。