使用OpenSSL自建一個HTTPS服務

1. 理論知識

1.1 什麼是https

傳統的 HTTP 協議以明文方式進行通信,不提供任何方式的數據加密,很容易被中間攻擊者破解通信內容或者偽裝成服務器與客戶端通信,在安全性上存在很大問題。

HTTPS 協議是由 HTTP 加上 TLS/SSL 協議構建的可進行加密傳輸、身份認證的網絡協議,主要通過數字證書、加密算法、非對稱密鑰等技術完成互聯網數據傳輸加密,實現互聯網傳輸安全保護。

關於非對稱加密以及公鑰私鑰相關知識不在贅述,可自行了解。

PS: TLS 是傳輸層加密協議,前身是由網景公司1995年發佈的 SSL 協議,不過在很多場合還是用 SSL 指代 TLS/SSL。

1.2 https的通信過程

下圖來源:菜鳥教程

rekhX6.png

1、客戶端發起 HTTPS 請求

用戶訪問 https 網址,連接到服務器的 443 端口,請求信息中包含了客戶端支持的對稱加密算法列表。

2、服務端的配置

支持 HTTPS 協議的服務器必須要有包含自己認證信息和公鑰的數字證書以及自己的私鑰。

3、下發證書

服務器下發自己的數字證書,這一步其實還包括從客戶端支持的對稱加密算法列表中選出來用於雙向通信的算法,因為非對稱加密是一個很耗費資源的過程,所以一般只用來協商之後用於通信的對稱加密算法。

當然這一步只是明文協商了對稱加密算法的名字,後面會用非對稱加密來傳輸用於對稱加密算法的秘鑰,由於非對稱加密的有效性,這個秘鑰是不會被破解的,後續的對稱加密傳輸也就不會被破解。

4、客戶端驗證證書有效性

客戶端驗證證書的有效性,如果證書沒有問題,那麼就生成一個隨機值(對稱加密秘鑰),然後用證書中的公鑰對該隨機值進行加密。

5、傳送用於對稱通信的秘鑰

用公鑰加密的隨機值被發送到服務端。

6、服務端解密秘鑰

服務端用自己的私鑰解密後,得到了客戶端傳過來的隨機值(對稱加密秘鑰)。

7、雙向對稱加密通信

接下來就使用之前協商好的對稱加密算法以及加密傳輸的秘鑰進行通信即可。

1.3 SSL證書的簽發流程

上面的 https 通信過程中,最關鍵的一步就是客戶端如何驗證服務端下發證書的有效性,因為下發證書是一個明文傳輸的過程,證書可能在傳輸過程中被攔截,被調包,被篡改,那麼客戶端怎麼確定自己收到的就是自己想訪問的網站的證書呢?

由於客戶端是發起通信的一方,雙方沒有協商好的加密算法,服務端也不可能持有客戶端的公鑰信息,所以證書不能被加密發送,那麼要解決這個問題就必須引入第三方可信機構(CA),客戶端信任它,它就可以對服務器證書做出認證,具體來說就是使用自己的私鑰對服務器證書進行簽名,簽名後的信息包含在證書內,這樣客戶端只需要持有各個 CA 的公鑰,便可以對簽名信息進行解密來驗證證書的有效性。

rek5nK.png

上圖就是 SSL 證書的簽發流程:

  1. 瀏覽器需要記住各大CA。瀏覽器是怎麼樣記住CA的呢?在瀏覽器開發的時候,各個CA就把自己的根證書交給了瀏覽器,那麼 CA 根證書中最重要的信息就是該 CA 機構的公鑰了,此外還有 CA 機構的標識信息以便匹配服務器證書中聲明對它簽名的機構。

  2. 各大網站,如支付寶,將自己的證書交給CA。CA此時需要做的事情是,通過各種法定機構,驗證網站的身份。如果CA確定該網站是真的,那麼就需要用自己的私鑰給網站的證書籤名。網站交給 CA 的證書中除了包含自己的 URL 等標識信息外,還必須有網站自己的公鑰信息,因為簽完名之後證書就不能動了,而後面客戶端需要服務器的公鑰來加密信息。

  3. 此處就回到了之前的 https 通信過程,瀏覽器拿到證書後首先根據該證書的信息,如哪個 CA 對它進行了簽名,結合瀏覽器已有的 CA 的根證書列表,對該證書進行驗證,具體來說就是用對應 CA 的公鑰對證書中的簽名信息進行解密,再與證書信息進行比對,如果驗證通過,那麼就可以確定這個證書的有效性。

