Support manually accepting invalid SSL certificates with NetworkSession
authorachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 Mar 2016 19:28:24 +0000 (19:28 +0000)
committerachristensen@apple.com <achristensen@apple.com@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Thu, 17 Mar 2016 19:28:24 +0000 (19:28 +0000)
https://bugs.webkit.org/show_bug.cgi?id=155442
<rdar://problem/24847398>

Reviewed by Darin Adler.

When we click continue after getting a warning about an invalid SSL certificate, we call
NSURLRequest setAllowsSpecificHTTPSCertificate in NetworkProcess::allowSpecificHTTPSCertificateForHost,
which stores information in CFNetwork about the specific invalid SSL certificate we want to accept.
If we see such a certificate during a server trust evaluation, we want to tell CFNetwork to accept it.
This fixes a loop when going to https://badssl.com, clicking on expired, and clicking continue.

* NetworkProcess/NetworkDataTask.h:
* NetworkProcess/NetworkLoad.cpp:
(WebKit::NetworkLoad::didReceiveChallenge):
(WebKit::NetworkLoad::continueCanAuthenticateAgainstProtectionSpace):
* NetworkProcess/cocoa/NetworkDataTaskCocoa.mm:
(WebKit::NetworkDataTask::transferSandboxExtensionToDownload):
(WebKit::certificatesMatch):
(WebKit::NetworkDataTask::allowsSpecificHTTPSCertificateForHost):
(WebKit::NetworkDataTask::suggestedFilename):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@198347 268f45cc-cd09-0410-ab3c-d52691b4dbfc

Source/WebKit2/ChangeLog
Source/WebKit2/NetworkProcess/NetworkDataTask.h
Source/WebKit2/NetworkProcess/NetworkLoad.cpp
Source/WebKit2/NetworkProcess/cocoa/NetworkDataTaskCocoa.mm

index 77ab2a6..842af58 100644 (file)
@@ -1,3 +1,27 @@
+2016-03-17  Alex Christensen  <achristensen@webkit.org>
+
+        Support manually accepting invalid SSL certificates with NetworkSession
+        https://bugs.webkit.org/show_bug.cgi?id=155442
+        <rdar://problem/24847398>
+
+        Reviewed by Darin Adler.
+
+        When we click continue after getting a warning about an invalid SSL certificate, we call
+        NSURLRequest setAllowsSpecificHTTPSCertificate in NetworkProcess::allowSpecificHTTPSCertificateForHost,
+        which stores information in CFNetwork about the specific invalid SSL certificate we want to accept.
+        If we see such a certificate during a server trust evaluation, we want to tell CFNetwork to accept it.
+        This fixes a loop when going to https://badssl.com, clicking on expired, and clicking continue.
+
+        * NetworkProcess/NetworkDataTask.h:
+        * NetworkProcess/NetworkLoad.cpp:
+        (WebKit::NetworkLoad::didReceiveChallenge):
+        (WebKit::NetworkLoad::continueCanAuthenticateAgainstProtectionSpace):
+        * NetworkProcess/cocoa/NetworkDataTaskCocoa.mm:
+        (WebKit::NetworkDataTask::transferSandboxExtensionToDownload):
+        (WebKit::certificatesMatch):
+        (WebKit::NetworkDataTask::allowsSpecificHTTPSCertificateForHost):
+        (WebKit::NetworkDataTask::suggestedFilename):
+
 2016-03-17  Csaba Osztrogon√°c  <ossy@webkit.org>
 
         [Mac][cmake] Unreviewed buildfix after r198070. Just for fun.
index d4604e3..6896eff 100644 (file)
@@ -127,6 +127,7 @@ public:
     String suggestedFilename();
     void willPerformHTTPRedirection(const WebCore::ResourceResponse&, WebCore::ResourceRequest&&, RedirectCompletionHandler);
     void transferSandboxExtensionToDownload(Download&);
+    bool allowsSpecificHTTPSCertificateForHost(const WebCore::AuthenticationChallenge&);
     
 private:
     NetworkDataTask(NetworkSession&, NetworkDataTaskClient&, const WebCore::ResourceRequest&, WebCore::StoredCredentials, WebCore::ContentSniffingPolicy, bool shouldClearReferrerOnHTTPSToHTTPRedirect);
