<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[260542] branches/safari-609.2.7-branch</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/260542">260542</a></dd>
<dt>Author</dt> <dd>alancoon@apple.com</dd>
<dt>Date</dt> <dd>2020-04-22 16:48:05 -0700 (Wed, 22 Apr 2020)</dd>
</dl>

<h3>Log Message</h3>
<pre>Apply patch. rdar://problem/62083319</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#branchessafari60927branchSourceWebKitChangeLog">branches/safari-609.2.7-branch/Source/WebKit/ChangeLog</a></li>
<li><a href="#branchessafari60927branchSourceWebKitNetworkProcesscocoaNetworkSessionCocoah">branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h</a></li>
<li><a href="#branchessafari60927branchSourceWebKitNetworkProcesscocoaNetworkSessionCocoamm">branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm</a></li>
<li><a href="#branchessafari60927branchToolsChangeLog">branches/safari-609.2.7-branch/Tools/ChangeLog</a></li>
<li><a href="#branchessafari60927branchToolsTestWebKitAPITestsWebKitCocoaChallengemm">branches/safari-609.2.7-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm</a></li>
<li><a href="#branchessafari60927branchToolsTestWebKitAPIcocoaHTTPServerh">branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.h</a></li>
<li><a href="#branchessafari60927branchToolsTestWebKitAPIcocoaHTTPServermm">branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.mm</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="branchessafari60927branchSourceWebKitChangeLog"></a>
<div class="modfile"><h4>Modified: branches/safari-609.2.7-branch/Source/WebKit/ChangeLog (260541 => 260542)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-609.2.7-branch/Source/WebKit/ChangeLog   2020-04-22 23:45:39 UTC (rev 260541)
+++ branches/safari-609.2.7-branch/Source/WebKit/ChangeLog      2020-04-22 23:48:05 UTC (rev 260542)
</span><span class="lines">@@ -1,3 +1,34 @@
</span><ins>+2020-04-22  Russell Epstein  <repstein@apple.com>
+
+        Apply patch. rdar://problem/62083319
+
+    2020-04-22  Alex Christensen  <achristensen@webkit.org>
+
+            NetworkSessionCocoa should request client certificate only once per host/port
+            https://bugs.webkit.org/show_bug.cgi?id=210626
+            <rdar://problem/60340449>
+
+            Reviewed by Geoffrey Garen.
+
+            NSURLSession creates more than one TCP connection to a server when using HTTP 1.1.
+            Each TCP connection with TLS generates a didReceiveChallenge to do the server trust evaluation of the certificate chain.
+            If the server requests a client certificate in the TLS handshake, it also generates a didReceiveChallenge to request client
+            certificates as well.  This is an implementation detail of our networking.  We should not actually ask the WKNavigationDelegate
+            for client certificates more than once per host/port.  We should remember the credential and give it to NSURLSession immediately
+            if we have used this credential in the past for a task that has received bytes (either a response or a redirect).  If the TLS
+            handshake fails, we should not reuse that same certificate automatically.
+
+            * NetworkProcess/cocoa/NetworkSessionCocoa.h:
+            * NetworkProcess/cocoa/NetworkSessionCocoa.mm:
+            (-[WKNetworkSessionDelegate URLSession:task:willPerformHTTPRedirection:newRequest:completionHandler:]):
+            (-[WKNetworkSessionDelegate URLSession:task:didReceiveChallenge:completionHandler:]):
+            (-[WKNetworkSessionDelegate URLSession:task:didCompleteWithError:]):
+            (-[WKNetworkSessionDelegate URLSession:dataTask:didReceiveResponse:completionHandler:]):
+            (WebKit::NetworkSessionCocoa::clientCertificateSuggestedForHost):
+            (WebKit::NetworkSessionCocoa::taskReceivedBytes):
+            (WebKit::NetworkSessionCocoa::taskFailed):
+            (WebKit::NetworkSessionCocoa::successfulClientCertificateForHost const):
+
</ins><span class="cx"> 2020-04-16  Alan Coon  <alancoon@apple.com>
</span><span class="cx"> 
</span><span class="cx">         Cherry-pick r259814. rdar://problem/61888315
</span></span></pre></div>
<a id="branchessafari60927branchSourceWebKitNetworkProcesscocoaNetworkSessionCocoah"></a>
<div class="modfile"><h4>Modified: branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h (260541 => 260542)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h  2020-04-22 23:45:39 UTC (rev 260541)
+++ branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h     2020-04-22 23:48:05 UTC (rev 260542)
</span><span class="lines">@@ -48,6 +48,7 @@
</span><span class="cx"> enum class NegotiatedLegacyTLS : bool;
</span><span class="cx"> class LegacyCustomProtocolManager;
</span><span class="cx"> class NetworkSessionCocoa;
</span><ins>+using HostAndPort = std::pair<String, uint16_t>;
</ins><span class="cx"> 
</span><span class="cx"> struct SessionWrapper : public CanMakeWeakPtr<SessionWrapper> {
</span><span class="cx">     void initialize(NSURLSessionConfiguration *, NetworkSessionCocoa&, WebCore::StoredCredentialsPolicy);
</span><span class="lines">@@ -95,6 +96,11 @@
</span><span class="cx"> 
</span><span class="cx">     SessionWrapper& sessionWrapperForTask(const WebCore::ResourceRequest&, WebCore::StoredCredentialsPolicy);
</span><span class="cx"> 
</span><ins>+    void clientCertificateSuggestedForHost(NetworkDataTaskCocoa::TaskIdentifier, NSURLCredential *, const String& host, uint16_t port);
+    void taskReceivedBytes(NetworkDataTaskCocoa::TaskIdentifier);
+    void taskFailed(NetworkDataTaskCocoa::TaskIdentifier);
+    NSURLCredential *successfulClientCertificateForHost(const String& host, uint16_t port) const;
+
</ins><span class="cx"> private:
</span><span class="cx">     void invalidateAndCancel() override;
</span><span class="cx">     void clearCredentials() override;
</span><span class="lines">@@ -133,6 +139,14 @@
</span><span class="cx">     Seconds m_loadThrottleLatency;
</span><span class="cx">     bool m_fastServerTrustEvaluationEnabled { false };
</span><span class="cx">     String m_dataConnectionServiceType;
</span><ins>+    
+    struct SuggestedClientCertificate {
+        String host;
+        uint16_t port { 0 };
+        RetainPtr<NSURLCredential> credential;
+    };
+    HashMap<NetworkDataTaskCocoa::TaskIdentifier, SuggestedClientCertificate> m_suggestedClientCertificates;
+    HashMap<HostAndPort, RetainPtr<NSURLCredential>> m_successfulClientCertificates;
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> } // namespace WebKit
</span></span></pre></div>
<a id="branchessafari60927branchSourceWebKitNetworkProcesscocoaNetworkSessionCocoamm"></a>
<div class="modfile"><h4>Modified: branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm (260541 => 260542)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm 2020-04-22 23:45:39 UTC (rev 260541)
+++ branches/safari-609.2.7-branch/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm    2020-04-22 23:48:05 UTC (rev 260542)
</span><span class="lines">@@ -527,6 +527,9 @@
</span><span class="cx">     if (auto* networkDataTask = [self existingTask:task]) {
</span><span class="cx">         auto completionHandlerCopy = Block_copy(completionHandler);
</span><span class="cx"> 
</span><ins>+        if (auto* sessionCocoa = static_cast<NetworkSessionCocoa*>(networkDataTask->networkSession()))
+            sessionCocoa->taskReceivedBytes(taskIdentifier);
+
</ins><span class="cx">         bool shouldIgnoreHSTS = false;
</span><span class="cx"> #if ENABLE(RESOURCE_LOAD_STATISTICS)
</span><span class="cx">         if (auto* sessionCocoa = networkDataTask->networkSession()) {
</span><span class="lines">@@ -677,6 +680,20 @@
</span><span class="cx"> #endif
</span><span class="cx">         }
</span><span class="cx">     }
</span><ins>+
+    if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
+        HostAndPort key { challenge.protectionSpace.host, challenge.protectionSpace.port };
+        if (auto* credential = sessionCocoa->successfulClientCertificateForHost(challenge.protectionSpace.host, challenge.protectionSpace.port))
+            return completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
+        sessionCocoa->continueDidReceiveChallenge(*_sessionWrapper, challenge, negotiatedLegacyTLS, taskIdentifier, [self existingTask:task], [completionHandler = makeBlockPtr(completionHandler), key, weakSessionCocoa = makeWeakPtr(sessionCocoa), taskIdentifier] (WebKit::AuthenticationChallengeDisposition disposition, const WebCore::Credential& credential) mutable {
+            NSURLCredential *nsCredential = credential.nsCredential();
+            if (disposition == WebKit::AuthenticationChallengeDisposition::UseCredential && nsCredential && weakSessionCocoa)
+                weakSessionCocoa->clientCertificateSuggestedForHost(taskIdentifier, nsCredential, key.first, key.second);
+            completionHandler(toNSURLSessionAuthChallengeDisposition(disposition), nsCredential);
+        });
+        return;
+    }
+
</ins><span class="cx">     sessionCocoa->continueDidReceiveChallenge(*_sessionWrapper, challenge, negotiatedLegacyTLS, taskIdentifier, [self existingTask:task], [completionHandler = makeBlockPtr(completionHandler)] (WebKit::AuthenticationChallengeDisposition disposition, const WebCore::Credential& credential) mutable {
</span><span class="cx">         completionHandler(toNSURLSessionAuthChallengeDisposition(disposition), credential.nsCredential());
</span><span class="cx">     });
</span><span class="lines">@@ -693,9 +710,11 @@
</span><span class="cx">         error = [NSError errorWithDomain:[error domain] code:[error code] userInfo:newUserInfo];
</span><span class="cx">     }
</span><span class="cx"> 
</span><del>-    if (auto* networkDataTask = [self existingTask:task])
</del><ins>+    if (auto* networkDataTask = [self existingTask:task]) {
+        if (auto* sessionCocoa = static_cast<NetworkSessionCocoa*>(networkDataTask->networkSession()))
+            sessionCocoa->taskFailed(task.taskIdentifier);
</ins><span class="cx">         networkDataTask->didCompleteWithError(error, networkDataTask->networkLoadMetrics());
</span><del>-    else if (error) {
</del><ins>+    } else if (error) {
</ins><span class="cx">         if (!_sessionWrapper)
</span><span class="cx">             return;
</span><span class="cx">         auto downloadID = _sessionWrapper->downloadMap.take(task.taskIdentifier);
</span><span class="lines">@@ -819,6 +838,9 @@
</span><span class="cx">     if (auto* networkDataTask = [self existingTask:dataTask]) {
</span><span class="cx">         ASSERT(RunLoop::isMain());
</span><span class="cx"> 
</span><ins>+        if (auto* sessionCocoa = static_cast<NetworkSessionCocoa*>(networkDataTask->networkSession()))
+            sessionCocoa->taskReceivedBytes(taskIdentifier);
+
</ins><span class="cx">         NegotiatedLegacyTLS negotiatedLegacyTLS = NegotiatedLegacyTLS::No;
</span><span class="cx"> #if HAVE(TLS_PROTOCOL_VERSION_T)
</span><span class="cx">         NSURLSessionTaskTransactionMetrics *metrics = dataTask._incompleteTaskMetrics.transactionMetrics.lastObject;
</span><span class="lines">@@ -982,6 +1004,38 @@
</span><span class="cx">     return [NSURLSessionConfiguration defaultSessionConfiguration];
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+void NetworkSessionCocoa::clientCertificateSuggestedForHost(NetworkDataTaskCocoa::TaskIdentifier taskID, NSURLCredential *credential, const String& host, uint16_t port)
+{
+    m_suggestedClientCertificates.set(taskID, SuggestedClientCertificate { host, port, credential });
+}
+
+void NetworkSessionCocoa::taskReceivedBytes(NetworkDataTaskCocoa::TaskIdentifier identifier)
+{
+    if (LIKELY(m_suggestedClientCertificates.isEmpty()))
+        return;
+
+    auto suggestedClientCertificate = m_suggestedClientCertificates.take(identifier);
+    HostAndPort key { suggestedClientCertificate.host, suggestedClientCertificate.port };
+    if (suggestedClientCertificate.credential && decltype(m_successfulClientCertificates)::isValidKey(key))
+        m_successfulClientCertificates.add(key, suggestedClientCertificate.credential);
+}
+
+void NetworkSessionCocoa::taskFailed(NetworkDataTaskCocoa::TaskIdentifier identifier)
+{
+    if (LIKELY(m_suggestedClientCertificates.isEmpty()))
+        return;
+
+    m_suggestedClientCertificates.take(identifier);
+}
+
+NSURLCredential *NetworkSessionCocoa::successfulClientCertificateForHost(const String& host, uint16_t port) const
+{
+    HostAndPort key { host, port };
+    if (!decltype(m_successfulClientCertificates)::isValidKey(key))
+        return nil;
+    return m_successfulClientCertificates.get(key).get();
+}
+
</ins><span class="cx"> const String& NetworkSessionCocoa::boundInterfaceIdentifier() const
</span><span class="cx"> {
</span><span class="cx">     return m_boundInterfaceIdentifier;
</span></span></pre></div>
<a id="branchessafari60927branchToolsChangeLog"></a>
<div class="modfile"><h4>Modified: branches/safari-609.2.7-branch/Tools/ChangeLog (260541 => 260542)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-609.2.7-branch/Tools/ChangeLog   2020-04-22 23:45:39 UTC (rev 260541)
+++ branches/safari-609.2.7-branch/Tools/ChangeLog      2020-04-22 23:48:05 UTC (rev 260542)
</span><span class="lines">@@ -1,3 +1,23 @@
</span><ins>+2020-04-22  Russell Epstein  <repstein@apple.com>
+
+        Apply patch. rdar://problem/62083319
+
+    2020-04-22  Alex Christensen  <achristensen@webkit.org>
+
+            NetworkSessionCocoa should request client certificate only once per host/port
+            https://bugs.webkit.org/show_bug.cgi?id=210626
+            <rdar://problem/60340449>
+
+            Reviewed by Geoffrey Garen.
+
+            * TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm:
+            (TestWebKitAPI::clientCertServerWithCertVerifier):
+            (TestWebKitAPI::TEST):
+            * TestWebKitAPI/cocoa/HTTPServer.h:
+            (TestWebKitAPI::HTTPServer::HTTPResponse::HTTPResponse):
+            * TestWebKitAPI/cocoa/HTTPServer.mm:
+            (TestWebKitAPI::HTTPServer::HTTPServer):
+
</ins><span class="cx"> 2020-04-10  Ryan Haddad  <ryanhaddad@apple.com>
</span><span class="cx"> 
</span><span class="cx">         Cherry-pick r259099. rdar://problem/59610140
</span></span></pre></div>
<a id="branchessafari60927branchToolsTestWebKitAPITestsWebKitCocoaChallengemm"></a>
<div class="modfile"><h4>Modified: branches/safari-609.2.7-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm (260541 => 260542)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-609.2.7-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm        2020-04-22 23:45:39 UTC (rev 260541)
+++ branches/safari-609.2.7-branch/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm   2020-04-22 23:48:05 UTC (rev 260542)
</span><span class="lines">@@ -29,6 +29,7 @@
</span><span class="cx"> #import "PlatformUtilities.h"
</span><span class="cx"> #import "TCPServer.h"
</span><span class="cx"> #import "Test.h"
</span><ins>+#import "TestNavigationDelegate.h"
</ins><span class="cx"> #import "TestWKWebView.h"
</span><span class="cx"> #import "WKWebViewConfigurationExtras.h"
</span><span class="cx"> #import <WebKit/WKNavigationDelegate.h>
</span><span class="lines">@@ -38,6 +39,7 @@
</span><span class="cx"> #import <WebKit/WebKit.h>
</span><span class="cx"> #import <WebKit/_WKErrorRecoveryAttempting.h>
</span><span class="cx"> #import <WebKit/_WKWebsiteDataStoreConfiguration.h>
</span><ins>+#import <wtf/BlockPtr.h>
</ins><span class="cx"> #import <wtf/Platform.h>
</span><span class="cx"> #import <wtf/RetainPtr.h>
</span><span class="cx"> #import <wtf/spi/cocoa/SecuritySPI.h>
</span><span class="lines">@@ -399,6 +401,102 @@
</span><span class="cx"> #endif
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+// FIXME: Find out why these tests time out on Mojave.
+#if HAVE(NETWORK_FRAMEWORK) && (!PLATFORM(MAC) || __MAC_OS_X_VERSION_MIN_REQUIRED >= 101500)
+
+static HTTPServer clientCertServer()
+{
+    Vector<LChar> chars(50000, 'a');
+    String longString(chars.data(), chars.size());
+    return HTTPServer({
+        { "/", { "<html><img src='1.png'/><img src='2.png'/><img src='3.png'/><img src='4.png'/><img src='5.png'/><img src='6.png'/></html>" } },
+        { "/1.png", { longString } },
+        { "/2.png", { longString } },
+        { "/3.png", { longString } },
+        { "/4.png", { longString } },
+        { "/5.png", { longString } },
+        { "/6.png", { longString } },
+        { "/redirectToError", { 301, {{ "Location", "/error" }} } },
+        { "/error", { HTTPServer::HTTPResponse::TerminateConnection::Yes } },
+    }, HTTPServer::Protocol::Https, [] (auto, auto, auto certificateAllowed) {
+        certificateAllowed(true);
+    });
+}
+
+static BlockPtr<void(WKWebView *, NSURLAuthenticationChallenge *, void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))> challengeHandler(Vector<RetainPtr<NSString>>& vector)
+{
+    return makeBlockPtr([&] (WKWebView *webView, NSURLAuthenticationChallenge *challenge, void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *)) {
+        NSString *method = challenge.protectionSpace.authenticationMethod;
+        vector.append(method);
+        if ([method isEqualToString:NSURLAuthenticationMethodClientCertificate])
+            return completionHandler(NSURLSessionAuthChallengeUseCredential, credentialWithIdentity().get());
+        if ([method isEqualToString:NSURLAuthenticationMethodServerTrust])
+            return completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
+        ASSERT_NOT_REACHED();
+    }).get();
+}
+
+static size_t countClientCertChallenges(Vector<RetainPtr<NSString>>& vector)
+{
+    vector.removeAllMatching([](auto& method) {
+        return ![method isEqualToString:NSURLAuthenticationMethodClientCertificate];
+    });
+    return vector.size();
+};
+
+TEST(MultipleClientCertificateConnections, Basic)
+{
+    auto server = clientCertServer();
+
+    Vector<RetainPtr<NSString>> methods;
+    auto delegate = adoptNS([TestNavigationDelegate new]);
+    delegate.get().didReceiveAuthenticationChallenge = challengeHandler(methods).get();
+
+    auto webView = adoptNS([WKWebView new]);
+    [webView setNavigationDelegate:delegate.get()];
+    [webView loadRequest:server.request()];
+    [delegate waitForDidFinishNavigation];
+    EXPECT_EQ(countClientCertChallenges(methods), 1u);
+}
+
+TEST(MultipleClientCertificateConnections, Redirect)
+{
+    auto server = clientCertServer();
+
+    Vector<RetainPtr<NSString>> methods;
+    auto delegate = adoptNS([TestNavigationDelegate new]);
+    delegate.get().didReceiveAuthenticationChallenge = challengeHandler(methods).get();
+
+    auto webView = adoptNS([WKWebView new]);
+    [webView setNavigationDelegate:delegate.get()];
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://127.0.0.1:%d/redirectToError", server.port()]]]];
+    [delegate waitForDidFailProvisionalNavigation];
+    EXPECT_EQ(countClientCertChallenges(methods), 1u);
+    [webView loadRequest:server.request()];
+    [delegate waitForDidFinishNavigation];
+    EXPECT_EQ(countClientCertChallenges(methods), 1u);
+}
+
+TEST(MultipleClientCertificateConnections, Failure)
+{
+    auto server = clientCertServer();
+
+    Vector<RetainPtr<NSString>> methods;
+    auto delegate = adoptNS([TestNavigationDelegate new]);
+    delegate.get().didReceiveAuthenticationChallenge = challengeHandler(methods).get();
+
+    auto webView = adoptNS([WKWebView new]);
+    [webView setNavigationDelegate:delegate.get()];
+    [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://127.0.0.1:%d/error", server.port()]]]];
+    [delegate waitForDidFailProvisionalNavigation];
+    size_t certChallengesAfterInitialFailure = countClientCertChallenges(methods);
+    [webView loadRequest:server.request()];
+    [delegate waitForDidFinishNavigation];
+    EXPECT_EQ(countClientCertChallenges(methods), certChallengesAfterInitialFailure + 1);
+}
+
+#endif // HAVE(NETWORK_FRAMEWORK)
+
</ins><span class="cx"> } // namespace TestWebKitAPI
</span><span class="cx"> 
</span><span class="cx"> #endif // HAVE(SSL)
</span></span></pre></div>
<a id="branchessafari60927branchToolsTestWebKitAPIcocoaHTTPServerh"></a>
<div class="modfile"><h4>Modified: branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.h (260541 => 260542)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.h    2020-04-22 23:45:39 UTC (rev 260541)
+++ branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.h       2020-04-22 23:48:05 UTC (rev 260542)
</span><span class="lines">@@ -40,7 +40,7 @@
</span><span class="cx"> public:
</span><span class="cx">     struct HTTPResponse;
</span><span class="cx">     enum class Protocol : uint8_t { Http, Https, HttpsWithLegacyTLS };
</span><del>-    HTTPServer(std::initializer_list<std::pair<String, HTTPResponse>>, Protocol = Protocol::Http);
</del><ins>+    HTTPServer(std::initializer_list<std::pair<String, HTTPResponse>>, Protocol = Protocol::Http, Function<void(sec_protocol_metadata_t, sec_trust_t, sec_protocol_verify_complete_t)>&& = nullptr);
</ins><span class="cx">     uint16_t port() const;
</span><span class="cx">     NSURLRequest *request() const;
</span><span class="cx">     
</span><span class="lines">@@ -49,15 +49,23 @@
</span><span class="cx">     
</span><span class="cx">     RetainPtr<nw_listener_t> m_listener;
</span><span class="cx">     const Protocol m_protocol;
</span><ins>+    const Function<void(sec_protocol_metadata_t, sec_trust_t, sec_protocol_verify_complete_t)> m_certVerifier;
</ins><span class="cx">     const HashMap<String, HTTPResponse> m_requestResponseMap;
</span><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> struct HTTPServer::HTTPResponse {
</span><del>-    HTTPResponse(String&& body)
-        : body(WTFMove(body)) { }
</del><ins>+    enum class TerminateConnection { No, Yes };
+
+    HTTPResponse(const String& body)
+        : body(body) { }
</ins><span class="cx">     HTTPResponse(String&& body, HashMap<String, String>&& headerFields)
</span><span class="cx">         : body(WTFMove(body))
</span><span class="cx">         , headerFields(WTFMove(headerFields)) { }
</span><ins>+    HTTPResponse(unsigned statusCode, HashMap<String, String>&& headerFields)
+        : headerFields(WTFMove(headerFields))
+        , statusCode(statusCode) { }
+    HTTPResponse(TerminateConnection terminateConnection)
+        : terminateConnection(terminateConnection) { }
</ins><span class="cx"> 
</span><span class="cx">     HTTPResponse(const HTTPResponse&) = default;
</span><span class="cx">     HTTPResponse(HTTPResponse&&) = default;
</span><span class="lines">@@ -67,6 +75,8 @@
</span><span class="cx">     
</span><span class="cx">     String body;
</span><span class="cx">     HashMap<String, String> headerFields;
</span><ins>+    unsigned statusCode { 200 };
+    TerminateConnection terminateConnection { TerminateConnection::No };
</ins><span class="cx"> };
</span><span class="cx"> 
</span><span class="cx"> } // namespace TestWebKitAPI
</span></span></pre></div>
<a id="branchessafari60927branchToolsTestWebKitAPIcocoaHTTPServermm"></a>
<div class="modfile"><h4>Modified: branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.mm (260541 => 260542)</h4>
<pre class="diff"><span>
<span class="info">--- branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.mm   2020-04-22 23:45:39 UTC (rev 260541)
+++ branches/safari-609.2.7-branch/Tools/TestWebKitAPI/cocoa/HTTPServer.mm      2020-04-22 23:48:05 UTC (rev 260542)
</span><span class="lines">@@ -34,8 +34,9 @@
</span><span class="cx"> 
</span><span class="cx"> namespace TestWebKitAPI {
</span><span class="cx"> 
</span><del>-HTTPServer::HTTPServer(std::initializer_list<std::pair<String, HTTPResponse>> responses, Protocol protocol)
</del><ins>+HTTPServer::HTTPServer(std::initializer_list<std::pair<String, HTTPResponse>> responses, Protocol protocol, Function<void(sec_protocol_metadata_t, sec_trust_t, sec_protocol_verify_complete_t)>&& verify)
</ins><span class="cx">     : m_protocol(protocol)
</span><ins>+    , m_certVerifier(WTFMove(verify))
</ins><span class="cx">     , m_requestResponseMap([](std::initializer_list<std::pair<String, HTTPServer::HTTPResponse>> list) {
</span><span class="cx">         HashMap<String, HTTPServer::HTTPResponse> map;
</span><span class="cx">         for (auto& pair : list)
</span><span class="lines">@@ -50,9 +51,15 @@
</span><span class="cx">         sec_protocol_options_set_local_identity(options.get(), identity.get());
</span><span class="cx">         if (protocol == Protocol::HttpsWithLegacyTLS)
</span><span class="cx">             sec_protocol_options_set_max_tls_protocol_version(options.get(), tls_protocol_version_TLSv10);
</span><ins>+        if (m_certVerifier) {
+            sec_protocol_options_set_peer_authentication_required(options.get(), true);
+            sec_protocol_options_set_verify_block(options.get(), ^(sec_protocol_metadata_t metadata, sec_trust_t trust, sec_protocol_verify_complete_t completion) {
+                m_certVerifier(metadata, trust, completion);
+            }, dispatch_get_main_queue());
+        }
</ins><span class="cx"> #else
</span><span class="cx">         UNUSED_PARAM(protocolOptions);
</span><del>-        ASSERT(protocol != Protocol::HttpsWithLegacyTLS);
</del><ins>+        ASSERT_UNUSED(protocol, protocol != Protocol::HttpsWithLegacyTLS);
</ins><span class="cx"> #endif
</span><span class="cx">     };
</span><span class="cx">     auto parameters = adoptNS(nw_parameters_create_secure_tcp(configureTLS, NW_PARAMETERS_DEFAULT_CONFIGURATION));
</span><span class="lines">@@ -73,6 +80,19 @@
</span><span class="cx">     Util::run(&ready);
</span><span class="cx"> }
</span><span class="cx"> 
</span><ins>+static const char* statusText(unsigned statusCode)
+{
+    switch (statusCode) {
+    case 200:
+        return "OK";
+    case 301:
+        return "Moved Permanently";
+    default:
+        ASSERT_NOT_REACHED();
+        return "Unrecognized status";
+    }
+}
+
</ins><span class="cx"> void HTTPServer::respondToRequests(nw_connection_t connection)
</span><span class="cx"> {
</span><span class="cx">     nw_connection_receive(connection, 1, std::numeric_limits<uint32_t>::max(), ^(dispatch_data_t content, nw_content_context_t context, bool complete, nw_error_t error) {
</span><span class="lines">@@ -94,10 +114,18 @@
</span><span class="cx">         size_t pathLength = pathEnd - request.data() - strlen(pathPrefix);
</span><span class="cx">         String path(request.data() + strlen(pathPrefix), pathLength);
</span><span class="cx">         ASSERT_WITH_MESSAGE(m_requestResponseMap.contains(path), "This HTTPServer does not know how to respond to a request for %s", path.utf8().data());
</span><del>-        
</del><ins>+
</ins><span class="cx">         auto response = m_requestResponseMap.get(path);
</span><ins>+        if (response.terminateConnection == HTTPResponse::TerminateConnection::Yes) {
+            nw_connection_cancel(connection);
+            return;
+        }
</ins><span class="cx">         StringBuilder responseBuilder;
</span><del>-        responseBuilder.append("HTTP/1.1 200 OK\r\nContent-Length: ");
</del><ins>+        responseBuilder.append("HTTP/1.1 ");
+        responseBuilder.appendNumber(response.statusCode);
+        responseBuilder.append(" ");
+        responseBuilder.append(statusText(response.statusCode));
+        responseBuilder.append("\r\nContent-Length: ");
</ins><span class="cx">         responseBuilder.appendNumber(response.body.length());
</span><span class="cx">         responseBuilder.append("\r\n");
</span><span class="cx">         for (auto& pair : response.headerFields) {
</span></span></pre>
</div>
</div>

</body>
</html>