HTTPS和Nginx的HTTPS配置

HTTPS协议和原理

想要深入了解HTTPS协议和原理,请移步这里
接下来会着重介绍SSL握手协议。
注意:如果没有特殊说明,本文中SSL和TLS是同义词。


SSL握手协议

SSL握手协议(SSL Handshake Protocol)可以分为四个阶段:
ssl_handshake

  • 1,客户端和服务器端安全信息的互相发送
    client_hello 客户端发送ClientHello信息,包含如下内容:
    (1)客户端可以支持的SSL的最高版本号
    (2)一个用于生成密钥的32字节的随机数
    (3)一个确定会话的会话ID
    (4)一个客户端可以支持的密码套件列表
    (5)一个客户端可以支持的压缩算法列表
    服务器端用ServerHello信息响应客户端,包含如下内容:
    (1)SSL版本号,取客户端支持的最高版本号和服务器端支持的最高版本号中的较低者
    (2)一个用于生成密钥的32字节的随机数
    (3)会话ID
    (4)从客户端的密码套件列表中选择的一个密码套件
    (5)从客户端的压缩方法列表中选择的压缩方法
    这个阶段之后,客户端服务端知道了下列内容:
    (1)SSL版本
    (2)密钥交换算法(也就是,非对称加密算法)、信息验证算法(也就是,HASH算法)和加密算法(也就是,对称加密算法)
    (3)压缩方法
    (4)有关密钥生成的两个随机数

  • 2,服务器推送证书到客户端进行密钥交换与客户端证书鉴别
    phrase2 (a)证书:服务器将数字证书和到根CA的整个证书链发给客户端,使客户端能够认证服务器
    (b)服务器密钥交换(可选):这里视密钥交换算法而定
    (c)证书请求:服务器可能要求客户端发送客户端证书
    (d)服务器握手完成:第二阶段结束,第三阶段开始
    上图中省略了:客户端利用服务器传过来的信息验证服务器的合法性,服务器的合法性包括:证书是否过期,发行服务器证书的 CA 是否可靠,发行者证书的公钥能否正确解开服务器证书的“发行者的数字签名”,服务器证书上的域名是否和服务器的实际域名相匹配。如果合法性验证没有通过,通讯将断开;如果合法性验证通过,通讯将继续。

  • 3,客户端推送证书密钥到服务器端
    与上一步类似,上一步是服务器端向客户端推送证书,客户端已经信任了服务器端,但是服务器端还要验证客户端,这一步的操作完成后,双向的SSL就完成了。
    phrase3 (a)证书(可选):为了向服务器证明自身,客户端要发送客户端证书
    (b)预备主密钥(Pre-master-secret):客户端生成预备主密钥,并将其发送给服务端,注意这里会使用服务端的公钥进行加密
    (c)证书验证(可选):对预备主密钥和随机数进行签名,证明客户端拥有(a)证书的公钥
    至此,服务器端和客户端各有一把私钥和公钥,私钥都是自己的,公钥都是对方的。

  • 4,服务器确认,SSL通讯建立,定期修改密码规范
    phrase4 服务器端收到客户端的公钥,服务器端认证客户端后,双方的可信就建立起来了。
    但是,每一次后期的传输,也可以通过设置实现:不使用同一个密码,而是像上图中最后一步一样,将摘要散列值和密码定期更换。
    至此,SSL通道就建立成功了。


Nginx的ngx_http_ssl_module模块

ngx_http_ssl_module模块提供了对HTTPS的支持。
默认,是不编译该模块的。如果想要使用这个模块,那么应该在编译时指定--with-http_ssl_module选项,该模块依赖OpenSSL库。
为了降低处理器的负载,建议:

  • 把worker进程数设置为处理器的数量
  • 开启keep-alive连接
  • 开启共享session缓存
  • 关闭内建的session缓存
  • 尽可能的延长session的生命周期(默认是5分钟)

