HTTPS协议和原理

本文着重介绍SSL握手协议。
注意:如果没有特殊说明,本文中SSL和TLS是同义词。


SSL握手协议

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


Nginx的ngx_http_ssl_module模块

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

示例配置:

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模块提供的指令

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

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

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

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

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会发布到第二个站点上。

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

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

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一种。该算法很容易被监听者,路由器等中间设备攻击,所以不提倡使用。

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

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

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

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

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;
    }
}
Syntax:	ssl_prefer_server_ciphers on | off;
Default:	
ssl_prefer_server_ciphers off;
Context:	http, server

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

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)的时候才起作用。

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。

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
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复用。

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

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

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

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

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变量来访问。

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

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


Nginx的ngx_http_proxy_module模块有关SSL的指令

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

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

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

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

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

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

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

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

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来传递。

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

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

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。

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

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

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.

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

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

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

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

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

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服务器证书链的验证深度。