Unreviewed, rolling out r245418.
[WebKit-https.git] / Tools / TestWebKitAPI / Tests / WebKitCocoa / Challenge.mm
1 /*
2  * Copyright (C) 2019 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #import "config.h"
27
28 #import "PlatformUtilities.h"
29 #import "TCPServer.h"
30 #import "Test.h"
31 #import "TestWKWebView.h"
32 #import "WKWebViewConfigurationExtras.h"
33 #import <WebKit/WKProcessPoolPrivate.h>
34 #import <WebKit/WKWebsiteDataRecordPrivate.h>
35 #import <wtf/RetainPtr.h>
36
37 static bool navigationFinished;
38
39 static void respondWithChallengeThenOK(int socket)
40 {
41     char readBuffer[1000];
42     auto bytesRead = ::read(socket, readBuffer, sizeof(readBuffer));
43     EXPECT_GT(bytesRead, 0);
44     EXPECT_TRUE(static_cast<size_t>(bytesRead) < sizeof(readBuffer));
45     
46     const char* challengeHeader =
47     "HTTP/1.1 401 Unauthorized\r\n"
48     "Date: Sat, 23 Mar 2019 06:29:01 GMT\r\n"
49     "Content-Length: 0\r\n"
50     "WWW-Authenticate: Basic realm=\"testrealm\"\r\n\r\n";
51     auto bytesWritten = ::write(socket, challengeHeader, strlen(challengeHeader));
52     EXPECT_EQ(static_cast<size_t>(bytesWritten), strlen(challengeHeader));
53     
54     bytesRead = ::read(socket, readBuffer, sizeof(readBuffer));
55     EXPECT_GT(bytesRead, 0);
56     EXPECT_TRUE(static_cast<size_t>(bytesRead) < sizeof(readBuffer));
57     
58     const char* responseHeader =
59     "HTTP/1.1 200 OK\r\n"
60     "Content-Length: 13\r\n\r\n"
61     "Hello, World!";
62     bytesWritten = ::write(socket, responseHeader, strlen(responseHeader));
63     EXPECT_EQ(static_cast<size_t>(bytesWritten), strlen(responseHeader));
64 }
65
66 #if PLATFORM(MAC)
67
68 static std::pair<RetainPtr<NSURLCredential>, RetainPtr<NSString>> credentialWithIdentityAndKeychainPath()
69 {
70     // Certificate and private key were generated by running this command:
71     // openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes
72     // and entering this information:
73     /*
74      Country Name (2 letter code) []:US
75      State or Province Name (full name) []:New Mexico
76      Locality Name (eg, city) []:Santa Fe
77      Organization Name (eg, company) []:Self
78      Organizational Unit Name (eg, section) []:Myself
79      Common Name (eg, fully qualified host name) []:Me
80      Email Address []:me@example.com
81      */
82     
83     NSString *pemEncodedCertificate = @""
84     "MIIFgDCCA2gCCQCKHiPRU5MQuDANBgkqhkiG9w0BAQsFADCBgTELMAkGA1UEBhMC"
85     "VVMxEzARBgNVBAgMCk5ldyBNZXhpY28xETAPBgNVBAcMCFNhbnRhIEZlMQ0wCwYD"
86     "VQQKDARTZWxmMQ8wDQYDVQQLDAZNeXNlbGYxCzAJBgNVBAMMAk1lMR0wGwYJKoZI"
87     "hvcNAQkBFg5tZUBleGFtcGxlLmNvbTAeFw0xOTAzMjMwNTUwMTRaFw0yMDAzMjIw"
88     "NTUwMTRaMIGBMQswCQYDVQQGEwJVUzETMBEGA1UECAwKTmV3IE1leGljbzERMA8G"
89     "A1UEBwwIU2FudGEgRmUxDTALBgNVBAoMBFNlbGYxDzANBgNVBAsMBk15c2VsZjEL"
90     "MAkGA1UEAwwCTWUxHTAbBgkqhkiG9w0BCQEWDm1lQGV4YW1wbGUuY29tMIICIjAN"
91     "BgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA3rhN4SPg8VY/PtGDNKY3T9JISgby"
92     "8YGMJx0vO+YZFZm3G3fsTUsyvDyEHwqp5abCZRB/By1PwWkNrfxn/XP8P034JPlE"
93     "6irViuAYQrqUh6k7ZR8CpOM5GEcRZgAUJGGQwNlOkEwaHnMGc8SsHurgDPh5XBpg"
94     "bDytd7BJuB1NoI/KJmhcajkAuV3varS+uPLofPHNqe+cL8hNnjZQwHWarP45ks4e"
95     "BcOD7twqxuHnVm/FWErpY8Ws5s1MrPThUdDahjEMf+YfDJ9KL8y304yS8J8feCxY"
96     "fcH4BvgLtJmBNHJgj3eND/EMZjJgz2FsBjrJk8kKD31cw+4Wp8UF4skWXCf46+mN"
97     "OHp13PeSCZLyF4ZAHazUVknDPcc2YNrWVV1i6n3T15kI0T5Z7bstdmALuSkE2cuJ"
98     "SVNO6gR+ZsVRTneuQxwWTU0MNEhAPFOX2BhGP5eisgEUzknxMJddFDn9Wxklu1Jh"
99     "gkzASA/+3AmlrFZMPhOhjEul0zjgNR5RBl1G8Hz92LAx5UEDBtdLg71I+I8AzQOh"
100     "d6LtBekECxA16pSappg5vcW9Z/8N6ZlsHnZ2FztA0nCOflkoO9iejOpcuFN4EVYD"
101     "xItwctKw1LCeND/s4kmoRRnXbX7k9O6cI1UUWM595Gsu5tPa33M5AZFCav2gOVuY"
102     "djppS0HOfo5hv6cCAwEAATANBgkqhkiG9w0BAQsFAAOCAgEAY8EWaAFEfw7OV+oD"
103     "XUZSIYXq3EH2E5p3q38AhIOLRjBuB+utyu7Q6rxMMHuw2TtsN+zbAR7yrjfsseA3"
104     "4TM1xe4Nk7NVNHRoZQ+C0Iqf9fvcioMvT1tTrma0MhKSjFQpx+PvyLVbD7YdP86L"
105     "meehKqU7h1pLGAiGwjoaZ9Ybh6Kuq/MTAHy3D8+wk7B36VBxF6diVlUPZJZQWKJy"
106     "MKy9G3sze1ZGt9WeE0AMvkN2HIef0HTKCUZ3eBvecOMijxL0WhWo5Qyf5k6ylCaU"
107     "2fx+M8DfDcwFo7tSgLxSK3GCFpxPfiDt6Qk8c9tQn5S1gY3t6LJuwVCFwUIXlNkB"
108     "JD7+cZ1Z/tCrEhzj3YCk0uUU8CifoU+4FG+HGFP+SPztsYE055mSj3+Esh+oyoVB"
109     "gBH90sE2T1i0eNI8f61oSgwYFeHsf7fC71XEXLFR+GwNdmwqlmwlDZEpTu7BoNN+"
110     "q7+Tfk1MRkJlL1PH6Yu/IPhZiNh4tyIqDOtlYfzp577A+OUU+q5PPRFRIsqheOxt"
111     "mNlHx4Uzd4U3ITfmogJazjqwYO2viBZY4jUQmyZs75eH/jiUFHWRsha3AdnW5LWa"
112     "G3PFnYbW8urH0NSJG/W+/9DA+Y7Aa0cs4TPpuBGZ0NU1W94OoCMo4lkO6H/y6Leu"
113     "3vjZD3y9kZk7mre9XHwkI8MdK5s=";
114     
115     NSString *pemEncodedPrivateKey = @""
116     "MIIJQgIBADANBgkqhkiG9w0BAQEFAASCCSwwggkoAgEAAoICAQDeuE3hI+DxVj8+"
117     "0YM0pjdP0khKBvLxgYwnHS875hkVmbcbd+xNSzK8PIQfCqnlpsJlEH8HLU/BaQ2t"
118     "/Gf9c/w/Tfgk+UTqKtWK4BhCupSHqTtlHwKk4zkYRxFmABQkYZDA2U6QTBoecwZz"
119     "xKwe6uAM+HlcGmBsPK13sEm4HU2gj8omaFxqOQC5Xe9qtL648uh88c2p75wvyE2e"
120     "NlDAdZqs/jmSzh4Fw4Pu3CrG4edWb8VYSuljxazmzUys9OFR0NqGMQx/5h8Mn0ov"
121     "zLfTjJLwnx94LFh9wfgG+Au0mYE0cmCPd40P8QxmMmDPYWwGOsmTyQoPfVzD7han"
122     "xQXiyRZcJ/jr6Y04enXc95IJkvIXhkAdrNRWScM9xzZg2tZVXWLqfdPXmQjRPlnt"
123     "uy12YAu5KQTZy4lJU07qBH5mxVFOd65DHBZNTQw0SEA8U5fYGEY/l6KyARTOSfEw"
124     "l10UOf1bGSW7UmGCTMBID/7cCaWsVkw+E6GMS6XTOOA1HlEGXUbwfP3YsDHlQQMG"
125     "10uDvUj4jwDNA6F3ou0F6QQLEDXqlJqmmDm9xb1n/w3pmWwednYXO0DScI5+WSg7"
126     "2J6M6ly4U3gRVgPEi3By0rDUsJ40P+ziSahFGddtfuT07pwjVRRYzn3kay7m09rf"
127     "czkBkUJq/aA5W5h2OmlLQc5+jmG/pwIDAQABAoICAGra/Cp/f0Xqvk9ST+Prt2/p"
128     "kNtLeDXclLSTcP0JCZHufQaFw+7VnFLpqe4GvLq9Bllcz8VOvQwrbe/CwNW+VxC8"
129     "RMjge2rqACgwGhOx1t87l46NkUQw7Ey0lCle8kr+MGgGGoZqrMFdKIRUoMv4nmQ6"
130     "tmc1FHv5pLRe9Q+Lp5nYQwGoYmZoUOueoOaOL08m49pGXQkiN8pJDMxSfO3Jvtsu"
131     "4cqIb6kOQ/dO1Is1CTvURld1IYLH7YuShi4ZEx2g2ac2Uyvt6YmxxvMmAjBSKpGd"
132     "loiepho3/NrDGUKdv3q9QYyzrA8w9GT32LDGqgBXJi1scBI8cExkp6P4iDllhv7s"
133     "vZsspvobRJa3O1zk863LHXa24JCnyuzimqezZ2Olh7l4olHoYD6UFC9jfd4KcHRg"
134     "1c4syqt/n8AK/1s1eBfS9dzb5Cfjt9MtKYslxvLzq1WwOINwz8rIYuRi0PcLm9hs"
135     "l+U0u/zB37eMgv6+iwDXk1fSjbuYsE/bETWYknKGNFFL5JSiKV7WCpmgNTTrrE4K"
136     "S8E6hR9uPOAaow7vPCCt4xLX/48l2EI6Zeq6qOpq1lJ2qcy8r4tyuQgNRLQMkZg1"
137     "AxQl6vnQ8Cu4iu+NIhef0y9Z7qkfNvZeCj5GlFB9c2YjV8Y2mdWfJB4qWK3Z/+MJ"
138     "QOTCKRz7/LxLNBUepRjJAoIBAQD3ZsV5tWU9ZSKcVJ9DC7TZk0P+lhcisZr0nL0t"
139     "PQuQO+pHvPI1MqRnNskHJhyPnqVCi+dp89tK/It590ULl8os6UC1FhytBPoT1YPd"
140     "WGWep2pOc7bVpi4ip31y+ImfgeZyJtMATdme3kBPAOe5NGE9Gig/l5nqLyb02sd1"
141     "QW7O0GdqLx3DpLw4SLlhMf6aE0uGRS8sfB085e4DGn54O2wEVuSZqZl5NNEf35Rz"
142     "Xgim3h+RWF1ZFSQzjB/smN0Zh+v3Iz7vEJ1h0ywV6o+GzvHkP9HE6gLIhtyV8OEw"
143     "vlyYk1Ga7pUVGRh8o8OMe6RR9DQi7JqC4eI7GckmBzaqzJcDAoIBAQDmde6ATew3"
144     "H9bQK6xnbMIncz/COpIISdlcFb23AHGEb4b4VhJFBNwxrNL6tHKSFLeYZFLhTdhx"
145     "PfXyULHNf5ozdEkl0WrleroDdogbCyWg5uJp9/Q68sbwbGr8CAlO7ZHYTrjuQf1K"
146     "AS9pCm77KP3k2d3UlG+pelDjXLoBziXq0NjxJpMz45vrIx8rSWzFNjMGjXT3fXaS"
147     "962k/0AXei5/bfuhBxlm7Pni0bQJIWFkeaUuGlrOaHDRxUiX1r9IZS9wv5lk1Ptg"
148     "idpbcWyw18cFGTvjdKhRbZH8EsbmzmNNsCGdgCMqFkKYsW16QKoCj/NAovI3n0qn"
149     "6VoRa0sGmTGNAoIBACl/mqZEsBuxSDHy29gSMZ7BXglpQa43HmfjlrPs5nCmLDEm"
150     "V3Zm7T7G6MeDNA0/LjdQYlvaZLFaVUb7HCDKsEYCRjFZ6St4hz4mdXz+Y+VN7b4F"
151     "GOkTe++iKp/LYsJXtsD1FDWb2WIVo7Hc1AGz8I+gQJoSIuYuTJmLzSM0+5JDUOV1"
152     "y8dSbaP/RuEv0qYjkGqQVk5e70SUyOzKV+ZxCThdHvFLiovTOTTgevUzE75xydfG"
153     "e7oCmtTurzgvl/69Vu5Ygij1n4CWPHHcq4CQW/DOZ7BhFGBwhrW79voHJF8PbwPO"
154     "+0DTudDGY3nAD5sTnF8zUuObYihJtfzj/t59fOMCggEBAIYuuBUASb62zQ4bv5/g"
155     "VRM/KSpfi9NDnEjfZ7x7h5zCiuVgx/ZjpAlQRO8vzV18roEOOKtx9cnJd8AEd+Hc"
156     "n93BoS1hx0mhsVh+1TRZwyjyBXYJpqwD2wz1Mz1XOIQ6EqbM/yPKTD2gfwg7yO53"
157     "qYxrxZsWagVVcG9Q+ARBERatTwLpoN+fcJLxuh4r/Ca/LepsxmOrKzTa/MGK1LhW"
158     "rWgIk2/ogEPLSptj2d1PEDO+GAzFz4VKjhW1NlUh9fGi6IJPLHLnBw3odbi0S8KT"
159     "gA9Z5+LBc5clotAP5rtQA8Wh/ZCEoPTKTTA2bjW2HMatJcbGmR0FpCQr3AM0Y1SO"
160     "MakCggEALru6QZ6YUwJJG45H1eq/rPdDY8tqqjJVViKoBVvzKj/XfJZYEVQiIw5p"
161     "uoGhDoyFuFUeIh/d1Jc2Iruy2WjoOkiQYtIugDHHxRrkLdQcjPhlCTCE/mmySJt+"
162     "bkUbiHIbQ8dJ5yj8SKr0bHzqEtOy9/JeRjkYGHC6bVWpq5FA2MBhf4dNjJ4UDlnT"
163     "vuePcTjr7nnfY1sztvfVl9D8dmgT+TBnOOV6yWj1gm5bS1DxQSLgNmtKxJ8tAh2u"
164     "dEObvcpShP22ItOVjSampRuAuRG26ZemEbGCI3J6Mqx3y6m+6HwultsgtdzDgrFe"
165     "qJfU8bbdbu2pi47Y4FdJK0HLffl5Rw==";
166     
167     NSData *derEncodedCertificate = [[[NSData alloc] initWithBase64EncodedString:pemEncodedCertificate options:0] autorelease];
168     RetainPtr<SecCertificateRef> certificate = adoptCF(SecCertificateCreateWithData(nullptr, (__bridge CFDataRef)derEncodedCertificate));
169     
170     NSData *derEncodedPrivateKey = [[[NSData alloc] initWithBase64EncodedString:pemEncodedPrivateKey options:0] autorelease];
171     NSDictionary* options = @{
172         (id)kSecAttrKeyType: (id)kSecAttrKeyTypeRSA,
173         (id)kSecAttrKeyClass: (id)kSecAttrKeyClassPrivate,
174         (id)kSecAttrKeySizeInBits: @4096,
175     };
176     const NSUInteger pemEncodedPrivateKeyHeaderLength = 26;
177     CFErrorRef error = nullptr;
178     RetainPtr<SecKeyRef> privateKey = adoptCF(SecKeyCreateWithData((__bridge CFDataRef)[derEncodedPrivateKey subdataWithRange:NSMakeRange(pemEncodedPrivateKeyHeaderLength, derEncodedPrivateKey.length - pemEncodedPrivateKeyHeaderLength)], (__bridge CFDictionaryRef)options, &error));
179     EXPECT_NULL(error);
180     EXPECT_NOT_NULL(privateKey.get());
181
182     SecKeychainRef keychainRef = nullptr;
183     const char* keychainPassword = "testpassword";
184     NSString *keychainPath = [NSTemporaryDirectory() stringByAppendingPathComponent:@"TestKeychain"];
185     OSStatus status = SecKeychainCreate([keychainPath UTF8String], strlen(keychainPassword), keychainPassword, NO, nullptr, &keychainRef);
186     EXPECT_TRUE(status == errSecSuccess);
187     RetainPtr<SecKeychainRef> keychain = adoptCF(keychainRef);
188     EXPECT_NOT_NULL(keychain);
189
190     SecExternalItemType type = kSecItemTypePrivateKey;
191     status = SecItemImport((__bridge CFDataRef)derEncodedPrivateKey, nullptr, nullptr, &type, 0, nullptr, keychain.get(), nullptr);
192     EXPECT_TRUE(status == errSecSuccess);
193
194     SecIdentityRef identityRef = nullptr;
195     status = SecIdentityCreateWithCertificate(keychain.get(), certificate.get(), &identityRef);
196     EXPECT_NOT_NULL(identityRef);
197     EXPECT_TRUE(status == errSecSuccess);
198     RetainPtr<SecIdentityRef> identity = adoptCF(identityRef);
199     
200     return {
201         [NSURLCredential credentialWithIdentity:identity.get() certificates:@[(id)certificate.get()] persistence:NSURLCredentialPersistenceNone],
202         keychainPath
203     };
204 }
205
206 static RetainPtr<NSString> keychainPath;
207
208 @interface ChallengeDelegate : NSObject <WKNavigationDelegate>
209 @end
210
211 @implementation ChallengeDelegate
212
213 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
214 {
215     navigationFinished = true;
216 }
217
218 - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
219 {
220     NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
221     EXPECT_NULL(challenge.proposedCredential);
222     EXPECT_EQ(challenge.previousFailureCount, 0);
223     EXPECT_TRUE([protectionSpace.realm isEqualToString:@"testrealm"]);
224     EXPECT_FALSE(protectionSpace.receivesCredentialSecurely);
225     EXPECT_FALSE(protectionSpace.isProxy);
226     EXPECT_TRUE([protectionSpace.host isEqualToString:@"127.0.0.1"]);
227     EXPECT_NULL(protectionSpace.proxyType);
228     EXPECT_TRUE([protectionSpace.protocol isEqualToString:NSURLProtectionSpaceHTTP]);
229     EXPECT_TRUE([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic]);
230     EXPECT_EQ([(NSHTTPURLResponse *)challenge.failureResponse statusCode], 401);
231
232     auto pair = credentialWithIdentityAndKeychainPath();
233     completionHandler(NSURLSessionAuthChallengeUseCredential, pair.first.get());
234     keychainPath = WTFMove(pair.second);
235 }
236
237 @end
238
239 namespace TestWebKitAPI {
240
241 TEST(Challenge, SecIdentity)
242 {
243     TCPServer server(respondWithChallengeThenOK);
244
245     auto webView = adoptNS([WKWebView new]);
246     auto delegate = adoptNS([ChallengeDelegate new]);
247     [webView setNavigationDelegate:delegate.get()];
248     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/", server.port()]]]];
249
250     Util::run(&navigationFinished);
251     
252     EXPECT_NOT_NULL(keychainPath.get());
253     NSError *error = nil;
254     [[NSFileManager defaultManager] removeItemAtPath:keychainPath.get() error:&error];
255     EXPECT_NULL(error);
256 }
257
258 } // namespace TestWebKitAPI
259
260 #endif
261
262 static bool receivedSecondChallenge;
263 static RetainPtr<NSURLCredential> persistentCredential;
264
265 @interface ProposedCredentialDelegate : NSObject <WKNavigationDelegate>
266 @end
267
268 @implementation ProposedCredentialDelegate
269
270 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
271 {
272     navigationFinished = true;
273 }
274
275 - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
276 {
277     static bool firstChallenge = true;
278     if (firstChallenge) {
279         firstChallenge = false;
280         persistentCredential = adoptNS([[NSURLCredential alloc] initWithUser:@"testuser" password:@"testpassword" persistence:NSURLCredentialPersistencePermanent]);
281         return completionHandler(NSURLSessionAuthChallengeUseCredential, persistentCredential.get());
282         
283     }
284     receivedSecondChallenge = true;
285     return completionHandler(NSURLSessionAuthChallengeUseCredential, nil);
286 }
287
288 @end
289
290 TEST(Challenge, BasicProposedCredential)
291 {
292     using namespace TestWebKitAPI;
293     TCPServer server(respondWithChallengeThenOK, 2);
294     auto configuration = retainPtr([WKWebViewConfiguration _test_configurationWithTestPlugInClassName:@"BasicProposedCredentialPlugIn"]);
295     auto webView = adoptNS([[WKWebView alloc] initWithFrame:CGRectZero configuration:configuration.get()]);
296     auto delegate = adoptNS([ProposedCredentialDelegate new]);
297     [webView setNavigationDelegate:delegate.get()];
298     RetainPtr<NSURLRequest> request = [NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://127.0.0.1:%d/", server.port()]]];
299     [webView loadRequest:request.get()];
300     Util::run(&navigationFinished);
301     navigationFinished = false;
302     [webView loadRequest:request.get()];
303     Util::run(&navigationFinished);
304     EXPECT_TRUE(receivedSecondChallenge);
305     
306     __block bool removedCredential = false;
307     WKWebsiteDataStore *websiteDataStore = [webView configuration].websiteDataStore;
308     [websiteDataStore fetchDataRecordsOfTypes:[NSSet setWithObject:_WKWebsiteDataTypeCredentials] completionHandler:^(NSArray<WKWebsiteDataRecord *> *dataRecords) {
309         [websiteDataStore removeDataOfTypes:[NSSet setWithObject:_WKWebsiteDataTypeCredentials] forDataRecords:dataRecords completionHandler:^(void) {
310             removedCredential = true;
311         }];
312     }];
313     Util::run(&removedCredential);
314 }
315
316 static void verifyCertificateAndPublicKey(SecTrustRef trust)
317 {
318     EXPECT_NOT_NULL(trust);
319
320     auto compareData = [] (const RetainPtr<CFDataRef>& data, const Vector<uint8_t>& expected) {
321         size_t length = CFDataGetLength(data.get());
322         EXPECT_EQ(length, expected.size());
323         const UInt8* bytes = CFDataGetBytePtr(data.get());
324         for (size_t i = 0; i < length; ++i)
325             EXPECT_EQ(expected[i], bytes[i]);
326     };
327
328     auto publicKey = adoptCF(SecKeyCopyExternalRepresentation(adoptCF(SecTrustCopyPublicKey(trust)).get(), nullptr));
329     compareData(publicKey, {
330         0x30, 0x81, 0x89, 0x02, 0x81, 0x81, 0x00, 0xd8, 0x2b, 0xc8, 0xa6, 0x32, 0xe4, 0x62, 0xff, 0x4d,
331         0xf3, 0xd0, 0xad, 0x59, 0x8b, 0x45, 0xa7, 0xbd, 0xf1, 0x47, 0xbf, 0x09, 0x58, 0x7b, 0x22, 0xbd,
332         0x35, 0xae, 0x97, 0x25, 0x86, 0x94, 0xa0, 0x80, 0xc0, 0xb4, 0x1f, 0x76, 0x91, 0x67, 0x46, 0x31,
333         0xd0, 0x10, 0x84, 0xb7, 0x22, 0x1e, 0x70, 0x23, 0x91, 0x72, 0xc8, 0xe9, 0x6d, 0x79, 0x3a, 0x85,
334         0x77, 0x80, 0x0f, 0xc4, 0x95, 0x16, 0x75, 0xc5, 0x4a, 0x71, 0x4c, 0xc8, 0x63, 0x3f, 0xa3, 0xf2,
335         0x63, 0x9c, 0x2a, 0x4f, 0x9a, 0xfa, 0xcb, 0xc1, 0x71, 0x6e, 0x28, 0x85, 0x28, 0xa0, 0x27, 0x1e,
336         0x65, 0x1c, 0xae, 0x07, 0xd5, 0x5b, 0x6f, 0x2d, 0x43, 0xed, 0x2b, 0x90, 0xb1, 0x8c, 0xaf, 0x24,
337         0x6d, 0xae, 0xe9, 0x17, 0x3a, 0x05, 0xc1, 0xbf, 0xb8, 0x1c, 0xae, 0x65, 0x3b, 0x1b, 0x58, 0xc2,
338         0xd9, 0xae, 0xd6, 0xaa, 0x67, 0x88, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01
339     });
340     
341     EXPECT_EQ(1, SecTrustGetCertificateCount(trust));
342     auto certificate = adoptCF(SecCertificateCopyData(SecTrustGetCertificateAtIndex(trust, 0)));
343     compareData(certificate, {
344         0x30, 0x82, 0x02, 0x58, 0x30, 0x82, 0x01, 0xc1, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x09, 0x00,
345         0xfb, 0xb0, 0x4c, 0x2e, 0xab, 0x10, 0x9b, 0x0c, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
346         0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
347         0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c,
348         0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06,
349         0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57,
350         0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30, 0x1e,
351         0x17, 0x0d, 0x31, 0x34, 0x30, 0x34, 0x32, 0x33, 0x32, 0x30, 0x35, 0x30, 0x34, 0x30, 0x5a, 0x17,
352         0x0d, 0x31, 0x37, 0x30, 0x34, 0x32, 0x32, 0x32, 0x30, 0x35, 0x30, 0x34, 0x30, 0x5a, 0x30, 0x45,
353         0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13, 0x30,
354         0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61,
355         0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, 0x6e, 0x74,
356         0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, 0x74,
357         0x79, 0x20, 0x4c, 0x74, 0x64, 0x30, 0x81, 0x9f, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
358         0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x81, 0x8d, 0x00, 0x30, 0x81, 0x89, 0x02, 0x81,
359         0x81, 0x00, 0xd8, 0x2b, 0xc8, 0xa6, 0x32, 0xe4, 0x62, 0xff, 0x4d, 0xf3, 0xd0, 0xad, 0x59, 0x8b,
360         0x45, 0xa7, 0xbd, 0xf1, 0x47, 0xbf, 0x09, 0x58, 0x7b, 0x22, 0xbd, 0x35, 0xae, 0x97, 0x25, 0x86,
361         0x94, 0xa0, 0x80, 0xc0, 0xb4, 0x1f, 0x76, 0x91, 0x67, 0x46, 0x31, 0xd0, 0x10, 0x84, 0xb7, 0x22,
362         0x1e, 0x70, 0x23, 0x91, 0x72, 0xc8, 0xe9, 0x6d, 0x79, 0x3a, 0x85, 0x77, 0x80, 0x0f, 0xc4, 0x95,
363         0x16, 0x75, 0xc5, 0x4a, 0x71, 0x4c, 0xc8, 0x63, 0x3f, 0xa3, 0xf2, 0x63, 0x9c, 0x2a, 0x4f, 0x9a,
364         0xfa, 0xcb, 0xc1, 0x71, 0x6e, 0x28, 0x85, 0x28, 0xa0, 0x27, 0x1e, 0x65, 0x1c, 0xae, 0x07, 0xd5,
365         0x5b, 0x6f, 0x2d, 0x43, 0xed, 0x2b, 0x90, 0xb1, 0x8c, 0xaf, 0x24, 0x6d, 0xae, 0xe9, 0x17, 0x3a,
366         0x05, 0xc1, 0xbf, 0xb8, 0x1c, 0xae, 0x65, 0x3b, 0x1b, 0x58, 0xc2, 0xd9, 0xae, 0xd6, 0xaa, 0x67,
367         0x88, 0xf1, 0x02, 0x03, 0x01, 0x00, 0x01, 0xa3, 0x50, 0x30, 0x4e, 0x30, 0x1d, 0x06, 0x03, 0x55,
368         0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0x8b, 0x75, 0xd5, 0xac, 0xcb, 0x08, 0xbe, 0x0e, 0x1f, 0x65,
369         0xb7, 0xfa, 0x56, 0xbe, 0x6c, 0xa7, 0x75, 0xda, 0x85, 0xaf, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d,
370         0x23, 0x04, 0x18, 0x30, 0x16, 0x80, 0x14, 0x8b, 0x75, 0xd5, 0xac, 0xcb, 0x08, 0xbe, 0x0e, 0x1f,
371         0x65, 0xb7, 0xfa, 0x56, 0xbe, 0x6c, 0xa7, 0x75, 0xda, 0x85, 0xaf, 0x30, 0x0c, 0x06, 0x03, 0x55,
372         0x1d, 0x13, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48,
373         0x86, 0xf7, 0x0d, 0x01, 0x01, 0x05, 0x05, 0x00, 0x03, 0x81, 0x81, 0x00, 0x3b, 0xe8, 0x78, 0x6d,
374         0x95, 0xd6, 0x3d, 0x6a, 0xf7, 0x13, 0x19, 0x2c, 0x1b, 0xc2, 0x88, 0xae, 0x22, 0xab, 0xf4, 0x8d,
375         0x32, 0xf5, 0x7c, 0x71, 0x67, 0xcf, 0x2d, 0xd1, 0x1c, 0xc2, 0xc3, 0x87, 0xe2, 0xe9, 0xbe, 0x89,
376         0x5c, 0xe4, 0x34, 0xab, 0x48, 0x91, 0xc2, 0x3f, 0x95, 0xae, 0x2b, 0x47, 0x9e, 0x25, 0x78, 0x6b,
377         0x4f, 0x9a, 0x10, 0xa4, 0x72, 0xfd, 0xcf, 0xf7, 0x02, 0x0c, 0xb0, 0x0a, 0x08, 0xa4, 0x5a, 0xe2,
378         0xe5, 0x74, 0x7e, 0x11, 0x1d, 0x39, 0x60, 0x6a, 0xc9, 0x1f, 0x69, 0xf3, 0x2e, 0x63, 0x26, 0xdc,
379         0x9e, 0xef, 0x6b, 0x7a, 0x0a, 0xe1, 0x54, 0x57, 0x98, 0xaa, 0x72, 0x91, 0x78, 0x04, 0x7e, 0x1f,
380         0x8f, 0x65, 0x4d, 0x1f, 0x0b, 0x12, 0xac, 0x9c, 0x24, 0x0f, 0x84, 0x14, 0x1a, 0x55, 0x2d, 0x1f,
381         0xbb, 0xf0, 0x9d, 0x09, 0xb2, 0x08, 0x5c, 0x59, 0x32, 0x65, 0x80, 0x26
382     });
383 }
384
385 @interface ServerTrustDelegate : NSObject <WKNavigationDelegate>
386 @end
387
388 @implementation ServerTrustDelegate
389
390 - (void)webView:(WKWebView *)webView didFinishNavigation:(null_unspecified WKNavigation *)navigation
391 {
392     navigationFinished = true;
393 }
394
395 - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler
396 {
397     SecTrustRef trust = challenge.protectionSpace.serverTrust;
398     verifyCertificateAndPublicKey(trust);
399     completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:trust]);
400 }
401
402 @end
403
404 namespace TestWebKitAPI {
405
406 TEST(WebKit, ServerTrust)
407 {
408     TCPServer server(TCPServer::Protocol::HTTPS, [] (SSL* ssl) {
409         char requestBuffer[1000];
410         auto readResult = SSL_read(ssl, requestBuffer, sizeof(requestBuffer));
411         ASSERT_UNUSED(readResult, readResult > 0);
412
413         const char* reply = ""
414         "HTTP/1.1 200 OK\r\n"
415         "Content-Length: 13\r\n\r\n"
416         "Hello, World!";
417         auto writeResult = SSL_write(ssl, reply, strlen(reply));
418         ASSERT_UNUSED(writeResult, writeResult == static_cast<int>(strlen(reply)));
419     });
420
421     auto webView = adoptNS([WKWebView new]);
422     auto delegate = adoptNS([ServerTrustDelegate new]);
423     [webView setNavigationDelegate:delegate.get()];
424
425     [webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"https://localhost:%d/", server.port()]]]];
426     TestWebKitAPI::Util::run(&navigationFinished);
427
428     verifyCertificateAndPublicKey([webView serverTrust]);
429 }
430
431 } // namespace TestWebKitAPI