關於使用HTTPS/SSL的必要性,可以自行baidu,援引的說法,EFF(Electronic Frontier Foundation),全球過半流量采用https。
https://www.oschina.net/news/82222/https-web
關於SSL的握手過程,簡單的來說,如下,線上報文流:
(1).client_hello
客戶端發起請求,以明文傳輸請求信息,包含版本信息,加密套件候選列表,壓縮算法候選列表,隨機數,擴展字段等信息,相關信息如下:
• 支持的最高TSL協議版本version,從低到高依次 SSLv2 SSLv3 TLSv1 TLSv1.1 TLSv1.2,當前基本不再使用低於 TLSv1 的版本;
• 客戶端支持的加密套件 cipher suites 列表, 每個加密套件對應前面 TLS 原理中的四個功能的組合:認證算法 Au (身份驗證)、密鑰交換算法 KeyExchange(密鑰協商)、對稱加密算法 Enc (信息加密)和信息摘要 Mac(完整性校驗);
• 支持的壓縮算法 compression methods 列表,用於后續的信息壓縮傳輸;
• 隨機數 random_C,用於后續的密鑰的生成;
• 擴展字段 extensions,支持協議與算法的相關參數以及其它輔助信息等,常見的 SNI 就屬於擴展字段,后續單獨討論該字段作用。
(2).server_hello+server_certificate+sever_hello_done
• server_hello, 服務端返回協商的信息結果,包括選擇使用的協議版本 version,選擇的加密套件 cipher suite,選擇的壓縮算法 compression method、隨機數 random_S 等,其中隨機數用於后續的密鑰協商;
• server_certificates, 服務器端配置對應的證書鏈,用於身份驗證與密鑰交換;
• server_hello_done,通知客戶端 server_hello 信息發送結束;
(3).證書校驗
客戶端驗證證書的合法性,如果驗證通過才會進行后續通信,否則根據錯誤情況不同做出提示和操作,合法性驗證包括如下:
• [證書鏈]的可信性 trusted certificate path,方法如前文所述;
• 證書是否吊銷 revocation,有兩類方式離線 CRL 與在線 OCSP,不同的客戶端行為會不同;
• 有效期 expiry date,證書是否在有效時間范圍;
• 域名 domain,核查證書域名是否與當前的訪問域名匹配,匹配規則后續分析;
(4).client_key_exchange+change_cipher_spec+encrypted_handshake_message
(a) client_key_exchange,合法性驗證通過之后,客戶端計算產生隨機數字 Pre-master,並用證書公鑰加密,發送給服務器;
(b) 此時客戶端已經獲取全部的計算協商密鑰需要的信息:兩個明文隨機數 random_C 和 random_S 與自己計算產生的 Pre-master,計算得到協商密鑰;
enc_key=Fuc(random_C, random_S, Pre-Master)
(c) change_cipher_spec,客戶端通知服務器后續的通信都采用協商的通信密鑰和加密算法進行加密通信;
(d) encrypted_handshake_message,結合之前所有通信參數的 hash 值與其它相關信息生成一段數據,采用協商密鑰 session secret 與算法進行加密,然后發送給服務器用於數據與握手驗證;
(5).change_cipher_spec+encrypted_handshake_message
(a) 服務器用私鑰解密加密的 Pre-master 數據,基於之前交換的兩個明文隨機數 random_C 和 random_S,計算得到協商密鑰:enc_key=Fuc(random_C, random_S, Pre-Master);
(b) 計算之前所有接收信息的 hash 值,然后解密客戶端發送的 encrypted_handshake_message,驗證數據和密鑰正確性;
(c) change_cipher_spec, 驗證通過之后,服務器同樣發送 change_cipher_spec 以告知客戶端后續的通信都采用協商的密鑰與算法進行加密通信;
(d) encrypted_handshake_message, 服務器也結合所有當前的通信參數信息生成一段數據並采用協商密鑰 session secret 與算法加密並發送到客戶端;
(6).握手結束
客戶端計算所有接收信息的 hash 值,並采用協商密鑰解密 encrypted_handshake_message,驗證服務器發送的數據和密鑰,驗證通過則握手完成;
(7).加密通信
開始使用協商密鑰與算法進行加密通信。
可知:
1、生成對話密鑰一共需要三個隨機數。
2、握手之后的對話使用"對話密鑰"加密(對稱加密),服務器的公鑰和私鑰只用於加密和解密"對話密鑰"(非對稱加密),無其他作用。
3、服務器公鑰放在服務器的數字證書之中。
找了張交互圖(不包含后面的應用交互,圖中實際上少了最后一步服務器結合所有當前的通信參數信息生成一段數據並采用協商密鑰 session secret 與算法加密、最后發送給客戶端)如下:
注意:
(a) 服務器也可以要求驗證客戶端,即雙向認證,可以在過程2要發送 client_certificate_request 信息,客戶端在過程4中先發送 client_certificate與certificate_verify_message 信息,證書的驗證方式基本相同,certificate_verify_message 是采用client的私鑰加密的一段基於已經協商的通信信息得到數據,服務器可以采用對應的公鑰解密並驗證;
(b) 根據使用的密鑰交換算法的不同,如 ECC 等,協商細節略有不同,總體相似;
(c) sever key exchange 的作用是 server certificate 沒有攜帶足夠的信息時,發送給客戶端以計算 pre-master,如基於 DH 的證書,公鑰不被證書中包含,需要單獨發送;
(d) change cipher spec 實際可用於通知對端改版當前使用的加密通信方式,當前沒有深入解析;
(e) alter message 用於指明在握手或通信過程中的狀態改變或錯誤信息,一般告警信息觸發條件是連接關閉,收到不合法的信息,信息解密失敗,用戶取消操作等,收到告警信息之后,通信會被斷開或者由接收方決定是否斷開連接。
不過在大部分的場景下,服務器並不要求驗證客戶端,除了在一些情況下比如我們在銀企直連、銀證轉賬、清算中心的一些系統中就采用的是雙向認證。
==================配置實戰=======================
首先,查看安裝了哪些模塊,如下:
[root@iZ23i5mx5vxZ sbin]# ./nginx -V
nginx version: nginx/1.10.0
built by gcc 4.4.7 20120313 (Red Hat 4.4.7-17) (GCC)
built with OpenSSL 1.0.1e-fips 11 Feb 2013
TLS SNI support enabled
configure arguments: --user=www --group=www --prefix=/usr/local/nginx --with-http_stub_status_module --with-http_ssl_module --with-http_v2_module --with-http_gzip_static_module --with-ipv6 --with-http_sub_module
建議把相關證書和key放在$NGINX_HOME/security或cert下,便於統一管理。
建議命名規范為:
ca-key.pem 一般來說是CA證書的rsa私鑰文件
ca.pem 可信Certificate Authority (CA)證書,通常內部通信的話,可以自簽名,通過openssl req -new -x509生成。
client-cert.pem 客戶端公鑰證書(一般HTTPS不用,瀏覽器會自動管理,但是自行開發的客戶端就需要了,比如java rpc,jdbc客戶端,nginx和upstream server的通信)
client-key.pem 客戶端私鑰(一般HTTPS不用,瀏覽器會自動管理,但是自行開發的客戶端就需要了,比如java rpc,jdbc客戶端,nginx和upstream server的通信)
server-cert.pem 服務器公鑰證書,必須由擁有它的CA認證,會發送給每個連接到服務器的客戶端。
server-key.pem 服務器私鑰,技術上來說公鑰證書和私鑰存儲在一起也是可以的,發送的時候會僅僅把公鑰部分發給客戶端。
[root@iZ23i5mx5vxZ sbin]# cd ../conf/
[root@iZ23i5mx5vxZ conf]# openssl genrsa -des3 -out server.key 1024 #證書現在有很多三方可以免費生成,如阿里雲,不一定要人工生成,從別的機器拷貝一個也是可以的。
Generating RSA private key, 1024 bit long modulus
.................................++++++
...................++++++
e is 65537 (0x10001)
Enter pass phrase for server.key:
Verifying - Enter pass phrase for server.key:
[root@iZ23i5mx5vxZ conf]# openssl req -new -key server.key -out server.csr
Enter pass phrase for server.key:
You are about to be asked to enter information that will be incorporated
into your certificate request.
What you are about to enter is what is called a Distinguished Name or a DN.
There are quite a few fields but you can leave some blank
For some fields there will be a default value,
If you enter '.', the field will be left blank.
-----
Country Name (2 letter code) [XX]:CN
State or Province Name (full name) []:
Locality Name (eg, city) [Default City]:
Organization Name (eg, company) [Default Company Ltd]:ldtrader.com
Organizational Unit Name (eg, section) []:
Common Name (eg, your name or your server's hostname) []:ldtrader.com # 從這里可以看出,openssl的定義要比keytool更加清晰
Email Address []:
Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:tomcat
An optional company name []:
[root@iZ23i5mx5vxZ conf]# openssl rsa -in server.key -out server_nopwd.key
Enter pass phrase for server.key:
writing RSA key
[root@iZ23i5mx5vxZ conf]# openssl x509 -req -days 3650 -in server.csr -signkey server_nopwd.key -out server.crt
Signature ok
subject=/C=CN/L=Default City/O=ldtrader.com/CN=ldtrader.com
Getting Private key
vim nginx.conf如下:
worker_processes 1; #error_log logs/error.log; #error_log logs/error.log notice; #error_log logs/error.log info; #pid logs/nginx.pid; events { use epoll; worker_connections 1024; } http { include mime.types; default_type application/octet-stream; log_format main '$remote_addr - $remote_user [$time_local] "$request" ' '$status $body_bytes_sent "$http_referer" ' '"$http_user_agent" "$http_x_forwarded_for"'; access_log logs/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; #keepalive_timeout 0; keepalive_timeout 65; #gzip on; # upstream web-admin { server localhost:8080 fail_timeout=15s; server localhost:8080 fail_timeout=15s; server localhost:8080 fail_timeout=15s; } server { # 也可以采用單獨一個server塊,通過重定向到443,效果一樣的 listen 80; listen 443 ssl; server_name localhost; # ssl on; 注意:400 Bad Request: The plain HTTP request was sent to HTTPS port,這里一定要注釋,否則就只能接收https,http請求會報錯 ssl_certificate /tmp/ssl/server.crt; ssl_certificate_key /tmp/ssl/server_nopwd.key; ssl_session_cache shared:SSL:1m; ssl_session_timeout 5m; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; error_page 497 https://$host$uri?$args; #charset koi8-r; #access_log logs/host.access.log main; location / { root html; index index.html index.htm; proxy_pass http://web-admin;
proxy_set_header Host $host:80; #注意, 原host必須配置, 否則傳遞給后台的值是web-admin,端口如果沒有輸入的話會是80, 這會導致連接失敗
proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "Upgrade";
}
#error_page 404 /404.html; # redirect server error pages to the static page /50x.html
#error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
啟動或者reload配置。
使用http://ip訪問,如下:
被自動重定向到https,可見在nginx層做https相比tomcat要簡單得多。
值得注意的是,給現有的網站加上nginx代理可能會導致一些資源引用的URL失效,如果有第三方也訪問本站比如websocket,切記一定要提前規划好資源走相對路徑,否則可能會導致后面的變更難以推進。
在spring boot上,有一些特殊注意點,可以參考:spring boot 1.x nginx前置https配置及注意點。
參考:
https://blog.cloudflare.com/announcing-keyless-ssl-all-the-benefits-of-cloudflare-without-having-to-turn-over-your-private-ssl-keys/
https://blog.cloudflare.com/keyless-ssl-the-nitty-gritty-technical-details/
https://www.nginx.com/resources/admin-guide/nginx-ssl-termination/
https://www.nginx.com/resources/admin-guide/nginx-https-upstreams/
http://blog.csdn.net/hherima/article/details/52469674
http://www.ruanyifeng.com/blog/2014/09/illustration-ssl.html