如何让 CRL 和 OCSP 检查在 iOS 上工作?

How to get CRL and OSCP Checking to work on iOS?

我无法在 iOS 上使用 CRL。我创建了两个测试用例。我有一个有效的证书,由 CA 颁发。我有另一个由 CA 颁发的有效证书,但 CA 已将该证书添加到其 CRL。

然后我设置了启用 CRL 检查的吊销策略,并要求它成功。

func crlValidationTest(trustedCert: SecCertificate, certToVerify: SecCertificate) -> Bool {

    let basicPolicy = SecPolicyCreateBasicX509()

    let crlPolicy = SecPolicyCreateRevocation(kSecRevocationOCSPMethod | kSecRevocationCRLMethod | kSecRevocationRequirePositiveResponse)!

    var trust: SecTrust?

    SecTrustCreateWithCertificates(NSArray(object: certToVerify), NSArray(objects: basicPolicy, crlPolicy), &trust)
    SecTrustSetAnchorCertificates(trust!, NSArray(object: trustedCert))
    SecTrustSetNetworkFetchAllowed(trust!, true)

    var trustResult = SecTrustResultType.invalid

    guard SecTrustEvaluate(trust!, &trustResult) == errSecSuccess else {
        return false
    }

    return trustResult == SecTrustResultType.proceed || trustResult == SecTrustResultType.unspecified
}

我的期望是 CRL 上的证书不受信任,而干净的证书将受信任。

鉴于上述配置,两者都因不受信任而失败。如果我删除 kSecRevocationRequirePositiveResponse 标志,两者都会成功。我已经尝试了仅使用 OSCP 或仅使用 CRL 的所有不同排列,但没有任何效果符合我的预期。

苹果 documentation SecPolicyCreateRevocation 状态:

It's usually not necessary to create a revocation policy yourself unless you wish to override default system behavior, for example to force a particular method, or to disable revocation checking entirely.

仅使用 SecPolicyCreateBasicX509 策略允许两者都成功(当第二个证书应该失败时),那么 Apple 的默认行为是根本不进行 CRL 检查吗?

我将 CharlesProxy 附加到我的设备,并在监听所有网络流量时多次 运行 代码,并且没有出站请求到达 CRL,这解释了为什么当 RequirePositiveResponse 标志被选中。

我还尝试使用 URLRequest 从设备直接导航到 CRL,并且能够毫无问题地获取设备上的 CRL 数据。

Apple 安全库不支持 CRL 检查吗?如果是,有没有人想出配置让它正确响应? bing 用于进行 CRL 验证的替代方案是什么,我假设在金融区或其他敏感区域交易的高安全性移动应用程序不会允许这种覆盖差距。

更新 为了比较,我 运行 certutil -f -urlfetch -verify MYCERT.cer 使用 certutil,并将 Fiddler 附加到 运行 命令框。我收到了 iOS 没有给我的预期结果,并且我看到通过 fiddler 通过 HTTP 对 CRL 的出站请求。

我创建了一个赏金项目以引起更多对此的兴趣。我希望有人有更多关于上面做错了什么的细节,或者为什么这在 iOS 上不起作用。

在 Apple 平台上,客户端既不检查 CA 的证书撤销列表 (CRL),也不默认使用 OCSP。

Apple 平台支持 OCSP stapling 并且他们提供了一种称为 Revocation Enhancement 的替代机制,这确实会导致OCSP 调用,如下所示。

OCSP 装订

首先解释一下OCSP stapling:

Online Certificate Status Protocol (OCSP) stapling, formally known as the TLS Certificate Status Request extension, is a standard for checking the revocation status of X.509 digital certificates.1 It allows the presenter of a certificate to bear the resource cost involved in providing Online Certificate Status Protocol (OCSP) responses by appending ("stapling") a time-stamped OCSP response signed by the CA to the initial TLS handshake, eliminating the need for clients to contact the CA, with the aim of improving both security and performance.

https://en.wikipedia.org/wiki/OCSP_stapling

OCSP 和 OCSP 装订的区别

如果客户端连接到传统 OCSP 流程中的服务器并检索证书,它会通过向 CA 发出请求来检查收到的证书是否已被吊销。这有一些缺点:例如,需要额外的网络连接,信息未加密,因此存在数据隐私问题。

通过 OCSP 装订,服务器向 CA 请求签名的吊销信息并将其添加到 TLS 握手中。

这也意味着当使用 OCSP 装订时,您看不到从 iOS 到 CA 服务器的 OCSP 请求。

OCSP 装订的缺点

您要连接的服务器必须支持 OCSP 装订。这也不能防止恶意服务器。

这些是 Apple 提供 撤销增强 的主要原因。

Apple 的吊销增强功能

工作原理如下:

  • 证书透明度 日志条目由 Apple 收集
  • 利用此信息,Apple 从 CA 收集有关撤销的信息
  • 此汇总信息会定期自动提供给所有 Apple 客户
  • 根据此信息,当 iOS 应用程序尝试连接到具有已撤销证书的服务器时,它会通过 OCSP 执行额外检查。

