使用OpenSSL自建一個HTTPS服務

Assassin007發表於2020-12-13

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            = Hui4401@qq.com    # 郵箱
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            = Hui4401@qq.com    # 郵箱
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

開啟瀏覽器訪問:https://localhost

rek7Ae.png

Chrome 比較嚴格,即使手動匯入了 CA 根證照,它仍然不信任這個網站,但是訪問是沒有問題的,檢視一下伺服器下發的證照資訊:

reko7D.png

手動搭建 https 服務完成。

相關文章