示例配置:

worker_processes auto;

http {

    ...

    server {
        listen              443 ssl;
        keepalive_timeout   70;

        ssl_protocols       TLSv1 TLSv1.1 TLSv1.2;
        ssl_ciphers         AES128-SHA:AES256-SHA:RC4-SHA:DES-CBC3-SHA:RC4-MD5;
        ssl_certificate     /usr/local/nginx/conf/cert.pem;
        ssl_certificate_key /usr/local/nginx/conf/cert.key;
        ssl_session_cache   shared:SSL:10m;
        ssl_session_timeout 10m;

        ...
    }

ngx_http_ssl_module模块提供的指令

  • ssl
Syntax:    ssl on | off;  
Default:  
ssl off;  
Context:    http, server  


对给定的虚拟主机,开启HTTPS协议(推荐使用listen指令的ssl参数来代替这个指令)。

  • ssl_buffer_size
Syntax:    ssl_buffer_size size;  
Default:  
ssl_buffer_size 16k;  
Context:    http, server  
This directive appeared in version 1.5.9.  


设置用于发送数据的缓冲区的大小。
默认情况下,缓冲区的大小是16k。当发送大的响应时,它是开销最小的。为了缩小第一个字节的发送时间,可以给这个指令指定一个较小的值,比如4k。

  • ssl_certificate
Syntax:    ssl_certificate file;  
Default:    —  
Context:    http, server  


为给定的虚拟主机指定一个包含PEM格式证书的文件。如果除了主证书,还包含中间证书,那么应该把它们以下面的顺序放在一个文件中:首先是主证书,然后是中间证书。
从1.11.0版本开始,为了加载不同类型(比如RSA和ECDSA)的证书,可以指定这个指令多次:

server {  
    listen              443 ssl;
    server_name         example.com;

    ssl_certificate     example.com.rsa.crt;
    ssl_certificate_key example.com.rsa.key;

    ssl_certificate     example.com.ecdsa.crt;
    ssl_certificate_key example.com.ecdsa.key;

    ...
}


只有OpenSSL 1.0.2或更高的版本,才支持用于不同的证书的分离的证书链。老版本只能使用一个证书链。
注意:由于HTTPS协议的限制,虚拟主机应该监听在不同的IP地址上:

server {  
    listen          192.168.1.1:443;
    server_name     one.example.com;
    ssl_certificate one.example.com.crt;
    ...
}

server {  
    listen          192.168.1.2:443;
    server_name     two.example.com;
    ssl_certificate two.example.com.crt;
    ...
}


否则第一个虚拟主机的IP会发布到第二个站点上。

  • ssl_certificate_key
Syntax:    ssl_certificate_key file;  
Default:    —  
Context:    http, server  


为给定的虚拟主机指定一个包含PEM格式的私钥的文件。

  • ssl_ciphers
Syntax:    ssl_ciphers ciphers;  
Default:  
ssl_ciphers HIGH:!aNULL:!MD5;  
Context:    http, server  


指定启用的算法。ciphers参数应该被指定为OpenSSL库所能理解的格式,比如ssl_ciphers ALL:!aNULL:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP;
使用“openssl ciphers”命令可以查看全部的算法列表。

CIPHER LIST的格式:
cipher list由许多cipher string组成,由冒号,逗号或者空格分隔开。但一般最常用的是用冒号。
cipher string又是什么?
它可以仅仅包含一个cipher,比如RC4-SHA。
它也可以仅仅包含一个加密算法,比如SHA,表示所有用到SHA的cipher都得列出来。 还可以使用三个符号来捏合各种不同的cipher,做出cipher string。这三个符号是+、 -、!。比如,MD5+DES表示同时使用了这俩种算法的cipher,!SHA就表示所有没有用到SHA的cipher, IDEA-CBC就表示使用了IDEA而没有使用CBC的所有cipher。
openssl还定义了一些通用的cipher string,有:
(1)DEFAULT:缺省的cipher list
(2)ALL:所有的cipher
(3)HIGH、LOW、MEDIUM:分别代表高强度,低强度和中等强度的cipher list,具体一点就是对称加密算法的key的长度分别是大于128bit,小于128bit和等于128bit的cipher。
(4)EXP、EXPORT、EXPORT40:前两者代表法律允许出口的加密算法,包括40bit、56bit长度的key的算法,后者表示只有40bit长度的key的加密算法。
(5)eNULL、NULL:表示不加密的算法
(6)aNULL:不提供身份验证的加密算法。目前只有DH一种。该算法很容易被监听者,路由器等中间设备攻击,所以不提倡使用。

  • ssl_client_certificate
Syntax:    ssl_client_certificate file;  
Default:    —  
Context:    http, server  


指定一个包含PEM格式的受信任的CA证书列表(用于验证客户端证书)的文件。
证书列表会被发送到客户端,如果没有客户端期望的,那么ssl_trusted_certificate指令指定的CA证书列表会被使用。

  • ssl_crl
Syntax:    ssl_crl file;  
Default:    —  
Context:    http, server  
This directive appeared in version 0.8.7.  


指定一个包含PEM格式的被吊销的证书列表的文件。

  • ssl_password_file
Syntax:    ssl_password_file file;  
Default:    —  
Context:    http, server  
This directive appeared in version 1.7.3.  


指定一个包含私钥的密码的文件,每行一个密码。在加载私钥的时候,会按次序尝试这些密码。比如:

http {  
    ssl_password_file /etc/keys/global.pass;
    ...

    server {
        server_name www1.example.com;
        ssl_certificate_key /etc/keys/first.key;
    }

    server {
        server_name www2.example.com;

        # named pipe can also be used instead of a file
        ssl_password_file /etc/keys/fifo;
        ssl_certificate_key /etc/keys/second.key;
    }
}
  • ssl_prefer_server_ciphers
Syntax:    ssl_prefer_server_ciphers on | off;  
Default:  
ssl_prefer_server_ciphers off;  
Context:    http, server  


指定当使用SSLV3和TLS协议的时候,服务器端算法优先于客户端算法。

  • ssl_protocols
Syntax:    ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2];  
Default:  
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;  
Context:    http, server  


