基于国密搭建双向认证Web服务器

目的

  1. 搭建一个Web服务器;
  2. 配置https访问,并且支持双向认证访问,即客户端和服务器端均需要提供证书访问;
  3. 所有的证书需要使用国密算法;

实验过程

Ubuntu安装Nginx

1
sudo apt install nginx

  • Nginx相关命令

    • 启动sudo systemctl start nginx
    • 关闭sudo systemctl stop nginx
    • 重启sudo systemctl restart nginx
  • 设置防火墙,保证局域网可以访问该web服务器

    1
    2
    3
    4
    sudo ufw app list
    sudo ufw allow 'Nginx HTTP'
    sudo ufw allow 'Nginx HTTPS'
    sudo ufw status

    局域网下另一台机器可以进行访问

HTTPS双向认证分析

  1. HTTPS单向认证流程

    单向认证流程中,服务器端保存着公钥证书和私钥两个文件,整个握手过程如下:

    1. 客户端发起建立HTTPS连接请求,将SSL协议版本的信息发送给服务器端;
    2. 服务器端将本机的公钥证书server.crt发送给客户端;
    3. 客户端读取公钥证书server.crt,取出了服务端公钥;
    4. 客户端生成一个随机数密钥R,用刚才得到的服务器公钥去加密这个随机数形成密文,发送给服务端;
    5. 服务端用自己的私钥server.key去解密这个密文,得到了密钥R;
    6. 服务端和客户端在后续通讯过程中就使用这个密钥R进行通信;

  2. HTTPS双向认证流程

    双认证流程握手过程如下:

    1. 客户端发起建立HTTPS连接请求,将SSL协议版本的信息发送给服务端;
    2. 服务器端将本机的公钥证书server.crt发送给客户端;
    3. 客户端读取公钥证书server.crt,取出了服务端公钥;
    4. 客户端将客户端公钥证书client.crt发送给服务器端;
    5. 服务器端解密客户端公钥证书,拿到客户端公钥;
    6. 客户端发送自己支持的加密方案给服务器端;
    7. 服务器端根据自己和客户端的能力,选择一个双方都能接受的加密方案,使用客户端的公钥加密后发送给客户端;
    8. 客户端使用自己的私钥解密加密方案,生成一个随机数R,使用服务器公钥加密后传给服务器端;
    9. 服务端用自己的私钥去解密这个密文,得到了密钥R;
    10. 服务端和客户端在后续通讯过程中就使用这个密钥R进行通信;

利用国密生成证书

如果要把整个双向认证的流程跑通,最终需要4个证书文件:

  1. 服务器端公钥证书:server.crt
  2. 服务器端私钥文件:server.key
  3. 客户端公钥证书:client.crt
  4. 客户端私钥文件:client.key

生成这一系列证书之前,需要先生成一个CA根证书,然后由这个CA根证书颁发服务器公钥证书和客户端公钥证书;

全程使用GmSSL来生成一系列的自签名证书,自签名证书没有经过证书机构的认证,很多浏览器会认为不安全,但用来实验是足够的。

  • 安装GmSSL

    1. 下载源代码并解压

      1
      sudo unzip GmSSL-master.zip
    2. 编译安装

      1
      2
      3
      ./config no-saf no-sdf no-skf no-sof no-zuc
      sudo make
      sudo make install
    3. 执行gmssl命令行工具检查安装成功

      1
      gmssl version

生成自签名根证书

1
2
3
4
5
6
7
8
sudo mkdir /etc/nginx/keys/
cd /etc/nginx/keys/
# 利用SM2创建根证书私钥
gmssl ecparam -genkey -name sm2p256v1 -out root.key
# 利用SM3创建根证书请求文件
gmssl req -sm3 -new -out root.csr -key root.key
# 创建根证书
gmssl x509 -req -in root.csr -out root.crt -signkey root.key -CAcreateserial -days 3650

经过上面的命令,可以得到一个签名有效期为10年的根证书root.crt,后续用这个根证书去颁发服务器证书和客户端证书。

证书中的pub与root.key是一致的,这样输出的root.crt中,包含了会话的公钥,用于分发加密。而最后的sm3的数字签名,用于通过上面的公钥来验证证书的完整性。

生成自签名服务器端证书

1
2
3
4
5
6
# 利用SM2生成服务器端证书私钥
gmssl ecparam -genkey -name sm2p256v1 -out server.key
# 利用SM3生成服务器端证书请求文件
gmssl req -sm3 -new -out server.csr -key server.key
# 生成服务器端公钥证书
gmssl x509 -req -in server.csr -out server.crt -signkey server.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650

经过上面命令得到:

  • server.key:服务器端的秘钥文件
  • server.crt:有效期十年的服务器端公钥证书(使用根证书和服务器端私钥文件一起生成)

生成自签名客户端证书

1
2
3
4
5
6
# 利用SM2生成客户端证书私钥
gmssl ecparam -genkey -name sm2p256v1 -out client.key
# 利用SM3生成客户端证书请求文件
gmssl req -sm3 -new -out client.csr -key client.key
# 生成客户端公钥证书
gmssl x509 -req -in client.csr -out client.crt -signkey client.key -CA root.crt -CAkey root.key -CAcreateserial -days 3650

经过上面的命令得到:

  • client.key:客户端的私钥文件
  • client.crt:有效期十年的客户端证书(使用根证书和客户端私钥一起生成)

Nginx配置

  1. 创建Nginx配置文件

    1
    2
    cd /etc/nginx/sites-enabled
    vim https.conf

    配置文件如下:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    server {
    listen 443 ssl;
    server_name 192.168.1.4;
    ssl on;
    ssl_certificate /etc/nginx/keys/server.crt;#配置证书位置
    ssl_certificate_key /etc/nginx/keys/server.key;#配置秘钥位置
    ssl_client_certificate /etc/nginx/keys/client.crt;#双向认证
    ssl_verify_client on; #双向认证
    ssl_session_timeout 5m;
    root html;
    index index.html;
    location / {
    try_files $uri $uri/ =404;
    }
    }
  2. 重载配置

    1
    nginx -s reload

验证

  1. 使用同一局域网下的另一台机器访问该nginx服务器https://192.168.1.4/;

显示如下,需要提供证书才能访问

  1. 将密钥复制到另一个文件夹下,使用curl进行验证

    1
    2
    3
    cp -r /etc/nginx/keys/ /tmp/
    cd /tmp/keys/
    curl --cert ./client.crt --key ./client.key https://192.168.1.4/ -k -v

    服务器端与客户端的握手流程如下:

    数据包相关参数如下图:

    返回的网页源代码如下,可以正常访问,说明双向认证的Web服务器搭建成功。