官方说明:详细看这里
本文主要针对以下两个主要配置从代码层次进行分析:
协议配置:
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;
# 其他配置略
}
这里存在一个问题:
而ssl_ciphers得配置就可以根据域名来区分开来。
从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来设置协议。
版本: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
先写到这里,稍后继续……
You can overload ssl_ciphers. You can’t overload ssl_protocols
because OpenSSL works this way: it selects the protocol used
before SNI callback (and this behaviour looks more or less natural
beacause the existance of SNI depends on the protocol used, and,
for example, you can’t enable SSLv3 in a SNI-based virtual host).
.
In general, whether or not some SSL feature can be tweaked for
SNI-based virtual hosts depends on two factors:
- if it’s at all possible;
- how OpenSSL handles it.
In some cases nginx also tries to provide per-virtualhost support
even for things OpenSSL doesn’t handle natively, e.g., ssl_verify,
ssl_verify_depth, ssl_prefer_server_ciphers.
补充相关链接 :https://mailman.nginx.org/pipermail/nginx/2017-January/052802.html