macOS 与 iOS 使用客户端证书访问 mTLS 网站
本文介绍如何在 macOS 和 iOS 设备上配置客户端证书,以访问启用双向 TLS(mTLS)的 HTTPS 网站。示例使用私有 CA、step-ca、Nginx、PKCS#12 和 PIV 智能卡。文中的域名、证书名称、文件名和 IP 地址均为示例,请替换为自己的实际值。
1. mTLS 认证流程
普通 HTTPS 主要由客户端验证服务器证书;mTLS 还要求服务器验证客户端证书。
基本流程:
- 客户端访问 HTTPS 站点。
- Nginx 在 TLS 握手期间请求客户端证书。
- 浏览器或系统选择客户端身份,并使用对应私钥完成签名。
- Nginx 根据配置验证客户端证书、证书链和有效期。
- 验证通过后,连接进入 HTTP 层,由站点策略决定是否允许访问。
客户端身份和服务器 CA 信任是两个独立配置:
- 客户端身份:客户端证书及与之匹配的私钥,用于向服务器证明身份。
- 服务器 CA 信任:客户端信任签发网站服务器证书的 CA,用于验证 HTTPS 服务器身份。
安装客户端证书不会自动让设备信任私有 CA。
2. macOS:使用 PIV 硬件密钥
如果客户端私钥保存在支持 PIV 的硬件密钥中,可以让私钥留在硬件设备内,避免导出私钥文件。浏览器是否能使用该身份,取决于 macOS、智能卡中间件、密钥设备和浏览器的支持情况。
一般检查步骤:
- 插入 PIV 硬件密钥。
- 确认 macOS 能识别智能卡及其证书。
- 打开浏览器并访问 mTLS 网站。
- 如果浏览器请求客户端证书,选择对应的身份。
- 按提示输入 PIN 或完成硬件操作。
- 结合页面结果和服务器日志确认认证是否成功。
可用以下命令检查系统状态:
security list-smartcards
sc_auth identities
sudo security smartcards token -l
不同 macOS 版本、硬件和中间件的输出可能不同。命令没有输出并不能单独证明证书无效。
3. macOS:将 CRT、PEM 和 KEY 导入钥匙串
如果已有客户端证书和对应私钥文件,可以将它们打包成 PKCS#12 文件(.p12),再导入 macOS 钥匙串。
示例文件:
client.crt:客户端证书。client.key:与证书匹配的私钥。intermediate-ca.crt:中间 CA 证书,可选。root-ca.crt:根 CA 证书,用于验证证书链或配置服务器信任。
请在受控目录中操作,并避免将私钥或 PKCS#12 文件提交到代码仓库。
3.1 检查客户端证书
openssl x509 -in client.crt -noout \
-subject -issuer -serial -dates
检查证书用途:
openssl x509 -in client.crt -noout -ext extendedKeyUsage
证书应包含 TLS Web Client Authentication,用途 OID 为 1.3.6.1.5.5.7.3.2。
验证证书与私钥是否匹配:
openssl x509 -in client.crt -pubkey -noout |
openssl pkey -pubin -outform DER |
shasum -a 256
openssl pkey -in client.key -pubout -outform DER |
shasum -a 256
两条命令输出的摘要应一致。
如果有中间 CA,可以验证证书链:
openssl verify \
-CAfile root-ca.crt \
-untrusted intermediate-ca.crt \
client.crt
如果没有中间 CA:
openssl verify -CAfile root-ca.crt client.crt
证书链有多级中间 CA 时,应提供完整且正确的中间证书链。
3.2 生成兼容 macOS 钥匙串的 PKCS#12 文件
部分 PKCS#12 文件虽然密码正确,仍可能因加密参数兼容性问题而无法被钥匙串导入。可以显式指定传统 PKCS#12 加密参数进行兼容性导出。
如果有中间 CA 证书:
umask 077
openssl pkcs12 -export \
-inkey client.key \
-in client.crt \
-certfile intermediate-ca.crt \
-name "Client Certificate" \
-keypbe PBE-SHA1-3DES \
-certpbe PBE-SHA1-3DES \
-macalg sha1 \
-out client-macos.p12
如果没有中间 CA,删除 -certfile intermediate-ca.crt 这一行。
执行时设置一个非空的导出密码。建议先使用英文字母和数字,减少密码编码差异造成的排查干扰。
验证文件:
openssl pkcs12 -info -in client-macos.p12 -noout
输入刚刚设置的密码。如果 OpenSSL 可以读取文件,但钥匙串仍然拒绝导入,可优先检查 PKCS#12 加密参数兼容性。
这里的 SHA-1 和 3DES 仅用于 PKCS#12 文件封装兼容性,不代表应在 TLS 连接中启用旧算法。它们不会改变客户端证书的公钥、私钥或扩展用途。
3.3 导入钥匙串
打开钥匙串访问:
open -a "Keychain Access"
然后:
- 选择
login钥匙串。 - 选择“文件 → 导入项目”。
- 选择
client-macos.p12。 - 输入导出密码。
- 在“我的证书(My Certificates)”中找到对应身份。
- 展开证书,确认其下存在对应私钥。
也可以通过命令行导入:
security import ./client-macos.p12 \
-k "$HOME/Library/Keychains/login.keychain-db" \
-f pkcs12
检查可用于 SSL 客户端认证的身份:
security find-identity -v -p ssl-client \
"$HOME/Library/Keychains/login.keychain-db"
如果没有有效身份,检查证书和私钥是否匹配、证书是否有效,以及是否具备客户端认证用途。
4. 配置服务器 CA 信任
如果网站使用私有 CA 签发服务器证书,客户端还必须信任正确的根 CA。
先检查根证书信息和指纹:
openssl x509 -in root-ca.crt -noout \
-subject -issuer -fingerprint -sha256
确认来源和指纹后,再将根 CA 导入相应设备的证书信任存储。若需要系统范围的信任,可在 macOS 的 System 钥匙串中安装,并按实际用途配置信任;这通常需要管理员权限。
不要将客户端证书当作根 CA,也不要把 CA 私钥安装到客户端设备。服务器应正确发送必要的中间证书。
5. 使用 curl 验证 mTLS
在测试浏览器之前,可以用 curl 验证证书、私钥和服务器配置。
curl -v \
--cert client.crt \
--key client.key \
--cacert root-ca.crt \
https://mtls.example.test/
如果需要指定服务器 IP,同时保留正确的主机名和 SNI:
curl -v \
--resolve mtls.example.test:443:192.0.2.10 \
--cert client.crt \
--key client.key \
--cacert root-ca.crt \
https://mtls.example.test/
示例 IP 192.0.2.10 是文档专用地址,请替换为实际服务器 IP。
如果服务器要求客户端发送中间 CA 证书,可将客户端证书与中间证书合并:
cat client.crt intermediate-ca.crt > client-chain.pem
然后将 --cert client.crt 替换为 --cert client-chain.pem。
常见结果:
- HTTP 200:请求成功,通常表示 TLS 和应用请求均已通过。
- HTTP 401/403:可能已完成 TLS,但被应用层授权策略拒绝。
certificate required:服务器要求客户端证书,但没有收到可用证书。unknown ca:服务端可能不信任客户端证书的签发 CA。certificate verify failed:检查服务器证书链、根 CA 和域名。
应结合客户端输出和 Nginx 日志判断。单凭 HTTP 状态码不能完全证明 mTLS 的每个环节都正确。
6. 在 macOS Chrome 和 Safari 中测试
Chrome
- 确认客户端身份已安装,且钥匙串中存在匹配私钥。
- 完全退出并重新打开 Chrome。
- 访问 mTLS 网站。
- 如果浏览器请求客户端证书,选择正确身份。
- 检查页面结果和服务器日志。
如果证书未出现,检查证书用途、私钥可用性,以及服务器是否在当前 TLS 握手中请求客户端证书。
Safari
用相同 URL 单独测试。Safari 和 Chrome 的证书选择行为可能不同,不应假定一个浏览器成功就意味着另一个也会使用同一身份。
如果出现类似“没有发送所需客户端证书”的错误,应检查浏览器是否选择并发送了正确身份、系统是否识别该身份,以及服务器在该次握手中是否收到客户端证书。
7. iOS:安装客户端身份
iPhone 可以使用独立的客户端证书。常见做法是在受控环境中生成专用私钥和 CSR,由现有 CA 签发客户端证书,再导出 PKCS#12 文件供 iOS 安装。
7.1 生成私钥和 CSR
mkdir -p ~/ios-mtls
cd ~/ios-mtls
umask 077
生成 P-256 私钥:
openssl ecparam \
-genkey \
-name prime256v1 \
-noout \
-out ios-client.key
生成 CSR:
openssl req \
-new \
-key ios-client.key \
-subj "/CN=Mobile Client" \
-out ios-client.csr
请根据实际设备和证书管理规范设置合适的身份名称。
7.2 使用 step-ca 签发
step ca sign \
ios-client.csr \
ios-client.crt \
--not-after=8760h
实际签发有效期受 CA 策略约束。签发后检查证书:
openssl x509 \
-in ios-client.crt \
-noout \
-subject \
-issuer \
-dates \
-ext extendedKeyUsage
确认签发者、有效期和客户端认证用途符合预期。如果没有 clientAuth,应先修正 CA 的签发策略。
7.3 导出 PKCS#12
如果有中间 CA 证书:
openssl pkcs12 -export \
-inkey ios-client.key \
-in ios-client.crt \
-certfile intermediate-ca.crt \
-name "Mobile Client" \
-keypbe PBE-SHA1-3DES \
-certpbe PBE-SHA1-3DES \
-macalg sha1 \
-out ios-client.p12
如果没有中间 CA,删除 -certfile intermediate-ca.crt 参数。
为文件设置非空密码,并在本机验证:
openssl pkcs12 -info -in ios-client.p12 -noout
不要将根 CA 或中间 CA 的私钥打包进客户端文件。PKCS#12 中只应包含该客户端身份所需的私钥、客户端证书和必要的证书链。
7.4 安装到 iPhone
- 通过受控方式将
.p12文件传到 iPhone。 - 打开文件并按照系统提示安装证书身份或配置描述文件。
- 输入 PKCS#12 导出密码。
- 在系统设置的相关描述文件或设备管理页面确认安装结果。
- 确认安装的是包含客户端证书和私钥的身份,而不是只有公开证书。
不同 iOS 版本的界面文字可能略有差异。如果网站使用私有 CA 签发的服务器证书,还需要单独安装并按系统要求信任根 CA。
7.5 通过 Safari 访问
- 确认 iPhone 已连接家庭网络或可以访问内网的 VPN。
- 在 Safari 中打开 mTLS 网站。
- 当系统或浏览器请求客户端身份时,选择对应证书。
- 检查页面结果和服务器日志。
如果访问失败,分别排查 DNS、网络可达性、服务器证书信任、客户端身份安装情况,以及服务器端验证日志。
8. iOS 浏览器和应用兼容性
iOS 上的浏览器和其他应用是否能使用系统安装的客户端证书,取决于应用实现、操作系统接口和具体版本。不能仅凭证书已安装,就断定所有应用都能使用它。
建议逐个验证目标应用:
- Safari:先验证系统客户端身份是否可用。
- Chrome:单独测试当前版本对客户端证书的支持情况。
- 其他应用:确认应用是否支持 mTLS、是否能访问系统身份,以及是否支持目标服务器地址。
如果某个应用不能调用系统身份,重新签发证书或改变密钥算法通常不能单独解决应用层的访问限制。
9. 证书与私钥安全
客户端证书通常可以公开,但对应私钥必须保护好。
.key文件和.p12文件都应视为敏感材料。- 不要将私钥或 PKCS#12 文件提交到公开仓库、博客附件或不受信任的文件分享服务。
- 传输完成后,检查临时目录、下载目录和文件传输记录中是否还保留不必要的副本。
- 如需备份,应采用加密方式妥善保存。
- 私钥泄露后,应撤销或停止信任对应客户端证书,并重新生成密钥和证书。
- 通过硬件密钥保存私钥时,应尽量避免导出私钥。
将私钥从 Mac 导入手机的方案,不等同于在手机的安全硬件中生成且不可导出的硬件绑定私钥。
10. 故障排查顺序
第一步:检查网络和 DNS
确认客户端能解析目标域名,并能连接到 Nginx 的 HTTPS 端口。
第二步:检查服务器证书信任
确认客户端信任网站服务器证书的签发 CA,且服务器证书的域名、有效期和证书链正确。
第三步:检查客户端身份
确认客户端证书有效、包含 clientAuth 用途,并且对应私钥可用。macOS 钥匙串方案可执行:
security find-identity -v -p ssl-client \
"$HOME/Library/Keychains/login.keychain-db"
第四步:检查 Nginx
检查 ssl_client_certificate 指向的 CA 文件是否包含正确的客户端证书签发链,并确认 ssl_verify_client 和 ssl_verify_depth 与实际 CA 层级相符。
第五步:区分未发送证书与验证失败
No required SSL certificate was sent:通常表示该次 TLS 握手没有收到服务器要求的客户端证书。- 客户端证书验证失败:可能与证书链、有效期、用途或服务端信任配置有关。
结合 Nginx error log 和客户端实际行为判断,不要只依据浏览器错误页面下结论。
11. 方案总结
| 平台 | 客户端身份方案 | 注意事项 |
|---|---|---|
| macOS Chrome | PIV 硬件密钥或钥匙串身份 | 单独验证证书选择和硬件支持 |
| macOS Safari | PIV 硬件密钥或钥匙串身份 | 不假设与 Chrome 行为完全一致 |
| iOS Safari | 系统安装的 PKCS#12 身份 | 另行确认服务器 CA 信任 |
| iOS Chrome | 取决于应用和系统版本的支持情况 | 必须在目标版本上实际测试 |
推荐做法是:Mac 优先考虑将私钥保存在 PIV 硬件密钥中;需要软件身份时,使用兼容的 PKCS#12 文件导入钥匙串;iPhone 使用独立客户端身份,并通过目标应用逐一验证。
如果后续需要为密码管理器等原生应用启用 mTLS,应将该应用自身的网络请求认证与浏览器访问网站的 mTLS 分开设计。