index 308ff0c..47a8d57 100644 (file)
@@ -206,7 +206,10 @@ void NetworkLoad::didReceiveChallenge(const AuthenticationChallenge& challenge,
     // Handle server trust evaluation at platform-level if requested, for performance reasons.
     if (challenge.protectionSpace().authenticationScheme() == ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested
         && !NetworkProcess::singleton().canHandleHTTPSServerTrustEvaluation()) {
-        completionHandler(AuthenticationChallengeDisposition::RejectProtectionSpace, Credential());
+        if (m_task && m_task->allowsSpecificHTTPSCertificateForHost(challenge))
+            completionHandler(AuthenticationChallengeDisposition::UseCredential, serverTrustCredential(challenge));
+        else
+            completionHandler(AuthenticationChallengeDisposition::RejectProtectionSpace, { });
         return;
     }
 
@@ -344,7 +347,10 @@ void NetworkLoad::continueCanAuthenticateAgainstProtectionSpace(bool result)
     ASSERT(m_challengeCompletionHandler);
     auto completionHandler = WTFMove(m_challengeCompletionHandler);
     if (!result) {
-        completionHandler(AuthenticationChallengeDisposition::RejectProtectionSpace, Credential());
+        if (m_task && m_task->allowsSpecificHTTPSCertificateForHost(m_challenge))
+            completionHandler(AuthenticationChallengeDisposition::UseCredential, serverTrustCredential(m_challenge));
+        else
+            completionHandler(AuthenticationChallengeDisposition::RejectProtectionSpace, { });
         return;
     }
     
@@ -354,7 +360,7 @@ void NetworkLoad::continueCanAuthenticateAgainstProtectionSpace(bool result)
     }
     
     if (m_parameters.clientCredentialPolicy == DoNotAskClientForAnyCredentials) {
-        completionHandler(AuthenticationChallengeDisposition::UseCredential, Credential());
+        completionHandler(AuthenticationChallengeDisposition::UseCredential, { });
         return;
     }
     
index aa69465..6c0ef54 100644 (file)
@@ -327,6 +327,42 @@ void NetworkDataTask::transferSandboxExtensionToDownload(Download& download)
     download.setSandboxExtension(WTFMove(m_sandboxExtension));
 }
 
+static bool certificatesMatch(SecTrustRef trust1, SecTrustRef trust2)
+{
+    if (!trust1 || !trust2)
+        return false;
+
+    CFIndex count1 = SecTrustGetCertificateCount(trust1);
+    CFIndex count2 = SecTrustGetCertificateCount(trust2);
+    if (count1 != count2)
+        return false;
+
+    for (CFIndex i = 0; i < count1; i++) {
+        if (!CFEqual(SecTrustGetCertificateAtIndex(trust1, i), SecTrustGetCertificateAtIndex(trust2, i)))
+            return false;
+    }
+
+    return true;
+}
+
+bool NetworkDataTask::allowsSpecificHTTPSCertificateForHost(const WebCore::AuthenticationChallenge& challenge)
+{
+    const String& host = challenge.protectionSpace().host();
+    NSArray *certificates = [NSURLRequest allowsSpecificHTTPSCertificateForHost:host];
+    if (!certificates)
+        return false;
+    
+    bool requireServerCertificates = challenge.protectionSpace().authenticationScheme() == WebCore::ProtectionSpaceAuthenticationScheme::ProtectionSpaceAuthenticationSchemeServerTrustEvaluationRequested;
+    RetainPtr<SecPolicyRef> policy = adoptCF(SecPolicyCreateSSL(requireServerCertificates, host.createCFString().get()));
+    
+    SecTrustRef trustRef = nullptr;
+    if (SecTrustCreateWithCertificates((CFArrayRef)certificates, policy.get(), &trustRef) != noErr)
+        return false;
+    RetainPtr<SecTrustRef> trust = adoptCF(trustRef);
+
+    return certificatesMatch(trust.get(), challenge.nsURLAuthenticationChallenge().protectionSpace.serverTrust);
+}
+
 String NetworkDataTask::suggestedFilename()
 {
     return m_task.get().response.suggestedFilename;