身份绑定:如何为你的 App 配置服务器认证
2021 年 1 月 14 日
如果你的 App 在网络上发送或接收数据,那么保护个人信息的隐私与完整、防止数据泄露与攻击就至关重要。你应该使用 Transport Layer Security(传输层安全,TLS)协议在数据传输时对内容加以保护,并对接收数据的服务器加以验证。
请注意:绑定证书并不是必要的。请谨慎部署绑定配置,且在绝对必要的情况下才进行部署。
通过 TLS 连接时,服务器会提供证书或证书链,以证实其身份。你可以在你的 App 内绑定服务器的公钥身份,以进一步限制 App 信任的服务器证书的范围。下面来看看如何开始。
何时使用绑定
在大多数情况下,绑定是不必要的,且应该避免。当你的 App 连接到安全的 TLS 网络时,系统会默认使用默认标准评估服务器的可靠性。这可以满足大多数 App 的安全要求,但某些 App 可能需要进一步限制受信任证书的范围。
例如,你的 App 可能需要满足相应法规要求,这些要求决定了哪些特定的 Certificate Authorities(认证机构,CA)是可以信任的。Apple 平台默认确保只有可信的认证机构参与,但你的 App 也可以使用身份绑定来进一步限制认证机构的范围,只信任与特定政府机构或组织相关的认证机构。
绑定无法放宽你的 App 的信任要求——它只会加强要求。在使用涉及 TLS 网络连接的公钥证书时,你仍然要始终满足系统的默认信任要求。
注意:你在配置 App 以对应某个服务器上的一组特定的公钥时,它将只在涉及该公钥的情况下,才能够连接到那个服务器。因此,如果服务器部署了新的证书,更改了公钥,你的 App 会拒绝连接。此时,你需要用对应新公钥的绑定配置来更新 App。
长期策略
如果你想在你的 App 内使用身份绑定,不妨考虑采取长期策略,纳入计划中与计划外的事件,以避免绑定失败。
你的 App 可以绑定认证机构而不是服务器的公钥,以提供出色的使用体验。这样一来,你可以部署由同一认证机构签署、包含新公钥的服务器证书,而无需绑定配置更新。
你也可以考虑绑定不止一个公钥,特别是在绑定服务器身份时。这样一来,即使证书被撤销或续期,你的 App 仍能连接到配置后的服务器。
此外,如果你的 App 在绑定失败后无法连接到服务器,也要准备提供补救体验。首先,设想你的 App 使用体验会遭到何种方式的影响,然后针对各种负面的副作用准备补救方案。这款 App 是否在连接不成功的情况下仍然可用,你能否为用户提供临时的恢复路径?
你也需要准备面对恢复路径出现的问题。处理绑定失败的一种方式是通过 App 更新进行新的绑定配置。根据你的 App 的用例来考虑这是否是可行的选项。
因此,我们强烈推荐在测试你的 App 时通过获取额外的公钥证书来模拟各种事件与潜在的失败环节,并据此更改你的服务器配置。
如何绑定认证机构公钥
绑定的认证机构公钥必须出现在证书链的中级证书或根证书里,绑定的公钥必须与域名相关联,除非绑定要求得到满足,否则 App 将拒绝连接到该域名。
举例来说:当连接到 example.org
域名时,你可以将下列项添加到 App 的 Info.plist
文件里,对特定 CA 公钥的存在提出要求。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.org</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=</string>
</dict>
</array>
</dict>
</dict>
</dict>
在这个范例中,绑定的公钥与 example.org
相关联,也与 math.example.org
和 history.example.org
等子域名相关联,但它不会与 advanced.math.example.org
或 ancient.history.example.org
相关联。
该公钥被表达为一个 X.509 证书的、经 DER 加密的 ASN.1 Subject Public Key Info 结构的 SHA-256 摘要,使用 Base64 进行编码。假设下面的 PEM 加密的公钥证书,存储在文件“ca.pem”里,你可以使用“openssl”命令来计算它的 SPKI-SHA256-BASE64 值。
-----BEGIN CERTIFICATE-----
MIIDrzCCApegAwIBAgIQCDvgVpBCRrGhdWrJWZHHSjANBgkqhkiG9w0BAQUFADBh
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBD
QTAeFw0wNjExMTAwMDAwMDBaFw0zMTExMTAwMDAwMDBaMGExCzAJBgNVBAYTAlVT
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
b20xIDAeBgNVBAMTF0RpZ2lDZXJ0IEdsb2JhbCBSb290IENBMIIBIjANBgkqhkiG
9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4jvhEXLeqKTTo1eqUKKPC3eQyaKl7hLOllsB
CSDMAZOnTjC3U/dDxGkAV53ijSLdhwZAAIEJzs4bg7/fzTtxRuLWZscFs3YnFo97
nh6Vfe63SKMI2tavegw5BmV/Sl0fvBf4q77uKNd0f3p4mVmFaG5cIzJLv07A6Fpt
43C/dxC//AH2hdmoRBBYMql1GNXRor5H4idq9Joz+EkIYIvUX7Q6hL+hqkpMfT7P
T19sdl6gSzeRntwi5m3OFBqOasv+zbMUZBfHWymeMr/y7vrTC0LUq7dBMtoM1O/4
gdW7jVg/tRvoSSiicNoxBN33shbyTApOB6jtSj1etX+jkMOvJwIDAQABo2MwYTAO
BgNVHQ8BAf8EBAMCAYYwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUA95QNVbR
TLtm8KPiGxvDl7I90VUwHwYDVR0jBBgwFoAUA95QNVbRTLtm8KPiGxvDl7I90VUw
DQYJKoZIhvcNAQEFBQADggEBAMucN6pIExIK+t1EnE9SsPTfrgT1eXkIoyQY/Esr
hMAtudXH/vTBH1jLuG2cenTnmCmrEbXjcKChzUyImZOMkXDiqw8cvpOp/2PV5Adg
06O/nVsJ8dWO41P0jmP6P6fbtGbfYmbW0W5BjfIttep3Sp+dWOIrWcBAI+0tKIJF
PnlUkiaY4IBIqDfv8NZ5YBberOgOzW6sRBc4L0na4UU+Krk2U886UAb3LujEV0ls
YSEY1QSteDwsOoBrp+uvFRTp2InBuThs4pFsiv9kuXclVzDAGySj4dzp30d8tbQk
CAUw7C29C79Fv1C5qfPrmAESrciIxpg0X40KPMbp1ZWVbd4=
-----END CERTIFICATE-----
$ cat ca.pem | openssl x509 -inform pem -noout -outform pem -pubkey | openssl pkey -pubin -inform pem -outform der | openssl dgst -sha256 -binary | openssl enc -base64
举例来说,要把多个公钥绑定到“example.net”服务器证书,你可以把独立的项,如数组中的项,添加到 App 的“Info.plist”文件里。要满足连接到“example.net”的绑定要求,服务器证书必须包含其中的一个公钥。
<key>NSAppTransportSecurity</key>
<dict>
<key>NSPinnedDomains</key>
<dict>
<key>example.org</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSPinnedCAIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=</string>
</dict>
</array>
</dict>
<key>example.net</key>
<dict>
<key>NSPinnedLeafIdentities</key>
<array>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>i9HaIScvf6T/skE3/A7QOq5n5cTYs8UHNOEFCnkguSI=</string>
</dict>
<dict>
<key>SPKI-SHA256-BASE64</key>
<string>i9HaIScvf6T/skE3/A7QOq5n5cTYs8UHNOEFCnkguSI=</string>
</dict>
</array>
</dict>
</dict>
</dict>
举例来说,要把多个公钥绑定到“example.net”服务器证书,你可以把独立的项,如数组中的项,添加到 App 的“Info.plist”文件里。要满足连接到“example.net”的绑定要求,服务器证书必须包含其中的一个公钥。