启用指定的协议,TLSv1.1TLSv1.2参数只在OpenSSL库的版本高于1.0.1(含1.0.1)的时候才起作用。

  • ssl_session_cache
Syntax:    ssl_session_cache off | none | [builtin[:size]] [shared:name:size];  
Default:  
ssl_session_cache none;  
Context:    http, server  


设置存储session参数的缓存的大小。缓存可以是下面的类型当中的任何一个:
(1)off:
会话缓存的使用被严格禁止。Nginx显式的告诉客户端session不会被重用。
(2)none:
会话缓存的使用被优雅的驳回。Nginx告诉客户端会话可能被重用,但是并不会真正的把会话参数保存到缓存中。
(3)builtin:
OpenSSL内建的缓存。只能被一个worker进程使用。缓存的大小是在会话中指定的。默认是保存20480个会话。内建的缓存的使用会导致内存碎片。
(4)shared:
所有worker进程共享的缓存。缓存的大小是按字节指定的。1M大约能存储4000个会话。可以给共享的缓存指定任意的名称。有相同名称的缓存可以被多个虚拟主机使用。
两种缓存类型可以同时使用,比如:ssl_session_cache builtin:1000 shared:SSL:10m;。但是使用共享的缓存比使用内建的缓存更高效。

下面插播一段关于SSL Session恢复的广告:
每次建立新的SSL连接都需要握手,如果由于某种原因对话中断,就需要重新握手。
这时有两种方法恢复原来的Session:1,Session ID;2,Session ticket:
(1)Session ID重用:
Session ID重用的前提是客户端和服务器端都保存了会话密钥。思想很简单,就是每次会话都有一个编号(Session ID)。如果会话中断,需要重连的时候,客户端只要给出这个编号,并且服务器端有这个记录,双方就可以重新使用已有的对话密钥,而不必重新生成。
session id 上图中,客户端给出Session ID,服务器确认该编号存在,双方就不再进行握手阶段剩余的步骤,而直接用已有的对话密钥进行加密通信。
Session ID是目前所有浏览器都支持的方法,但是它的缺点是Session ID 往往只保留在一台服务器上。所以,如果客户端的请求发到另一台服务器,就无法恢复对话。
Session ID重用在Apache中可以通过SSLSessionCache 配置,在nginx可以通过ssl_session_cache设置。
(2)Session ticket重用:
session ticket 在会话ticket复用中,服务器不用为每个session保存状态,它用一个blob数据保存状态,然后将它发给客户端用来维护后来连接,会话ticket允许服务器将其存储状态委托给客户端,类似HTTP cookie一样。
一个会话ticket是一个加密的数据blob,其中包含需要重用的TLS连接信息,如会话key等,它一般是使用ticket key加密,因为ticket key只有服务器端知道,在初始握手中服务器发送一个会话ticket到客户端,存储到客户端本地,当重用会话时,客户端发送会话ticket到服务器,服务器解密然后重用会话。
会话ticket重用在Apache中可以用SSLTicketKeyDefault配置,在nginx中使用ssl_session_tickets,但是它们都没有自动轮换ticket key的机制,只能通过重启apache、nginx来重新加载或创建新的随机ticket key。

  • ssl_session_ticket_key
