在web上使用mTLS

什么是mTLS

正常的tls只是能让客户端知道服务端是可信的, 而对于服务端来说, 并不能知道客户端是可信的

mTLS相当于在tls的基础上加了个反向的tls, 即服务器配置公钥, 客户端配置私钥, 来验证客户端身份

我部署了一些只希望我自己使用的web服务, 比如web版的rss阅读器, bt软件的webui等, 并不希望我信任的人以外的任何人访问, 因此给web套一层mTLS

生成自签证书

用下面这套命令生成, 不携带CN之外的信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 生成本地CA根证书的私钥
openssl genrsa -out ca.key 2048
# 2. 使用私钥签发出 CA 根证书
# CA根证书的有效期尽量设长一点, 因为不方便更新换代, 这里设了 10 年
# 更改最后的CN为自定义的名字, CN意为Common Name, 会在安装证书的时候显示
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/CN=QriCA"

# 3. 生成服务端证书的RSA私钥
openssl genrsa -out client.key 2048
# 4. 生成证书签名请求. 更改后面的CN
openssl req -new -key client.key -out client.csr -subj "/CN=QriClient"

# 使用 CA 根证书直接签发证书, 有效期还是设3650天
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days 3650 -sha256

服务端部署

我是用caddy对内网http做的反代

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
xxx.ggemo.com:7326 {
reverse_proxy http://127.0.0.1:8000

tls {
# 这一条是常规tls配置, 与mTLS无关, 是通过acme.sh获取/更新服务端的https证书
dns cloudflare {$CLOUDFLARE_API_TOKEN}

# 这里是mtls的配置
client_auth {
# 要求客户端有对应的私钥, 否则不许访问
mode require_and_verify
# 信任的ca, 将上面自签的ca配进去
trusted_ca_cert_file /etc/caddy/ca.crt
}
}
}

客户端部署

客户端遇到坑了, 本来是这样的命令生成的p12:

1
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt -name "QriClientCert"

然后发现只有windows能装的上, 安卓/ios/ipadOS/macOS都装不了, 而且安装的时候很迷惑, 就是提示输入证书的密码, 但是怎么输都不对, 除了密码输不对给报错

后来发现, 在OpenSSL3.0以后的版本中, 因为一些旧的加密算法和密码套件被认为是不安全的, 所以默认禁用了, 而上面那些os并没有跟进, 所以装不上, 需要在命令上加个-legacy参数

1
openssl pkcs12 -legacy -export -out client.p12 -inkey client.key -in client.crt -name "QriClientCert"

之后, 把生成的p12传到各个终端安装就可以了