123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- /*
-
- TSKNSURLSessionDelegateProxy.m
- TrustKit
-
- Copyright 2015 The TrustKit Project Authors
- Licensed under the MIT license, see associated LICENSE file for terms.
- See AUTHORS file for the list of project authors.
-
- */
- #import "TSKNSURLSessionDelegateProxy.h"
- #import "../TrustKit.h"
- #import "../TSKLog.h"
- #import "../TSKTrustDecision.h"
- #import "../TSKPinningValidator.h"
- #import "../Dependencies/RSSwizzle/RSSwizzle.h"
- @interface TSKNSURLSessionDelegateProxy ()
- /* The NSURLSessionDelegate we're going to proxy */
- @property (nonatomic) id<NSURLSessionDelegate, NSURLSessionTaskDelegate> originalDelegate;
- @property (nonatomic) TrustKit *trustKit;
- @end
- @implementation TSKNSURLSessionDelegateProxy
- #pragma mark Public methods
- + (void)swizzleNSURLSessionConstructors:(TrustKit *)trustKit
- {
- // + sessionWithConfiguration:delegate:delegateQueue:
- #pragma clang diagnostic push
- #pragma clang diagnostic ignored "-Wshadow"
- RSSwizzleClassMethod(NSClassFromString(@"NSURLSession"),
- @selector(sessionWithConfiguration:delegate:delegateQueue:),
- RSSWReturnType(NSURLSession *),
- RSSWArguments(NSURLSessionConfiguration * _Nonnull configuration, id _Nullable delegate, NSOperationQueue * _Nullable queue),
- RSSWReplacement(
- {
- NSURLSession *session;
- if (delegate == nil)
- {
- // Just display a warning
- //TSKLog(@"WARNING: +sessionWithConfiguration:delegate:delegateQueue: was called with a nil delegate; TrustKit cannot enforce SSL pinning for any connection initiated by this session");
- session = RSSWCallOriginal(configuration, delegate, queue);
- }
- // Do not swizzle TrustKit objects (such as the reporter)
- else if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"])
- {
- session = RSSWCallOriginal(configuration, delegate, queue);
- }
- else
- {
- // Replace the delegate with our own so we can intercept and handle authentication challenges
- TSKNSURLSessionDelegateProxy *swizzledDelegate = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:trustKit
- sessionDelegate:delegate];
- session = RSSWCallOriginal(configuration, swizzledDelegate, queue);
- }
- return session;
- }));
- // Not hooking the following methods as they end up calling +sessionWithConfiguration:delegate:delegateQueue:
- // +sessionWithConfiguration:
- // +sharedSession
- #pragma clang diagnostic pop
- }
- - (instancetype _Nullable)initWithTrustKit:(TrustKit *)trustKit sessionDelegate:(id<NSURLSessionDelegate, NSURLSessionTaskDelegate>)delegate
- {
- NSParameterAssert(delegate);
-
- self = [super init];
- if (self)
- {
- _originalDelegate = delegate;
- _trustKit = trustKit;
- }
- TSKLog(@"Proxy-ing NSURLSessionDelegate: %@", NSStringFromClass([delegate class]));
- return self;
- }
- #pragma mark Delegate methods
- - (BOOL)respondsToSelector:(SEL)aSelector
- {
- if (aSelector == @selector(URLSession:task:didReceiveChallenge:completionHandler:))
- {
- // For the task-level handler, mirror the delegate
- return [_originalDelegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)];
- }
- else if (aSelector == @selector(URLSession:didReceiveChallenge:completionHandler:))
- {
- if ([_originalDelegate respondsToSelector:@selector(URLSession:didReceiveChallenge:completionHandler:)] == YES)
- {
- return YES;
- }
- else if ([_originalDelegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)] == NO)
- {
- // If the task-level handler is not implemented in the delegate, we need to implement the session-level handler
- // regardless of what the delegate implements, to ensure we get to handle auth challenges so we can do pinning validation
- return YES;
- }
- else
- {
- // Let the task-level handler handle auth challenges
- return NO;
- }
- }
- else
- {
- // The delegate proxy should mirror the original delegate's methods so that it doesn't change the app flow
- return [_originalDelegate respondsToSelector:aSelector];
- }
- }
- - (id)forwardingTargetForSelector:(SEL)sel
- {
- return _originalDelegate;
- }
- - (BOOL)common_URLSession:(NSURLSession * _Nonnull)session
- challenge:(NSURLAuthenticationChallenge * _Nonnull)challenge
- completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition,
- NSURLCredential * _Nullable credential))completionHandler
- {
- // For SSL pinning we only care about server authentication
- if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
- {
- // Check the trust object against the pinning policy
- TSKTrustDecision trustDecision = [self.trustKit.pinningValidator evaluateTrust:challenge.protectionSpace.serverTrust
- forHostname:challenge.protectionSpace.host];
- if (trustDecision == TSKTrustDecisionShouldBlockConnection)
- {
- // Pinning validation failed - block the connection
- completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, NULL);
- return YES; // Challenge handled (blocked), stop here!
- }
- }
- return NO;
- }
- - (void)URLSession:(NSURLSession * _Nonnull)session
- didReceiveChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge
- completionHandler:(TSKURLSessionAuthChallengeCallback)completionHandler
- {
- if ([self common_URLSession:session challenge:challenge completionHandler:completionHandler])
- {
- // Challenge handled, stop here!
- return;
- }
-
- // Forward all challenges (including client auth challenges) to the original delegate
- // We will also get here if the pinning validation succeeded or the domain was not pinned
- if ([_originalDelegate respondsToSelector:@selector(URLSession:didReceiveChallenge:completionHandler:)])
- {
- [_originalDelegate URLSession:session didReceiveChallenge:challenge completionHandler:completionHandler];
- }
- else
- {
- // The original delegate could not handle the challenge; use the default handler
- completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, NULL);
- }
- }
- - (void)URLSession:(NSURLSession * _Nonnull)session
- task:(NSURLSessionTask * _Nonnull)task
- didReceiveChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge
- completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition,
- NSURLCredential * _Nullable credential))completionHandler
- {
- if ([self common_URLSession:session challenge:challenge completionHandler:completionHandler])
- {
- // Challenge handled, stop here!
- return;
- }
-
- // Forward all challenges (including client auth challenges) to the original delegate
- // We will also get here if the pinning validation succeeded or the domain was not pinned
- if ([_originalDelegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)])
- {
- [_originalDelegate URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler];
- }
- else
- {
- // The original delegate could not handle the challenge; use the default handler
- completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, NULL);
- }
- }
- @end
|