要求

应用支持此功能的唯一要求是将使用的服务器证书添加到证书透明度日志中。 public 证书颁发机构可能已经这样做了,但您应该检查域证书是否在 public 证书的活动透明度日志中,例如通过使用以下 link:https://transparencyreport.google.com/https/certificates

WWDC 2017,session701

有一个很棒的 WWDC session,其中详细解释了这个主题和 Apple 的动机:WWDC 2017,session 701:https://developer.apple.com/videos/play/wwdc2017/701/

大约 12:10 分钟,一位 Apple 工程师详细解释了整个撤销主题。在 15:30 左右,她解释说正常的 OCSP 需要使用额外的 API。

iOS

上的 OCSP 装订测试

为了测试,我们需要一个支持 OCSP 装订并使用吊销证书的服务器:例如https://revoked.grc.com (在此服务器故障答案中找到此服务器:https://serverfault.com/a/645066

然后我们可以尝试从 iOS 连接一个小测试程序,该程序尝试下载 HTML 响应并将其输出到控制台。

根据上述 WWDC session 中的信息,连接尝试应该会失败:

...
let session = URLSession(configuration: .default)
...

func onDownloadAction() {
    let url = URL(string: "https://revoked.grc.com")!
    self.download(from: url) { (result, error) in
        if let result = result {
            print("result: " + result)
        } else {
            print("download failed")
            if let error = error {
                print("error: \(error)")
            }
        }
    }
}


func download(from url: URL, completion: @escaping(String?, Error?)->Void) {
    let dataTask = self.session.dataTask(with: url) { data, response, error in
        guard let data = data else {
            if let error = error {
                completion(nil, error)
                return
            }
            completion(nil, NSError(domain: "DownloadFailure", code: 0, userInfo:nil))
            return
        }

        guard let response = response as? HTTPURLResponse else {
            completion(nil, NSError(domain: "ResponseFailure", code: 0, userInfo:nil))
            return
        }
        print("http status: \(response.statusCode)")
        let res = String(bytes: data, encoding: .utf8)
        completion(res, nil)
    }
    dataTask.resume()
}

如果我们在iOS模拟器中执行上述例程,我们可以使用Wireshark检查time-stamped CA签署的OCSP响应是否被装订到TLS握手中。

使用 nslookup revoked.grc.com 我们可以得到服务器的 IP 地址,并且可以使用 ip.addr==4.79.142.205 在 Wireshark 中进行过滤。

在屏幕截图中,可以看到证书的状态为 revoked

因此,查看 Xcode 控制台,可以看到以下输出:

2019-10-12 21:32:25.734382+0200 OCSPTests[6701:156558] ATS failed system trust
2019-10-12 21:32:25.734526+0200 OCSPTests[6701:156558] Connection 1: system TLS Trust evaluation failed(-9802)
2019-10-12 21:32:25.734701+0200 OCSPTests[6701:156558] Connection 1: TLS Trust encountered error 3:-9802
2019-10-12 21:32:25.734787+0200 OCSPTests[6701:156558] Connection 1: encountered error(3:-9802)
2019-10-12 21:32:25.737672+0200 OCSPTests[6701:156558] Task <12408947-689F-4537-9642-C8F95E86CA62>.<1> HTTP load failed, 0/0 bytes (error code: -1200 [3:-9802])
download failed
error: Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x6000037f8510>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=(
    "<cert(0x7fda78828200) s: revoked.grc.com i: DigiCert SHA2 Secure Server CA>",
    "<cert(0x7fda7882b200) s: DigiCert SHA2 Secure Server CA i: DigiCert Global Root CA>"
), NSUnderlyingError=0x600000be9170 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFStreamPropertySSLClientCertificateState=0, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x6000037f8510>, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, kCFStreamPropertySSLPeerCertificates=(
    "<cert(0x7fda78828200) s: revoked.grc.com i: DigiCert SHA2 Secure Server CA>",
    "<cert(0x7fda7882b200) s: DigiCert SHA2 Secure Server CA i: DigiCert Global Root CA>"
)}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://revoked.grc.com/, NSErrorFailingURLStringKey=https://revoked.grc.com/, NSErrorClientCertificateStateKey=0}

iOS 使用 TLS 错误中止连接到服务器的尝试。

测试revoked.badssl.com

revoked.badssl.com 不支持 OCSP 装订。

如果我们查看 https://revoked.badssl.com 的证书详细信息,我们会发现:

如果下载 .crl 文件 (2.5MB) 并运行

openssl crl -inform DER -text -in ssca-sha2-g6.crl | grep 0371B58A86F6CE9C3ECB7BF42F9208FC

通过CRL可以看出这个证书确实被吊销了

有趣的是,Safari、Chrome 和 iOS 都没有识别出这个撤销状态。只有 Mozilla Firefox 显示错误消息(Peer's Certificate has been revoked. Error code: SEC_ERROR_REVOKED_CERTIFICATE)。

原因可能是证书仅在几天前更新,因此尚未进入所有本地 revoke 浏览器和操作系统列表。