1. Nginx https相关配置
本文主要针对以下两个主要配置从代码层次进行分析:
协议配置:
- Syntax: ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2] [TLSv1.3];
- Default:
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- Context: http, server
以及加密套件配置
- Syntax: ssl_ciphers ciphers;
- Default:
- ssl_ciphers HIGH:!aNULL:!MD5;
- Context: http, server
Nginx的虚拟主机配置,使多个网站可以部署在同一个服务器(同一IP地址)对外提供服务。但是在实际测试中发现,虽然两个配置都在server 块内,ssl_protocols 却属于全局配置,而 ssl_ciphers 却针对特定的虚拟主机起作用。
- server {
- server_name www.a.com;
- ssl_ciphers ECDHE–RSA–AES128–GCM–SHA256;
- ssl_protocols TLSv1.2;
- # 其他配置略
- }
- server {
- server_name www.b.com;
- ssl_ciphers ECDHE–RSA–AES256–GCM–SHA384;
- ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
- # 其他配置略
- }
这里存在一个问题:
当存在多个server块的时候,即使两个(如上)ssl_protocols 的配置不同,但是 浏览器访问(携带SNI信息)www.a.com的时候,仍然可以使用 TLSv1 TLSv1.1 协议,即使没有配置。
而ssl_ciphers得配置就可以根据域名来区分开来。
从Nginx的使用文档上来看,两者的配置生效区域相同,却发挥了不同的作用。于是从源码开始分析原因在哪里。
2 Nginx源码分析
Nginx在启动过程中,加载配置文件的时候,对于每个server块的解析,会调用ngx_int_t ngx_ssl_create(ngx_ssl_t *ssl, ngx_uint_t protocols, void *data) 这个函数。
看参数,其中 protocols 就是配置文件中对应的配置项,他是一个 ngx_uint_t 类型,Nginx中关于协议的标志是:ngx_event_openssl.h中定义
- #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【十进制56】。
那么问题来了,造成server间protocols配置混乱的一定是其中某个地方出错了。
那么我们可以先看一下这个protocols的取值是不是对应两个server的取值,就可以看到是不是一开始解析配置文件的时候,就搞错了,于是Nginx源码debug 分析。
得到如下:
- (gdb) b ngx_event_openssl.c:ngx_ssl_create
- Breakpoint 1 at 0x473b91: file src/event/ngx_event_openssl.c, line 234.
- (gdb) r
- Breakpoint 1, ngx_ssl_create (ssl=0x9ad548, protocols=56, data=0x9ad540)
- at src/event/ngx_event_openssl.c:234
- (gdb) p protocols
- $1 = 56
- (gdb) c
- Continuing.
- Breakpoint 1, ngx_ssl_create (ssl=0x9bec60, protocols=32, data=0x9bec58)
- at src/event/ngx_event_openssl.c:234
- (gdb) p protocols
- $2 = 32
可以看到,没问题,一个32,一个65,没毛病!
那只能继续往下跟踪了。
接下来马上进入Openssl的源码了,看一下nginx在调用Openssl接口的地方代码:
- 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
- # define SSL_OP_NO_SSLv2 0x01000000L
- # define SSL_OP_NO_SSLv3 0x02000000L
- # define SSL_OP_NO_TLSv1 0x04000000L
- # define SSL_OP_NO_TLSv1_2 0x08000000L
- # define SSL_OP_NO_TLSv1_1 0x10000000L
大概意思就是,如果没有定义SSLv2、SSLv3、TLSv1、TLSv1.1、TLSv1.2那么就调用接口SSL_CTX_set_options 进入Openssl来设置协议。
3 Openssl源码分析
版本:Openssl-1.0.2j
源码文件 ssl.h
- # define SSL_CTX_set_options(ctx,op) \
- SSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,(op),NULL)
- # define SSL_CTX_clear_options(ctx,op) \
- SSL_CTX_ctrl((ctx),SSL_CTRL_CLEAR_OPTIONS,(op),NULL)
- # define SSL_CTX_get_options(ctx) \
- SSL_CTX_ctrl((ctx),SSL_CTRL_OPTIONS,0,NULL)
- # define SSL_set_options(ssl,op) \
- SSL_ctrl((ssl),SSL_CTRL_OPTIONS,(op),NULL)
- # define SSL_clear_options(ssl,op) \
- SSL_ctrl((ssl),SSL_CTRL_CLEAR_OPTIONS,(op),NULL)
- # define SSL_get_options(ssl) \
- SSL_ctrl((ssl),SSL_CTRL_OPTIONS,0,NULL)
所以要去看 SSL_CTX_ctrl 的源码
源码文件 ssl_lib.c
SSL_CTRL_OPTIONS 的值是32 在下面代码中有个switch(cmd)
关键代码
- long SSL_CTX_ctrl(SSL_CTX *ctx, int cmd, long larg, void *parg)
- {
- …
- switch (cmd) {
- …
- case SSL_CTRL_CLEAR_OPTIONS:
- return (ctx->options &= ~larg);
- …
- }
- }
最后的操作是: ctx->options &= ~larg
再回到Nginx 源码:
对于每种协议,都要调用一次 SSL_CTX_set_options(ssl->ctx, SSL_OP_NO_XXXXX);
也就是 ssl->ctx->options &= ~SSL_OP_NO_XXXXX
评论前必须登录!
注册