Mac 与 iPhone 使用客户端证书访问 mTLS 网站

本文记录在 macOS 和 iPhone 上使用客户端证书访问内网 mTLS 网站的方法,涉及 CanoKey PIV、step-ca、Nginx 和 iOS 系统证书安装。

1. 环境与认证流程

当前环境:

  • CA:已有的 CX-Homelab-CA Root CA 和 CX-Homelab-CA Intermediate CA。
  • 证书签发:step-ca。
  • Web 服务器:Nginx 1.25.0。
  • 测试站点:https://www.home.arpa。
  • Mac 客户端:CanoKey PIV 9A。
  • iPhone 客户端:通过 PKCS#12 文件导入客户端证书及私钥。

mTLS 与普通 HTTPS 的区别是:服务器验证客户端证书,客户端也需要验证服务器证书。客户端证书必须包含对应的私钥,单独安装 .crt 文件通常不足以完成客户端身份认证。

基本流程如下:

  1. 客户端访问 HTTPS 站点。
  2. Nginx 请求客户端证书。
  3. 浏览器或系统选择客户端身份,并使用对应私钥完成 TLS 握手。
  4. Nginx 验证客户端证书的有效性、证书链和用途。
  5. 验证通过后,服务器允许访问受保护的资源。

需要注意,客户端证书和服务器 CA 信任是两个不同的问题。安装客户端身份,并不意味着设备已经信任签发网站服务器证书的私有 CA。

2. Mac:使用 CanoKey PIV 9A

2.1 当前方案

Mac 使用 CanoKey 的 PIV 9A 槽保存客户端身份。macOS 通过智能卡相关机制识别证书和私钥,浏览器在 TLS 握手时使用该身份。

目前已经确认:

  • CanoKey PIV 9A 的客户端证书已签发。
  • 证书包含 TLS Web Client Authentication 用途。
  • macOS 上的 Chrome 已成功使用 CanoKey PIV 9A 访问 mTLS 站点。

这证明该次 Chrome 访问的客户端证书、私钥签名和服务器端验证链路可以正常工作。

2.2 使用方法

  1. 将 CanoKey 插入 Mac。
  2. 确认系统能够识别智能卡及其证书。
  3. 打开 Chrome。
  4. 访问 https://www.home.arpa。
  5. 如果浏览器要求选择客户端证书,选择对应的 CanoKey PIV 9A 身份。
  6. 按需完成 PIN 输入或硬件操作。
  7. 确认页面正常打开,并检查 Nginx 日志。

如果没有弹出证书选择界面,也不能直接断定证书不可用。浏览器可能已经记住了选择,或者服务器没有在该次连接中请求客户端证书。

2.3 排查命令

查看系统识别的智能卡:

security list-smartcards

查看与智能卡相关的认证身份:

sc_auth identities

查看智能卡 Token 状态:

sudo security smartcards token -l

如果 Chrome 可以访问,而 Safari 返回:

400 Bad Request
No required SSL certificate was sent

应分别检查浏览器的客户端证书访问行为、macOS 对智能卡的识别情况,以及 Nginx 收到的 TLS 客户端证书信息。

不要仅因为一个浏览器成功,就认定另一个浏览器一定会使用相同的客户端身份。

3. iPhone:导入客户端证书

3.1 方案说明

iPhone 使用单独的客户端证书,不与 CanoKey 上的私钥共用。

当前采用的方案是:

  1. 在 Mac 上生成 iPhone 专用私钥。
  2. 生成 CSR。
  3. 使用现有 step-ca 签发客户端证书。
  4. 将证书和私钥打包为加密的 PKCS#12 文件(.p12)。
  5. 将 .p12 安装到 iPhone。
  6. 使用支持系统客户端证书的应用访问 mTLS 网站。

此方案不需要重新初始化 Root CA,也不要求先创建新的 RSA Intermediate CA。

3.2 在 Mac 上生成私钥和 CSR

建立专用目录:

mkdir -p ~/iphone-mtls
cd ~/iphone-mtls
umask 077

生成椭圆曲线私钥:

openssl ecparam \
  -genkey \
  -name prime256v1 \
  -noout \
  -out iphone.key

生成 CSR:

openssl req \
  -new \
  -key iphone.key \
  -subj "/CN=CX iPhone 16 Pro Max" \
  -out iphone.csr

这里使用 P-256(prime256v1)密钥。签发前应确认 CA 的证书模板或 Provisioner 为客户端证书配置了正确的扩展用途。

3.3 使用 step-ca 签发

step ca sign \
  iphone.csr \
  iphone.crt \
  --not-after=8760h

上述有效期参数表示请求签发约一年的证书;实际有效期仍受 CA 策略约束。

检查证书内容:

openssl x509 \
  -in iphone.crt \
  -noout \
  -subject \
  -issuer \
  -dates \
  -ext extendedKeyUsage

重点确认:

  • Subject 是 iPhone 专用身份。
  • Issuer 是预期的 Homelab Intermediate CA。
  • 有效期符合预期。
  • Extended Key Usage 包含 TLS Web Client Authentication。

如果客户端证书没有 clientAuth 用途,应先修正签发配置,而不是直接导入 iPhone。

3.4 导出 PKCS#12 文件

openssl pkcs12 \
  -export \
  -inkey iphone.key \
  -in iphone.crt \
  -out iphone.p12

执行时为 .p12 设置导出密码。安装时通常需要输入该密码。

