TSKNSURLConnectionDelegateProxy.m 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210
  1. /*
  2. TSKNSURLConnectionDelegateProxy.h
  3. TrustKit
  4. Copyright 2015 The TrustKit Project Authors
  5. Licensed under the MIT license, see associated LICENSE file for terms.
  6. See AUTHORS file for the list of project authors.
  7. */
  8. #import "TSKNSURLConnectionDelegateProxy.h"
  9. #import "../TrustKit.h"
  10. #import "../TSKLog.h"
  11. #import "../TSKTrustDecision.h"
  12. #import "../TSKPinningValidator.h"
  13. #import "../Dependencies/RSSwizzle/RSSwizzle.h"
  14. typedef void (^AsyncCompletionHandler)(NSURLResponse *response, NSData *data, NSError *connectionError);
  15. @interface TSKNSURLConnectionDelegateProxy ()
  16. @property (nonatomic) id<NSURLConnectionDelegate> originalDelegate; // The NSURLConnectionDelegate we're going to proxy
  17. @property (nonatomic) TrustKit *trustKit;
  18. @end
  19. @implementation TSKNSURLConnectionDelegateProxy
  20. #pragma mark Public methods
  21. + (void)swizzleNSURLConnectionConstructors:(TrustKit *)trustKit
  22. {
  23. #pragma clang diagnostic push
  24. #pragma clang diagnostic ignored "-Wshadow"
  25. // - initWithRequest:delegate:
  26. RSSwizzleInstanceMethod(NSClassFromString(@"NSURLConnection"),
  27. @selector(initWithRequest:delegate:),
  28. RSSWReturnType(NSURLConnection*),
  29. RSSWArguments(NSURLRequest *request, id<NSURLConnectionDelegate> delegate),
  30. RSSWReplacement(
  31. {
  32. NSURLConnection *connection;
  33. if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"])
  34. {
  35. // Don't proxy ourselves
  36. connection = RSSWCallOriginal(request, delegate);
  37. }
  38. else
  39. {
  40. // Replace the delegate with our own so we can intercept and handle authentication challenges
  41. TSKNSURLConnectionDelegateProxy *swizzledDelegate = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:trustKit
  42. connectionDelegate:delegate];
  43. connection = RSSWCallOriginal(request, swizzledDelegate);
  44. }
  45. return connection;
  46. }), RSSwizzleModeAlways, NULL);
  47. // - initWithRequest:delegate:startImmediately:
  48. RSSwizzleInstanceMethod(NSClassFromString(@"NSURLConnection"),
  49. @selector(initWithRequest:delegate:startImmediately:),
  50. RSSWReturnType(NSURLConnection*),
  51. RSSWArguments(NSURLRequest *request, id<NSURLConnectionDelegate> delegate, BOOL startImmediately),
  52. RSSWReplacement(
  53. {
  54. NSURLConnection *connection;
  55. if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"])
  56. {
  57. // Don't proxy ourselves
  58. connection = RSSWCallOriginal(request, delegate, startImmediately);
  59. }
  60. else
  61. {
  62. // Replace the delegate with our own so we can intercept and handle authentication challenges
  63. TSKNSURLConnectionDelegateProxy *swizzledDelegate = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:trustKit
  64. connectionDelegate:delegate];
  65. connection = RSSWCallOriginal(request, swizzledDelegate, startImmediately);
  66. }
  67. return connection;
  68. }), RSSwizzleModeAlways, NULL);
  69. // Not hooking + connectionWithRequest:delegate: as it ends up calling initWithRequest:delegate:
  70. // Log a warning for methods that do not have a delegate (ie. we can't protect these connections)
  71. // + sendAsynchronousRequest:queue:completionHandler:
  72. RSSwizzleClassMethod(NSClassFromString(@"NSURLConnection"),
  73. @selector(sendAsynchronousRequest:queue:completionHandler:),
  74. RSSWReturnType(void),
  75. RSSWArguments(NSURLRequest *request, NSOperationQueue *queue, AsyncCompletionHandler handler),
  76. RSSWReplacement(
  77. {
  78. // Just display a warning
  79. TSKLog(@"WARNING: +sendAsynchronousRequest:queue:completionHandler: was called to connect to %@. This method does not expose a delegate argument for handling authentication challenges; TrustKit cannot enforce SSL pinning for these connections", [[request URL]host]);
  80. RSSWCallOriginal(request, queue, handler);
  81. }));
  82. // + sendSynchronousRequest:returningResponse:error:
  83. RSSwizzleClassMethod(NSClassFromString(@"NSURLConnection"),
  84. @selector(sendSynchronousRequest:returningResponse:error:),
  85. RSSWReturnType(NSData *),
  86. RSSWArguments(NSURLRequest *request, NSURLResponse * _Nullable *response, NSError * _Nullable *error),
  87. RSSWReplacement(
  88. {
  89. // Just display a warning
  90. TSKLog(@"WARNING: +sendSynchronousRequest:returningResponse:error: was called to connect to %@. This method does not expose a delegate argument for handling authentication challenges; TrustKit cannot enforce SSL pinning for these connections", [[request URL]host]);
  91. NSData *data = RSSWCallOriginal(request, response, error);
  92. return data;
  93. }));
  94. #pragma clang diagnostic pop
  95. }
  96. #pragma mark Instance Constructors
  97. - (instancetype)initWithTrustKit:(TrustKit *)trustKit connectionDelegate:(id<NSURLConnectionDelegate>)delegate
  98. {
  99. self = [super init];
  100. if (self)
  101. {
  102. _originalDelegate = delegate;
  103. _trustKit = trustKit;
  104. }
  105. TSKLog(@"Proxy-ing NSURLConnectionDelegate: %@", NSStringFromClass([delegate class]));
  106. return self;
  107. }
  108. #pragma mark NSObject overrides
  109. - (BOOL)respondsToSelector:(SEL)aSelector
  110. {
  111. if (aSelector == @selector(connection:willSendRequestForAuthenticationChallenge:))
  112. {
  113. // The delegate proxy should always receive authentication challenges
  114. // This will disrupt the delegate flow for (old) delegates that use the connection:didReceiveAuthenticationChallenge: method
  115. return YES;
  116. }
  117. else
  118. {
  119. // The delegate proxy should mirror the original delegate's methods so that it doesn't change the app flow
  120. return [_originalDelegate respondsToSelector:aSelector];
  121. }
  122. }
  123. - (id)forwardingTargetForSelector:(SEL)sel
  124. {
  125. return _originalDelegate;
  126. }
  127. #pragma mark Instance methods
  128. #pragma GCC diagnostic push
  129. #pragma GCC diagnostic ignored "-Wdeprecated-declarations" // NSURLConnection is deprecated in iOS 9
  130. - (BOOL)forwardToOriginalDelegateAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge forConnection:(NSURLConnection *)connection
  131. {
  132. // Can the original delegate handle this challenge ?
  133. if ([_originalDelegate respondsToSelector:@selector(connection:willSendRequestForAuthenticationChallenge:)])
  134. {
  135. // Yes - forward the challenge to the original delegate
  136. [_originalDelegate connection:connection willSendRequestForAuthenticationChallenge:challenge];
  137. return YES;
  138. }
  139. if ([_originalDelegate respondsToSelector:@selector(connection:canAuthenticateAgainstProtectionSpace:)]
  140. && [_originalDelegate connection:connection canAuthenticateAgainstProtectionSpace:challenge.protectionSpace])
  141. {
  142. // Yes - forward the challenge to the original delegate
  143. [_originalDelegate connection:connection didReceiveAuthenticationChallenge:challenge];
  144. return YES;
  145. }
  146. return NO;
  147. }
  148. - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
  149. {
  150. // For SSL pinning we only care about server authentication
  151. if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
  152. {
  153. SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
  154. NSString *serverHostname = challenge.protectionSpace.host;
  155. // Check the trust object against the pinning policy
  156. TSKTrustDecision trustDecision = [self.trustKit.pinningValidator evaluateTrust:serverTrust
  157. forHostname:serverHostname];
  158. if (trustDecision == TSKTrustDecisionShouldBlockConnection)
  159. {
  160. // Pinning validation failed - block the connection
  161. [challenge.sender cancelAuthenticationChallenge:challenge];
  162. return;
  163. }
  164. }
  165. // Forward all challenges (including client auth challenges) to the original delegate
  166. // We will also get here if the pinning validation succeeded or the domain was not pinned
  167. if ([self forwardToOriginalDelegateAuthenticationChallenge:challenge forConnection:connection] == NO)
  168. {
  169. // The original delegate could not handle the challenge; use the default handler
  170. [challenge.sender performDefaultHandlingForAuthenticationChallenge:challenge];
  171. }
  172. }
  173. #pragma GCC diagnostic pop
  174. @end