Syntax:    ssl_session_ticket_key file;  
Default:    —  
Context:    http, server  
This directive appeared in version 1.5.7.  


指定一个包含ticket key的文件。当相同的ticket key在多个服务器间共享的时候,这个指令非常有必要。默认情况下,ticket key是随机生成的。
如果指定了多个ticket key,那么只有第一个会被用来加密TLS Session ticket。
文件的内容是48个字节的随机数据,可以使用下面的命令来生成:

openssl rand 48 > ticket.key  
  • ssl_session_tickets
Syntax:    ssl_session_tickets on | off;  
Default:  
ssl_session_tickets on;  
Context:    http, server  
This directive appeared in version 1.5.9.  


开启或关闭TLS Session ticket复用。

  • ssl_session_timeout
Syntax:    ssl_session_timeout time;  
Default:  
ssl_session_timeout 5m;  
Context:    http, server  


指定一个超时时间,在这个超时时间内,客户端可能会重用存储在缓存中的会话参数。

  • ssl_trusted_certificate
Syntax:    ssl_trusted_certificate file;  
Default:    —  
Context:    http, server  
This directive appeared in version 1.3.7.  


指定一个包含PEM格式的受信任的CA证书(用于验证客户端证书)的文件。
ssl_client_certificate指定指定的证书对比,这些证书的列表不会发送给客户端。

  • ssl_verify_client
Syntax:    ssl_verify_client on | off | optional | optional_no_ca;  
Default:  
ssl_verify_client off;  
Context:    http, server  


开启对客户端证书的验证。验证的结果被保存在$ssl_client_verify变量中。
optional参数请求客户端证书,并且如果客户端提供了证书,那么验证它。
optional_no_ca参数请求客户端证书,但是不需要证书是由受信任的CA签名的。证书的内容可以通过$ssl_client_cert变量来访问。

  • ssl_verify_depth
Syntax:    ssl_verify_depth number;  
Default:  
ssl_verify_depth 1;  
Context:    http, server  


设置客户端证书链的验证深度。


Nginx的ngx_http_proxy_module模块有关SSL的指令

  • proxy_ssl_certificate
Syntax:    proxy_ssl_certificate file;  
Default:    —  
Context:    http, server, location  
This directive appeared in version 1.7.8.  