2. 搭建https服務

本機環境:Ubuntu 20.04,openssl 1.1.1h

明白了 https 工作過程後,我們便可以利用開源的安全傳輸層密碼庫 openssl 來自建一個實驗性的 https 服務。

想一想我們需要什麼?

  • 一個提供 https 服務的服務器需要有經過 CA 認證的數字證書和自己的公鑰私鑰(公鑰放在證書里)
  • CA 認證是需要收費的,所以我們需要自建一個 CA 機構
  • 自建的 CA 機構需要有標識自己身份的根證書(包含公鑰),以及用來對服務器證書進行簽名的私鑰
  • 一個 https 站點,收到客戶端訪問時下發自己的數字證書
  • 客戶端需要持有 CA 的根證書,所以我們需要手動導入自建 CA 的證書

2.1 自建CA

首先創建一個 myCA 目錄來存放自建 CA 的相關信息:

# 創建用戶目錄下的 https 目錄作為實驗目錄,在其中創建 myCA 目錄存放 CA 相關信息
# myCA/signedcerts:存放經 CA 認證的證書副本
# myCA/private:存放 CA 私鑰
cd && mkdir -p https/myCA/signedcerts && mkdir https/myCA/private && cd https/myCA

# 創建證書庫,這兩個文件存放了 CA 每一次頒發證書的記錄
echo '01' > serial && touch index.txt

使用任何你熟悉的編輯器在 myCA 目錄下新建一個 caconfig.cnf 文件來配置 CA 信息,其中需要注意的部分有:

  • username:修改為自己的用戶名

  • default_md:默認對證書籤名時的摘要算法,不要使用 sha2 以下的算法,否則服務器會啟動失敗(老版本可能沒這個限制)

# My sample caconfig.cnf file.
#
# Default configuration to use when one is not provided on the command line.
#
[ ca ]
default_ca      = local_ca
#
#
# Default location of directories and files needed to generate certificates.
#
[ local_ca ]
dir             = /home/{username}/https/myCA    # CA 目錄
certificate     = $dir/cacert.pem
database        = $dir/index.txt
new_certs_dir   = $dir/signedcerts
private_key     = $dir/private/cakey.pem
serial          = $dir/serial
#      
#
# Default expiration and encryption policies for certificates.
# 認證其他服務器證書設置
default_crl_days        = 365                  # 默認吊銷證書列表更新時間
default_days            = 1825                 # 默認證書有效期
default_md              = sha256               # 默認對證書籤名時的摘要算法
#      
policy          = local_ca_policy
x509_extensions = local_ca_extensions
#      
#
# Default policy to use when generating server certificates.  The following
# fields must be defined in the server certificate.
#
[ local_ca_policy ]
commonName              = supplied
stateOrProvinceName     = supplied
countryName             = supplied
emailAddress            = supplied
organizationName        = supplied
organizationalUnitName  = supplied
#      
#
# x509 extensions to use when generating server certificates.
#
[ local_ca_extensions ]
subjectAltName          = DNS:alt.tradeshowhell.com
basicConstraints        = CA:false
nsCertType              = server
#      
#
# The default root certificate generation policy.
# 生成 CA 根證書設置
[ req ]
default_bits    = 2048                                          # 默認生成證書請求時的秘鑰長度
default_keyfile = /home/{username}/https/myCA/private/cakey.pem # 默認私鑰存放位置
default_md      = sha256                                        # 默認證書籤名時使用的摘要算法
#     
prompt                  = no
distinguished_name      = root_ca_distinguished_name
x509_extensions         = root_ca_extensions
#
#
# Root Certificate Authority distinguished name.  Change these fields to match
# your local environment!
#
[ root_ca_distinguished_name ]
commonName              = myCA              # CA 機構名
stateOrProvinceName     = JS                # 所在省份
countryName             = CN                # 所在國家(僅限兩字符)
emailAddress            = [email protected]    # 郵箱
organizationName        = USTC              # 組織名
organizationalUnitName  = SE                # 單位名
#      
[ root_ca_extensions ]
basicConstraints        = CA:true

然後生成自簽名的 CA 根證書和秘鑰,CA 的根證書是自簽名的,也就是用自己的私鑰對自己的證書籤名,因為它是可信機構,不需要別人認證:

