背景及需求

最近,我把自定义的 Surge 配置文件托管到了 GitHub Gist 上(当然,用的是 secret gist)。同时,为了在 Surge app 里看着舒服,我把配置文件写成了 Managed Configuration 的形式。由于 GitHub Gist RAW 文件的 URL 会带有 commit ID,此方式不可用,咱还是太蠢了…(其实把 URL 后面的部分删了就行了,咱确实太年轻了 QAQ…)但是,这也带来了一定的问题 —— Surge 的 Managed Configuration 是完全不可更改的,因此也不能在本地生成并安装 CA 根证书。

为了让 Managed Configuration 也能实现 MitM,我们必须提前自签发 CA 根证书,并在 iOS 系统中安装和信任,同时确保 Surge 配置文件中含有正确的 ca-passphraseca-p12

除此之外,自签 CA 根证书还能自定义证书的名称、有效期等,让强迫症看着更舒服。

准备工具

macOS 自带了 OpenSSL(LibreSSL),不需要另行安装。Ubuntu 可以使用包管理器安装 OpenSSL:

apt install openssl

LibreSSL 与 OpenSSL 在使用上几乎一致,不过默认摘要算法有所不同。下文命令中的 -sha256 在较新版本 OpenSSL (Ubuntu) 下可以省略,但在 LibreSSL (macOS) 下不能省略。[1]

Easy Way:使用 openssl x509 工具签发

首先做好准备工作:

mkdir cert && cd cert  # 创建存放证书的文件夹

生成私钥:

openssl genrsa -out ca.key 2048  # 生成 RSA 私钥

# 或者
openssl ecparam -genkey -name prime256v1 -out ca.key  # 生成 ECC 私钥
# -name 后的字段指定了椭圆曲线算法
# 可通过 openssl ecparam -list_curves 查看所有可选项
# 目前可用算法只有 prime256v1 (secp256r1), secp384r1, secp521r1 三种

随后,用 openssl req 生成证书请求:

openssl req -new -sha256 -key ca.key -out ca.csr

# 参数说明:
# -new  生成新的证书请求
# -sha256  使用 SHA-256 摘要算法
# -key  指定已有的私钥文件
# -out  指定生成的证书请求名

此时,openssl 会让你逐条填入证书主题 (subject):

$ openssl req -new -sha256 -key ca.key -out ca.csr

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]:
State or Province Name (full name) [Some-State]:
Locality Name (eg, city) []:
Organization Name (eg, company) [Internet Widgits Pty Ltd]:
Organizational Unit Name (eg, section) []:
Common Name (e.g. server FQDN or YOUR name) []:
Email Address []:

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:
An optional company name []:

其中,Common Name 为必填项,它将成为证书安装时的名称;其他项目均为可选填。如果要留空某个项目,则填入 . 即可。以下是填写示例:

Country Name (2 letter code) [AU]:HK
State or Province Name (full name) [Some-State]:.
Locality Name (eg, city) []:.
Organization Name (eg, company) [Internet Widgits Pty Ltd]:.
Organizational Unit Name (eg, section) []:.
Common Name (e.g. server FQDN or YOUR name) []:Custom CA
Email Address []:.

Please enter the following 'extra' attributes
to be sent with your certificate request
A challenge password []:.
An optional company name []:.

如果你不想一条条地填写,那么你也可以在生成证书请求时直接指定证书主题:

openssl req -new -sha256 -key ca.key -out ca.csr -subj "/C=HK/CN=Custom CA"

最后,用 openssl x509 进行自签名,得到根证书:

openssl x509 -req -sha256 -days 3650 -in ca.csr -signkey ca.key -out ca.crt

# 参数说明:
# -req  指定输入的是证书请求文件
# -sha256  使用 SHA-256 摘要算法
# -days  指定证书的有效时间,示例中为 3650 天
# -in  指定证书请求文件
# -signkey  指定私钥文件以进行自签名
# -out  指定生成的证书名

另外,用 openssl req 生成证书请求和用 openssl x509 自签名的两步也可以只用 openssl req 一行命令完成:

openssl req -x509 -new -sha256 -key ca.key -days 3650 -out ca.crt -subj "/C=HK/CN=Custom CA"

# 注意这里并不是用 x509,而是用 req 工具签发的证书

# 参数说明:
# -x509  指定输出一个 X509 格式的证书
# -new  生成新的证书请求
# -sha256  使用 SHA-256 摘要算法
# -key  指定已有的私钥文件
# -days  指定证书的有效时间,示例中为 3650 天
# -out  指定生成的证书名
# -subj  指定证书主题,格式参见示例

Hard Way:使用 openssl ca 签发

使用 openssl x509 工具签发非常方便,然而,如果我们要自定义更多内容(尤其是证书的生效时间、失效时间),openssl x509 就不能实现了。这时,我们就需要借助 openssl ca 来完成。

