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 = '