如何为团队内部提供免费的可信证书解决方案

前言

内网 HTTPS 化的必要性

  开发团队或者公司内部一般会采用内外网隔离、上网行为过滤等措施,比较可靠地保证了内部设备无法被外部网络所侦测,从而可能认为 HTTP 内网站点是一个相对安全的存在。即使在 HTTPS 证书如此盛行的今天,也还暂时不考虑内部站点的 HTTPS 化。IP + Port 或者 http://本地域名 的访问方式依旧是座上宾。当然,如果考虑到购买 HTTPS 证书的成本或者团队内网站点采用 Letsencrypt 等免费证书过于麻烦(只能采用 DNS 验证的方式每三个月申请一次新证书),那么自签名 SSL 证书则成为首选了。不过,如果为每一个内网站点都生成一个 SSL 证书,然后让大家都手动把 HTTPS 标为可信,那么当面临大量内网站点时,大家可能要被搞崩溃。更为可行的办法是,生成一个内网用的根证书,只标记该根证书可信

根证书

  与其相信别人根证书生成的 SSL 证书,不如相信自己根证书生成的。我们的目的毕竟不是要任何一个人都把我们自签名的证书标为可信,只要在内网内使用内网站点的设备能够信任即可。而且成为一个受到公众信任的根证书是非常困难的一件事,即使经过几十年可能也没有办法做到。如今现有的根证书实际上有限,像我们平常熟悉的 Letsencrypt、ZeroSSL、Cloudflare 等等并不是根证书而是中间证书。有点类似总代理和分代理的感觉,根证书在业界具有广泛的公信力,但是让根证书去给个人或者企业生成证书可能有点忙不过来。于是根证书生成若干个中间证书,再由中间证书来为个人或者企业生成实际的证书。

  一般来说,操作系统或浏览器的产商会预置国际上认可的根证书。如下所示,为 Mac OS 上预置的根证书列表。

Mac OS 预置根证书 Root Certificate

实践

  话不多说,让我们来实践一下如何生成自己的根证书和签发 SSL 证书吧。

生成根证书

安装 OpenSSL (可选)

  此处只考虑 Mac OS 和 Ubuntu,其他环境如何安装可以自行搜索。

# Mac OS
brew install openssl

# Ubuntu
sudo apt install -y openssl

创建根密钥

  使用以下命令创建根密钥 zhonger-key.pem

openssl genrsa -out zhonger-key.pem 4096

创建根证书并签名

  使用刚创建好的根密钥 zhonger-key.pem 生成根证书,并输入相关信息。

openssl req -new -x509 -days 3600 -key zhonger-key.pem -out zhongerca.pem
╰─$ openssl req -new -x509 -days 3600 -key zhonger-key.pem -out zhongerca.pem 
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) [AU]:CN
State or Province Name (full name) [Some-State]:Shanghai
Locality Name (eg, city) []:Shanghai
Organization Name (eg, company) [Internet Widgits Pty Ltd]:zhonger
Organizational Unit Name (eg, section) []:zhonger
Common Name (e.g. server FQDN or YOUR name) []:lisz.me
Email Address []:contact@lisz.me

验证根证书

