身份绑定:如何为你的 App 配置服务器认证

如果你的 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 公钥的存在提出要求。

NSAppTransportSecurity NSPinnedDomains example.org NSIncludesSubdomains NSPinnedCAIdentities SPKI-SHA256-BASE64 r/mIkG3eEpVdm+u/ko/cwxzOMo1bk4TyHIlByibiA5E=
<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.orghistory.example.org 等子域名相关联,但它不会与 advanced.math.example.organcient.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”的绑定要求,服务器证书必须包含其中的一个公钥。

Resources

NSAppTransportSecurity