如果证书链配置要求客户端同时发送 Intermediate CA 证书,应确认导出的 PKCS#12 包含适当的中间证书,并检查最终导入的身份是否完整。不要把 Root CA 私钥或 Intermediate CA 私钥打包进客户端文件。

3.5 安装到 iPhone

  1. 通过 AirDrop 等受控方式将 iphone.p12 传到 iPhone。
  2. 在 iPhone 上打开文件。
  3. 按系统提示进入设置,安装下载的配置描述文件或证书身份。
  4. 按提示输入 PKCS#12 密码。
  5. 在“设置”中的 VPN 与设备管理或相关描述文件页面确认安装结果。不同 iOS 版本的界面文字可能略有差异。
  6. 确认安装的是包含客户端证书和私钥的身份,而不只是公开证书。

如果 iPhone 还不信任网站使用的私有 CA,需要单独安装 Root CA。对于手动安装的根证书,还要根据 iOS 的证书信任设置要求启用完全信任。安装客户端身份和信任服务器 CA 是两个独立步骤。

3.6 通过 Safari 访问

  1. 确认 iPhone 已连接家庭网络或可以访问内网的 VPN。
  2. 在 Safari 中打开 https://www.home.arpa。
  3. 当服务器请求客户端证书时,按系统提示选择相应的客户端身份。
  4. 如果页面正常打开,检查 Nginx 日志确认客户端证书验证通过。

如果没有成功,应分别排查 DNS、网络可达性、服务器证书信任、客户端身份安装情况,以及 Nginx 的客户端证书验证日志。

4. 为什么 iPhone Chrome 不能直接使用系统证书

iOS 版 Chrome 不能像 macOS Chrome 一样,直接使用通过系统设置安装的客户端证书身份。

Chromium 官方策略文档明确说明,Android 和 iOS 版 Chrome 只能选择由 Chrome 自身配置的客户端证书,不能直接访问操作系统层面安装的证书:

https://chromium.googlesource.com/chromium/src/+/refs/heads/main/components/policy/resources/templates/policy_definitions/ContentSettings/AutoSelectCertificateForUrls.yaml

因此,已经导入 iPhone 系统的 iphone.p12,并不意味着 Chrome 可以使用它。

重新签发证书、改变证书算法或再次导出 .p12,都不能单独解决这个应用权限限制。

对于当前方案:

  • iPhone Safari:优先使用系统已安装的客户端身份。
  • iPhone Chrome:不能直接使用系统安装的客户端证书。
  • iOS 上的其他应用:需要逐个确认它们是否实现了客户端证书认证,以及是否支持所需的服务器地址。

5. 证书与私钥安全

客户端证书不是秘密,但对应的私钥必须保护好。

需要注意:

  • iphone.key 是 iPhone 客户端身份的私钥。
  • iphone.p12 同时包含客户端证书和私钥,应视为敏感文件。
  • 不要将 .key 或 .p12 上传到公开仓库、博客附件或不受信任的文件分享服务。
  • 传输完成后,检查 Mac 的临时目录、下载目录和文件传输记录中是否还有不必要的副本。
  • 如果希望保留恢复能力,应将私钥或 PKCS#12 文件加密备份,而不是无保护地长期保存。
  • 如果私钥泄露,应撤销或停止信任相应客户端证书,并重新生成密钥和证书。

需要明确:本方案的 iPhone 私钥是在 Mac 上生成后导入 iPhone 的,不等同于在 iPhone Secure Enclave 内生成且不可导出的硬件绑定私钥。

6. 故障排查顺序

当 mTLS 访问失败时,按以下顺序排查,避免把所有问题都归因于证书。

第一步:确认网络和 DNS

确认客户端能解析 www.home.arpa,并且可以连接到 Nginx 的 HTTPS 端口。

第二步:确认服务器证书信任

确认客户端信任网站服务器证书的签发 CA,并且服务器证书的域名、有效期和证书链均正确。

第三步:确认客户端身份

检查客户端证书有效期、clientAuth 扩展用途,以及客户端私钥是否可用。

第四步:确认 Nginx 验证配置

检查 ssl_client_certificate 指向的 CA 文件是否包含正确的客户端证书签发链,并确认 ssl_verify_client 和 ssl_verify_depth 设置符合当前 CA 层级。

第五步:区分客户端没有发送证书和证书验证失败

这两种错误不是一回事:

  • No required SSL certificate was sent:通常意味着该次 TLS 握手没有收到服务器要求的客户端证书。
  • 客户端证书验证失败:可能与证书链、有效期、用途或信任配置有关。

应结合 Nginx error log 和具体客户端行为判断,不要只根据浏览器显示的错误页面下结论。

7. 当前方案总结

平台客户端身份当前状态
macOS ChromeCanoKey PIV 9A已确认成功
macOS SafariCanoKey PIV 9A需独立排查浏览器行为
iPhone Safari导入的 PKCS#12 身份按系统证书身份方式使用
iPhone Chrome系统安装的 PKCS#12 身份不支持直接调用系统证书

目前最简单的分工是:Mac 使用 CanoKey PIV 9A,iPhone 使用独立的客户端证书;需要访问内网 mTLS 网站时,优先使用支持相应客户端身份的浏览器。

后续如果要把客户端证书认证用于 Bitwarden,应将 Bitwarden App 自身连接服务器的 mTLS,与浏览器访问普通网站的 mTLS 分开设计。