$ openssl x509 -text -in zhongerca.pem -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            44:48:03:56:ff:15:57:03:00:34:1f:85:61:ca:f7:7a:1e:4f:38:8f
        Signature Algorithm: sha256WithRSAEncryption
        Issuer: C = CN, ST = Shanghai, L = Shanghai, O = zhonger, OU = zhonger, CN = lisz.me, emailAddress = contact@lisz.me
        Validity
            Not Before: Aug  3 05:25:47 2022 GMT
            Not After : Jun 11 05:25:47 2032 GMT
        Subject: C = CN, ST = Shanghai, L = Shanghai, O = zhonger, OU = zhonger, CN = lisz.me, emailAddress = contact@lisz.me
        Subject Public Key Info:
            Public Key Algorithm: rsaEncryption
                RSA Public-Key: (4096 bit)
                Modulus:
                    00:b2:d1:47:73:8a:83:48:e3:47:1a:41:01:f6:63:
                    69:43:39:71:eb:2b:74:be:dc:63:f3:df:79:66:ee:
                    00:30:65:b3:4f:7e:58:88:00:13:09:e6:4f:74:57:
                    fa:a3:56:24:cd:b6:1f:53:25:77:98:bf:9f:45:64:
                    7c:6c:04:23:c4:8f:0f:bf:2e:b3:d1:2e:4c:05:4d:
                    4c:e6:65:54:ad:0c:35:b7:d9:c8:74:97:19:c7:a5:
                    cd:9a:a4:73:37:13:71:80:34:7c:bc:b3:41:5a:34:
                    bb:16:82:44:18:a1:0a:a5:f5:f1:07:ca:8d:b3:9a:
                    ef:74:fb:a0:6c:72:4a:53:5c:59:74:6f:aa:c7:bc:
                    48:26:af:1b:70:f3:5f:7f:c7:df:8d:e5:da:e4:f4:
                    d2:fa:90:d3:e2:67:e1:9a:df:c7:c4:c7:53:6f:62:
                    25:ed:ff:0a:17:cf:8d:4d:84:6b:38:cb:49:e7:3d:
                    c5:2b:15:76:e6:eb:cc:17:94:40:20:7d:ee:8c:36:
                    6d:cf:9c:d7:1f:a6:41:20:9d:45:cd:57:8f:a8:61:
                    f8:8b:e9:31:6a:a9:96:c1:db:57:64:0b:09:da:ca:
                    b3:07:d9:55:ed:fe:69:a0:9c:78:5b:59:a5:7b:a1:
                    2b:4d:68:22:b4:7f:db:c6:c1:12:ee:eb:9b:29:38:
                    ae:7b:4c:0d:2a:ab:33:3f:af:a8:7b:ca:89:2c:62:
                    0f:a8:ef:89:60:9e:fd:a2:df:36:6d:70:82:8b:fa:
                    b3:ee:79:7e:fd:3f:e7:90:84:58:85:7e:7e:69:07:
                    1e:50:05:0b:87:4d:66:e4:17:6b:c2:97:03:48:e4:
                    7d:08:b4:81:a6:05:80:60:5c:eb:8d:53:db:7c:62:
                    a8:6d:a7:75:f1:56:b6:d9:0d:6b:3b:be:8b:72:39:
                    8d:e7:2d:77:74:e3:4d:a1:fd:8b:44:f9:ee:fd:0d:
                    04:ec:6a:fc:f3:d2:15:fc:18:ff:7d:33:44:2b:6d:
                    7f:3c:33:21:e1:d8:5f:08:fa:53:fd:26:fb:6e:74:
                    d7:4b:51:62:d3:15:1b:3b:44:78:78:9b:91:c7:ba:
                    82:2b:12:d7:b2:83:0a:39:ec:5e:a9:a9:c1:04:a6:
                    2e:64:a5:ea:15:c3:85:e9:ac:38:6b:22:eb:3b:08:
                    b8:0a:31:10:df:45:1d:76:81:e0:0f:88:e4:00:ef:
                    6e:90:59:8c:d8:36:e9:77:bf:4a:0e:3d:03:02:4d:
                    5d:a7:90:16:81:11:e0:81:bb:e0:18:a3:bb:dc:8d:
                    7d:c6:cf:c6:0b:d2:80:53:ea:d0:27:e6:6a:cc:8e:
                    2b:b3:72:e4:ab:84:88:e2:e9:a5:bb:72:9a:c6:a2:
                    0e:5a:cb
                Exponent: 65537 (0x10001)
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                EE:EF:AE:DB:73:45:9A:6E:82:00:3C:A7:05:0D:60:E4:20:81:3B:02
            X509v3 Authority Key Identifier:
                keyid:EE:EF:AE:DB:73:45:9A:6E:82:00:3C:A7:05:0D:60:E4:20:81:3B:02

            X509v3 Basic Constraints: critical
                CA:TRUE
    Signature Algorithm: sha256WithRSAEncryption
         98:cf:f4:23:61:d2:2a:64:ce:51:57:1d:fb:61:2f:34:68:86:
         c9:02:5a:c8:97:80:58:c1:7f:04:e1:97:f5:0b:35:d5:c4:91:
         fa:98:8c:73:16:43:b3:af:63:af:2c:30:cf:6a:8e:10:99:bc:
         fd:3d:84:c7:3d:01:e0:8d:8d:d8:76:74:12:69:1a:f5:e5:ec:
         ef:eb:dc:f8:08:0c:c7:03:19:de:c5:e8:c7:4e:b4:5c:67:39:
         9f:33:11:6f:29:e1:03:d8:4e:70:09:7a:69:bd:3a:db:96:71:
         2b:38:c4:46:87:f6:59:34:f9:dc:5c:6d:34:9a:ba:ea:36:13:
         d8:e3:e3:91:ea:70:3b:ea:39:cb:fc:fd:08:0f:73:e5:16:c3:
         0d:9a:62:20:3f:5a:28:90:e6:b2:65:23:a1:ba:d0:77:c0:8e:
         16:51:55:44:f6:4b:16:b9:a1:97:bc:f8:95:70:af:a6:d4:07:
         27:21:96:78:0b:58:18:51:45:a6:ea:07:c8:09:1b:ad:f3:e1:
         16:be:64:bf:8f:b7:4c:d1:e6:d0:c6:c1:db:cd:3d:e9:88:ec:
         e2:87:ff:bd:c3:7b:31:23:00:c3:71:53:90:68:46:99:7d:1d:
         e1:78:26:76:6a:41:8d:9e:9a:55:97:63:a5:df:86:fc:03:9b:
         28:13:55:ff:74:f2:56:d9:20:02:e8:c9:90:4f:b1:5d:1b:66:
         57:4e:f7:c6:50:4f:c9:8b:ff:39:a1:9e:b4:ee:2b:8a:bf:46:
         b4:3e:65:cb:34:12:73:bc:ae:ba:a5:41:20:d4:b9:c5:c4:da:
         89:bd:50:83:27:71:7a:9f:2c:3e:cf:de:db:13:b1:39:cf:4a:
         39:62:68:b3:f5:dc:49:44:3e:c1:cf:0c:a4:9a:4b:cb:5e:ec:
         aa:33:a5:57:ae:c6:f3:4f:69:01:d1:6a:a7:12:90:88:05:e9:
         18:d8:3a:a7:89:70:55:ab:18:ba:4f:28:74:5b:5f:21:8e:66:
         bc:ae:ff:1b:c7:ed:42:73:c1:1c:a4:97:f2:e6:c7:5a:8f:a8:
         44:a5:ed:b7:76:ac:cf:40:f0:a4:4f:22:03:d0:db:db:6e:18:
         32:33:4a:79:c2:bb:98:20:71:03:a7:9c:ea:4e:7e:0a:28:79:
         30:f3:3f:ef:03:b2:e0:00:b0:2b:71:27:8b:fc:f9:a0:e5:b9:
         a0:9e:6f:93:3a:f3:d3:1c:87:8a:b7:2d:5c:38:ab:f9:ff:39:
         8b:52:a5:9a:95:2f:a0:82:b9:b6:f8:9a:c3:e3:55:dd:4b:b5:
         e4:e3:fb:f8:8b:10:50:f8:42:7d:03:fe:72:40:c1:d3:f7:26:
         a7:f9:de:b9:9d:30:26:94

