TrustKit.m 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290
  1. /*
  2. TrustKit.m
  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 "TrustKit.h"
  9. #import "Reporting/TSKBackgroundReporter.h"
  10. #import "Pinning/TSKSPKIHashCache.h"
  11. #import "Swizzling/TSKNSURLConnectionDelegateProxy.h"
  12. #import "Swizzling/TSKNSURLSessionDelegateProxy.h"
  13. #import "Pinning/TSKSPKIHashCache.h"
  14. #import "parse_configuration.h"
  15. #import "TSKPinningValidatorResult.h"
  16. #import "TSKLog.h"
  17. #import "TSKPinningValidator_Private.h"
  18. // Info.plist key we read the public key hashes from
  19. static NSString * const kTSKConfiguration = @"TSKConfiguration";
  20. // Default report URI - can be disabled with TSKDisableDefaultReportUri
  21. // Email info@datatheorem.com if you need a free dashboard to see your App's reports
  22. static NSString * const kTSKDefaultReportUri = @"https://overmind.datatheorem.com/trustkit/report";
  23. static const char kTSKPinFailureReporterQueueLabel[] = "com.datatheorem.trustkit.reporterqueue";
  24. #pragma mark TrustKit Global State
  25. // Shared TrustKit singleton instance
  26. static TrustKit *sharedTrustKit;
  27. // A shared hash cache for use by all TrustKit instances
  28. static TSKSPKIHashCache *sharedHashCache;
  29. // Default logger block: only log in debug builds and add TrustKit at the beginning of the line
  30. #if DEBUG
  31. void (^_loggerBlock)(NSString *) = ^void(NSString *message) { NSLog(@"=== TrustKit: %@", message); };
  32. #else
  33. void (^_loggerBlock)(NSString *) = NULL;
  34. #endif
  35. // The logging function we use within TrustKit
  36. void TSKLog(NSString *format, ...)
  37. {
  38. if (_loggerBlock) {
  39. va_list args;
  40. va_start(args, format);
  41. NSString *message = [[NSString alloc] initWithFormat:format arguments:args];
  42. va_end(args);
  43. _loggerBlock(message);
  44. }
  45. }
  46. #pragma mark TrustKit Private API
  47. @interface TrustKit ()
  48. @property (nonatomic) TSKBackgroundReporter *pinFailureReporter;
  49. @property (nonatomic) dispatch_queue_t pinFailureReporterQueue;
  50. @property (nonatomic, readonly, nullable) NSDictionary *configuration;
  51. @end
  52. @implementation TrustKit
  53. #pragma mark Singleton Initialization
  54. + (instancetype)sharedInstance
  55. {
  56. if (!sharedTrustKit) {
  57. // TrustKit should only be initialized once so we don't double swizzle or get into anything unexpected
  58. [NSException raise:@"TrustKit was not initialized"
  59. format:@"TrustKit must be initialized using +initSharedInstanceWithConfiguration: prior to accessing sharedInstance"];
  60. }
  61. return sharedTrustKit;
  62. }
  63. + (void)initSharedInstanceWithConfiguration:(NSDictionary<TSKGlobalConfigurationKey, id> *)trustKitConfig
  64. {
  65. return [self initSharedInstanceWithConfiguration:trustKitConfig sharedContainerIdentifier:nil];
  66. }
  67. + (void)initSharedInstanceWithConfiguration:(NSDictionary<TSKGlobalConfigurationKey, id> *)trustKitConfig
  68. sharedContainerIdentifier:(NSString *)sharedContainerIdentifier
  69. {
  70. TSKLog(@"Configuration passed via explicit call to initSharedInstanceWithConfiguration:");
  71. static dispatch_once_t onceToken;
  72. dispatch_once(&onceToken, ^{
  73. sharedTrustKit = [[TrustKit alloc] initWithConfiguration:trustKitConfig
  74. sharedContainerIdentifier:sharedContainerIdentifier
  75. isSingleton:YES];
  76. // Hook network APIs if needed
  77. if ([sharedTrustKit.configuration[kTSKSwizzleNetworkDelegates] boolValue]) {
  78. // NSURLConnection
  79. [TSKNSURLConnectionDelegateProxy swizzleNSURLConnectionConstructors:sharedTrustKit];
  80. // NSURLSession
  81. [TSKNSURLSessionDelegateProxy swizzleNSURLSessionConstructors:sharedTrustKit];
  82. }
  83. });
  84. }
  85. #pragma mark Instance Initialization
  86. - (instancetype)initWithConfiguration:(NSDictionary<TSKGlobalConfigurationKey, id> *)trustKitConfig
  87. {
  88. return [self initWithConfiguration:trustKitConfig sharedContainerIdentifier:nil];
  89. }
  90. - (instancetype)initWithConfiguration:(NSDictionary<TSKGlobalConfigurationKey, id> *)trustKitConfig
  91. sharedContainerIdentifier:(NSString *)sharedContainerIdentifier
  92. {
  93. return [self initWithConfiguration:trustKitConfig
  94. sharedContainerIdentifier:sharedContainerIdentifier
  95. isSingleton:NO];
  96. }
  97. - (instancetype)initWithConfiguration:(NSDictionary<TSKGlobalConfigurationKey, id> *)trustKitConfig
  98. sharedContainerIdentifier:(NSString *)sharedContainerIdentifier
  99. isSingleton:(BOOL)isSingleton
  100. {
  101. NSParameterAssert(trustKitConfig);
  102. if (!trustKitConfig) {
  103. return nil;
  104. }
  105. self = [super init];
  106. if (self && [trustKitConfig count] > 0) {
  107. // Convert and store the SSL pins in our global variable
  108. _configuration = parseTrustKitConfiguration(trustKitConfig);
  109. _pinningValidatorCallbackQueue = dispatch_get_main_queue();
  110. // Create a dispatch queue for activating the reporter
  111. // We use a serial queue targetting the global default queue in order to ensure reports are sent one by one
  112. // even when a lot of pin failures are occuring, instead of spamming the global queue with events to process
  113. _pinFailureReporterQueue = dispatch_queue_create(kTSKPinFailureReporterQueueLabel, DISPATCH_QUEUE_SERIAL);
  114. // Create our reporter for sending pin validation failures; do this before hooking NSURLSession so we don't hook ourselves
  115. _pinFailureReporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:YES
  116. sharedContainerIdentifier:sharedContainerIdentifier];
  117. // Handle global configuration flags here
  118. // TSKIgnorePinningForUserDefinedTrustAnchors
  119. #if TARGET_OS_IPHONE
  120. BOOL userTrustAnchorBypass = NO;
  121. #else
  122. BOOL userTrustAnchorBypass = [_configuration[kTSKIgnorePinningForUserDefinedTrustAnchors] boolValue];
  123. #endif
  124. // TSKSwizzleNetworkDelegates - check if we are initializing the singleton / shared instance
  125. if (!isSingleton)
  126. {
  127. if ([_configuration[kTSKSwizzleNetworkDelegates] boolValue] == YES)
  128. {
  129. // TSKSwizzleNetworkDelegates can only be enabled when using the shared instance, to avoid double swizzling
  130. [NSException raise:@"TrustKit configuration invalid"
  131. format:@"Cannot use TSKSwizzleNetworkDelegates outside the TrustKit sharedInstance"];
  132. }
  133. }
  134. // Configure the pinning validator and register for pinning callbacks in order to
  135. // trigger reports on the pinning failure reporter background queue.
  136. static dispatch_once_t onceToken;
  137. dispatch_once(&onceToken, ^{
  138. sharedHashCache = [[TSKSPKIHashCache alloc] initWithIdentifier:kTSKSPKISharedHashCacheIdentifier];
  139. });
  140. __weak typeof(self) weakSelf = self;
  141. _pinningValidator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:_configuration[kTSKPinnedDomains]
  142. hashCache:sharedHashCache
  143. ignorePinsForUserTrustAnchors:userTrustAnchorBypass
  144. validationCallbackQueue:_pinFailureReporterQueue
  145. validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, TKSDomainPinningPolicy *_Nonnull notedHostnamePinningPolicy) {
  146. typeof(self) strongSelf = weakSelf;
  147. if (!strongSelf) {
  148. return;
  149. }
  150. // Invoke client handler if set
  151. TSKPinningValidatorCallback userDefinedCallback = strongSelf.pinningValidatorCallback;
  152. if (userDefinedCallback) {
  153. dispatch_async(strongSelf.pinningValidatorCallbackQueue, ^{
  154. userDefinedCallback(result, notedHostname, notedHostnamePinningPolicy);
  155. });
  156. }
  157. // Send analytics report
  158. [strongSelf sendValidationReport:result
  159. notedHostname:notedHostname
  160. pinningPolicy:notedHostnamePinningPolicy];
  161. }];
  162. TSKLog(@"Successfully initialized with configuration %@", _configuration);
  163. }
  164. return self;
  165. }
  166. #pragma mark Validation Callback
  167. // The block which receives pin validation results and turns them into pin validation reports
  168. - (void)sendValidationReport:(TSKPinningValidatorResult *)result notedHostname:(NSString *)notedHostname pinningPolicy:(NSDictionary<TSKDomainConfigurationKey, id> *)notedHostnamePinningPolicy
  169. {
  170. TSKTrustEvaluationResult validationResult = result.evaluationResult;
  171. // Send a report only if the there was a pinning failure
  172. if (validationResult != TSKTrustEvaluationSuccess)
  173. {
  174. #if !TARGET_OS_IPHONE
  175. if (validationResult != TSKTrustEvaluationFailedUserDefinedTrustAnchor)
  176. #endif
  177. {
  178. // Pin validation failed: retrieve the list of configured report URLs
  179. NSMutableArray *reportUris = [NSMutableArray arrayWithArray:notedHostnamePinningPolicy[kTSKReportUris]];
  180. // Also enable the default reporting URL
  181. if ([notedHostnamePinningPolicy[kTSKDisableDefaultReportUri] boolValue] == NO)
  182. {
  183. [reportUris addObject:[NSURL URLWithString:kTSKDefaultReportUri]];
  184. }
  185. // If some report URLs have been defined, send the pin failure report
  186. if (reportUris.count > 0)
  187. {
  188. [self.pinFailureReporter pinValidationFailedForHostname:result.serverHostname
  189. port:nil
  190. certificateChain:result.certificateChain
  191. notedHostname:notedHostname
  192. reportURIs:reportUris
  193. includeSubdomains:[notedHostnamePinningPolicy[kTSKIncludeSubdomains] boolValue]
  194. enforcePinning:[notedHostnamePinningPolicy[kTSKEnforcePinning] boolValue]
  195. knownPins:notedHostnamePinningPolicy[kTSKPublicKeyHashes]
  196. validationResult:validationResult
  197. expirationDate:notedHostnamePinningPolicy[kTSKExpirationDate]];
  198. }
  199. }
  200. }
  201. }
  202. - (void)setPinningValidatorCallbackQueue:(dispatch_queue_t)pinningValidatorCallbackQueue
  203. {
  204. _pinningValidatorCallbackQueue = pinningValidatorCallbackQueue ?: dispatch_get_main_queue();
  205. }
  206. #pragma mark Configuring Logging
  207. + (void)setLoggerBlock:(void (^)(NSString *))block
  208. {
  209. _loggerBlock = block;
  210. }
  211. @end
  212. #pragma mark TrustKit Implicit Initialization via Library Constructor
  213. __attribute__((constructor)) static void initializeWithInfoPlist(int argc, const char **argv)
  214. {
  215. // TrustKit just got started in the App
  216. CFBundleRef appBundle = CFBundleGetMainBundle();
  217. // Retrieve the configuration from the App's Info.plist file
  218. NSDictionary *trustKitConfigFromInfoPlist = (__bridge NSDictionary *)CFBundleGetValueForInfoDictionaryKey(appBundle, (__bridge CFStringRef)kTSKConfiguration);
  219. if (trustKitConfigFromInfoPlist)
  220. {
  221. TSKLog(@"Configuration supplied via the App's Info.plist");
  222. [TrustKit initSharedInstanceWithConfiguration:trustKitConfigFromInfoPlist];
  223. }
  224. }