# 設置 openssl 環境變量,以 CA 身份運行接下來的 openssl 命令
export OPENSSL_CONF=~/https/myCA/caconfig.cnf

# 生成 rsa 秘鑰對和 pem 格式的 CA 自簽名根證書,有效期 1825天
# 需要輸入密碼,每次對服務器證書進行簽名都需要這個密碼,最少4位
openssl req -x509 -newkey rsa:2048 -out cacert.pem -outform PEM -days 1825

以上步驟生成了自建 CA 機構的根證書和私鑰文件:

  • myCA/cacert.pem: CA 根證書
  • myCA/private/cakey.pem: CA 私鑰

2.2 創建服務器證書並用CA簽名

首先創建一個服務器目錄來存放服務器相關信息:

# 在 myCA 同級目錄下創建 server 目錄存放服務器相關信息
cd .. && mkdir server && cd server

使用任何你熟悉的編輯器在 server 目錄下新建一個 server.cnf 文件來配置服務器信息:

#
# server.cnf
#

[ req ]
prompt                  = no
distinguished_name      = server_distinguished_name

[ server_distinguished_name ]
commonName              = localhost         # 服務器域名,由於在本地測試,設為 localhost 即可
stateOrProvinceName     = JS                # 服務器所在省份
countryName             = CN                # 服務器所在國家(僅限2字符)
emailAddress            = [email protected]    # 郵箱
organizationName        = USTC              # 組織名
organizationalUnitName  = SE                # 單位名

生成秘鑰對和未經簽名的服務器證書文件:

# 設置 openssl 環境變量,以服務器身份運行接下來的 openssl 命令
export OPENSSL_CONF=~/https/server/server.cnf

# 生成臨時服務器秘鑰和未經簽名的證書文件
openssl req -newkey rsa:2048 -keyout tempkey.pem -keyform PEM -out tempreq.pem -outform PEM

# 將臨時私鑰轉換為未加密狀態,也可以直接改名,這樣就是加密狀態,要輸入密碼才能啟動服務器
openssl rsa < tempkey.pem > server_key.pem

然後使用自建 CA 對服務器證書進行簽名:

# 設置 openssl 環境變量,以 CA 身份運行接下來的 openssl 命令
export OPENSSL_CONF=~/https/myCA/caconfig.cnf
# 對服務器證書進行簽名
openssl ca -in tempreq.pem -out server_crt.pem

刪除臨時證書和私鑰文件:

rm tempkey.pem && rm tempreq.pem

現在,自簽名的服務器證書和私鑰文件便產生了:

  • server/server_crt.pem: 服務器證書
  • server/server_key.pem: 服務器私鑰

2.3 使用https訪問服務器

以 apache 為例來配置 https 服務,首先安裝 apache:

sudo apt install apache2

apache 默認是 http 訪問的,我們需要配置它的 https 服務並啟用,首先 cd 到 apache 的可用站點目錄:

cd /etc/apache2/sites-available/

其中有一個默認的 ssl 站點 default-ssl.conf,我們不用新建站點,修改它裏面的 ssl 配置並啟用即可,修改這個文件需要 sudo 權限,打開後找到其中的證書和私鑰設置,修改為自己的服務器證書和私鑰文件位置:

SSLCertificateFile  /home/{username}/https/server/server_crt.pem
SSLCertificateKeyFile /home/{username}/https/server/server_key.pem

然後重啟 apache 服務器:

# 啟用默認 ssl 站點和 ssl 模塊
a2ensite default-ssl.conf
a2enmod ssl
# 重啟 apache 服務
systemctl restart apache2

到這一步我們的 https 服務就搭建完成了,接下來在瀏覽器中手動導入自建 CA 的根證書,以 Chrome 為例,進入設置 -> 隱私設置和安全性 -> 安全 -> 管理證書 -> 授權機構,點擊導入,選擇 myCA 目錄下的證書文件 cacert.pem,確定導入,然後就可以在列表中找到我們的 CA 根證書,看看證書信息:

rekI0O.png

打開瀏覽器訪問://localhost

rek7Ae.png

Chrome 比較嚴格,即使手動導入了 CA 根證書,它仍然不信任這個網站,但是訪問是沒有問題的,查看一下服務器下發的證書信息:

reko7D.png

手動搭建 https 服務完成。