安装根证书

  首次打开刚刚生成的根证书 zhongerca.pem 会像下面这样显示“此根证书不被信任”,我们可以将下面的使用此证书时的使用系统默认改成始终信任,然后输入操作系统用户密码即可保存修改。改完之后再次打开如下下图所示,显示“此证书已标记为受此账户信任”。这样一来,由该根证书签发的证书就都会被信任了。

打开根证书 Open Root Certificate file

始终信任根证书 Always trust Root Certificate file

签发证书

  这里我们打算采用 jsha/minica 来辅助快速签发证书。

安装 minica

# Mac OS
brew install minica

# Other OS
go install github.com/jsha/minica@latest

签发 SSL 证书

# 给域名签发 SSL 证书
minica -ca-cert zhongerca.pem -ca-key zhonger-key.pem --domains "sni.lisz.me,zhonger.io,*.zhonger.io"

# 给 IP 签发 SSL 证书
minica -ca-cert zhongerca.pem -ca-key zhonger-key.pem --ip-addresses "127.0.0.1"

  minica 提供了非常简单的方式来签发 SSL 证书,比如说指定根证书和根密钥、指定单个或多个域名、通配符域名以及 IP。minica 签发的证书默认时效为 2年30天(相信可能是考虑到 30天 的缓冲期所以多了一个月)。这里,我们模仿了 Cloudflare 的 SSL 证书生成方式,第一个域名是 sni.根证书域名,第二个开始才是真正想要签发的域名。由于 minica 默认会将第一个域名作为文件夹的名字生成 SSL 证书 cert.pemkey.pem 文件,如果采取这种方式在同一目录执行以上签发命令势必会使得旧文件被覆盖,因此推荐像 Certonly 或者 acme.sh 那样修改目录名来区分。

