Apply patch. rdar://problem/62083319
Canonical link: https://commits.webkit.org/218903.521@safari-609.2.7-branch
git-svn-id: https://svn.webkit.org/repository/webkit/branches/safari-609.2.7-branch@260542 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/Source/WebKit/ChangeLog b/Source/WebKit/ChangeLog
index e0931bf..709bdbf 100644
--- a/Source/WebKit/ChangeLog
+++ b/Source/WebKit/ChangeLog
@@ -1,3 +1,34 @@
+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):
+
2020-04-16 Alan Coon <alancoon@apple.com>
Cherry-pick r259814. rdar://problem/61888315
diff --git a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h
index 4ad2c10..05fc986 100644
--- a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h
+++ b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.h
@@ -48,6 +48,7 @@
enum class NegotiatedLegacyTLS : bool;
class LegacyCustomProtocolManager;
class NetworkSessionCocoa;
+using HostAndPort = std::pair<String, uint16_t>;
struct SessionWrapper : public CanMakeWeakPtr<SessionWrapper> {
void initialize(NSURLSessionConfiguration *, NetworkSessionCocoa&, WebCore::StoredCredentialsPolicy);
@@ -95,6 +96,11 @@
SessionWrapper& sessionWrapperForTask(const WebCore::ResourceRequest&, WebCore::StoredCredentialsPolicy);
+ 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;
+
private:
void invalidateAndCancel() override;
void clearCredentials() override;
@@ -133,6 +139,14 @@
Seconds m_loadThrottleLatency;
bool m_fastServerTrustEvaluationEnabled { false };
String m_dataConnectionServiceType;
+
+ struct SuggestedClientCertificate {
+ String host;
+ uint16_t port { 0 };
+ RetainPtr<NSURLCredential> credential;
+ };
+ HashMap<NetworkDataTaskCocoa::TaskIdentifier, SuggestedClientCertificate> m_suggestedClientCertificates;
+ HashMap<HostAndPort, RetainPtr<NSURLCredential>> m_successfulClientCertificates;
};
} // namespace WebKit
diff --git a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm
index 09e388e..84dfad8 100644
--- a/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm
+++ b/Source/WebKit/NetworkProcess/cocoa/NetworkSessionCocoa.mm
@@ -527,6 +527,9 @@
if (auto* networkDataTask = [self existingTask:task]) {
auto completionHandlerCopy = Block_copy(completionHandler);
+ if (auto* sessionCocoa = static_cast<NetworkSessionCocoa*>(networkDataTask->networkSession()))
+ sessionCocoa->taskReceivedBytes(taskIdentifier);
+
bool shouldIgnoreHSTS = false;
#if ENABLE(RESOURCE_LOAD_STATISTICS)
if (auto* sessionCocoa = networkDataTask->networkSession()) {
@@ -677,6 +680,20 @@
#endif
}
}
+
+ 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;
+ }
+
sessionCocoa->continueDidReceiveChallenge(*_sessionWrapper, challenge, negotiatedLegacyTLS, taskIdentifier, [self existingTask:task], [completionHandler = makeBlockPtr(completionHandler)] (WebKit::AuthenticationChallengeDisposition disposition, const WebCore::Credential& credential) mutable {
completionHandler(toNSURLSessionAuthChallengeDisposition(disposition), credential.nsCredential());
});
@@ -693,9 +710,11 @@
error = [NSError errorWithDomain:[error domain] code:[error code] userInfo:newUserInfo];
}
- if (auto* networkDataTask = [self existingTask:task])
+ if (auto* networkDataTask = [self existingTask:task]) {
+ if (auto* sessionCocoa = static_cast<NetworkSessionCocoa*>(networkDataTask->networkSession()))
+ sessionCocoa->taskFailed(task.taskIdentifier);
networkDataTask->didCompleteWithError(error, networkDataTask->networkLoadMetrics());
- else if (error) {
+ } else if (error) {
if (!_sessionWrapper)
return;
auto downloadID = _sessionWrapper->downloadMap.take(task.taskIdentifier);
@@ -819,6 +838,9 @@
if (auto* networkDataTask = [self existingTask:dataTask]) {
ASSERT(RunLoop::isMain());
+ if (auto* sessionCocoa = static_cast<NetworkSessionCocoa*>(networkDataTask->networkSession()))
+ sessionCocoa->taskReceivedBytes(taskIdentifier);
+
NegotiatedLegacyTLS negotiatedLegacyTLS = NegotiatedLegacyTLS::No;
#if HAVE(TLS_PROTOCOL_VERSION_T)
NSURLSessionTaskTransactionMetrics *metrics = dataTask._incompleteTaskMetrics.transactionMetrics.lastObject;
@@ -982,6 +1004,38 @@
return [NSURLSessionConfiguration defaultSessionConfiguration];
}
+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();
+}
+
const String& NetworkSessionCocoa::boundInterfaceIdentifier() const
{
return m_boundInterfaceIdentifier;
diff --git a/Tools/ChangeLog b/Tools/ChangeLog
index 30f3772..673c5c3 100644
--- a/Tools/ChangeLog
+++ b/Tools/ChangeLog
@@ -1,3 +1,23 @@
+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):
+
2020-04-10 Ryan Haddad <ryanhaddad@apple.com>
Cherry-pick r259099. rdar://problem/59610140
diff --git a/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm b/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm
index 9acd174..d9a8b92 100644
--- a/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm
+++ b/Tools/TestWebKitAPI/Tests/WebKitCocoa/Challenge.mm
@@ -29,6 +29,7 @@
#import "PlatformUtilities.h"
#import "TCPServer.h"
#import "Test.h"
+#import "TestNavigationDelegate.h"
#import "TestWKWebView.h"
#import "WKWebViewConfigurationExtras.h"
#import <WebKit/WKNavigationDelegate.h>
@@ -38,6 +39,7 @@
#import <WebKit/WebKit.h>
#import <WebKit/_WKErrorRecoveryAttempting.h>
#import <WebKit/_WKWebsiteDataStoreConfiguration.h>
+#import <wtf/BlockPtr.h>
#import <wtf/Platform.h>
#import <wtf/RetainPtr.h>
#import <wtf/spi/cocoa/SecuritySPI.h>
@@ -399,6 +401,102 @@
#endif
}
+// 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)
+
} // namespace TestWebKitAPI
#endif // HAVE(SSL)
diff --git a/Tools/TestWebKitAPI/cocoa/HTTPServer.h b/Tools/TestWebKitAPI/cocoa/HTTPServer.h
index 0070485..60ea630 100644
--- a/Tools/TestWebKitAPI/cocoa/HTTPServer.h
+++ b/Tools/TestWebKitAPI/cocoa/HTTPServer.h
@@ -40,7 +40,7 @@
public:
struct HTTPResponse;
enum class Protocol : uint8_t { Http, Https, HttpsWithLegacyTLS };
- HTTPServer(std::initializer_list<std::pair<String, HTTPResponse>>, Protocol = Protocol::Http);
+ 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);
uint16_t port() const;
NSURLRequest *request() const;
@@ -49,15 +49,23 @@
RetainPtr<nw_listener_t> m_listener;
const Protocol m_protocol;
+ const Function<void(sec_protocol_metadata_t, sec_trust_t, sec_protocol_verify_complete_t)> m_certVerifier;
const HashMap<String, HTTPResponse> m_requestResponseMap;
};
struct HTTPServer::HTTPResponse {
- HTTPResponse(String&& body)
- : body(WTFMove(body)) { }
+ enum class TerminateConnection { No, Yes };
+
+ HTTPResponse(const String& body)
+ : body(body) { }
HTTPResponse(String&& body, HashMap<String, String>&& headerFields)
: body(WTFMove(body))
, headerFields(WTFMove(headerFields)) { }
+ HTTPResponse(unsigned statusCode, HashMap<String, String>&& headerFields)
+ : headerFields(WTFMove(headerFields))
+ , statusCode(statusCode) { }
+ HTTPResponse(TerminateConnection terminateConnection)
+ : terminateConnection(terminateConnection) { }
HTTPResponse(const HTTPResponse&) = default;
HTTPResponse(HTTPResponse&&) = default;
@@ -67,6 +75,8 @@
String body;
HashMap<String, String> headerFields;
+ unsigned statusCode { 200 };
+ TerminateConnection terminateConnection { TerminateConnection::No };
};
} // namespace TestWebKitAPI
diff --git a/Tools/TestWebKitAPI/cocoa/HTTPServer.mm b/Tools/TestWebKitAPI/cocoa/HTTPServer.mm
index 4cee389..d527b87 100644
--- a/Tools/TestWebKitAPI/cocoa/HTTPServer.mm
+++ b/Tools/TestWebKitAPI/cocoa/HTTPServer.mm
@@ -34,8 +34,9 @@
namespace TestWebKitAPI {
-HTTPServer::HTTPServer(std::initializer_list<std::pair<String, HTTPResponse>> responses, Protocol protocol)
+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)
: m_protocol(protocol)
+ , m_certVerifier(WTFMove(verify))
, m_requestResponseMap([](std::initializer_list<std::pair<String, HTTPServer::HTTPResponse>> list) {
HashMap<String, HTTPServer::HTTPResponse> map;
for (auto& pair : list)
@@ -50,9 +51,15 @@
sec_protocol_options_set_local_identity(options.get(), identity.get());
if (protocol == Protocol::HttpsWithLegacyTLS)
sec_protocol_options_set_max_tls_protocol_version(options.get(), tls_protocol_version_TLSv10);
+ 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());
+ }
#else
UNUSED_PARAM(protocolOptions);
- ASSERT(protocol != Protocol::HttpsWithLegacyTLS);
+ ASSERT_UNUSED(protocol, protocol != Protocol::HttpsWithLegacyTLS);
#endif
};
auto parameters = adoptNS(nw_parameters_create_secure_tcp(configureTLS, NW_PARAMETERS_DEFAULT_CONFIGURATION));
@@ -73,6 +80,19 @@
Util::run(&ready);
}
+static const char* statusText(unsigned statusCode)
+{
+ switch (statusCode) {
+ case 200:
+ return "OK";
+ case 301:
+ return "Moved Permanently";
+ default:
+ ASSERT_NOT_REACHED();
+ return "Unrecognized status";
+ }
+}
+
void HTTPServer::respondToRequests(nw_connection_t connection)
{
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) {
@@ -94,10 +114,18 @@
size_t pathLength = pathEnd - request.data() - strlen(pathPrefix);
String path(request.data() + strlen(pathPrefix), pathLength);
ASSERT_WITH_MESSAGE(m_requestResponseMap.contains(path), "This HTTPServer does not know how to respond to a request for %s", path.utf8().data());
-
+
auto response = m_requestResponseMap.get(path);
+ if (response.terminateConnection == HTTPResponse::TerminateConnection::Yes) {
+ nw_connection_cancel(connection);
+ return;
+ }
StringBuilder responseBuilder;
- responseBuilder.append("HTTP/1.1 200 OK\r\nContent-Length: ");
+ responseBuilder.append("HTTP/1.1 ");
+ responseBuilder.appendNumber(response.statusCode);
+ responseBuilder.append(" ");
+ responseBuilder.append(statusText(response.statusCode));
+ responseBuilder.append("\r\nContent-Length: ");
responseBuilder.appendNumber(response.body.length());
responseBuilder.append("\r\n");
for (auto& pair : response.headerFields) {