Review of Manual OCSP Requests for SecTrustEvaluate (iOS makes OCSP requests outside of NSURLProtocol)

Originator:m.kuratczyk
Number:rdar://716337334 Date Originated:July 9, 2019
Status:Open Resolved:
Product:iOS Product Version:iOS 8+
Classification:Bug Reproducible:Always
 
DESCRIPTION OF PROBLEM

OCSP requests do not follow NSURLProtocol subclassing. Therefore OCSP requests are not proxied when using an NSURLProtocol subclass to proxy all traffic.

Issue in more detail: https://github.com/OnionBrowser/OnionBrowser/issues/178.

Our current workaround is to inspect the X.509v3 certificate being authenticated for OCSP URLs and then make the requests ourselves; subsequently attaching the response with `SecTrustSetOCSPResponse()`. Reference: https://github.com/Psiphon-Labs/OCSPCache.

Our Solution
=========

In our implementation we do the following:
1. Check if there is a pinned OCSP response
- Call SecTrustEvaluate with revocation policy `SecPolicyCreateRevocation(kSecRevocationOCSPMethod|kSecRevocationRequirePositiveResponse|kSecRevocationNetworkAccessDisabled)`
- If `SecTrustEvaluate` succeeds, assume there was a pinned OCSP response

2. Make OCSP request manually through local proxy
- Inspect leaf X.509v3 certificate of serverTrust in `URLSession:task:didReceiveChallenge:completionHandler:` for OCSP URLs
- Make OCSP requests and attach OCSP response to trust
- Call `SecTrustEvaluate` with revocation policy `SecPolicyCreateRevocation(kSecRevocationOCSPMethod|kSecRevocationRequirePositiveResponse|kSecRevocationNetworkAccessDisabled)`

Note: Since we are caching OCSP responses, but do not know when iOS considers responses valid/invalid we only evict responses when `SecTrustEvaluate` fails with an OCSP response fetched out of band. If the response we attach was cached then we make another remote request for a new response. This can be seen here: https://github.com/Psiphon-Labs/OCSPCache/blob/b945a5784cd88ed5693a62a931617bd371f3c9a8/OCSPCache/Classes/OCSPAuthURLSessionDelegate.m#L196-L216.

3. Fallback on CRLs

Questions
=======

- Is the assumption above regarding pinned OCSP responses correct?

- Are the revocation checks in https://github.com/Psiphon-Labs/OCSPCache/blob/b945a5784cd88ed5693a62a931617bd371f3c9a8/OCSPCache/Classes/OCSPAuthURLSessionDelegate.m#L138 performed correctly?

- We are only attaching the OCSP response for the leaf certificate being authenticated. Will plaintext OCSP requests be made by the system for intermediate certificates?


STEPS TO REPRODUCE 

1. Create NSURLProtocol subclass which proxies all traffic through a SOCKS or HTTP proxy by setting connection proxy dictionary (https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1411499-connectionproxydictionary?language=objc)

2.
```
SecPolicyRef policy = SecPolicyCreateRevocation(kSecRevocationOCSPMethod);
SecTrustSetPolicies(trust, policy);

SecTrustResultType trustResultType;
SecTrustEvaluate(trust, &trustResultType); // triggers plaintext OCSP request outside of proxy
```

---

Note: There are tests in https://github.com/Psiphon-Labs/OCSPCache which shed some light on implementation details and expected behaviour.

Comments

Summary from the resulting conversation with Apple

Summary

  • OCSP requests cannot be intercepted by NSURLProtocol as they are performed out of process. We have submitted a bug report for this.

  • Simply calling SecTrustPolicies() will remove any policies previously set on the trust. In our case we were unknowingly removing the hostname check. Fixed here: https://github.com/Psiphon-Labs/OCSPCache/commit/18bd7eea0f702268d965ca90e53f007002a10c1d.

  • Calling SecTrustEvaluate() with SecPolicyCreateRevocation(kSecRevocationOCSPMethod | kSecRevocationNetworkAccessDisabled) will check if there is a pinned OCSP response with OCSP stapling.

  • kSecRevocationRequirePositiveResponse only applies when OCSP requests are made by the system; as this flag refers to the status code in the response.

  • Since the OCSP code varies across iOS versions, testing is the best way to determine the underlying functionality.

Intermediate Certificates

There are two solutions for making OCSP requests for intermediate certificates

Option 1. Plaintext OCSP requests made by the system

If we are OK with intermediate OCSP requests going in the clear, we could enable network access for the revocation check after attaching an OCSP response for the leaf; since the attached response should be used and then the system will make the intermediate checks in the clear.

Option 2. Make all OCSP requests ourselves

If we want to handle all of the OCSP requests ourselves, we could:

  1. Attach OCSP response for the leaf and call SecTrustEvaluate(). This step will download all the intermediate certificates. If this check fails, stop here.
  2. Download all the intermediate OCSP data and attach it.
  3. Do SecTrustEvaluate() again.
By m.kuratczyk at Oct. 7, 2019, 9:03 p.m. (reply...)

Please note: Reports posted here will not necessarily be seen by Apple. All problems should be submitted at bugreport.apple.com before they are posted here. Please only post information for Radars that you have filed yourself, and please do not include Apple confidential information in your posts. Thank you!