指定一个包含PEM格式的证书的文件。该证书用于被代理的HTTPS服务器验证代理服务器。

  • proxy_ssl_certificate_key
Syntax:    proxy_ssl_certificate_key file;  
Default:    —  
Context:    http, server, location  
This directive appeared in version 1.7.8.  


指定一个包含PEM格式的私钥的文件。该私钥用于被代理的HTTPS服务器验证代理服务器。

  • proxy_ssl_ciphers
Syntax:    proxy_ssl_ciphers ciphers;  
Default:  
proxy_ssl_ciphers DEFAULT;  
Context:    http, server, location  
This directive appeared in version 1.5.6.  


当请求被代理的HTTPS服务器时,启用的加密算法。算法应该被指定为OpenSSL库所能理解的格式。

  • proxy_ssl_crl
Syntax:    proxy_ssl_crl file;  
Default:    —  
Context:    http, server, location  
This directive appeared in version 1.7.0.  


指定一个包含PEM格式的证书吊销列表的文件。用于验证被代理的HTTPS服务器。

  • proxy_ssl_name
Syntax:    proxy_ssl_name name;  
Default:  
proxy_ssl_name $proxy_host;  
Context:    http, server, location  
This directive appeared in version 1.7.0.  


允许重写服务器的名称,在验证被代理的HTTPS服务器的证书时会用到。同时,当建立到被代理的HTTPS服务器的SSL连接时,该名称会通过SNI来传递。

  • proxy_ssl_password_file
Syntax:    proxy_ssl_password_file file;  
Default:    —  
Context:    http, server, location  
This directive appeared in version 1.7.8.  


指定一个包含私钥的密码的文件。文件的每一行是一个密码。当加载私钥的时候,会按次序尝试每一个密码。

  • proxy_ssl_server_name
Syntax:    proxy_ssl_server_name on | off;  
Default:  
proxy_ssl_server_name off;  
Context:    http, server, location  
This directive appeared in version 1.7.0.  


当建立到被代理的HTTPS服务器的SSL连接时,开启或关闭通过SNI传递hostname。

  • proxy_ssl_session_reuse
Syntax:    proxy_ssl_session_reuse on | off;  
Default:  
proxy_ssl_session_reuse on;  
Context:    http, server, location  


决定是否能够重用SSL会话。如果日志中出现了SSL3_GET_FINISHED:digest check failed错误,那么应该尝试关闭会话重用。

  • proxy_ssl_protocols
Syntax:    proxy_ssl_protocols [SSLv2] [SSLv3] [TLSv1] [TLSv1.1] [TLSv1.2];  
Default:  
proxy_ssl_protocols TLSv1 TLSv1.1 TLSv1.2;  
Context:    http, server, location  
This directive appeared in version 1.5.6.  


当请求被代理的服务器时,启用指定的协议。

  • proxy_ssl_trusted_certificate
Syntax:    proxy_ssl_trusted_certificate file;  
Default:    —  
Context:    http, server, location  
This directive appeared in version 1.7.0.  


指定一个包含PEM格式的受信任的CA证书列表的文件。用于验证被代理的HTTPS服务器。

  • proxy_ssl_verify
Syntax:    proxy_ssl_verify on | off;  
Default:  
proxy_ssl_verify off;  
Context:    http, server, location  
This directive appeared in version 1.7.0.  


开启或关闭对被代理的HTTPS服务器证书的验证。

  • proxy_ssl_verify_depth
Syntax:    proxy_ssl_verify_depth number;  
Default:  
proxy_ssl_verify_depth 1;  
Context:    http, server, location  
This directive appeared in version 1.7.0.  


指定对被代理的HTTPS服务器证书链的验证深度。

感谢浏览tim chow的作品!

如果您喜欢,可以分享到: 更多

如果您有任何疑问或想要与tim chow进行交流

可点此给tim chow发信

如有问题,也可在下面留言: