AuthURLSessionTaskDelegate.m 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160
  1. //
  2. // AuthURLSessionTaskDelegate.m
  3. // TunneledWebRequest
  4. //
  5. /*
  6. Licensed under Creative Commons Zero (CC0).
  7. https://creativecommons.org/publicdomain/zero/1.0/
  8. */
  9. // NOTE: this file is shared by TunneledWebRequest and TunneledWebView
  10. #import "AuthURLSessionTaskDelegate.h"
  11. #import "OCSPCache.h"
  12. #import "OCSPURLEncode.h"
  13. @implementation AuthURLSessionTaskDelegate {
  14. OCSPCache *ocspCache;
  15. }
  16. - (id)initWithLogger:(void (^)(NSString*))logger
  17. andLocalHTTPProxyPort:(NSInteger)port {
  18. self = [super init];
  19. if (self) {
  20. self.logger = logger;
  21. self.localHTTPProxyPort = port;
  22. self->ocspCache = [[OCSPCache alloc] initWithLogger:^(NSString * _Nonnull logLine) {
  23. [self logWithFormat:@"[OCSPCache] %@", logLine];
  24. }];
  25. }
  26. return self;
  27. }
  28. - (void)logWithFormat:(NSString *)format, ... NS_FORMAT_FUNCTION(1, 2) {
  29. if (self.logger != nil) {
  30. va_list arguments;
  31. va_start(arguments, format);
  32. NSString *message = [[NSString alloc] initWithFormat:format arguments:arguments];
  33. va_end(arguments);
  34. self.logger(message);
  35. }
  36. }
  37. - (void)URLSession:(NSURLSession *)session
  38. task:(NSURLSessionTask *)task
  39. didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
  40. completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
  41. {
  42. #pragma unused(session)
  43. #pragma unused(task)
  44. assert(challenge != nil);
  45. assert(completionHandler != nil);
  46. // Resolve NSURLAuthenticationMethodServerTrust ourselves
  47. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
  48. [self logWithFormat:@"Got SSL certificate for %@, mainDocumentURL: %@, URL: %@",
  49. challenge.protectionSpace.host,
  50. [task.currentRequest mainDocumentURL],
  51. [task.currentRequest URL]];
  52. SecTrustRef trust = challenge.protectionSpace.serverTrust;
  53. if (trust == nil) {
  54. assert(NO);
  55. }
  56. SecPolicyRef policy = SecPolicyCreateRevocation(kSecRevocationOCSPMethod |
  57. kSecRevocationRequirePositiveResponse |
  58. kSecRevocationNetworkAccessDisabled);
  59. SecTrustSetPolicies(trust, policy);
  60. CFRelease(policy);
  61. // Check if there is a pinned or cached OCSP response
  62. SecTrustResultType trustResultType;
  63. SecTrustEvaluate(trust, &trustResultType);
  64. if ( trustResultType == kSecTrustResultProceed
  65. || trustResultType == kSecTrustResultUnspecified) {
  66. [self logWithFormat:@"Pinned or cached OCSP response found by the system"];
  67. NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
  68. assert(credential != nil);
  69. completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  70. return;
  71. }
  72. // No pinned OCSP response, try fetching one
  73. [self logWithFormat:@"Fetching OCSP response through OCSPCache"];
  74. NSURL* (^modifyOCSPURL)(NSURL *url) = ^NSURL*(NSURL *url) {
  75. return [self modifyOCSPURL:url];
  76. };
  77. [ocspCache lookup:trust
  78. andTimeout:0
  79. modifyOCSPURL:modifyOCSPURL
  80. completion:
  81. ^(OCSPCacheLookupResult * _Nonnull result) {
  82. assert(result.response != nil);
  83. assert(result.err == nil);
  84. if (result.cached) {
  85. [self logWithFormat:@"Got cached OCSP response from OCSPCache"];
  86. } else {
  87. [self logWithFormat:@"Fetched OCSP response from remote"];
  88. }
  89. CFDataRef d = (__bridge CFDataRef)result.response.data;
  90. SecTrustSetOCSPResponse(trust, d);
  91. SecTrustResultType trustResultType;
  92. SecTrustEvaluate(trust, &trustResultType);
  93. if ( trustResultType == kSecTrustResultProceed
  94. || trustResultType == kSecTrustResultUnspecified) {
  95. NSURLCredential *credential = [NSURLCredential credentialForTrust:trust];
  96. assert(credential != nil);
  97. completionHandler(NSURLSessionAuthChallengeUseCredential, credential);
  98. return;
  99. }
  100. // Reject the protection space.
  101. // Do not use NSURLSessionAuthChallengePerformDefaultHandling because it can trigger
  102. // plaintext OCSP requests.
  103. completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
  104. return;
  105. }];
  106. return;
  107. }
  108. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
  109. }
  110. // Modify the OCSP URLs so they use the local HTTP proxy
  111. - (nonnull NSURL *)modifyOCSPURL:(nonnull NSURL *)url {
  112. // The target URL must be encoded, so as to be valid within a query parameter.
  113. NSString *encodedTargetUrl = [URLEncode encode:url.absoluteString];
  114. NSNumber *httpProxyPort = [NSNumber numberWithInt:(int)self.localHTTPProxyPort];
  115. NSString *proxiedURLString = [NSString stringWithFormat:@"http://127.0.0.1:%@/tunneled/%@",
  116. httpProxyPort,
  117. encodedTargetUrl];
  118. NSURL *proxiedURL = [NSURL URLWithString:proxiedURLString];
  119. [self logWithFormat:@"[OCSPCache] updated OCSP URL %@ to %@", url, proxiedURL];
  120. return proxiedURL;
  121. }
  122. @end