首先依旧是做好准备工作:

mkdir cert && cd cert  # 创建存放证书的文件夹

openssl genrsa -out ca.key 2048  # 生成 RSA 私钥
# 或者
openssl ecparam -genkey -name prime256v1 -out ca.key  # 生成 ECC 私钥

随后,用 openssl req 生成证书请求:

openssl req -new -sha256 -key ca.key -out ca.csr -subj "/C=HK/CN=Custom CA"

在用 openssl ca 签发前,我们还需要进行一些配置。首先将默认的 openssl ca 配置文件拷贝到当前目录:

cp /private/etc/ssl/openssl.cnf .  # macOS

cp /usr/lib/ssl/openssl.cnf .  # Ubuntu

编辑配置文件:vim openssl.cnf

对于配置文件,我们需要修改 [ CA_default ]dir = 这一项,将其改为 .,即当前目录。

随后,准备必须的文件:

touch index.txt && echo 01 > serial

最后,签发证书:

openssl ca -selfsign -in ca.csr -out ca.crt -outdir . -keyfile ca.key -startdate 20190101000000Z -enddate 20290101000000Z -config openssl.cnf -extensions v3_ca -notext -md sha256 -policy policy_anything

# 参数说明:
# -selfsign  使用证书请求中附带的私钥对该请求进行签名,即自签名
# -in  指定证书请求文件
# -out  指定生成的证书名
# -outdir  	指定新证书的输出位置
# -keyfile  指定已有的私钥文件
# -startdate  指定证书生效时间,格式为 YYYYMMDDHHMMSSZ 或 YYMMDDHHMMSSZ,时区为 GMT
# -enddate  指定证书失效时间,格式同上
# -config  指定所使用的配置文件
# -extensions  指定使用的扩展字段,选取 v3_ca 来签发 CA 机构证书
# -notext  在生成的证书文件中,不输出文本格式的证书信息
# -md  指定摘要算法,选择 SHA-256
# -policy  指定 CA 策略,使用 policy_anything 以允许不完整的证书主题

在提示 Sign the certificate?1 out of 1 certificate requests certified, commit? 时,按 y 并回车确认即可。

将证书打包并编码

首先将证书打包为 p12 格式:

openssl pkcs12 -export -clcerts -in ca.crt -inkey ca.key -out ca.p12 -password pass:AbCd1234

# 参数说明:
# -export  指定输出一个 PKCS 12 文件
# -clcerts  仅输出客户端证书
# -in  指定证书文件
# -inkey  指定私钥文件
# -out  指定生成的文件名
# -password  指定密码,示例中密码为 AbCd1234

随后对 p12 格式的证书进行 BASE64 编码:

base64 ca.p12

# 或者直接将编码结果复制到剪贴板:
base64 ca.p12 | pbcopy

修改 Surge 配置文件

在 Surge 配置文件的 [MITM] 部分写入如下内容:

[MITM]
ca-passphrase = 上一步中设置的 password
ca-p12 = 上一步中 BASE64 编码后的结果

安装并信任证书

方法 1:借助 Quantumult X 安装

在 Quantumult X 配置文件的 [mitm] 部分写入如下内容:

[mitm]
passphrase = 上一步中设置的 password
p12 = 上一步中 BASE64 编码后的结果

随后在设置中点击 Install CA 安装证书,再到 设置 > 通用 > 关于 > 证书信任设置 中打开对该根证书的完全信任即可。

方法 2:借助 GitHub Gist 下载安装

打开 GitHub,登录你的 GitHub 账号,然后打开 GitHub Gists

使用以下命令将要安装的证书内容拷贝到剪贴板:

pbcopy < ca.crt

在新建的 gist 中,Filename including extension 填入 ca.crt,文件内容中粘贴你复制到的内容。随后点击 Create secret gist 新建 gist。

随后,在该 gist 页面右键点击右上角的 Raw,选择 Copy Link。将获得的网址粘贴到 iOS Safari 浏览器中并前往,按提示安装证书,再到 设置 > 通用 > 关于 > 证书信任设置 中打开对该根证书的完全信任即可。

小结

至此,我们成功为 Surge 签发并安装了 CA 根证书。另外,我们得到的 p12passphrase 也可以用于其他代理软件的 MitM,从而使我们不需要多次生成和安装证书。


↩︎ 注

  1. 在签发根证书(即本文目的)时,并不一定要指定摘要算法为 SHA-256;但在签发服务器证书时,必须指定为 SHA-256,否则 iOS 13/macOS 10.15 以上的系统将不再信任所用证书(参见 Apple 支持页面)。在本文完成时,macOS 自带的 OpenSSL (LibreSSL 2.8.3) 默认摘要算法仍为 SHA-1,必须手动指定摘要算法;而 Ubuntu 通过包管理器安装的 OpenSSL 1.1.1 默认摘要算法已更改为 SHA-256,不需要手动指定。