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 文件通常不足以完成客户端身份认证。
基本流程如下:
- 客户端访问 HTTPS 站点。
- Nginx 请求客户端证书。
- 浏览器或系统选择客户端身份,并使用对应私钥完成 TLS 握手。
- Nginx 验证客户端证书的有效性、证书链和用途。
- 验证通过后,服务器允许访问受保护的资源。
需要注意,客户端证书和服务器 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 使用方法
- 将 CanoKey 插入 Mac。
- 确认系统能够识别智能卡及其证书。
- 打开 Chrome。
- 访问
https://www.home.arpa。 - 如果浏览器要求选择客户端证书,选择对应的 CanoKey PIV 9A 身份。
- 按需完成 PIN 输入或硬件操作。
- 确认页面正常打开,并检查 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 上的私钥共用。
当前采用的方案是:
- 在 Mac 上生成 iPhone 专用私钥。
- 生成 CSR。
- 使用现有
step-ca签发客户端证书。 - 将证书和私钥打包为加密的 PKCS#12 文件(
.p12)。 - 将
.p12安装到 iPhone。 - 使用支持系统客户端证书的应用访问 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
- 通过 AirDrop 等受控方式将
iphone.p12传到 iPhone。 - 在 iPhone 上打开文件。
- 按系统提示进入设置,安装下载的配置描述文件或证书身份。
- 按提示输入 PKCS#12 密码。
- 在“设置”中的 VPN 与设备管理或相关描述文件页面确认安装结果。不同 iOS 版本的界面文字可能略有差异。
- 确认安装的是包含客户端证书和私钥的身份,而不只是公开证书。
如果 iPhone 还不信任网站使用的私有 CA,需要单独安装 Root CA。对于手动安装的根证书,还要根据 iOS 的证书信任设置要求启用完全信任。安装客户端身份和信任服务器 CA 是两个独立步骤。
3.6 通过 Safari 访问
- 确认 iPhone 已连接家庭网络或可以访问内网的 VPN。
- 在 Safari 中打开
https://www.home.arpa。 - 当服务器请求客户端证书时,按系统提示选择相应的客户端身份。
- 如果页面正常打开,检查 Nginx 日志确认客户端证书验证通过。
如果没有成功,应分别排查 DNS、网络可达性、服务器证书信任、客户端身份安装情况,以及 Nginx 的客户端证书验证日志。
4. 为什么 iPhone Chrome 不能直接使用系统证书
iOS 版 Chrome 不能像 macOS Chrome 一样,直接使用通过系统设置安装的客户端证书身份。
Chromium 官方策略文档明确说明,Android 和 iOS 版 Chrome 只能选择由 Chrome 自身配置的客户端证书,不能直接访问操作系统层面安装的证书:
因此,已经导入 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 Chrome | CanoKey PIV 9A | 已确认成功 |
| macOS Safari | CanoKey PIV 9A | 需独立排查浏览器行为 |
| iPhone Safari | 导入的 PKCS#12 身份 | 按系统证书身份方式使用 |
| iPhone Chrome | 系统安装的 PKCS#12 身份 | 不支持直接调用系统证书 |
目前最简单的分工是:Mac 使用 CanoKey PIV 9A,iPhone 使用独立的客户端证书;需要访问内网 mTLS 网站时,优先使用支持相应客户端身份的浏览器。
后续如果要把客户端证书认证用于 Bitwarden,应将 Bitwarden App 自身连接服务器的 mTLS,与浏览器访问普通网站的 mTLS 分开设计。