TSKPinningValidator.m 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. /*
  2. TSKPinningValidator.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 "TSKPinningValidator.h"
  9. #import "TSKTrustKitConfig.h"
  10. #import "TSKTrustDecision.h"
  11. #import "TSKPinningValidatorResult.h"
  12. #import "Pinning/TSKSPKIHashCache.h"
  13. #import "Pinning/ssl_pin_verifier.h"
  14. #import "configuration_utils.h"
  15. #import "TrustKit.h"
  16. #import "TSKLog.h"
  17. #import "TSKPinningValidator_Private.h"
  18. @interface TSKPinningValidator ()
  19. @property (nonatomic) TSKSPKIHashCache *spkiHashCache;
  20. /**
  21. The dictionary of domains that were configured and their corresponding pinning policy.
  22. */
  23. @property (nonatomic, readonly, nonnull) NSDictionary<NSString *, TKSDomainPinningPolicy *> *domainPinningPolicies;
  24. /**
  25. Set to true to ignore the trust anchors in the user trust store. Only applicable
  26. to platforms that support a user trust store (Mac OS).
  27. */
  28. @property (nonatomic, readonly) BOOL ignorePinsForUserTrustAnchors;
  29. /**
  30. The callback invoked with validation results.
  31. */
  32. @property (nonatomic, readonly, nonnull) TSKPinningValidatorCallback validationCallback;
  33. /**
  34. The queue use when invoking the `validationCallback`.
  35. */
  36. @property (nonatomic, readonly, nonnull) dispatch_queue_t validationCallbackQueue;
  37. @end
  38. @implementation TSKPinningValidator
  39. + (BOOL)allowsAdditionalTrustAnchors
  40. {
  41. static const BOOL allowAdditionalTrustAnchors = {
  42. #if DEBUG == 1
  43. YES
  44. #else
  45. NO
  46. #endif
  47. };
  48. return allowAdditionalTrustAnchors;
  49. }
  50. #pragma mark Instance Methods
  51. - (instancetype _Nullable)initWithDomainPinningPolicies:(NSDictionary<NSString *, TKSDomainPinningPolicy *> * _Nonnull)domainPinningPolicies
  52. hashCache:(TSKSPKIHashCache * _Nonnull)hashCache
  53. ignorePinsForUserTrustAnchors:(BOOL)ignorePinsForUserTrustAnchors
  54. validationCallbackQueue:(dispatch_queue_t _Nonnull)validationCallbackQueue
  55. validationCallback:(TSKPinningValidatorCallback)validationCallback
  56. {
  57. self = [super init];
  58. if (self) {
  59. _domainPinningPolicies = domainPinningPolicies;
  60. _ignorePinsForUserTrustAnchors = ignorePinsForUserTrustAnchors;
  61. _validationCallbackQueue = validationCallbackQueue;
  62. _validationCallback = validationCallback;
  63. _spkiHashCache = hashCache;
  64. }
  65. return self;
  66. }
  67. - (TSKTrustDecision)evaluateTrust:(SecTrustRef _Nonnull)serverTrust forHostname:(NSString * _Nonnull)serverHostname
  68. {
  69. TSKTrustDecision finalTrustDecision = TSKTrustDecisionShouldBlockConnection;
  70. if ((serverTrust == NULL) || (serverHostname == nil))
  71. {
  72. TSKLog(@"Pin validation error - invalid parameters for %@", serverHostname);
  73. return finalTrustDecision;
  74. }
  75. CFRetain(serverTrust);
  76. // Register start time for duration computations
  77. NSTimeInterval validationStartTime = [NSDate timeIntervalSinceReferenceDate];
  78. // Retrieve the pinning configuration for this specific domain, if there is one
  79. NSString *domainConfigKey = getPinningConfigurationKeyForDomain(serverHostname, self.domainPinningPolicies);
  80. if (domainConfigKey == nil)
  81. {
  82. // The domain has no pinning policy: nothing to do/validate
  83. finalTrustDecision = TSKTrustDecisionDomainNotPinned;
  84. }
  85. else
  86. {
  87. // This domain has a pinning policy
  88. NSDictionary *domainConfig = self.domainPinningPolicies[domainConfigKey];
  89. // Has the pinning policy expired?
  90. NSDate *expirationDate = domainConfig[kTSKExpirationDate];
  91. if (expirationDate != nil && [expirationDate compare:[NSDate date]] == NSOrderedAscending)
  92. {
  93. // Yes the policy has expired
  94. finalTrustDecision = TSKTrustDecisionDomainNotPinned;
  95. }
  96. else if ([domainConfig[kTSKExcludeSubdomainFromParentPolicy] boolValue])
  97. {
  98. // This is a subdomain that was explicitly excluded from the parent domain's policy
  99. finalTrustDecision = TSKTrustDecisionDomainNotPinned;
  100. }
  101. else
  102. {
  103. // The domain has a pinning policy that has not expired
  104. // Look for one the configured public key pins in the server's evaluated certificate chain
  105. TSKTrustEvaluationResult validationResult = verifyPublicKeyPin(serverTrust,
  106. serverHostname,
  107. domainConfig[kTSKPublicKeyHashes],
  108. self.spkiHashCache);
  109. if (validationResult == TSKTrustEvaluationSuccess)
  110. {
  111. // Pin validation was successful
  112. TSKLog(@"Pin validation succeeded for %@", serverHostname);
  113. finalTrustDecision = TSKTrustDecisionShouldAllowConnection;
  114. }
  115. else
  116. {
  117. // Pin validation failed
  118. TSKLog(@"Pin validation failed for %@", serverHostname);
  119. #if !TARGET_OS_IPHONE
  120. if ((validationResult == TSKTrustEvaluationFailedUserDefinedTrustAnchor)
  121. && (self.ignorePinsForUserTrustAnchors))
  122. {
  123. // OS-X only: user-defined trust anchors can be whitelisted (for corporate proxies, etc.) so don't send reports
  124. TSKLog(@"Ignoring pinning failure due to user-defined trust anchor for %@", serverHostname);
  125. finalTrustDecision = TSKTrustDecisionShouldAllowConnection;
  126. }
  127. else
  128. #endif
  129. {
  130. if (validationResult == TSKTrustEvaluationFailedNoMatchingPin)
  131. {
  132. // Is pinning enforced?
  133. if ([domainConfig[kTSKEnforcePinning] boolValue] == YES)
  134. {
  135. // Yes - Block the connection
  136. finalTrustDecision = TSKTrustDecisionShouldBlockConnection;
  137. }
  138. else
  139. {
  140. finalTrustDecision = TSKTrustDecisionShouldAllowConnection;
  141. }
  142. }
  143. else
  144. {
  145. // Misc pinning errors (such as invalid certificate chain) - block the connection
  146. finalTrustDecision = TSKTrustDecisionShouldBlockConnection;
  147. }
  148. }
  149. }
  150. // Send a notification after all validation is done; this will also trigger a report if pin validation failed
  151. if (self.validationCallbackQueue && self.validationCallback) {
  152. NSTimeInterval validationDuration = [NSDate timeIntervalSinceReferenceDate] - validationStartTime;
  153. TSKPinningValidatorResult *result = [[TSKPinningValidatorResult alloc] initWithServerHostname:serverHostname
  154. serverTrust:serverTrust
  155. validationResult:validationResult
  156. finalTrustDecision:finalTrustDecision
  157. validationDuration:validationDuration];
  158. dispatch_async(self.validationCallbackQueue, ^{
  159. self.validationCallback(result, domainConfigKey, domainConfig);
  160. });
  161. }
  162. }
  163. }
  164. CFRelease(serverTrust);
  165. return finalTrustDecision;
  166. }
  167. - (BOOL)handleChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler
  168. {
  169. BOOL wasChallengeHandled = NO;
  170. if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
  171. {
  172. // Check the trust object against the pinning policy
  173. SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
  174. NSString *serverHostname = challenge.protectionSpace.host;
  175. TSKTrustDecision trustDecision = [self evaluateTrust:serverTrust forHostname:serverHostname];
  176. if (trustDecision == TSKTrustDecisionShouldAllowConnection)
  177. {
  178. // Success
  179. wasChallengeHandled = YES;
  180. completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:serverTrust]);
  181. }
  182. else if (trustDecision == TSKTrustDecisionDomainNotPinned)
  183. {
  184. // Domain was not pinned; we need to do the default validation to avoid disabling SSL validation for all non-pinned domains
  185. wasChallengeHandled = YES;
  186. completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, NULL);
  187. }
  188. else
  189. {
  190. // Pinning validation failed - block the connection
  191. wasChallengeHandled = YES;
  192. completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, NULL);
  193. }
  194. }
  195. return wasChallengeHandled;
  196. }
  197. @end