使用OpenSSL自建一个HTTPS服务

1. 理论知识

1.1 什么是https

传统的 HTTP 协议以明文方式进行通信,不提供任何方式的数据加密,很容易被中间攻击者破解通信内容或者伪装成服务器与客户端通信,在安全性上存在很大问题。

HTTPS 协议是由 HTTP 加上 TLS/SSL 协议构建的可进行加密传输、身份认证的网络协议,主要通过数字证书、加密算法、非对称密钥等技术完成互联网数据传输加密,实现互联网传输安全保护。

关于非对称加密以及公钥私钥相关知识不在赘述,可自行了解。

PS: TLS 是传输层加密协议,前身是由网景公司1995年发布的 SSL 协议,不过在很多场合还是用 SSL 指代 TLS/SSL。

1.2 https的通信过程

下图来源:菜鸟教程

使用OpenSSL自建一个HTTPS服务

1、客户端发起 HTTPS 请求

用户访问 https 网址,连接到服务器的 443 端口,请求信息中包含了客户端支持的对称加密算法列表。

2、服务端的配置

支持 HTTPS 协议的服务器必须要有包含自己认证信息和公钥的数字证书以及自己的私钥。

3、下发证书

服务器下发自己的数字证书,这一步其实还包括从客户端支持的对称加密算法列表中选出来用于双向通信的算法,因为非对称加密是一个很耗费资源的过程,所以一般只用来协商之后用于通信的对称加密算法。

当然这一步只是明文协商了对称加密算法的名字,后面会用非对称加密来传输用于对称加密算法的秘钥,由于非对称加密的有效性,这个秘钥是不会被破解的,后续的对称加密传输也就不会被破解。

4、客户端验证证书有效性

客户端验证证书的有效性,如果证书没有问题,那么就生成一个随机值(对称加密秘钥),然后用证书中的公钥对该随机值进行加密。

5、传送用于对称通信的秘钥

用公钥加密的随机值被发送到服务端。

6、服务端解密秘钥

服务端用自己的私钥解密后,得到了客户端传过来的随机值(对称加密秘钥)。

7、双向对称加密通信

接下来就使用之前协商好的对称加密算法以及加密传输的秘钥进行通信即可。

1.3 SSL证书的签发流程

上面的 https 通信过程中,最关键的一步就是客户端如何验证服务端下发证书的有效性,因为下发证书是一个明文传输的过程,证书可能在传输过程中被拦截,被调包,被篡改,那么客户端怎么确定自己收到的就是自己想访问的网站的证书呢?

由于客户端是发起通信的一方,双方没有协商好的加密算法,服务端也不可能持有客户端的公钥信息,所以证书不能被加密发送,那么要解决这个问题就必须引入第三方可信机构(CA),客户端信任它,它就可以对服务器证书做出认证,具体来说就是使用自己的私钥对服务器证书进行签名,签名后的信息包含在证书内,这样客户端只需要持有各个 CA 的公钥,便可以对签名信息进行解密来验证证书的有效性。

使用OpenSSL自建一个HTTPS服务

上图就是 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 ]
basicConstraints        = CA:false
#      
#
# 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            = example@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            = example@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 根证书,看看证书信息:

使用OpenSSL自建一个HTTPS服务

打开浏览器访问:https://localhost

使用OpenSSL自建一个HTTPS服务

https 访问成功,查看一下服务器下发的证书信息:

使用OpenSSL自建一个HTTPS服务

手动搭建 https 服务完成。

发表评论

相关文章