Hi,
Background: I am working on an application which supports SAML based login. To support SAML based login we open the IDP url in the WKWebView and the IDP take it from there. The IDP based URLs are mostly HTTPS urls and hence they work fine with ATS for our application.
Problem: But unfortunately today we enouctered the problem where the certificate of the IDP is not supported by iOS. The certificate fulfills all requirements except the "Forward Secrecy" requirement. Currently we have "NSAllowsArbitraryLoads" set to NO in our application. Now when we run the application and hit the IDP URL we get the following error in didFailProvisionalNavigation:
didFailProvisionalNavigation::Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_WKRecoveryAttempterErrorKey=<WKReloadFrameErrorRecoveryAttempter: 0x7f86110935e0>, NSErrorFailingURLStringKey=<URL>, NSErrorFailingURLKey=<URL>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSUnderlyingError=0x7f861109d5d0 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={_kCFStreamErrorDomainKey=3, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9824, _kCFStreamPropertySSLClientCertificateState=0, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=<URL>, NSErrorFailingURLStringKey=<URL>, _kCFStreamErrorCodeKey=-9824}}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made.}
I have removed the URL string here.
To fix this I tried implementing the -(void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler method.
Below is the code for the same:
if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
[ActivityIndicatorHelper hideGlobalHud];
NSLog(@"protectionSpace::%@", [challenge protectionSpace].description);
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
if (trust != NULL) {
SecTrustResultType secresult = kSecTrustResultInvalid;
if (SecTrustEvaluate(trust, &secresult) != errSecSuccess) {
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
return;
}
NSLog(@"secresult::%u",secresult);
}
completionHandler(NSURLSessionAuthChallengeUseCredential, nil);The above NSLog on line 18 always prints result as "4" which is kSecTrustResultUnspecified which as per definition is obtained only if the certificate is invalid. But in this case it seems that the certificate is "invalid" because it does not fulfill the "Forward Secrecy" requirement. I was banking on the status to be kSecTrustResultRecoverableTrustFailure so that I can use the above method to present the user with an alert with message that warns them that the certificate is not trusted. If they wish to proceed, I will modify the trust setting and let them go ahead. But if I get kSecTrustResultUnspecified which can also be obtained for valid URLs, how can I differentiate between kSecTrustResultRecoverableTrustFailure andkSecTrustResultUnspecified?
To ensure that "forward secrecy" of the certificate is a problem, I modified the ATS setting for my app to be as below and the IDP page started loading properly:
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>main.app.domain</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
<key>idp.vender.domain</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<false/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
</dict>
</dict>
</dict>Is the above behavior of obtaining result as kSecTrustResultUnspecified expected? If yes, how do I differentiate between valid certificate and invalid certificate with "forward secrecy" missing? Any solution is highly appreciated.
- This one is available only for iOS 10 and above. And on iOS 10, it doesnt work (this was the first thing I tried). We support iOS 9 and iOS 10.NSAllowsArbitraryLoadsInWebContent
Yeah, I was worried about that.
NSAllowsArbitraryLoadsInWebContent does work in most cases but there was a bug that causes the client to not offer non-forward secrecy cypher suites when you use HTTPS (r. 27892687). I believe we fixed that in 10.2. So, things break down as follows:
iOS 8 — No ATS to worry about
iOS 9 — Rely on
NSAllowsArbitraryLoadsiOS 10.x, x < 2 — I would ignore this case, requiring your users to update to iOS 10.2 or later.
iOS 10.2 and later — Rely on
NSAllowsArbitraryLoadsInWebContent
IMPORTANT The presence of
NSAllowsArbitraryLoadsInWebContent causes iOS 10 to ignore
NSAllowsArbitraryLoads. This results in best practice security on iOS 10 while maintaining compatibility with iOS 9.
Why is the connection failing for Forward Secrecy not falling under kSecTrustResultRecoverableTrustFailure … ?
You’re talking about two different subsystems:
Cypher suite negotiation is done by the TLS subsystem (CoreTLS > Secure Transport > CFSocketStream (and friends) > NSURLSession)
Server trust evaluation is done by way of a trust object (SecTrust)
These are not tightly connected. Specifically, the trust object you get via the
NSURLAuthenticationMethodServerTrust authentication challenge does not know (or care) what cypher suite was negotiated; it only looks at the certificate chain and the various policies.
If you walk through the process of constructing a trust object (by calling
SecTrustCreateWithCertificates, feeding it a policy you create using
SecPolicyCreateSSL), you’ll find that at know point do you tell the trust object what cypher suite was used.
In theory this could happen — we could add a cypher suite policy, or extend the TLS policy to accept the cypher suite as an option — but this is not how things work today.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"