其他

mkcert

  除了 minica 之外,FiloSottile/mkcert 提供的 mkcert 工具也非常简单方便。mkcert 会自行生成根证书,然后签发证书。个人觉得,相比 minica 而言,mkcert 更适合个人本地开发 HTTPS 化,而非团队内网 HTTPS 化。

根证书被伪造

  自生成根证书比较令人担心的地方可能就是任何人都可以用同样的方法伪造出相似的根证书。实际上,根证书是独一无二的,即使所有的信息都设置成一样,还是两个根证书。我们需要做的是:

  • 保护好根证书密钥,因为采用同一个密钥是可以生成比较相似的根证书的。当然两个根证书的序列 ID有效时间是不会完全一样的。如果密钥不同,自然两个根证书的密钥 ID 也不会相同。
  • 告诉用户真的根证书是什么样的(序列 ID、密钥 ID、有效时间等)、应该从哪里下载到。这里需要在内网建立一个用于提供下载根证书的站点,而这个站点的SSL 证书最好采用购买的或申请的证书。也就是说,从可靠站点下载的内网 HTTPS 化根证书也是可靠的。

  如下所示,是上面生成的根证书和签发的 SSL 证书的密钥 ID 对比。可以看到,两者完全一样,即可信任的 SSL 证书。

根证书密钥 ID Root Certificate Key ID

SSL 证书密钥 ID the Key ID in SSL Certificate

其他平台安装根证书

  上面已经提到了在 Mac OS 中如何安装根证书,其他平台比如 Windows、Linux、Android、IOS 等也是可以按照类似的方式,略微有些差别。考虑到 Windows 和 Linux 桌面版安装根证书的步骤几乎与 Mac OS 一样就不再赘述,这里主要讲一下 Linux Server、Android、IOS 平台的安装方法。

Linux Server

sudo cp zhongerca.pem /usr/local/share/ca-certificates/zhongerca.pem
sudo update-ca-certificates

Android 或 HarmonyOS

  以华为鸿蒙系统(HarmonyOS)为例,首先下载根证书到设备上,然后在 设置 > 安全 > 更多安全设置 > 加密和凭据 > 从存储设备上安装 中选择已下载的根证书完成安装。安装完成之后可以在同级别的 受信任的凭据 > 用户 下面看到安装好的根证书。

IOS 或 iPad OS

  首先下载根证书到设备上,在文件中点击打开(会自动跳转到 设置 > 通用 > VPN与设备管理)。可以在 配置描述文件 列表中看到根证书,点击进去输入密码并验证即可。

参考资料

版权声明:如无特别声明,本文版权归 Mr Li 所有,转载请注明本文链接。

(采用 CC BY-NC-SA 4.0 许可协议进行授权)

本文标题:《 内网 HTTPS 可信证书 》

本文链接:https://lisz.me/tech/webmaster/ca-ssl.html