From 2a03c0313c0b7bea09118d6aa429c4ee28ef37a7 Mon Sep 17 00:00:00 2001 From: Alexander DiBenedetto Date: Thu, 22 Oct 2026 15:54:49 -0500 Subject: [PATCH 1/3] Import what TSKPinningValidator_Private.h uses --- TrustKit/TSKPinningValidator_Private.h | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/TrustKit/TSKPinningValidator_Private.h b/TrustKit/TSKPinningValidator_Private.h index 3eb0b498..2bd6996b 100644 --- a/TrustKit/TSKPinningValidator_Private.h +++ b/TrustKit/TSKPinningValidator_Private.h @@ -1,14 +1,18 @@ /* - + TSKPinningValidator_Private.h TrustKit - + Copyright 2017 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 "Pinning/TSKSPKIHashCache.h" +#import "public/TrustKit/TSKPinningValidator.h" +#import "public/TrustKit/TSKPinningValidatorCallback.h" + NS_ASSUME_NONNULL_BEGIN /* Methods that are internal to TrustKit */ @@ -16,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN /** Initialize an instance of TSKPinningValidator. - + @param domainPinningPolicies A dictionnary of domains and the corresponding pinning policy. @param hashCache The hash cache to use. If nil, no caching is performed, performance may suffer. @param ignorePinsForUserTrustAnchors Set to true to ignore the trust anchors in the user trust store @@ -32,7 +36,6 @@ NS_ASSUME_NONNULL_BEGIN @end - @interface TSKPinningValidatorResult (Internal) - (instancetype _Nullable)initWithServerHostname:(NSString *)serverHostname From acdbb648052d3072a91492dcc1d5ba3067130362 Mon Sep 17 00:00:00 2001 From: Alexander DiBenedetto Date: Thu, 22 Oct 2026 15:55:11 -0500 Subject: [PATCH 2/3] Add SwiftLint, swift-format and clang-format checks --- .clang-format | 39 +++++++++++++++++++++ .github/workflows/ci.yml | 13 +++++++ .swift-format | 75 ++++++++++++++++++++++++++++++++++++++++ .swiftlint.yml | 18 ++++++++++ scripts/lint.sh | 31 +++++++++++++++++ 5 files changed, 176 insertions(+) create mode 100644 .clang-format create mode 100644 .swift-format create mode 100644 .swiftlint.yml create mode 100755 scripts/lint.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 00000000..7352b3ba --- /dev/null +++ b/.clang-format @@ -0,0 +1,39 @@ +--- +BasedOnStyle: WebKit +AlignAfterOpenBracket: BlockIndent +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignConsecutiveMacros: true +AlignEscapedNewlines: Left +AllowAllArgumentsOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: true +AllowShortFunctionsOnASingleLine: Empty +AlwaysBreakBeforeMultilineStrings: true +BinPackArguments: false +BreakBeforeBraces: Attach +BreakBeforeTernaryOperators: true +ColumnLimit: 180 +Cpp11BracedListStyle: false +IncludeBlocks: Preserve +IndentWidth: 4 +InsertBraces: true +KeepEmptyLinesAtTheStartOfBlocks: true +NamespaceIndentation: None +ObjCBinPackProtocolList: Never +ObjCBlockIndentWidth: 4 +ObjCSpaceAfterProperty: true +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: true +SpaceBeforeCtorInitializerColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesInContainerLiterals: false +UseTab: Never + +... diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7e5d71fa..bbc28693 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,18 @@ env: DEVELOPER_DIR: /Applications/Xcode_27.0.app/Contents/Developer jobs: + lint: + name: Lint + runs-on: xcode-27 + timeout-minutes: 10 + steps: + - uses: actions/checkout@v7 + - run: brew install swiftlint clang-format + - run: ./scripts/lint.sh --check + test: name: Test ${{ matrix.platform }} + needs: lint runs-on: xcode-27 timeout-minutes: 30 strategy: @@ -39,6 +49,7 @@ jobs: demo: name: Demo UI tests ${{ matrix.scheme }} + needs: lint runs-on: xcode-27 timeout-minutes: 30 strategy: @@ -57,6 +68,7 @@ jobs: xcframework: name: Build XCFramework + needs: lint runs-on: xcode-27 timeout-minutes: 30 steps: @@ -65,6 +77,7 @@ jobs: swift-package: name: Validate Swift package + needs: lint runs-on: xcode-27 timeout-minutes: 30 steps: diff --git a/.swift-format b/.swift-format new file mode 100644 index 00000000..dc1cfb95 --- /dev/null +++ b/.swift-format @@ -0,0 +1,75 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentConditionalCompilationBlocks" : true, + "indentSwitchCaseLabels" : false, + "indentation" : { + "spaces" : 4 + }, + "lineBreakAroundMultilineExpressionChainComponents" : true, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineBreakBetweenDeclarationAttributes" : false, + "lineLength" : 120, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : false, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "reflowMultilineStringLiterals" : "never", + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : false, + "AmbiguousTrailingClosureOverload" : false, + "AvoidRetroactiveConformances" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : true, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : false, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyLinesOpeningClosingBraces" : false, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : true, + "UseExplicitNilCheckInConditions" : true, + "UseLetInEveryBoundCaseVariable" : true, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : true, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "spacesBeforeEndOfLineComments" : 2, + "tabWidth" : 4, + "version" : 1 +} diff --git a/.swiftlint.yml b/.swiftlint.yml new file mode 100644 index 00000000..4e06c22b --- /dev/null +++ b/.swiftlint.yml @@ -0,0 +1,18 @@ +disabled_rules: + - line_length + - file_length + - inclusive_language + - large_tuple + - function_body_length + - type_body_length + - trailing_whitespace # Let swift-format handle whitespace + - vertical_whitespace # Let swift-format handle line breaks + - opening_brace # Let swift-format handle brace placement + - closure_parameter_position # Let swift-format handle closure parameter position + +opt_in_rules: + - private_over_fileprivate + +excluded: + - output + - .build diff --git a/scripts/lint.sh b/scripts/lint.sh new file mode 100755 index 00000000..cbdc88f8 --- /dev/null +++ b/scripts/lint.sh @@ -0,0 +1,31 @@ +#!/usr/bin/env bash + +. "$(dirname "$0")/lib.sh" + +case "${1:-}" in + "") CHECK=false ;; + --check) CHECK=true ;; + *) + echo "usage: $0 [--check]" >&2 + exit 1 + ;; +esac + +# depot_tools ships clang-format and swift-format wrappers that shadow Homebrew's. +PATH="/opt/homebrew/bin:$PATH" + +swift_files() { git ls-files -z --cached --others --exclude-standard -- '*.swift'; } +objc_files() { git ls-files -z --cached --others --exclude-standard -- '*.h' '*.m' ':!TrustKit/Dependencies'; } + +if ! $CHECK; then + swift_files | xargs -0 xcrun swift format --in-place --parallel + swiftlint --fix --quiet + objc_files | xargs -0 clang-format -i +fi + +# `swiftlint --fix` exits 0 even when violations it cannot correct remain, so always lint afterwards. +FAILED=0 +swift_files | xargs -0 xcrun swift format lint --strict --parallel || FAILED=1 +swiftlint lint --strict --quiet || FAILED=1 +objc_files | xargs -0 clang-format --dry-run --Werror || FAILED=1 +exit $FAILED From 9a7932cf6d5a277a357de52f10458bf218689929 Mon Sep 17 00:00:00 2001 From: Alexander DiBenedetto Date: Thu, 22 Oct 2026 15:55:32 -0500 Subject: [PATCH 3/3] Apply lint formatting --- Package.swift | 9 +- TrustKit/Pinning/TSKPublicKeyAlgorithm.h | 11 +- TrustKit/Pinning/TSKSPKIHashCache.h | 12 +- TrustKit/Pinning/TSKSPKIHashCache.m | 101 +- TrustKit/Pinning/pinning_utils.h | 1 - TrustKit/Pinning/pinning_utils.m | 9 +- TrustKit/Pinning/ssl_pin_verifier.h | 12 +- TrustKit/Pinning/ssl_pin_verifier.m | 81 +- TrustKit/Reporting/TSKBackgroundReporter.h | 16 +- TrustKit/Reporting/TSKBackgroundReporter.m | 185 +-- TrustKit/Reporting/TSKPinFailureReport.h | 40 +- TrustKit/Reporting/TSKPinFailureReport.m | 102 +- TrustKit/Reporting/TSKReportsRateLimiter.h | 9 +- TrustKit/Reporting/TSKReportsRateLimiter.m | 41 +- TrustKit/Reporting/reporting_utils.h | 6 +- TrustKit/Reporting/reporting_utils.m | 27 +- TrustKit/Reporting/vendor_identifier.h | 6 +- TrustKit/Reporting/vendor_identifier.m | 16 +- TrustKit/Swift/SubjectPublicKeyInfo.swift | 27 +- .../TSKNSURLConnectionDelegateProxy.h | 8 +- .../TSKNSURLConnectionDelegateProxy.m | 223 ++- .../Swizzling/TSKNSURLSessionDelegateProxy.h | 18 +- .../Swizzling/TSKNSURLSessionDelegateProxy.m | 169 +- TrustKit/TSKLog.h | 7 +- TrustKit/TSKPinningValidator.m | 119 +- TrustKit/TSKPinningValidatorResult.m | 15 +- TrustKit/TSKTrustKitConfig.m | 8 +- TrustKit/TrustKit-Bridging-Header.h | 1 - TrustKit/TrustKit.m | 179 +-- TrustKit/configuration_utils.h | 16 +- TrustKit/configuration_utils.m | 98 +- TrustKit/parse_configuration.h | 6 +- TrustKit/parse_configuration.m | 186 +-- .../public/TrustKit/TSKPinningValidator.h | 63 +- .../TrustKit/TSKPinningValidatorCallback.h | 21 +- .../TrustKit/TSKPinningValidatorResult.h | 18 +- TrustKit/public/TrustKit/TSKTrustDecision.h | 33 +- TrustKit/public/TrustKit/TSKTrustKitConfig.h | 87 +- TrustKit/public/TrustKit/TrustKit.h | 93 +- TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.h | 7 +- TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.m | 93 +- .../TrustKitDemo-ObjC/ViewController.h | 7 +- .../TrustKitDemo-ObjC/ViewController.m | 88 +- .../TrustKitDemo-ObjC/WebViewController.m | 26 +- TrustKitDemo/TrustKitDemo-ObjC/main.m | 10 +- .../TrustKitDemo-Swift/AppDelegate.swift | 45 +- .../TrustKitDemo-Swift/DTViewController.swift | 99 +- .../WebViewController.swift | 45 +- TrustKitTests/TSKCertificateUtils.h | 6 +- TrustKitTests/TSKCertificateUtils.m | 40 +- TrustKitTests/TSKEndToEndNSURLSessionTests.m | 412 +++-- TrustKitTests/TSKEndToEndSwizzlingTests.m | 266 ++-- TrustKitTests/TSKLoggerTests.m | 47 +- TrustKitTests/TSKNSURLConnectionTests.m | 219 ++- TrustKitTests/TSKNSURLSessionTests.m | 288 ++-- TrustKitTests/TSKPinConfigurationTests.m | 878 ++++++----- TrustKitTests/TSKPinningValidatorTests.m | 1366 +++++++++-------- TrustKitTests/TSKPublicKeyAlgorithmTests.m | 130 +- TrustKitTests/TSKReporterTests.m | 142 +- TrustKitTests/TSKReportsRateLimiterTests.m | 91 +- TrustKitTests/TSKSwizzlingTests.m | 59 +- 61 files changed, 3037 insertions(+), 3406 deletions(-) diff --git a/Package.swift b/Package.swift index ff64ad3d..9ef91b55 100644 --- a/Package.swift +++ b/Package.swift @@ -15,15 +15,14 @@ let package = Package( .library( name: "TrustKit", targets: ["TrustKit"] - ), - ], - dependencies: [ + ) ], + dependencies: [], targets: [ .target( name: "TrustKit", dependencies: ["TrustKitSwift"], - path: "TrustKit", + path: "TrustKit", exclude: ["Swift"], resources: [.process("PrivacyInfo.xcprivacy")], publicHeadersPath: "public", @@ -32,6 +31,6 @@ let package = Package( .target( name: "TrustKitSwift", path: "TrustKit/Swift" - ), + ) ] ) diff --git a/TrustKit/Pinning/TSKPublicKeyAlgorithm.h b/TrustKit/Pinning/TSKPublicKeyAlgorithm.h index f4347199..3e93df2d 100644 --- a/TrustKit/Pinning/TSKPublicKeyAlgorithm.h +++ b/TrustKit/Pinning/TSKPublicKeyAlgorithm.h @@ -1,12 +1,12 @@ /* - + TSKPublicKeyAlgorithm.h 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. - + */ #ifndef TSKPublicKeyAlgorithm_h @@ -19,14 +19,13 @@ #endif // The internal enum we use for public key algorithms; not to be confused with the exported TSKSupportedAlgorithm -typedef NS_ENUM(NSInteger, TSKPublicKeyAlgorithm) -{ +typedef NS_ENUM(NSInteger, TSKPublicKeyAlgorithm) { // Some assumptions are made about this specific ordering in public_key_utils.m TSKPublicKeyAlgorithmRsa2048 = 0, TSKPublicKeyAlgorithmRsa4096 = 1, TSKPublicKeyAlgorithmEcDsaSecp256r1 = 2, TSKPublicKeyAlgorithmEcDsaSecp384r1 = 3, - + TSKPublicKeyAlgorithmLast = TSKPublicKeyAlgorithmEcDsaSecp384r1 } __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); diff --git a/TrustKit/Pinning/TSKSPKIHashCache.h b/TrustKit/Pinning/TSKSPKIHashCache.h index 9ae33a3d..0ef77f9c 100644 --- a/TrustKit/Pinning/TSKSPKIHashCache.h +++ b/TrustKit/Pinning/TSKSPKIHashCache.h @@ -1,12 +1,12 @@ /* - + TSKSPKIHashCache.h 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. - + */ #if __has_feature(modules) @@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN // The identifier used for the default shared hash cache. Use this identifier // in the TSKSPKIHashCache constructor to use the shared cache. // Versioned so that hashes cached by TrustKit 3.x, which rebuilt the SPKI from the key, are not reused. -static NSString * const kTSKSPKISharedHashCacheIdentifier = @"spki-hash-v2.cache"; +static NSString *const kTSKSPKISharedHashCacheIdentifier = @"spki-hash-v2.cache"; // Each key is a raw certificate data (for easy lookup) and each value is the certificate's raw SPKI data typedef NSMutableDictionary SPKICacheDictionnary; @@ -43,7 +43,7 @@ typedef NSMutableDictionary SPKICacheDictionnary; @param uniqueIdentifier A unique identifier that is stable across app launches/instance creation @return An initialized hash cache. */ -- (instancetype _Nullable)initWithIdentifier:(NSString*)uniqueIdentifier NS_DESIGNATED_INITIALIZER; +- (instancetype _Nullable)initWithIdentifier:(NSString *)uniqueIdentifier NS_DESIGNATED_INITIALIZER; /** Get a pin cache for the provided certificate. The pins @@ -52,7 +52,7 @@ typedef NSMutableDictionary SPKICacheDictionnary; @param certificate The certificate containing the public key that will be hashed @return The hash of the public key or nil if the hash could not be generated */ -- (NSData * _Nullable)hashSubjectPublicKeyInfoFromCertificate:(SecCertificateRef)certificate; +- (NSData *_Nullable)hashSubjectPublicKeyInfoFromCertificate:(SecCertificateRef)certificate; @end diff --git a/TrustKit/Pinning/TSKSPKIHashCache.m b/TrustKit/Pinning/TSKSPKIHashCache.m index 4b9f2440..238ce884 100644 --- a/TrustKit/Pinning/TSKSPKIHashCache.m +++ b/TrustKit/Pinning/TSKSPKIHashCache.m @@ -1,18 +1,18 @@ /* - + TSKSPKIHashCache.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 -#import #import "TSKSPKIHashCache.h" #import "../TSKLog.h" #import +#import +#import #if SWIFT_PACKAGE @import TrustKitSwift; #else @@ -28,7 +28,6 @@ @interface TSKSPKIHashCache () @property (nonatomic) BOOL persistenceScheduled; @property (nonatomic) NSString *spkiCacheFilename; - /** Load the SPKI cache from the filesystem. This triggers blocking file I/O. */ @@ -36,12 +35,10 @@ - (SPKICacheDictionnary *)loadSPKICacheFromFileSystem; @end - @implementation TSKSPKIHashCache // Must not block: it runs while holding lockQueue, and waiting on the main queue there can deadlock -static void isProtectedDataAvailableAsync(dispatch_queue_t callbackQueue, void (^callback)(BOOL available)) -{ +static void isProtectedDataAvailableAsync(dispatch_queue_t callbackQueue, void (^callback)(BOOL available)) { NSCParameterAssert(callbackQueue); NSCParameterAssert(callback); @@ -70,28 +67,25 @@ static BOOL _isProtectedDataAvailable(void) { return (((BOOL (*)(id, SEL))objc_msgSend)(sharedApp, isProtectedDataAvailableSelector)); } } - + return YES; } -- (instancetype)initWithIdentifier:(NSString *)uniqueIdentifier -{ +- (instancetype)initWithIdentifier:(NSString *)uniqueIdentifier { self = [super init]; if (self) { // Initialize our locks _lockQueue = dispatch_queue_create("TSKSPKIHashLock", DISPATCH_QUEUE_SERIAL); - _persistenceQueue = dispatch_queue_create("TSKSPKIHashPersistence", - dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0)); + _persistenceQueue = dispatch_queue_create("TSKSPKIHashPersistence", dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_UTILITY, 0)); // Ensure a non-nil identifier was provided NSAssert(uniqueIdentifier, @"TSKSPKIHashCache initializer must be passed a unique identifier"); _spkiCacheFilename = uniqueIdentifier; - + // First try to load a cached version from the filesystem _spkiCache = [self loadSPKICacheFromFileSystem]; os_log_debug(TSKLogHandle(), "Loaded %lu SPKI cache entries from the filesystem", (unsigned long)_spkiCache.count); - if (_spkiCache == nil) - { + if (_spkiCache == nil) { _spkiCache = [NSMutableDictionary new]; } } @@ -100,55 +94,51 @@ - (instancetype)initWithIdentifier:(NSString *)uniqueIdentifier - (NSData *)hashSubjectPublicKeyInfoFromCertificate:(SecCertificateRef)certificate { __block NSData *hash = nil; - + dispatch_sync(self.lockQueue, ^{ hash = [self _hashSubjectPublicKeyInfoFromCertificate:certificate]; }); - + return hash; } -- (NSData *)_hashSubjectPublicKeyInfoFromCertificate:(SecCertificateRef)certificate -{ +- (NSData *)_hashSubjectPublicKeyInfoFromCertificate:(SecCertificateRef)certificate { // Have we seen this certificate before? Look for the SPKI in the cache NSData *certificateData = (__bridge_transfer NSData *)(SecCertificateCopyData(certificate)); NSData *cachedSubjectPublicKeyInfo = self->_spkiCache[certificateData]; - - if (cachedSubjectPublicKeyInfo) - { + + if (cachedSubjectPublicKeyInfo) { os_log_debug(TSKLogHandle(), "Subject Public Key Info hash was found in the cache"); return cachedSubjectPublicKeyInfo; } - + // We didn't this certificate in the cache so we need to generate its SPKI hash os_log_debug(TSKLogHandle(), "Generating Subject Public Key Info hash..."); - + NSData *subjectPublicKeyInfo = [TSKSubjectPublicKeyInfo dataFromCertificate:certificateData]; - if (subjectPublicKeyInfo == nil) - { + if (subjectPublicKeyInfo == nil) { os_log_error(TSKLogHandle(), "Could not read the Subject Public Key Info from the certificate"); return nil; } - + NSMutableData *subjectPublicKeyInfoHash = [NSMutableData dataWithLength:CC_SHA256_DIGEST_LENGTH]; CC_SHA256([subjectPublicKeyInfo bytes], (CC_LONG)[subjectPublicKeyInfo length], [subjectPublicKeyInfoHash mutableBytes]); - + // Store the hash in our memory cache self->_spkiCache[certificateData] = subjectPublicKeyInfoHash; - + // Update the cache on the filesystem [self schedulePersistence]; - + return subjectPublicKeyInfoHash; } -- (void)schedulePersistence -{ +- (void)schedulePersistence { if (!self.spkiCacheFilename.length || self.persistenceScheduled) { return; } self.persistenceScheduled = YES; - + isProtectedDataAvailableAsync(self.persistenceQueue, ^(BOOL available) { __block SPKICacheDictionnary *snapshot = nil; dispatch_sync(self.lockQueue, ^{ @@ -157,15 +147,13 @@ - (void)schedulePersistence snapshot = [self.spkiCache mutableCopy]; } }); - + if (snapshot == nil) { os_log_info(TSKLogHandle(), "Protected data not available, skipping SPKI cache persistence"); return; } - - NSData *serializedSpkiCache = [NSKeyedArchiver archivedDataWithRootObject:snapshot - requiringSecureCoding:YES - error:nil]; + + NSData *serializedSpkiCache = [NSKeyedArchiver archivedDataWithRootObject:snapshot requiringSecureCoding:YES error:nil]; NSURL *cachePath = [self SPKICachePath]; if (!cachePath || ![serializedSpkiCache writeToURL:cachePath atomically:YES]) { NSAssert(false, @"Failed to write cache"); @@ -174,48 +162,45 @@ - (void)schedulePersistence }); } -- (SPKICacheDictionnary *)loadSPKICacheFromFileSystem -{ +- (SPKICacheDictionnary *)loadSPKICacheFromFileSystem { NSMutableDictionary *spkiCache = nil; NSData *serializedSpkiCache = [NSData dataWithContentsOfURL:[self SPKICachePath]]; if (serializedSpkiCache) { NSError *decodingError = nil; - spkiCache = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[SPKICacheDictionnary class], [NSData class]]] fromData:serializedSpkiCache error:&decodingError]; - if (decodingError) - { + spkiCache = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet setWithArray:@[[SPKICacheDictionnary class], [NSData class]]] + fromData:serializedSpkiCache + error:&decodingError]; + if (decodingError) { os_log_error(TSKLogHandle(), "Could not retrieve SPKI cache from the filesystem: %{public}@", decodingError); } } return spkiCache; } -- (NSURL *)SPKICachePath -{ - NSURL *cachesDirUrl = [NSFileManager.defaultManager URLsForDirectory:NSCachesDirectory - inDomains:NSUserDomainMask].firstObject; +- (NSURL *)SPKICachePath { + NSURL *cachesDirUrl = [NSFileManager.defaultManager URLsForDirectory:NSCachesDirectory inDomains:NSUserDomainMask].firstObject; return [cachesDirUrl URLByAppendingPathComponent:self.spkiCacheFilename]; } @end - @implementation TSKSPKIHashCache (TestSupport) -- (void)resetSubjectPublicKeyInfoDiskCache -{ +- (void)resetSubjectPublicKeyInfoDiskCache { // Discard SPKI cache [self waitForPendingSubjectPublicKeyInfoDiskWrites]; [NSFileManager.defaultManager removeItemAtURL:[self SPKICachePath] error:nil]; } -- (void)waitForPendingSubjectPublicKeyInfoDiskWrites -{ - dispatch_sync(self.persistenceQueue, ^{}); +- (void)waitForPendingSubjectPublicKeyInfoDiskWrites { + dispatch_sync( + self.persistenceQueue, + ^ { + } + ); } - -- (SPKICacheDictionnary *)getSubjectPublicKeyInfoHashesCache -{ +- (SPKICacheDictionnary *)getSubjectPublicKeyInfoHashesCache { return _spkiCache; } diff --git a/TrustKit/Pinning/pinning_utils.h b/TrustKit/Pinning/pinning_utils.h index 64b426cc..d290c0a7 100644 --- a/TrustKit/Pinning/pinning_utils.h +++ b/TrustKit/Pinning/pinning_utils.h @@ -21,7 +21,6 @@ #import #endif - #ifndef TrustKit_pinning_utils_h #define TrustKit_pinning_utils_h diff --git a/TrustKit/Pinning/pinning_utils.m b/TrustKit/Pinning/pinning_utils.m index 65ab4b19..32b78600 100644 --- a/TrustKit/Pinning/pinning_utils.m +++ b/TrustKit/Pinning/pinning_utils.m @@ -16,13 +16,10 @@ void evaluateCertificateChainTrust(SecTrustRef serverTrust, SecTrustResultType * bool certificateEvaluationSucceeded = SecTrustEvaluateWithError(serverTrust, &errorRef); OSStatus status = SecTrustGetTrustResult(serverTrust, trustResult); if (error != NULL) { - if (status != errSecSuccess) - { + if (status != errSecSuccess) { NSString *errDescription = [NSString stringWithFormat:@"got status %d", (int)status]; - *error = [[NSError alloc] initWithDomain:@"com.datatheorem.trustkit" code:1 userInfo:@{NSLocalizedDescriptionKey:errDescription}]; - } - else if (!certificateEvaluationSucceeded) - { + *error = [[NSError alloc] initWithDomain:@"com.datatheorem.trustkit" code:1 userInfo:@ { NSLocalizedDescriptionKey: errDescription }]; + } else if (!certificateEvaluationSucceeded) { *error = (__bridge_transfer NSError *)errorRef; } } diff --git a/TrustKit/Pinning/ssl_pin_verifier.h b/TrustKit/Pinning/ssl_pin_verifier.h index 8993e513..6969fd4f 100644 --- a/TrustKit/Pinning/ssl_pin_verifier.h +++ b/TrustKit/Pinning/ssl_pin_verifier.h @@ -1,12 +1,12 @@ /* - + ssl_pin_verifier.h 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 "../public/TrustKit/TSKTrustDecision.h" @@ -19,7 +19,5 @@ @class TSKSPKIHashCache; // Validate that the server trust contains at least one of the know/expected pins -TSKTrustEvaluationResult verifyPublicKeyPin(SecTrustRef _Nonnull serverTrust, - NSString * _Nonnull serverHostname, - NSSet * _Nonnull knownPins, - TSKSPKIHashCache * _Nullable hashCache); +TSKTrustEvaluationResult +verifyPublicKeyPin(SecTrustRef _Nonnull serverTrust, NSString *_Nonnull serverHostname, NSSet *_Nonnull knownPins, TSKSPKIHashCache *_Nullable hashCache); diff --git a/TrustKit/Pinning/ssl_pin_verifier.m b/TrustKit/Pinning/ssl_pin_verifier.m index 74f73b0f..0da286ac 100644 --- a/TrustKit/Pinning/ssl_pin_verifier.m +++ b/TrustKit/Pinning/ssl_pin_verifier.m @@ -1,29 +1,26 @@ /* - + ssl_pin_verifier.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 "ssl_pin_verifier.h" -#import "TSKSPKIHashCache.h" -#import "../configuration_utils.h" #import "../TSKLog.h" +#import "../configuration_utils.h" +#import "TSKSPKIHashCache.h" #import "pinning_utils.h" - #pragma mark SSL Pin Verifier -TSKTrustEvaluationResult verifyPublicKeyPin(SecTrustRef serverTrust, NSString *serverHostname, NSSet *knownPins, TSKSPKIHashCache *hashCache) -{ +TSKTrustEvaluationResult verifyPublicKeyPin(SecTrustRef serverTrust, NSString *serverHostname, NSSet *knownPins, TSKSPKIHashCache *hashCache) { NSCParameterAssert(serverTrust); NSCParameterAssert(knownPins); - if ((serverTrust == NULL) || (knownPins == nil)) - { + if ((serverTrust == NULL) || (knownPins == nil)) { os_log_error(TSKLogHandle(), "Invalid pinning parameters for %{public}@", serverHostname); return TSKTrustEvaluationErrorInvalidParameters; } @@ -32,26 +29,24 @@ TSKTrustEvaluationResult verifyPublicKeyPin(SecTrustRef serverTrust, NSString *s // This gives us revocation (only for EV certs I think?) and also ensures the certificate chain is sane // And also gives us the exact path that successfully validated the chain CFRetain(serverTrust); - + // Create and use a sane SSL policy to force hostname validation, even if the supplied trust has a bad // policy configured (such as one from SecPolicyCreateBasicX509()) SecPolicyRef SslPolicy = SecPolicyCreateSSL(YES, (__bridge CFStringRef)(serverHostname)); SecTrustSetPolicies(serverTrust, SslPolicy); CFRelease(SslPolicy); - + NSError *error = NULL; SecTrustResultType trustResult = 0; - + evaluateCertificateChainTrust(serverTrust, &trustResult, &error); - if ((error != NULL) && (trustResult == kSecTrustResultInvalid)) - { + if ((error != NULL) && (trustResult == kSecTrustResultInvalid)) { os_log_error(TSKLogHandle(), "SecTrustEvaluate error for %{public}@: %{public}@", serverHostname, [error localizedDescription]); CFRelease(serverTrust); return TSKTrustEvaluationErrorInvalidParameters; } - if ((trustResult != kSecTrustResultUnspecified) && (trustResult != kSecTrustResultProceed)) - { + if ((trustResult != kSecTrustResultUnspecified) && (trustResult != kSecTrustResultProceed)) { // Default SSL validation failed CFDictionaryRef evaluationDetails = SecTrustCopyResult(serverTrust); os_log_error(TSKLogHandle(), "Default SSL validation failed for %{public}@: %{public}@", serverHostname, evaluationDetails); @@ -59,79 +54,67 @@ TSKTrustEvaluationResult verifyPublicKeyPin(SecTrustRef serverTrust, NSString *s CFRelease(serverTrust); return TSKTrustEvaluationFailedInvalidCertificateChain; } - + // Check each certificate in the server's certificate chain (the trust object); start with the CA all the way down to the leaf CFIndex certificateChainLen = SecTrustGetCertificateCount(serverTrust); - for(int i=(int)certificateChainLen-1;i>=0;i--) - { + for (int i = (int)certificateChainLen - 1; i >= 0; i--) { // Extract the certificate SecCertificateRef certificate = getCertificateAtIndex(serverTrust, i); - - if (os_log_debug_enabled(TSKLogHandle())) - { + + if (os_log_debug_enabled(TSKLogHandle())) { CFStringRef certificateSubject = SecCertificateCopySubjectSummary(certificate); - if (certificateSubject != nil) - { + if (certificateSubject != nil) { os_log_debug(TSKLogHandle(), "Checking certificate with CN: %{public}@", certificateSubject); CFRelease(certificateSubject); - } - else - { + } else { os_log_debug(TSKLogHandle(), "Could not parse certificate subject"); } } - + // Generate the subject public key info hash NSData *subjectPublicKeyInfoHash = [hashCache hashSubjectPublicKeyInfoFromCertificate:certificate]; - if (subjectPublicKeyInfoHash == nil) - { + if (subjectPublicKeyInfoHash == nil) { os_log_error(TSKLogHandle(), "Could not generate the SPKI hash for %{public}@", serverHostname); CFRelease(serverTrust); return TSKTrustEvaluationErrorCouldNotGenerateSpkiHash; } - + // Is the generated hash in our set of pinned hashes ? os_log_debug(TSKLogHandle(), "Testing SSL Pin %{public}@", [subjectPublicKeyInfoHash base64EncodedStringWithOptions:0]); - if ([knownPins containsObject:subjectPublicKeyInfoHash]) - { + if ([knownPins containsObject:subjectPublicKeyInfoHash]) { os_log_debug(TSKLogHandle(), "SSL Pin found for %{public}@", serverHostname); CFRelease(serverTrust); return TSKTrustEvaluationSuccess; } } - + #if !TARGET_OS_IPHONE // macOS only: if user-defined anchors are whitelisted, allow the App to not enforce pin validation NSMutableArray *customRootCerts = [NSMutableArray array]; - + // Retrieve the macOS host's list of user-defined CA certificates CFArrayRef userRootCerts; OSStatus status = SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainUser, &userRootCerts); - if (status == errSecSuccess) - { + if (status == errSecSuccess) { [customRootCerts addObjectsFromArray:(__bridge NSArray *)(userRootCerts)]; CFRelease(userRootCerts); } CFArrayRef adminRootCerts; status = SecTrustSettingsCopyCertificates(kSecTrustSettingsDomainAdmin, &adminRootCerts); - if (status == errSecSuccess) - { + if (status == errSecSuccess) { [customRootCerts addObjectsFromArray:(__bridge NSArray *)(adminRootCerts)]; CFRelease(adminRootCerts); } - + // Is any certificate in the chain a custom anchor that was manually added to the OS' trust store ? // If we get there, we shouldn't have to check the custom certificates' trust setting (trusted / not trusted) // as the chain validation was successful right before - if ([customRootCerts count] > 0) - { - for(int i=0;i 0) { + for (int i = 0; i < certificateChainLen; i++) { SecCertificateRef certificate = getCertificateAtIndex(serverTrust, i); - + // Is the certificate chain's anchor a user-defined anchor ? - if ([customRootCerts containsObject:(__bridge id)(certificate)]) - { + if ([customRootCerts containsObject:(__bridge id)(certificate)]) { os_log_debug(TSKLogHandle(), "Detected user-defined trust anchor in the certificate chain"); CFRelease(serverTrust); return TSKTrustEvaluationFailedUserDefinedTrustAnchor; @@ -139,7 +122,7 @@ TSKTrustEvaluationResult verifyPublicKeyPin(SecTrustRef serverTrust, NSString *s } } #endif - + // If we get here, we didn't find any matching SPKI hash in the chain os_log_debug(TSKLogHandle(), "SSL Pin not found for %{public}@", serverHostname); CFRelease(serverTrust); diff --git a/TrustKit/Reporting/TSKBackgroundReporter.h b/TrustKit/Reporting/TSKBackgroundReporter.h index f926ba7e..60bec383 100644 --- a/TrustKit/Reporting/TSKBackgroundReporter.h +++ b/TrustKit/Reporting/TSKBackgroundReporter.h @@ -1,12 +1,12 @@ /* - + TSKBackgroundReporter.h 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 "../Pinning/ssl_pin_verifier.h" @@ -19,7 +19,7 @@ /** `TSKSimpleBackgroundReporter` is a class for uploading pin failure reports using the background transfer service. - + */ @interface TSKBackgroundReporter : NSObject @@ -29,16 +29,15 @@ /** Initializes a background reporter. - + @param shouldRateLimitReports Prevent identical pin failure reports from being sent more than once per day. @param sharedContainerIdentifier The container identifier for an app extension. This must be set in order for reports to be sent from an app extension. See https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1409450-sharedcontaineridentifier @exception NSException Thrown when the App does not have a bundle ID, meaning we're running in unit tests where the background transfer service can't be used. - + */ -- (nonnull instancetype)initAndRateLimitReports:(BOOL)shouldRateLimitReports - sharedContainerIdentifier:(nullable NSString *)sharedContainerIdentifier; +- (nonnull instancetype)initAndRateLimitReports:(BOOL)shouldRateLimitReports sharedContainerIdentifier:(nullable NSString *)sharedContainerIdentifier; ///---------------------- /// @name Sending Reports @@ -61,4 +60,3 @@ - (void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error; @end - diff --git a/TrustKit/Reporting/TSKBackgroundReporter.m b/TrustKit/Reporting/TSKBackgroundReporter.m index d210e45c..7eba6e2f 100644 --- a/TrustKit/Reporting/TSKBackgroundReporter.m +++ b/TrustKit/Reporting/TSKBackgroundReporter.m @@ -1,28 +1,27 @@ /* - + TSKBackgroundReporter.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 "TSKBackgroundReporter.h" -#import "../public/TrustKit/TSKTrustKitConfig.h" -#import "../configuration_utils.h" #import "../TSKLog.h" +#import "../configuration_utils.h" +#import "../public/TrustKit/TSKTrustKitConfig.h" #import "TSKPinFailureReport.h" -#import "reporting_utils.h" #import "TSKReportsRateLimiter.h" +#import "reporting_utils.h" #import "vendor_identifier.h" - // Session identifier for background uploads: .TSKBackgroundReporter -static NSString * const kTSKBackgroundSessionIdentifierFormat = @"%@.TSKBackgroundReporter.%@"; +static NSString *const kTSKBackgroundSessionIdentifierFormat = @"%@.TSKBackgroundReporter.%@"; -@interface TSKBackgroundReporter() +@interface TSKBackgroundReporter () @property (nonatomic, nonnull) NSURLSession *backgroundSession; @property (nonatomic, nonnull) NSString *appBundleId; @@ -39,15 +38,12 @@ @implementation TSKBackgroundReporter #pragma mark Public methods -- (nonnull instancetype)initAndRateLimitReports:(BOOL)shouldRateLimitReports - sharedContainerIdentifier:(NSString *)sharedContainerIdentifier -{ +- (nonnull instancetype)initAndRateLimitReports:(BOOL)shouldRateLimitReports sharedContainerIdentifier:(NSString *)sharedContainerIdentifier { self = [super init]; - if (self) - { + if (self) { _shouldRateLimitReports = shouldRateLimitReports; _rateLimiter = [TSKReportsRateLimiter new]; - + // Retrieve the App and device's information #if TARGET_OS_IPHONE #if TARGET_OS_TV @@ -64,52 +60,39 @@ - (nonnull instancetype)initAndRateLimitReports:(BOOL)shouldRateLimitReports #else _appPlatform = @"MACOS"; #endif - + NSOperatingSystemVersion version = [[NSProcessInfo processInfo] operatingSystemVersion]; - _appPlatformVersion = [NSString stringWithFormat:@"%ld.%ld.%ld", - (long)version.majorVersion, - (long)version.minorVersion, - (long)version.patchVersion]; - - + _appPlatformVersion = [NSString stringWithFormat:@"%ld.%ld.%ld", (long)version.majorVersion, (long)version.minorVersion, (long)version.patchVersion]; + CFBundleRef appBundle = CFBundleGetMainBundle(); - _appVersion = (__bridge NSString *)CFBundleGetValueForInfoDictionaryKey(appBundle, (CFStringRef) @"CFBundleShortVersionString"); - if (_appVersion == nil) - { + _appVersion = (__bridge NSString *)CFBundleGetValueForInfoDictionaryKey(appBundle, (CFStringRef) @"CFBundleShortVersionString"); + if (_appVersion == nil) { _appVersion = @""; } - + _appBundleId = (__bridge NSString *)CFBundleGetIdentifier(appBundle); - if (_appBundleId == nil) - { + if (_appBundleId == nil) { // The bundle ID we get is nil if we're running tests on Travis. If the bundle ID is nil, background sessions can't be used // backgroundSessionConfigurationWithIdentifier: will throw an exception os_log_debug(TSKLogHandle(), "Null bundle ID: we are running the test suite; falling back to a normal session."); _appBundleId = @"N/A"; _appVendorId = @"unit-tests"; - - _backgroundSession = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration() - delegate:self - delegateQueue:nil]; - } - else - { + _backgroundSession = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration() delegate:self delegateQueue:nil]; + } else { // Get the vendor identifier _appVendorId = identifier_for_vendor(); - - + // We're not running unit tests - use a background session // AppleDoc (currently 10.3) state that multiple background sessions with the same // identifier should never be created, so ensure that cannot happen by creating // a unique ID per instance. - NSString *backgroundSessionId = [NSString stringWithFormat:kTSKBackgroundSessionIdentifierFormat, - _appBundleId, [[NSUUID UUID] UUIDString]]; - + NSString *backgroundSessionId = [NSString stringWithFormat:kTSKBackgroundSessionIdentifierFormat, _appBundleId, [[NSUUID UUID] UUIDString]]; + NSURLSessionConfiguration *backgroundConfiguration = [NSURLSessionConfiguration backgroundSessionConfigurationWithIdentifier:backgroundSessionId]; backgroundConfiguration.discretionary = YES; backgroundConfiguration.sharedContainerIdentifier = sharedContainerIdentifier; - + #if TARGET_OS_IPHONE // iOS-only settings // Do not wake up the App after completing the upload @@ -119,73 +102,64 @@ - (nonnull instancetype)initAndRateLimitReports:(BOOL)shouldRateLimitReports backgroundConfiguration.multipathServiceType = NSURLSessionMultipathServiceTypeHandover; #endif #endif - + // We have to use a delegate as background sessions can't use completion handlers - _backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration - delegate:self - delegateQueue:nil]; + _backgroundSession = [NSURLSession sessionWithConfiguration:backgroundConfiguration delegate:self delegateQueue:nil]; } } return self; } - -- (void) pinValidationFailedForHostname:(nonnull NSString *)serverHostname - port:(nullable NSNumber *)serverPort - certificateChain:(nonnull NSArray *)certificateChain - notedHostname:(nonnull NSString *)notedHostname - reportURIs:(nonnull NSArray *)reportURIs - includeSubdomains:(BOOL)includeSubdomains - enforcePinning:(BOOL)enforcePinning - knownPins:(nonnull NSSet *)knownPins - validationResult:(TSKTrustEvaluationResult)validationResult - expirationDate:(nullable NSDate *)knownPinsExpirationDate -{ +- (void)pinValidationFailedForHostname:(nonnull NSString *)serverHostname + port:(nullable NSNumber *)serverPort + certificateChain:(nonnull NSArray *)certificateChain + notedHostname:(nonnull NSString *)notedHostname + reportURIs:(nonnull NSArray *)reportURIs + includeSubdomains:(BOOL)includeSubdomains + enforcePinning:(BOOL)enforcePinning + knownPins:(nonnull NSSet *)knownPins + validationResult:(TSKTrustEvaluationResult)validationResult + expirationDate:(nullable NSDate *)knownPinsExpirationDate { // Default port to 0 if not specified - if (serverPort == nil) - { + if (serverPort == nil) { serverPort = @(0); } - - if (reportURIs == nil) - { + + if (reportURIs == nil) { [NSException raise:@"TSKBackgroundReporter configuration invalid" - format:@"Reporter was given an invalid value for reportURIs: %@ for domain %@", - reportURIs, notedHostname]; + format:@"Reporter was given an invalid value for reportURIs: %@ for domain %@", reportURIs, notedHostname]; } - + // Create the pin validation failure report NSArray *formattedPins = convertPinsToHpkpPins(knownPins); - TSKPinFailureReport *report = [[TSKPinFailureReport alloc]initWithAppBundleId:_appBundleId - appVersion:_appVersion - appPlatform:_appPlatform - appPlatformVersion:_appPlatformVersion - appVendorId:_appVendorId - trustkitVersion:TrustKitVersion - hostname:serverHostname - port:serverPort - dateTime:[NSDate date] // current date & time - notedHostname:notedHostname - includeSubdomains:includeSubdomains - enforcePinning:enforcePinning - validatedCertificateChain:certificateChain - knownPins:formattedPins - validationResult:validationResult - expirationDate:knownPinsExpirationDate]; - + TSKPinFailureReport *report = [[TSKPinFailureReport alloc] initWithAppBundleId:_appBundleId + appVersion:_appVersion + appPlatform:_appPlatform + appPlatformVersion:_appPlatformVersion + appVendorId:_appVendorId + trustkitVersion:TrustKitVersion + hostname:serverHostname + port:serverPort + dateTime:[NSDate date] // current date & time + notedHostname:notedHostname + includeSubdomains:includeSubdomains + enforcePinning:enforcePinning + validatedCertificateChain:certificateChain + knownPins:formattedPins + validationResult:validationResult + expirationDate:knownPinsExpirationDate]; + // Should we rate-limit this report? - if (_shouldRateLimitReports && [self.rateLimiter shouldRateLimitReport:report]) - { + if (_shouldRateLimitReports && [self.rateLimiter shouldRateLimitReport:report]) { // We recently sent the exact same report; do not send this report os_log_info(TSKLogHandle(), "Pin failure report for %{public}@ was not sent due to rate-limiting", serverHostname); return; } - + // Create a temporary file for storing the JSON data in ~/tmp NSURL *tmpDirURL = [NSURL fileURLWithPath:NSTemporaryDirectory() isDirectory:YES]; - NSURL *tmpFileURL = [[tmpDirURL URLByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] - URLByAppendingPathExtension:@"tsk-report"]; - + NSURL *tmpFileURL = [[tmpDirURL URLByAppendingPathComponent:[[NSProcessInfo processInfo] globallyUniqueString]] URLByAppendingPathExtension:@"tsk-report"]; + // Write the JSON report data to the temporary file NSError *error; NSUInteger writeOptions = NSDataWritingAtomic; @@ -193,15 +167,13 @@ - (void) pinValidationFailedForHostname:(nonnull NSString *)serverHostname // Ensure the report is accessible when locked on iOS, in case the App has the NSFileProtectionComplete entitlement writeOptions = writeOptions | NSDataWritingFileProtectionCompleteUntilFirstUserAuthentication; #endif - - if (!([[report json] writeToURL:tmpFileURL options:writeOptions error:&error])) - { + + if (!([[report json] writeToURL:tmpFileURL options:writeOptions error:&error])) { #if DEBUG // Only raise this exception for debug as not being able to save the report would crash a prod App // https://github.com/datatheorem/TrustKit/issues/32 // This might happen when the device's storage is full? - [NSException raise:@"TSKBackgroundReporter runtime error" - format:@"Report cannot be saved to file: %@", [error description]]; + [NSException raise:@"TSKBackgroundReporter runtime error" format:@"Report cannot be saved to file: %@", [error description]]; #endif // In production, if the report could not be written, bail out here. Otherwise we would go on to // hand a non-existent file to -uploadTaskWithRequest:fromFile:, which throws an uncaught @@ -211,37 +183,26 @@ - (void) pinValidationFailedForHostname:(nonnull NSString *)serverHostname return; } os_log_debug(TSKLogHandle(), "Report for %{public}@ created at: %@", serverHostname, [tmpFileURL path]); - - + // Create the HTTP request for all the configured report URIs and send it - for (NSURL *reportUri in reportURIs) - { + for (NSURL *reportUri in reportURIs) { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:reportUri]; [request setHTTPMethod:@"POST"]; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; - + // Pass the URL and the temporary file to the background upload task and start uploading - NSURLSessionUploadTask *uploadTask = [_backgroundSession uploadTaskWithRequest:request - fromFile:tmpFileURL]; - + NSURLSessionUploadTask *uploadTask = [_backgroundSession uploadTaskWithRequest:request fromFile:tmpFileURL]; + [uploadTask resume]; } } - - -- (void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error -{ - if (error == nil) - { +- (void)URLSession:(nonnull NSURLSession *)session task:(nonnull NSURLSessionTask *)task didCompleteWithError:(nullable NSError *)error { + if (error == nil) { os_log_info(TSKLogHandle(), "Background upload - task completed successfully: pinning failure report sent"); - } - else - { + } else { os_log_error(TSKLogHandle(), "Background upload - task completed with error: %{public}@ (code %ld)", [error localizedDescription], (long)error.code); } } - @end - diff --git a/TrustKit/Reporting/TSKPinFailureReport.h b/TrustKit/Reporting/TSKPinFailureReport.h index 029ec0d0..d56f1465 100644 --- a/TrustKit/Reporting/TSKPinFailureReport.h +++ b/TrustKit/Reporting/TSKPinFailureReport.h @@ -1,12 +1,12 @@ /* - + TSKPinFailureReport.h 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 "../Pinning/ssl_pin_verifier.h" @@ -36,24 +36,23 @@ @property (readonly, nonatomic) BOOL enforcePinning; // Not part of the HPKP spec @property (readonly, nonatomic, nullable) NSDate *knownPinsExpirationDate; // Not part of the HPKP spec - // Init with default bundle ID and current time as the date-time -- (nonnull instancetype) initWithAppBundleId:(nonnull NSString *)appBundleId - appVersion:(nonnull NSString *)appVersion - appPlatform:(nonnull NSString *)appPlatform - appPlatformVersion:(nonnull NSString *)appPlatformVersion - appVendorId:(nonnull NSString *)appVendorId - trustkitVersion:(nonnull NSString *)trustkitVersion - hostname:(nonnull NSString *)serverHostname - port:(nonnull NSNumber *)serverPort - dateTime:(nonnull NSDate *)dateTime - notedHostname:(nonnull NSString *)notedHostname - includeSubdomains:(BOOL)includeSubdomains - enforcePinning:(BOOL)enforcePinning - validatedCertificateChain:(nonnull NSArray *)validatedCertificateChain - knownPins:(nonnull NSArray *)knownPins - validationResult:(TSKTrustEvaluationResult)validationResult - expirationDate:(nullable NSDate *)knownPinsExpirationDate; +- (nonnull instancetype)initWithAppBundleId:(nonnull NSString *)appBundleId + appVersion:(nonnull NSString *)appVersion + appPlatform:(nonnull NSString *)appPlatform + appPlatformVersion:(nonnull NSString *)appPlatformVersion + appVendorId:(nonnull NSString *)appVendorId + trustkitVersion:(nonnull NSString *)trustkitVersion + hostname:(nonnull NSString *)serverHostname + port:(nonnull NSNumber *)serverPort + dateTime:(nonnull NSDate *)dateTime + notedHostname:(nonnull NSString *)notedHostname + includeSubdomains:(BOOL)includeSubdomains + enforcePinning:(BOOL)enforcePinning + validatedCertificateChain:(nonnull NSArray *)validatedCertificateChain + knownPins:(nonnull NSArray *)knownPins + validationResult:(TSKTrustEvaluationResult)validationResult + expirationDate:(nullable NSDate *)knownPinsExpirationDate; // Return the report in JSON format for POSTing it - (nonnull NSData *)json; @@ -61,5 +60,4 @@ // Return a request ready to be sent with the report in JSON format in the response's body - (nonnull NSMutableURLRequest *)requestToUri:(nonnull NSURL *)reportUri; - @end diff --git a/TrustKit/Reporting/TSKPinFailureReport.m b/TrustKit/Reporting/TSKPinFailureReport.m index 99d29320..f01af034 100644 --- a/TrustKit/Reporting/TSKPinFailureReport.m +++ b/TrustKit/Reporting/TSKPinFailureReport.m @@ -1,38 +1,36 @@ /* - + TSKPinFailureReport.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 "TSKPinFailureReport.h" @implementation TSKPinFailureReport -- (nonnull instancetype) initWithAppBundleId:(nonnull NSString *)appBundleId - appVersion:(nonnull NSString *)appVersion - appPlatform:(nonnull NSString *)appPlatform - appPlatformVersion:(nonnull NSString *)appPlatformVersion - appVendorId:(nonnull NSString *)appVendorId - trustkitVersion:(nonnull NSString *)trustkitVersion - hostname:(nonnull NSString *)serverHostname - port:(nonnull NSNumber *)serverPort - dateTime:(nonnull NSDate *)dateTime - notedHostname:(nonnull NSString *)notedHostname - includeSubdomains:(BOOL)includeSubdomains - enforcePinning:(BOOL)enforcePinning - validatedCertificateChain:(nonnull NSArray *)validatedCertificateChain - knownPins:(nonnull NSArray *)knownPins - validationResult:(TSKTrustEvaluationResult)validationResult - expirationDate:(nullable NSDate *)knownPinsExpirationDate -{ +- (nonnull instancetype)initWithAppBundleId:(nonnull NSString *)appBundleId + appVersion:(nonnull NSString *)appVersion + appPlatform:(nonnull NSString *)appPlatform + appPlatformVersion:(nonnull NSString *)appPlatformVersion + appVendorId:(nonnull NSString *)appVendorId + trustkitVersion:(nonnull NSString *)trustkitVersion + hostname:(nonnull NSString *)serverHostname + port:(nonnull NSNumber *)serverPort + dateTime:(nonnull NSDate *)dateTime + notedHostname:(nonnull NSString *)notedHostname + includeSubdomains:(BOOL)includeSubdomains + enforcePinning:(BOOL)enforcePinning + validatedCertificateChain:(nonnull NSArray *)validatedCertificateChain + knownPins:(nonnull NSArray *)knownPins + validationResult:(TSKTrustEvaluationResult)validationResult + expirationDate:(nullable NSDate *)knownPinsExpirationDate { self = [super init]; - if (self) - { + if (self) { _appBundleId = appBundleId; _appVersion = appVersion; _appPlatform = appPlatform; @@ -53,7 +51,6 @@ - (nonnull instancetype) initWithAppBundleId:(nonnull NSString *)appBundleId return self; } - - (nonnull NSData *)json; { // NSDateFormatter (and NSNumberFormatter) is extremely expensive to initialize, doesn't @@ -65,70 +62,66 @@ - (nonnull NSData *)json; /// Date AND time formatter for JSON DateTimeFormatter = ({ NSDateFormatter *df = [[NSDateFormatter alloc] init]; - + // en_US_POSIX keeps the fixed format independent of the user's locale and 12/24-hour setting df.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - + df.dateFormat = @"yyyy-MM-dd'T'HH:mm:ss'Z'"; df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; df; }); - + /// Date ONLY formatter DateFormatter = ({ NSDateFormatter *df = [[NSDateFormatter alloc] init]; - + // en_US_POSIX keeps the fixed format independent of the user's locale and 12/24-hour setting df.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"]; - + df.dateFormat = @"yyyy-MM-dd"; df.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; df; }); }); - + id dateStr = [NSNull null]; - if (self.dateTime) - { + if (self.dateTime) { dateStr = [DateTimeFormatter stringFromDate:self.dateTime] ?: [NSNull null]; } - + id expirationDateStr = [NSNull null]; - if (self.knownPinsExpirationDate) - { + if (self.knownPinsExpirationDate) { // For the expiration date, only return the expiration day, as specified in the pinning policy expirationDateStr = [DateFormatter stringFromDate:self.knownPinsExpirationDate] ?: [NSNull null]; } - + // Create the dictionary NSDictionary *requestData = @{ - @"app-bundle-id": self.appBundleId, - @"app-version": self.appVersion, - @"app-platform": self.appPlatform, - @"app-platform-version": self.appPlatformVersion, - @"app-vendor-id": self.appVendorId, - @"trustkit-version": self.trustkitVersion, - @"date-time": dateStr, - @"hostname": self.hostname, - @"port": self.port, - @"noted-hostname": self.notedHostname, - @"include-subdomains": @(self.includeSubdomains), - @"enforce-pinning": @(self.enforcePinning), + @"app-bundle-id": self.appBundleId, + @"app-version": self.appVersion, + @"app-platform": self.appPlatform, + @"app-platform-version": self.appPlatformVersion, + @"app-vendor-id": self.appVendorId, + @"trustkit-version": self.trustkitVersion, + @"date-time": dateStr, + @"hostname": self.hostname, + @"port": self.port, + @"noted-hostname": self.notedHostname, + @"include-subdomains": @(self.includeSubdomains), + @"enforce-pinning": @(self.enforcePinning), @"validated-certificate-chain": self.validatedCertificateChain, - @"known-pins": self.knownPins, - @"validation-result": @(self.validationResult), - @"known-pins-expiration-date": expirationDateStr + @"known-pins": self.knownPins, + @"validation-result": @(self.validationResult), + @"known-pins-expiration-date": expirationDateStr }; - + NSError *error; NSData *jsonData = [NSJSONSerialization dataWithJSONObject:requestData options:(NSJSONWritingOptions)0 error:&error]; // FIXME: error is unhandled. return jsonData; } - -- (nonnull NSMutableURLRequest *)requestToUri:(NSURL *)reportUri -{ +- (nonnull NSMutableURLRequest *)requestToUri:(NSURL *)reportUri { NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:reportUri]; request.HTTPMethod = @"POST"; [request setValue:@"application/json" forHTTPHeaderField:@"Content-Type"]; @@ -136,5 +129,4 @@ - (nonnull NSMutableURLRequest *)requestToUri:(NSURL *)reportUri return request; } - @end diff --git a/TrustKit/Reporting/TSKReportsRateLimiter.h b/TrustKit/Reporting/TSKReportsRateLimiter.h index 220fd86f..afd6ee8b 100644 --- a/TrustKit/Reporting/TSKReportsRateLimiter.h +++ b/TrustKit/Reporting/TSKReportsRateLimiter.h @@ -1,12 +1,12 @@ /* - + TSKReportsRateLimiter.h 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 "TSKPinFailureReport.h" @@ -17,7 +17,6 @@ #import #endif - /* * Simple helper class which caches reports for 24 hours to prevent identical reports from being sent twice * during this 24 hour period. @@ -33,6 +32,6 @@ @return True if the report should be ignored under the rate-limiting policy that is in effect. */ -- (BOOL)shouldRateLimitReport:(TSKPinFailureReport * _Nonnull)report; +- (BOOL)shouldRateLimitReport:(TSKPinFailureReport *_Nonnull)report; @end diff --git a/TrustKit/Reporting/TSKReportsRateLimiter.m b/TrustKit/Reporting/TSKReportsRateLimiter.m index 1d5588fc..d7ed79f1 100644 --- a/TrustKit/Reporting/TSKReportsRateLimiter.m +++ b/TrustKit/Reporting/TSKReportsRateLimiter.m @@ -1,12 +1,12 @@ /* - + TSKReportsRateLimiter.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 "TSKReportsRateLimiter.h" @@ -29,8 +29,7 @@ @interface TSKReportsRateLimiter () @implementation TSKReportsRateLimiter -- (instancetype)init -{ +- (instancetype)init { self = [super init]; if (self) { // Initialize all the internal state for rate-limiting report uploads @@ -41,51 +40,41 @@ - (instancetype)init return self; } -- (BOOL)shouldRateLimitReport:(TSKPinFailureReport *)report -{ +- (BOOL)shouldRateLimitReport:(TSKPinFailureReport *)report { NSParameterAssert(report); - + // Check if we need to clear the reports cache for rate-limiting NSTimeInterval secondsSinceCacheReset = -[self.lastReportsCacheResetDate timeIntervalSinceNow]; - + // Create an array containg the gist of the pin failure report; do not include the dates - NSArray *pinFailureInfo = @[ report.notedHostname, - report.hostname, - report.port, - report.validatedCertificateChain, - report.knownPins, - @(report.validationResult) ]; - + NSArray *pinFailureInfo = @[report.notedHostname, report.hostname, report.port, report.validatedCertificateChain, report.knownPins, @(report.validationResult)]; + __block BOOL shouldRateLimitReport = NO; __weak typeof(self) weakSelf = self; dispatch_sync(self.reportsCacheQueue, ^{ typeof(self) strongSelf = weakSelf; - - if (secondsSinceCacheReset > kIntervalBetweenReportsCacheReset) - { + + if (secondsSinceCacheReset > kIntervalBetweenReportsCacheReset) { // Reset the cache [strongSelf.reportsCache removeAllObjects]; strongSelf.lastReportsCacheResetDate = [NSDate date]; } - + // Check if the exact same report has already been sent recently shouldRateLimitReport = [strongSelf.reportsCache containsObject:pinFailureInfo]; - if (shouldRateLimitReport == NO) - { + if (shouldRateLimitReport == NO) { // An identical report has NOT been sent recently // Add this report to the cache for rate-limiting [strongSelf.reportsCache addObject:pinFailureInfo]; } }); - + return shouldRateLimitReport; } -- (void)setLastReportsCacheResetDate:(NSDate *)lastReportsCacheResetDate -{ +- (void)setLastReportsCacheResetDate:(NSDate *)lastReportsCacheResetDate { NSParameterAssert(lastReportsCacheResetDate); _lastReportsCacheResetDate = lastReportsCacheResetDate; } @end - diff --git a/TrustKit/Reporting/reporting_utils.h b/TrustKit/Reporting/reporting_utils.h index 8f55a36c..8cfc34a2 100644 --- a/TrustKit/Reporting/reporting_utils.h +++ b/TrustKit/Reporting/reporting_utils.h @@ -1,12 +1,12 @@ /* - + reporting_utils.h 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. - + */ #if __has_feature(modules) diff --git a/TrustKit/Reporting/reporting_utils.m b/TrustKit/Reporting/reporting_utils.m index 325a24f1..9902ad3b 100644 --- a/TrustKit/Reporting/reporting_utils.m +++ b/TrustKit/Reporting/reporting_utils.m @@ -1,47 +1,40 @@ /* - + reporting_utils.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 "reporting_utils.h" #import "../Pinning/pinning_utils.h" -NSArray *convertTrustToPemArray(SecTrustRef serverTrust) -{ +NSArray *convertTrustToPemArray(SecTrustRef serverTrust) { // Convert the trust object into an array of PEM certificates // Warning: SecTrustEvaluate() always needs to be called first on the serverTrust to be able to extract the certificates NSMutableArray *certificateChain = [NSMutableArray array]; CFIndex chainLen = SecTrustGetCertificateCount(serverTrust); - for (CFIndex i=0;i *convertPinsToHpkpPins(NSSet *knownPins) -{ +NSArray *convertPinsToHpkpPins(NSSet *knownPins) { // Convert the know pins from a set of data to an array of strings as described in the HPKP spec NSMutableArray *formattedPins = [NSMutableArray array]; - for (NSData *pin in knownPins) - { + for (NSData *pin in knownPins) { [formattedPins addObject:[NSString stringWithFormat:@"pin-sha256=\"%@\"", [pin base64EncodedStringWithOptions:(NSDataBase64EncodingOptions)0]]]; } return formattedPins; } - diff --git a/TrustKit/Reporting/vendor_identifier.h b/TrustKit/Reporting/vendor_identifier.h index 225cb207..d003041e 100644 --- a/TrustKit/Reporting/vendor_identifier.h +++ b/TrustKit/Reporting/vendor_identifier.h @@ -1,12 +1,12 @@ /* - + vendor_identifier.h TrustKit - + Copyright 2016 The TrustKit Project Authors Licensed under the MIT license, see associated LICENSE file for terms. See AUTHORS file for the list of project authors. - + */ #if __has_feature(modules) diff --git a/TrustKit/Reporting/vendor_identifier.m b/TrustKit/Reporting/vendor_identifier.m index 711cafd5..dee193d7 100644 --- a/TrustKit/Reporting/vendor_identifier.m +++ b/TrustKit/Reporting/vendor_identifier.m @@ -1,25 +1,23 @@ /* - + vendor_identifier.m TrustKit - + Copyright 2016 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 "vendor_identifier.h" -static NSString * const kTSKVendorIdentifierKey = @"TSKVendorIdentifier"; +static NSString *const kTSKVendorIdentifierKey = @"TSKVendorIdentifier"; -NSString *identifier_for_vendor(void) -{ +NSString *identifier_for_vendor(void) { // Try to retrieve the vendor ID from the preferences NSUserDefaults *preferences = NSUserDefaults.standardUserDefaults; NSString *vendorId = [preferences stringForKey:kTSKVendorIdentifierKey]; - if (vendorId == nil) - { + if (vendorId == nil) { // Generate and store a new UUID vendorId = NSUUID.UUID.UUIDString; [preferences setObject:vendorId forKey:kTSKVendorIdentifierKey]; @@ -27,5 +25,3 @@ } return vendorId; } - - diff --git a/TrustKit/Swift/SubjectPublicKeyInfo.swift b/TrustKit/Swift/SubjectPublicKeyInfo.swift index 2529f8a9..8c7e43e1 100644 --- a/TrustKit/Swift/SubjectPublicKeyInfo.swift +++ b/TrustKit/Swift/SubjectPublicKeyInfo.swift @@ -1,13 +1,9 @@ -/* - - SubjectPublicKeyInfo.swift - TrustKit - - Copyright 2026 The TrustKit Project Authors - Licensed under the MIT license, see associated LICENSE file for terms. - See AUTHORS file for the list of project authors. - - */ +// SubjectPublicKeyInfo.swift +// TrustKit +// +// Copyright 2026 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 Foundation @@ -33,7 +29,8 @@ private struct DERElement { private func subjectPublicKeyInfoRange(in der: [UInt8]) -> Range? { guard let certificate = readElement(der, in: der.indices), certificate.tag == 0x30, - let tbsCertificate = readElement(der, in: certificate.content), tbsCertificate.tag == 0x30 else { + let tbsCertificate = readElement(der, in: certificate.content), tbsCertificate.tag == 0x30 + else { return nil } @@ -74,7 +71,9 @@ private func readElement(_ der: [UInt8], in range: Range) -> DERElement? { guard range.upperBound - contentStart >= length else { return nil } - return DERElement(tag: tag, - content: contentStart.. +@interface TSKNSURLConnectionDelegateProxy : NSObject // Initalize our hooks + (void)swizzleNSURLConnectionConstructors:(TrustKit *)trustKit; diff --git a/TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.m b/TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.m index 35691210..08cde199 100644 --- a/TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.m +++ b/TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.m @@ -1,20 +1,20 @@ /* - + TSKNSURLConnectionDelegateProxy.h 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 "TSKNSURLConnectionDelegateProxy.h" -#import "../public/TrustKit/TrustKit.h" +#import "../Dependencies/RSSwizzle/RSSwizzle.h" #import "../TSKLog.h" -#import "../public/TrustKit/TSKTrustDecision.h" #import "../public/TrustKit/TSKPinningValidator.h" -#import "../Dependencies/RSSwizzle/RSSwizzle.h" +#import "../public/TrustKit/TSKTrustDecision.h" +#import "../public/TrustKit/TrustKit.h" typedef void (^AsyncCompletionHandler)(NSURLResponse *response, NSData *data, NSError *connectionError); @@ -27,101 +27,103 @@ @implementation TSKNSURLConnectionDelegateProxy #pragma mark Public methods -+ (void)swizzleNSURLConnectionConstructors:(TrustKit *)trustKit -{ ++ (void)swizzleNSURLConnectionConstructors:(TrustKit *)trustKit { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wshadow" // - initWithRequest:delegate: - RSSwizzleInstanceMethod(NSClassFromString(@"NSURLConnection"), - @selector(initWithRequest:delegate:), - RSSWReturnType(NSURLConnection*), - RSSWArguments(NSURLRequest *request, id delegate), - RSSWReplacement( - { - NSURLConnection *connection; - - if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"]) - { - // Don't proxy ourselves - connection = RSSWCallOriginal(request, delegate); - } - else - { - // Replace the delegate with our own so we can intercept and handle authentication challenges - TSKNSURLConnectionDelegateProxy *swizzledDelegate = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:trustKit - connectionDelegate:delegate]; - connection = RSSWCallOriginal(request, swizzledDelegate); - } - return connection; - }), RSSwizzleModeAlways, NULL); - - - + RSSwizzleInstanceMethod( + NSClassFromString(@"NSURLConnection"), + @selector(initWithRequest:delegate:), + RSSWReturnType(NSURLConnection *), + RSSWArguments(NSURLRequest * request, id delegate), + RSSWReplacement({ + NSURLConnection *connection; + + if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"]) { + // Don't proxy ourselves + connection = RSSWCallOriginal(request, delegate); + } else { + // Replace the delegate with our own so we can intercept and handle authentication challenges + TSKNSURLConnectionDelegateProxy *swizzledDelegate = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:trustKit connectionDelegate:delegate]; + connection = RSSWCallOriginal(request, swizzledDelegate); + } + return connection; + }), + RSSwizzleModeAlways, + NULL + ); + // - initWithRequest:delegate:startImmediately: - RSSwizzleInstanceMethod(NSClassFromString(@"NSURLConnection"), - @selector(initWithRequest:delegate:startImmediately:), - RSSWReturnType(NSURLConnection*), - RSSWArguments(NSURLRequest *request, id delegate, BOOL startImmediately), - RSSWReplacement( - { - NSURLConnection *connection; - - if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"]) - { - // Don't proxy ourselves - connection = RSSWCallOriginal(request, delegate, startImmediately); - } - else - { - // Replace the delegate with our own so we can intercept and handle authentication challenges - TSKNSURLConnectionDelegateProxy *swizzledDelegate = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:trustKit - connectionDelegate:delegate]; - connection = RSSWCallOriginal(request, swizzledDelegate, startImmediately); - } - return connection; - }), RSSwizzleModeAlways, NULL); - - + RSSwizzleInstanceMethod( + NSClassFromString(@"NSURLConnection"), + @selector(initWithRequest:delegate:startImmediately:), + RSSWReturnType(NSURLConnection *), + RSSWArguments(NSURLRequest * request, id delegate, BOOL startImmediately), + RSSWReplacement({ + NSURLConnection *connection; + + if ([NSStringFromClass([delegate class]) hasPrefix:@"TSK"]) { + // Don't proxy ourselves + connection = RSSWCallOriginal(request, delegate, startImmediately); + } else { + // Replace the delegate with our own so we can intercept and handle authentication challenges + TSKNSURLConnectionDelegateProxy *swizzledDelegate = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:trustKit connectionDelegate:delegate]; + connection = RSSWCallOriginal(request, swizzledDelegate, startImmediately); + } + return connection; + }), + RSSwizzleModeAlways, + NULL + ); + // Not hooking + connectionWithRequest:delegate: as it ends up calling initWithRequest:delegate: - + // Log a warning for methods that do not have a delegate (ie. we can't protect these connections) // + sendAsynchronousRequest:queue:completionHandler: - - RSSwizzleClassMethod(NSClassFromString(@"NSURLConnection"), - @selector(sendAsynchronousRequest:queue:completionHandler:), - RSSWReturnType(void), - RSSWArguments(NSURLRequest *request, NSOperationQueue *queue, AsyncCompletionHandler handler), - RSSWReplacement( - { - // Just display a warning - os_log_error(TSKLogHandle(), "+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]); - RSSWCallOriginal(request, queue, handler); - })); - - + + RSSwizzleClassMethod( + NSClassFromString(@"NSURLConnection"), + @selector(sendAsynchronousRequest:queue:completionHandler:), + RSSWReturnType(void), + RSSWArguments(NSURLRequest * request, NSOperationQueue * queue, AsyncCompletionHandler handler), + RSSWReplacement({ + // Just display a warning + os_log_error( + TSKLogHandle(), + "+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] + ); + RSSWCallOriginal(request, queue, handler); + }) + ); + // + sendSynchronousRequest:returningResponse:error: - RSSwizzleClassMethod(NSClassFromString(@"NSURLConnection"), - @selector(sendSynchronousRequest:returningResponse:error:), - RSSWReturnType(NSData *), - RSSWArguments(NSURLRequest *request, NSURLResponse * _Nullable *response, NSError * _Nullable *error), - RSSWReplacement( - { - // Just display a warning - os_log_error(TSKLogHandle(), "+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]); - NSData *data = RSSWCallOriginal(request, response, error); - return data; - })); + RSSwizzleClassMethod( + NSClassFromString(@"NSURLConnection"), + @selector(sendSynchronousRequest:returningResponse:error:), + RSSWReturnType(NSData *), + RSSWArguments(NSURLRequest * request, NSURLResponse *_Nullable * response, NSError *_Nullable * error), + RSSWReplacement({ + // Just display a warning + os_log_error( + TSKLogHandle(), + "+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] + ); + NSData *data = RSSWCallOriginal(request, response, error); + return data; + }) + ); #pragma clang diagnostic pop } - #pragma mark Instance Constructors -- (instancetype)initWithTrustKit:(TrustKit *)trustKit connectionDelegate:(id)delegate -{ +- (instancetype)initWithTrustKit:(TrustKit *)trustKit connectionDelegate:(id)delegate { self = [super init]; - if (self) - { + if (self) { _originalDelegate = delegate; _trustKit = trustKit; } @@ -131,45 +133,35 @@ - (instancetype)initWithTrustKit:(TrustKit *)trustKit connectionDelegate:(id)delegate NS_DESIGNATED_INITIALIZER; -- (void)URLSession:(NSURLSession *)session -didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(TSKURLSessionAuthChallengeCallback)completionHandler; +- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(TSKURLSessionAuthChallengeCallback)completionHandler; - (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge - completionHandler:(TSKURLSessionAuthChallengeCallback)completionHandler; + task:(NSURLSessionTask *)task + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(TSKURLSessionAuthChallengeCallback)completionHandler; // Forward messages to the original delegate if the proxy doesn't implement the method - (id)forwardingTargetForSelector:(SEL)sel; diff --git a/TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.m b/TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.m index 93166850..d329e9ff 100644 --- a/TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.m +++ b/TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.m @@ -1,20 +1,20 @@ /* - + 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 "../public/TrustKit/TrustKit.h" +#import "../Dependencies/RSSwizzle/RSSwizzle.h" #import "../TSKLog.h" -#import "../public/TrustKit/TSKTrustDecision.h" #import "../public/TrustKit/TSKPinningValidator.h" -#import "../Dependencies/RSSwizzle/RSSwizzle.h" +#import "../public/TrustKit/TSKTrustDecision.h" +#import "../public/TrustKit/TrustKit.h" @interface TSKNSURLSessionDelegateProxy () /* The NSURLSessionDelegate we're going to proxy */ @@ -26,53 +26,50 @@ @implementation TSKNSURLSessionDelegateProxy #pragma mark Public methods -+ (void)swizzleNSURLSessionConstructors:(TrustKit *)trustKit -{ ++ (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) - { - os_log_debug(TSKLogHandle(), "+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; - })); + 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) { + os_log_debug( + TSKLogHandle(), + "+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)delegate -{ +- (instancetype _Nullable)initWithTrustKit:(TrustKit *)trustKit sessionDelegate:(id)delegate { NSParameterAssert(delegate); - + self = [super init]; - if (self) - { + if (self) { _originalDelegate = delegate; _trustKit = trustKit; } @@ -82,58 +79,39 @@ - (instancetype _Nullable)initWithTrustKit:(TrustKit *)trustKit sessionDelegate: #pragma mark Delegate methods -- (BOOL)respondsToSelector:(SEL)aSelector -{ - if (aSelector == @selector(URLSession:task:didReceiveChallenge:completionHandler:)) - { +- (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) - { + } 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) - { + } 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 - { + } else { // Let the task-level handler handle auth challenges return NO; } - } - else - { + } 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 -{ +- (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 -{ +- (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]) - { + 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) - { + 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! @@ -142,49 +120,38 @@ - (BOOL)common_URLSession:(NSURLSession * _Nonnull)session return NO; } -- (void)URLSession:(NSURLSession * _Nonnull)session -didReceiveChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge - completionHandler:(TSKURLSessionAuthChallengeCallback)completionHandler -{ - if ([self common_URLSession:session challenge:challenge completionHandler:completionHandler]) - { +- (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:)]) - { + if ([_originalDelegate respondsToSelector:@selector(URLSession:didReceiveChallenge:completionHandler:)]) { [_originalDelegate URLSession:session didReceiveChallenge:challenge completionHandler:completionHandler]; - } - else - { + } 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]) - { +- (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:)]) - { + if ([_originalDelegate respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]) { [_originalDelegate URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; - } - else - { + } else { // The original delegate could not handle the challenge; use the default handler completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, NULL); } diff --git a/TrustKit/TSKLog.h b/TrustKit/TSKLog.h index eecaa988..7b26f90a 100644 --- a/TrustKit/TSKLog.h +++ b/TrustKit/TSKLog.h @@ -1,12 +1,12 @@ /* - + TSKLog.h 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. - + */ #ifndef TSKLog_h @@ -16,5 +16,4 @@ os_log_t TSKLogHandle(void); - #endif /* TSKLog_h */ diff --git a/TrustKit/TSKPinningValidator.m b/TrustKit/TSKPinningValidator.m index 5a30edb2..6924fb09 100644 --- a/TrustKit/TSKPinningValidator.m +++ b/TrustKit/TSKPinningValidator.m @@ -1,25 +1,24 @@ /* - + TSKPinningValidator.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 "public/TrustKit/TSKPinningValidator.h" -#import "public/TrustKit/TSKTrustKitConfig.h" -#import "public/TrustKit/TSKTrustDecision.h" -#import "public/TrustKit/TSKPinningValidatorResult.h" #import "Pinning/TSKSPKIHashCache.h" #import "Pinning/ssl_pin_verifier.h" -#import "configuration_utils.h" -#import "public/TrustKit/TrustKit.h" #import "TSKLog.h" #import "TSKPinningValidator_Private.h" - +#import "configuration_utils.h" +#import "public/TrustKit/TSKPinningValidatorResult.h" +#import "public/TrustKit/TSKTrustDecision.h" +#import "public/TrustKit/TSKTrustKitConfig.h" +#import "public/TrustKit/TrustKit.h" @interface TSKPinningValidator () @@ -50,8 +49,7 @@ to platforms that support a user trust store (Mac OS). @implementation TSKPinningValidator -+ (BOOL)allowsAdditionalTrustAnchors -{ ++ (BOOL)allowsAdditionalTrustAnchors { static const BOOL allowAdditionalTrustAnchors = { #if DEBUG == 1 YES @@ -64,12 +62,11 @@ + (BOOL)allowsAdditionalTrustAnchors #pragma mark Instance Methods -- (instancetype _Nullable)initWithDomainPinningPolicies:(NSDictionary * _Nonnull)domainPinningPolicies - hashCache:(TSKSPKIHashCache * _Nonnull)hashCache +- (instancetype _Nullable)initWithDomainPinningPolicies:(NSDictionary *_Nonnull)domainPinningPolicies + hashCache:(TSKSPKIHashCache *_Nonnull)hashCache ignorePinsForUserTrustAnchors:(BOOL)ignorePinsForUserTrustAnchors validationCallbackQueue:(dispatch_queue_t _Nonnull)validationCallbackQueue - validationCallback:(TSKPinningValidatorCallback)validationCallback -{ + validationCallback:(TSKPinningValidatorCallback)validationCallback { self = [super init]; if (self) { _domainPinningPolicies = domainPinningPolicies; @@ -81,95 +78,70 @@ - (instancetype _Nullable)initWithDomainPinningPolicies:(NSDictionary *)trustKitConfig -{ ++ (void)initSharedInstanceWithConfiguration:(NSDictionary *)trustKitConfig { return [self initSharedInstanceWithConfiguration:trustKitConfig sharedContainerIdentifier:nil]; } -+ (void)initSharedInstanceWithConfiguration:(NSDictionary *)trustKitConfig - sharedContainerIdentifier:(NSString *)sharedContainerIdentifier -{ ++ (void)initSharedInstanceWithConfiguration:(NSDictionary *)trustKitConfig sharedContainerIdentifier:(NSString *)sharedContainerIdentifier { os_log_debug(TSKLogHandle(), "Configuration passed via explicit call to initSharedInstanceWithConfiguration:"); - + static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - sharedTrustKit = [[TrustKit alloc] initWithConfiguration:trustKitConfig - sharedContainerIdentifier:sharedContainerIdentifier - isSingleton:YES]; - + sharedTrustKit = [[TrustKit alloc] initWithConfiguration:trustKitConfig sharedContainerIdentifier:sharedContainerIdentifier isSingleton:YES]; + // Hook network APIs if needed if ([sharedTrustKit.configuration[kTSKSwizzleNetworkDelegates] boolValue]) { // NSURLConnection [TSKNSURLConnectionDelegateProxy swizzleNSURLConnectionConstructors:sharedTrustKit]; - + // NSURLSession [TSKNSURLSessionDelegateProxy swizzleNSURLSessionConstructors:sharedTrustKit]; } }); } - #pragma mark Instance Initialization - -- (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig -{ +- (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig { return [self initWithConfiguration:trustKitConfig sharedContainerIdentifier:nil]; } -- (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig - sharedContainerIdentifier:(NSString *)sharedContainerIdentifier -{ - return [self initWithConfiguration:trustKitConfig - sharedContainerIdentifier:sharedContainerIdentifier - isSingleton:NO]; +- (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig sharedContainerIdentifier:(NSString *)sharedContainerIdentifier { + return [self initWithConfiguration:trustKitConfig sharedContainerIdentifier:sharedContainerIdentifier isSingleton:NO]; } - - (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig sharedContainerIdentifier:(NSString *)sharedContainerIdentifier - isSingleton:(BOOL)isSingleton -{ + isSingleton:(BOOL)isSingleton { NSParameterAssert(trustKitConfig); if (!trustKitConfig) { return nil; } - + self = [super init]; if (self && [trustKitConfig count] > 0) { // Convert and store the SSL pins in our global variable _configuration = parseTrustKitConfiguration(trustKitConfig); - + _pinningValidatorCallbackQueue = dispatch_get_main_queue(); - + // Create a dispatch queue for activating the reporter // We use a serial queue targetting the global default queue in order to ensure reports are sent one by one // even when a lot of pin failures are occuring, instead of spamming the global queue with events to process _pinFailureReporterQueue = dispatch_queue_create(kTSKPinFailureReporterQueueLabel, DISPATCH_QUEUE_SERIAL); - + // Create our reporter for sending pin validation failures; do this before hooking NSURLSession so we don't hook ourselves - _pinFailureReporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:YES - sharedContainerIdentifier:sharedContainerIdentifier]; - + _pinFailureReporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:YES sharedContainerIdentifier:sharedContainerIdentifier]; + // Handle global configuration flags here // TSKIgnorePinningForUserDefinedTrustAnchors #if TARGET_OS_IPHONE @@ -154,83 +127,77 @@ - (instancetype)initWithConfiguration:(NSDictionary *)notedHostnamePinningPolicy -{ +- (void)sendValidationReport:(TSKPinningValidatorResult *)result + notedHostname:(NSString *)notedHostname + pinningPolicy:(NSDictionary *)notedHostnamePinningPolicy { TSKTrustEvaluationResult validationResult = result.evaluationResult; - + // Send a report only if the there was a pinning failure - if (validationResult != TSKTrustEvaluationSuccess) - { + if (validationResult != TSKTrustEvaluationSuccess) { #if !TARGET_OS_IPHONE if (validationResult != TSKTrustEvaluationFailedUserDefinedTrustAnchor) #endif { // Pin validation failed: retrieve the list of configured report URLs NSMutableArray *reportUris = [NSMutableArray arrayWithArray:notedHostnamePinningPolicy[kTSKReportUris]]; - + // Also enable the default reporting URL - if ([notedHostnamePinningPolicy[kTSKDisableDefaultReportUri] boolValue] == NO) - { + if ([notedHostnamePinningPolicy[kTSKDisableDefaultReportUri] boolValue] == NO) { [reportUris addObject:[NSURL URLWithString:kTSKDefaultReportUri]]; } - + // If some report URLs have been defined, send the pin failure report - if (reportUris.count > 0) - { + if (reportUris.count > 0) { [self.pinFailureReporter pinValidationFailedForHostname:result.serverHostname port:nil certificateChain:result.certificateChain @@ -246,26 +213,22 @@ - (void)sendValidationReport:(TSKPinningValidatorResult *)result notedHostname:( } } -- (void)setPinningValidatorCallbackQueue:(dispatch_queue_t)pinningValidatorCallbackQueue -{ +- (void)setPinningValidatorCallbackQueue:(dispatch_queue_t)pinningValidatorCallbackQueue { _pinningValidatorCallbackQueue = pinningValidatorCallbackQueue ?: dispatch_get_main_queue(); } @end - #pragma mark TrustKit Implicit Initialization via Library Constructor #if !DISABLE_INIT_WITH_INFO_PLIST -__attribute__((constructor)) static void initializeWithInfoPlist(int argc, const char **argv) -{ +__attribute__((constructor)) static void initializeWithInfoPlist(int argc, const char **argv) { // TrustKit just got started in the App CFBundleRef appBundle = CFBundleGetMainBundle(); - + // Retrieve the configuration from the App's Info.plist file NSDictionary *trustKitConfigFromInfoPlist = (__bridge NSDictionary *)CFBundleGetValueForInfoDictionaryKey(appBundle, (__bridge CFStringRef)kTSKConfiguration); - if (trustKitConfigFromInfoPlist) - { + if (trustKitConfigFromInfoPlist) { os_log_debug(TSKLogHandle(), "Configuration supplied via the App's Info.plist"); [TrustKit initSharedInstanceWithConfiguration:trustKitConfigFromInfoPlist]; } diff --git a/TrustKit/configuration_utils.h b/TrustKit/configuration_utils.h index f2d34d9e..6d4f9d87 100644 --- a/TrustKit/configuration_utils.h +++ b/TrustKit/configuration_utils.h @@ -1,12 +1,12 @@ /* - + configuration_utils.h TrustKit - + Copyright 2017 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 "public/TrustKit/TSKPinningValidatorCallback.h" @@ -18,16 +18,16 @@ #endif // Lowercase the hostname, drop a trailing dot and IPv6 brackets, and write IP addresses in their standard form -NSString * _Nonnull canonicalHostname(NSString * _Nonnull hostname); +NSString *_Nonnull canonicalHostname(NSString *_Nonnull hostname); -BOOL isIPAddress(NSString * _Nonnull hostname); +BOOL isIPAddress(NSString *_Nonnull hostname); // Whether a canonical hostname is an IP address or a well-formed domain name -BOOL isValidPinnedDomain(NSString * _Nonnull domain); +BOOL isValidPinnedDomain(NSString *_Nonnull domain); // Figure out if a specific domain is pinned and retrieve this domain's configuration key; returns nil if no configuration was found -NSString * _Nullable getPinningConfigurationKeyForDomain(NSString * _Nonnull hostname , NSDictionary * _Nonnull domainPinningPolicies); +NSString *_Nullable getPinningConfigurationKeyForDomain(NSString *_Nonnull hostname, NSDictionary *_Nonnull domainPinningPolicies); // Create an ephemeral NSURLSessionConfiguration, with best practices defaults // for the current platform -NSURLSessionConfiguration * _Nonnull ephemeralNSURLSessionConfiguration(void); +NSURLSessionConfiguration *_Nonnull ephemeralNSURLSessionConfiguration(void); diff --git a/TrustKit/configuration_utils.m b/TrustKit/configuration_utils.m index 9473c4cf..070bb0f5 100644 --- a/TrustKit/configuration_utils.m +++ b/TrustKit/configuration_utils.m @@ -1,124 +1,112 @@ /* - + configuration_utils.m TrustKit - + Copyright 2017 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 "configuration_utils.h" -#import "public/TrustKit/TSKTrustKitConfig.h" #import "TSKLog.h" +#import "public/TrustKit/TSKTrustKitConfig.h" #include - -static NSString * _Nullable canonicalIPAddress(NSString *hostname) -{ +static NSString *_Nullable canonicalIPAddress(NSString *hostname) { // inet_pton embeds an IPv6 zone such as %en0 into the address instead of rejecting it - if ([hostname containsString:@"%"]) - { + if ([hostname containsString:@"%"]) { return nil; } - - const int addressFamilies[] = {AF_INET, AF_INET6}; - for (size_t i = 0; i < sizeof(addressFamilies) / sizeof(addressFamilies[0]); i++) - { + + const int addressFamilies[] = { AF_INET, AF_INET6 }; + for (size_t i = 0; i < sizeof(addressFamilies) / sizeof(addressFamilies[0]); i++) { struct in6_addr address; char addressString[INET6_ADDRSTRLEN]; - if (inet_pton(addressFamilies[i], [hostname UTF8String], &address) == 1) - { + if (inet_pton(addressFamilies[i], [hostname UTF8String], &address) == 1) { return @(inet_ntop(addressFamilies[i], &address, addressString, sizeof(addressString))); } } return nil; } -NSString *canonicalHostname(NSString *hostname) -{ +NSString *canonicalHostname(NSString *hostname) { NSString *canonical = [hostname lowercaseString]; - if ([canonical hasSuffix:@"."]) - { + if ([canonical hasSuffix:@"."]) { canonical = [canonical substringToIndex:[canonical length] - 1]; } - if ([canonical hasPrefix:@"["] && [canonical hasSuffix:@"]"]) - { + if ([canonical hasPrefix:@"["] && [canonical hasSuffix:@"]"]) { canonical = [canonical substringWithRange:NSMakeRange(1, [canonical length] - 2)]; } return canonicalIPAddress(canonical) ?: canonical; } -BOOL isIPAddress(NSString *hostname) -{ +BOOL isIPAddress(NSString *hostname) { return canonicalIPAddress(hostname) != nil; } -BOOL isValidPinnedDomain(NSString *domain) -{ - if (isIPAddress(domain)) - { +BOOL isValidPinnedDomain(NSString *domain) { + if (isIPAddress(domain)) { return YES; } - + NSCharacterSet *invalidCharacters = [[NSCharacterSet characterSetWithCharactersInString:@"abcdefghijklmnopqrstuvwxyz0123456789-_."] invertedSet]; - if ([domain rangeOfCharacterFromSet:invalidCharacters].location != NSNotFound - || [[domain componentsSeparatedByString:@"."] containsObject:@""]) - { + if ([domain rangeOfCharacterFromSet:invalidCharacters].location != NSNotFound || [[domain componentsSeparatedByString:@"."] containsObject:@""]) { return NO; } - + // Reject a mistyped IPv4 address such as 10.0.0.256 instead of treating it as a domain name NSCharacterSet *nonIPv4Characters = [[NSCharacterSet characterSetWithCharactersInString:@"0123456789."] invertedSet]; return [domain rangeOfCharacterFromSet:nonIPv4Characters].location != NSNotFound; } -static NSUInteger isSubdomain(NSString *domain, NSString *subdomain) -{ +static NSUInteger isSubdomain(NSString *domain, NSString *subdomain) { return [subdomain hasSuffix:[@"." stringByAppendingString:domain]] ? [domain length] : 0; } -NSString * _Nullable getPinningConfigurationKeyForDomain(NSString * _Nonnull serverHostname , NSDictionary * _Nonnull domainPinningPolicies) -{ +NSString *_Nullable getPinningConfigurationKeyForDomain(NSString *_Nonnull serverHostname, NSDictionary *_Nonnull domainPinningPolicies) { NSString *hostname = canonicalHostname(serverHostname); NSString *notedHostname = nil; - if (domainPinningPolicies[hostname] == nil) - { + if (domainPinningPolicies[hostname] == nil) { NSUInteger bestMatch = 0; // No pins explicitly configured for this domain // Look for an includeSubdomain pin that applies - for (NSString *pinnedServerName in domainPinningPolicies) - { + for (NSString *pinnedServerName in domainPinningPolicies) { // Check each domain configured with the includeSubdomain flag - if ([domainPinningPolicies[pinnedServerName][kTSKIncludeSubdomains] boolValue]) - { + if ([domainPinningPolicies[pinnedServerName][kTSKIncludeSubdomains] boolValue]) { // Is the server a subdomain of this pinned server? os_log_debug(TSKLogHandle(), "Checking includeSubdomains configuration for %{public}@", pinnedServerName); NSUInteger currentMatch = isSubdomain(pinnedServerName, hostname); - if (currentMatch > 0 && currentMatch > bestMatch) - { + if (currentMatch > 0 && currentMatch > bestMatch) { // Yes; let's use the parent domain's pinning configuration - os_log_debug(TSKLogHandle(), "Applying includeSubdomains configuration from %{public}@ to %{public}@ (new best match of %lu chars)", pinnedServerName, hostname, (unsigned long)currentMatch); + os_log_debug( + TSKLogHandle(), + "Applying includeSubdomains configuration from %{public}@ to %{public}@ (new best match of %lu chars)", + pinnedServerName, + hostname, + (unsigned long)currentMatch + ); bestMatch = currentMatch; notedHostname = pinnedServerName; - } - else if (currentMatch > 0) - { - os_log_debug(TSKLogHandle(), "Not applying includeSubdomains configuration from %{public}@ to %{public}@ (current match of %lu chars does not exceed best match)", pinnedServerName, hostname, (unsigned long)currentMatch); + } else if (currentMatch > 0) { + os_log_debug( + TSKLogHandle(), + "Not applying includeSubdomains configuration from %{public}@ to %{public}@ (current match of %lu chars does not exceed best match)", + pinnedServerName, + hostname, + (unsigned long)currentMatch + ); } } } - } - else - { + } else { // This hostname has a pinnning configuration notedHostname = hostname; } - - if (notedHostname == nil) - { + + if (notedHostname == nil) { os_log_debug(TSKLogHandle(), "Domain %@ is not pinned", hostname); } return notedHostname; diff --git a/TrustKit/parse_configuration.h b/TrustKit/parse_configuration.h index 67ddcbdf..c32edb2e 100644 --- a/TrustKit/parse_configuration.h +++ b/TrustKit/parse_configuration.h @@ -1,12 +1,12 @@ /* - + parse_configuration.h TrustKit - + Copyright 2016 The TrustKit Project Authors Licensed under the MIT license, see associated LICENSE file for terms. See AUTHORS file for the list of project authors. - + */ #ifndef parse_configuration_h diff --git a/TrustKit/parse_configuration.m b/TrustKit/parse_configuration.m index 502ab6cd..c48dadfb 100644 --- a/TrustKit/parse_configuration.m +++ b/TrustKit/parse_configuration.m @@ -1,138 +1,110 @@ /* - + parse_configuration.m TrustKit - + Copyright 2016 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 "public/TrustKit/TSKTrustKitConfig.h" #import "parse_configuration.h" -#import #import "configuration_utils.h" +#import "public/TrustKit/TSKTrustKitConfig.h" +#import - -NSDictionary *parseTrustKitConfiguration(NSDictionary *trustKitArguments) -{ +NSDictionary *parseTrustKitConfiguration(NSDictionary *trustKitArguments) { // Convert settings supplied by the user to a configuration dictionary that can be used by TrustKit // This includes checking the sanity of the settings and converting public key hashes/pins from an // NSSArray of NSStrings (as provided by the user) to an NSSet of NSData (as needed by TrustKit) - - NSMutableDictionary *finalConfiguration = [[NSMutableDictionary alloc]init]; - finalConfiguration[kTSKPinnedDomains] = [[NSMutableDictionary alloc]init]; - - + + NSMutableDictionary *finalConfiguration = [[NSMutableDictionary alloc] init]; + finalConfiguration[kTSKPinnedDomains] = [[NSMutableDictionary alloc] init]; + // Retrieve global settings - + // Should we auto-swizzle network delegates NSNumber *shouldSwizzleNetworkDelegates = trustKitArguments[kTSKSwizzleNetworkDelegates]; - if (shouldSwizzleNetworkDelegates == nil) - { + if (shouldSwizzleNetworkDelegates == nil) { // Default setting is NO finalConfiguration[kTSKSwizzleNetworkDelegates] = @(NO); - } - else - { + } else { finalConfiguration[kTSKSwizzleNetworkDelegates] = shouldSwizzleNetworkDelegates; } - - + #if !TARGET_OS_IPHONE // macOS only: extract the optional ignorePinningForUserDefinedTrustAnchors setting NSNumber *shouldIgnorePinningForUserDefinedTrustAnchors = trustKitArguments[kTSKIgnorePinningForUserDefinedTrustAnchors]; - if (shouldIgnorePinningForUserDefinedTrustAnchors == nil) - { + if (shouldIgnorePinningForUserDefinedTrustAnchors == nil) { // Default setting is YES finalConfiguration[kTSKIgnorePinningForUserDefinedTrustAnchors] = @(YES); - } - else - { + } else { finalConfiguration[kTSKIgnorePinningForUserDefinedTrustAnchors] = shouldIgnorePinningForUserDefinedTrustAnchors; } #endif - + // Retrieve the pinning policy for each domains - if ((trustKitArguments[kTSKPinnedDomains] == nil) || ([trustKitArguments[kTSKPinnedDomains] count] < 1)) - { + if ((trustKitArguments[kTSKPinnedDomains] == nil) || ([trustKitArguments[kTSKPinnedDomains] count] < 1)) { [NSException raise:@"TrustKit configuration invalid" - format:@"TrustKit was initialized with no pinned domains. The configuration format has changed: ensure your domain pinning policies are under the TSKPinnedDomains key within TSKConfiguration."]; + format: + @"TrustKit was initialized with no pinned domains. The configuration format has changed: ensure your domain pinning policies are under the " + @"TSKPinnedDomains key within TSKConfiguration."]; } - - - for (NSString *configuredDomainName in trustKitArguments[kTSKPinnedDomains]) - { + + for (NSString *configuredDomainName in trustKitArguments[kTSKPinnedDomains]) { // Sanity checks on the domain name NSString *domainName = canonicalHostname(configuredDomainName); - if (!isValidPinnedDomain(domainName)) - { + if (!isValidPinnedDomain(domainName)) { [NSException raise:@"TrustKit configuration invalid" format:@"TrustKit was initialized with an invalid domain %@; expected a domain name such as www.datatheorem.com or an IP address", configuredDomainName]; } - if (finalConfiguration[kTSKPinnedDomains][domainName] != nil) - { - [NSException raise:@"TrustKit configuration invalid" - format:@"TrustKit was initialized with domain %@ more than once", domainName]; + if (finalConfiguration[kTSKPinnedDomains][domainName] != nil) { + [NSException raise:@"TrustKit configuration invalid" format:@"TrustKit was initialized with domain %@ more than once", domainName]; } - - + // Retrieve the supplied arguments for this domain NSDictionary *domainPinningPolicy = trustKitArguments[kTSKPinnedDomains][configuredDomainName]; - NSMutableDictionary *domainFinalConfiguration = [[NSMutableDictionary alloc]init]; - - + NSMutableDictionary *domainFinalConfiguration = [[NSMutableDictionary alloc] init]; + // Always start with the optional excludeSubDomain setting; if it set, no other TSKDomainConfigurationKey can be set for this domain NSNumber *shouldExcludeSubdomain = domainPinningPolicy[kTSKExcludeSubdomainFromParentPolicy]; - if (shouldExcludeSubdomain != nil && [shouldExcludeSubdomain boolValue]) - { + if (shouldExcludeSubdomain != nil && [shouldExcludeSubdomain boolValue]) { // Confirm that no other TSKDomainConfigurationKeys were set for this domain - if ([[domainPinningPolicy allKeys] count] > 1) - { + if ([[domainPinningPolicy allKeys] count] > 1) { [NSException raise:@"TrustKit configuration invalid" format:@"TrustKit was initialized with TSKExcludeSubdomainFromParentPolicy for domain %@ but detected additional configuration keys", domainName]; } - + // Store the whole configuration and continue to the next domain entry domainFinalConfiguration[kTSKExcludeSubdomainFromParentPolicy] = @(YES); finalConfiguration[kTSKPinnedDomains][domainName] = [NSDictionary dictionaryWithDictionary:domainFinalConfiguration]; continue; - } - else - { + } else { // Default setting is NO domainFinalConfiguration[kTSKExcludeSubdomainFromParentPolicy] = @(NO); } - - + // Extract the optional includeSubdomains setting NSNumber *shouldIncludeSubdomains = domainPinningPolicy[kTSKIncludeSubdomains]; - if (shouldIncludeSubdomains == nil) - { + if (shouldIncludeSubdomains == nil) { // Default setting is NO domainFinalConfiguration[kTSKIncludeSubdomains] = @(NO); - } - else - { - if ([shouldIncludeSubdomains boolValue] == YES) - { + } else { + if ([shouldIncludeSubdomains boolValue] == YES) { // Prevent pinning on *.com - if (isIPAddress(domainName) || ![domainName containsString:@"."]) - { + if (isIPAddress(domainName) || ![domainName containsString:@"."]) { [NSException raise:@"TrustKit configuration invalid" format:@"TrustKit was initialized with includeSubdomains for an IP address or single-label domain %@", domainName]; } } - + domainFinalConfiguration[kTSKIncludeSubdomains] = shouldIncludeSubdomains; } - - + // Extract the optional expiration date setting NSString *expirationDateStr = domainPinningPolicy[kTSKExpirationDate]; - if (expirationDateStr != nil) - { + if (expirationDateStr != nil) { // Convert the string in the yyyy-MM-dd format into an actual date in UTC NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; dateFormat.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; @@ -141,94 +113,78 @@ NSDate *expirationDate = [dateFormat dateFromString:expirationDateStr]; domainFinalConfiguration[kTSKExpirationDate] = expirationDate; } - - + // Extract the optional enforcePinning setting NSNumber *shouldEnforcePinning = domainPinningPolicy[kTSKEnforcePinning]; - if (shouldEnforcePinning != nil) - { + if (shouldEnforcePinning != nil) { domainFinalConfiguration[kTSKEnforcePinning] = shouldEnforcePinning; - } - else - { + } else { // Default setting is YES domainFinalConfiguration[kTSKEnforcePinning] = @(YES); } - // Extract the optional disableDefaultReportUri setting NSNumber *shouldDisableDefaultReportUri = domainPinningPolicy[kTSKDisableDefaultReportUri]; - if (shouldDisableDefaultReportUri != nil) - { + if (shouldDisableDefaultReportUri != nil) { domainFinalConfiguration[kTSKDisableDefaultReportUri] = shouldDisableDefaultReportUri; - } - else - { + } else { // Default setting is NO domainFinalConfiguration[kTSKDisableDefaultReportUri] = @(NO); } - + // Extract and convert the report URIs if defined NSArray *reportUriList = domainPinningPolicy[kTSKReportUris]; - if (reportUriList != nil) - { + if (reportUriList != nil) { NSMutableArray *reportUriListFinal = [NSMutableArray array]; - for (NSString *reportUriStr in reportUriList) - { + for (NSString *reportUriStr in reportUriList) { NSURL *reportUri = [NSURL URLWithString:reportUriStr]; - if (reportUri == nil) - { - [NSException raise:@"TrustKit configuration invalid" - format:@"TrustKit was initialized with an invalid value for %@ for domain %@", kTSKReportUris, domainName]; + if (reportUri == nil) { + [NSException raise:@"TrustKit configuration invalid" format:@"TrustKit was initialized with an invalid value for %@ for domain %@", kTSKReportUris, domainName]; } [reportUriListFinal addObject:reportUri]; } - + domainFinalConfiguration[kTSKReportUris] = [NSArray arrayWithArray:reportUriListFinal]; } - - + // Extract and convert the subject public key info hashes NSArray *serverSslPinsBase64 = domainPinningPolicy[kTSKPublicKeyHashes]; NSMutableSet *serverSslPinsSet = [NSMutableSet set]; - + for (NSString *pinnedKeyHashBase64 in serverSslPinsBase64) { NSData *pinnedKeyHash = [[NSData alloc] initWithBase64EncodedString:pinnedKeyHashBase64 options:(NSDataBase64DecodingOptions)0]; - - if ([pinnedKeyHash length] != CC_SHA256_DIGEST_LENGTH) - { + + if ([pinnedKeyHash length] != CC_SHA256_DIGEST_LENGTH) { // The subject public key info hash doesn't have a valid size - [NSException raise:@"TrustKit configuration invalid" - format:@"TrustKit was initialized with an invalid Pin %@ for domain %@", pinnedKeyHashBase64, domainName]; + [NSException raise:@"TrustKit configuration invalid" format:@"TrustKit was initialized with an invalid Pin %@ for domain %@", pinnedKeyHashBase64, domainName]; } - + [serverSslPinsSet addObject:pinnedKeyHash]; } - - + NSUInteger requiredNumberOfPins = [domainFinalConfiguration[kTSKEnforcePinning] boolValue] ? 2 : 1; - if([serverSslPinsSet count] < requiredNumberOfPins) - { + if ([serverSslPinsSet count] < requiredNumberOfPins) { [NSException raise:@"TrustKit configuration invalid" - format:@"TrustKit was initialized with less than %lu pins (ie. no backup pins) for domain %@. This might brick your App; please review the Getting Started guide in ./docs/getting-started.md", (unsigned long)requiredNumberOfPins, domainName]; + format: + @"TrustKit was initialized with less than %lu pins (ie. no backup pins) for domain %@. This might brick your App; please review the Getting Started " + @"guide in ./docs/getting-started.md", + (unsigned long)requiredNumberOfPins, + domainName]; } - + // Save the hashes for this server as an NSSet for quick lookup domainFinalConfiguration[kTSKPublicKeyHashes] = [NSSet setWithSet:serverSslPinsSet]; - + // Store the whole configuration finalConfiguration[kTSKPinnedDomains][domainName] = [NSDictionary dictionaryWithDictionary:domainFinalConfiguration]; } - + // Lastly, ensure that we can find a parent policy for subdomains configured with TSKExcludeSubdomainFromParentPolicy - for (NSString *domainName in finalConfiguration[kTSKPinnedDomains]) - { - if ([finalConfiguration[kTSKPinnedDomains][domainName][kTSKExcludeSubdomainFromParentPolicy] boolValue]) - { + for (NSString *domainName in finalConfiguration[kTSKPinnedDomains]) { + if ([finalConfiguration[kTSKPinnedDomains][domainName][kTSKExcludeSubdomainFromParentPolicy] boolValue]) { // To force the lookup of a parent domain, we append 'a' to this subdomain so we don't retrieve its policy NSString *parentDomainConfigKey = getPinningConfigurationKeyForDomain([@"a" stringByAppendingString:domainName], finalConfiguration[kTSKPinnedDomains]); - if (parentDomainConfigKey == nil) - { + if (parentDomainConfigKey == nil) { [NSException raise:@"TrustKit configuration invalid" format:@"TrustKit was initialized with TSKExcludeSubdomainFromParentPolicy for domain %@ but could not find a policy for a parent domain", domainName]; } diff --git a/TrustKit/public/TrustKit/TSKPinningValidator.h b/TrustKit/public/TrustKit/TSKPinningValidator.h index eeb815be..920531f4 100644 --- a/TrustKit/public/TrustKit/TSKPinningValidator.h +++ b/TrustKit/public/TrustKit/TSKPinningValidator.h @@ -1,12 +1,12 @@ /* - + TSKPinningValidator.h 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. - + */ #if __has_include() @@ -19,18 +19,17 @@ @class TSKPinningValidatorResult; @class TSKSPKIHashCache; - /** A `TSKPinningValidator` instance can be used to verify a server's identity against an SSL pinning policy. - + In specific scenarios, TrustKit cannot intercept outgoing SSL connections and automatically validate the server's identity against the pinning policy: - + * All connections within an App that disables TrustKit's network delegate swizzling by setting the `kTSKSwizzleNetworkDelegates` configuration key to `NO`. * Connections that do not rely on the `NSURLConnection` or `NSURLSession` APIs: * `WKWebView` connections. * Connections leveraging low-level network APIs (such as `NSStream`). * Connections initiated using a third-party SSL library such as OpenSSL. - + For these connections, pin validation must be manually triggered using one of the two available methods within `TSKPinningValidator`. */ @interface TSKPinningValidator : NSObject @@ -39,8 +38,9 @@ /** Helper method for handling authentication challenges received within a `NSURLSessionDelegate`, `NSURLSessionTaskDelegate` or `WKNavigationDelegate`. - - This method will evaluate the server trust within the authentication challenge against the global SSL pinning policy previously configured, and then call the `completionHandler` with the corresponding `disposition` and `credential`. For example, this method can be leveraged in a NSURLSessionDelegate challenge handler method: + + This method will evaluate the server trust within the authentication challenge against the global SSL pinning policy previously configured, and then call the `completionHandler` + with the corresponding `disposition` and `credential`. For example, this method can be leveraged in a NSURLSessionDelegate challenge handler method: - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task @@ -56,41 +56,42 @@ completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } } - + @param challenge The authentication challenge, supplied by the URL loading system to the delegate's challenge handler method. - + @param completionHandler A block to invoke to respond to the challenge, supplied by the URL loading system to the delegate's challenge handler method. - - @return `YES` if the challenge was handled and the `completionHandler` was successfuly invoked. `NO` if the challenge could not be handled because it was not for server certificate validation (ie. the challenge's `authenticationMethod` was not `NSURLAuthenticationMethodServerTrust`). - + + @return `YES` if the challenge was handled and the `completionHandler` was successfuly invoked. `NO` if the challenge could not be handled because it was not for server + certificate validation (ie. the challenge's `authenticationMethod` was not `NSURLAuthenticationMethodServerTrust`). + @exception NSException Thrown when TrustKit has not been initialized with a pinning policy. */ -- (BOOL)handleChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge - completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * _Nullable credential))completionHandler; - +- (BOOL)handleChallenge:(NSURLAuthenticationChallenge *_Nonnull)challenge + completionHandler:(void (^_Nonnull)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential))completionHandler; #pragma mark Low-level Validation Method /** Evaluate the supplied server trust against the SSL pinning policy previously configured. If the validation fails, a pin failure report will be sent. - + When using the `NSURLSession` or `WKWebView` network APIs, the `handleChallenge:completionHandler:` method should be called instead, as it is simpler to use. - - When using low-level network APIs (such as `NSStream`), instructions on how to retrieve the connection's `serverTrust` are available at https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html . - - @param serverTrust The trust object representing the server's certificate chain. The trust's evaluation policy is always overridden using `SecTrustSetPolicies()` to ensure all the proper SSL checks (expiration, hostname validation, etc.) are enabled. - + + When using low-level network APIs (such as `NSStream`), instructions on how to retrieve the connection's `serverTrust` are available at + https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html . + + @param serverTrust The trust object representing the server's certificate chain. The trust's evaluation policy is always overridden using `SecTrustSetPolicies()` to ensure all the + proper SSL checks (expiration, hostname validation, etc.) are enabled. + @param serverHostname The hostname of the server whose identity is being validated. - + @return A `TSKTrustDecision` which describes whether the SSL connection should be allowed or blocked, based on the configured pinning policy. - - @warning If no SSL pinning policy was configured for the supplied _serverHostname_, this method has no effect and will return `TSKTrustDecisionDomainNotPinned` without validating the supplied _serverTrust_ at all. This means that the server's _serverTrust_ object __must__ be verified against the device's trust store using `SecTrustEvaluate()`. Failing to do so will __disable SSL certificate validation__. - - @exception NSException Thrown when TrustKit has not been initialized with a pinning policy. - */ -- (TSKTrustDecision)evaluateTrust:(SecTrustRef _Nonnull)serverTrust forHostname:(NSString * _Nonnull)serverHostname; + @warning If no SSL pinning policy was configured for the supplied _serverHostname_, this method has no effect and will return `TSKTrustDecisionDomainNotPinned` without validating + the supplied _serverTrust_ at all. This means that the server's _serverTrust_ object __must__ be verified against the device's trust store using `SecTrustEvaluate()`. Failing to + do so will __disable SSL certificate validation__. + @exception NSException Thrown when TrustKit has not been initialized with a pinning policy. + */ +- (TSKTrustDecision)evaluateTrust:(SecTrustRef _Nonnull)serverTrust forHostname:(NSString *_Nonnull)serverHostname; @end diff --git a/TrustKit/public/TrustKit/TSKPinningValidatorCallback.h b/TrustKit/public/TrustKit/TSKPinningValidatorCallback.h index 5f7bdad5..4616cc17 100644 --- a/TrustKit/public/TrustKit/TSKPinningValidatorCallback.h +++ b/TrustKit/public/TrustKit/TSKPinningValidatorCallback.h @@ -1,12 +1,12 @@ /* - + TSKPinningValidatorCallback.h TrustKit - + Copyright 2017 The TrustKit Project Authors Licensed under the MIT license, see associated LICENSE file for terms. See AUTHORS file for the list of project authors. - + */ #ifndef TSKPinningValidatorCallback_h @@ -25,31 +25,28 @@ */ typedef NSDictionary TKSDomainPinningPolicy; - /** A block that can be set in a `TrustKit` instance to be invoked for every request that is going through instance's pinning validation logic. - + The callback will be invoked every time the validator performs pinning validation against a server's certificate chain; if the server's hostname is not defined in the pinning policy, no invocations will result as no pinning validation was performed. - + The callback provides the following arguments: - + * The `TSKPinningValidatorResult` resulting from the validation of the server's identity. * The `notedHostname`, which is the entry within the SSL pinning configuration that was used for the server being validated. * The `notedHostname`'s pinning policy, which was used for the server being validated. - + The callback can be used for advanced features such as performance measurement or customizing the reporting mechanism. Hence, most Apps should not have to use this callback. If set, the callback may be invoked very frequently and is not a suitable place for expensive tasks. - + Lastly, the callback is always invoked after the validation has been completed, and therefore cannot be used to modify the result of the validation (for example to accept invalid certificates). */ -typedef void (^TSKPinningValidatorCallback)(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, TKSDomainPinningPolicy * _Nonnull policy); - - +typedef void (^TSKPinningValidatorCallback)(TSKPinningValidatorResult *_Nonnull result, NSString *_Nonnull notedHostname, TKSDomainPinningPolicy *_Nonnull policy); #endif /* TSKPinningValidatorCallback_h */ diff --git a/TrustKit/public/TrustKit/TSKPinningValidatorResult.h b/TrustKit/public/TrustKit/TSKPinningValidatorResult.h index e418da6e..c45ce9ee 100644 --- a/TrustKit/public/TrustKit/TSKPinningValidatorResult.h +++ b/TrustKit/public/TrustKit/TSKPinningValidatorResult.h @@ -1,12 +1,12 @@ /* - + TSKPinningValidator.h 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. - + */ #if __has_include() @@ -28,18 +28,18 @@ @property (nonatomic, readonly, nonnull) NSString *serverHostname; /** - The result of validating the server's certificate chain against the set of SSL pins configured for + The result of validating the server's certificate chain against the set of SSL pins configured for the `notedHostname`. */ @property (nonatomic, readonly) TSKTrustEvaluationResult evaluationResult; /** - The trust decision returned for this connection, which describes whether the connection should be blocked - or allowed, based on the `evaluationResult` returned when evaluating the `serverTrust` and the SSL pining + The trust decision returned for this connection, which describes whether the connection should be blocked + or allowed, based on the `evaluationResult` returned when evaluating the `serverTrust` and the SSL pining policy configured for this server. - For example, the pinning validation could have failed (ie. evaluationResult being - `TSKTrustEvaluationFailedNoMatchingPin`) but the policy might be set to ignore pinning validation failures + For example, the pinning validation could have failed (ie. evaluationResult being + `TSKTrustEvaluationFailedNoMatchingPin`) but the policy might be set to ignore pinning validation failures for this server, thereby returning `TSKTrustDecisionShouldAllowConnection`. */ @property (nonatomic, readonly) TSKTrustDecision finalTrustDecision; @@ -50,7 +50,7 @@ @property (nonatomic, readonly) NSTimeInterval validationDuration; /** - The certificate chain sent by the server when establishing the connection as PEM-formatted certificates. + The certificate chain sent by the server when establishing the connection as PEM-formatted certificates. */ @property (nonatomic, readonly, nonnull) NSArray *certificateChain; diff --git a/TrustKit/public/TrustKit/TSKTrustDecision.h b/TrustKit/public/TrustKit/TSKTrustDecision.h index b813682a..dcfe5dbf 100644 --- a/TrustKit/public/TrustKit/TSKTrustDecision.h +++ b/TrustKit/public/TrustKit/TSKTrustDecision.h @@ -1,12 +1,12 @@ /* - + TSKTrustDecision.h 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 @@ -14,30 +14,30 @@ /** Possible return values when verifying a server's identity against a set of pins. */ -typedef NS_ENUM(NSInteger, TSKTrustEvaluationResult) -{ +typedef NS_ENUM(NSInteger, TSKTrustEvaluationResult) { /** The server trust was succesfully evaluated and contained at least one of the configured pins. */ TSKTrustEvaluationSuccess, - + /** The server trust was succesfully evaluated but did not contain any of the configured pins. */ TSKTrustEvaluationFailedNoMatchingPin, - + /** The server trust's evaluation failed: the server's certificate chain is not trusted. */ TSKTrustEvaluationFailedInvalidCertificateChain, - + /** The server trust could not be evaluated due to invalid parameters. */ TSKTrustEvaluationErrorInvalidParameters, - + /** - The server trust was succesfully evaluated but did not contain any of the configured pins. However, the certificate chain terminates at a user-defined trust anchor (ie. a custom/private CA that was manually added to the macOS trust store). Only available on macOS; not available to Mac Catalyst apps. + The server trust was succesfully evaluated but did not contain any of the configured pins. However, the certificate chain terminates at a user-defined trust anchor (ie. a + custom/private CA that was manually added to the macOS trust store). Only available on macOS; not available to Mac Catalyst apps. */ TSKTrustEvaluationFailedUserDefinedTrustAnchor API_UNAVAILABLE(ios, tvos, watchos, visionos), @@ -50,23 +50,24 @@ typedef NS_ENUM(NSInteger, TSKTrustEvaluationResult) /** Possible return values when verifying a server's identity against an SSL pinning policy. */ -typedef NS_ENUM(NSInteger, TSKTrustDecision) -{ +typedef NS_ENUM(NSInteger, TSKTrustDecision) { /** Based on the server's certificate chain and the configured pinning policy for this domain, the SSL connection should be allowed. - This return value does not necessarily mean that the pinning validation succeded (for example if `kTSKEnforcePinning` was set to `NO` for this domain). If a pinning validation failure occured and if a report URI was configured, a pin failure report was sent. + This return value does not necessarily mean that the pinning validation succeded (for example if `kTSKEnforcePinning` was set to `NO` for this domain). If a pinning validation + failure occured and if a report URI was configured, a pin failure report was sent. */ TSKTrustDecisionShouldAllowConnection, - + /** Based on the server's certificate chain and the configured pinning policy for this domain, the SSL connection should be blocked. A pinning validation failure occured and if a report URI was configured, a pin failure report was sent. */ TSKTrustDecisionShouldBlockConnection, - + /** No pinning policy was configured for this domain and TrustKit did not validate the server's identity. - Because this will happen in an authentication handler, it means that the server's _serverTrust_ object __needs__ to be verified against the device's trust store using `SecTrustEvaluate()`. Failing to do so will __disable SSL certificate validation__. + Because this will happen in an authentication handler, it means that the server's _serverTrust_ object __needs__ to be verified against the device's trust store using + `SecTrustEvaluate()`. Failing to do so will __disable SSL certificate validation__. */ TSKTrustDecisionDomainNotPinned, }; diff --git a/TrustKit/public/TrustKit/TSKTrustKitConfig.h b/TrustKit/public/TrustKit/TSKTrustKitConfig.h index 27a3e2c9..935afcc8 100644 --- a/TrustKit/public/TrustKit/TSKTrustKitConfig.h +++ b/TrustKit/public/TrustKit/TSKTrustKitConfig.h @@ -1,12 +1,12 @@ /* - + TSKTrustKitConfig.h TrustKit - + Copyright 2017 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 @@ -14,150 +14,136 @@ /** The version of TrustKit, such as "1.4.0". */ -FOUNDATION_EXPORT NSString * const TrustKitVersion; - +FOUNDATION_EXPORT NSString *const TrustKitVersion; #pragma mark Configuration Keys - /** A global, App-wide configuration key that can be set in the pinning policy. */ typedef NSString *TSKGlobalConfigurationKey; - /** A domain-specific configuration key (to defined for a domain under the `kTSKPinnedDomains` key) that can be set in the pinning policy. */ typedef NSString *TSKDomainConfigurationKey; - #pragma mark Global Configuration Keys - Required - /** A boolean. If set to `YES`, TrustKit will perform method swizzling on the App's `NSURLConnection` and `NSURLSession` delegates in order to automatically add SSL pinning - validation to the App's connections. This option can only be used if TrustKit is + validation to the App's connections. This option can only be used if TrustKit is initialized in singleton mode; default value is `NO`. - + Swizzling allows enabling pinning within an App without having to find and modify each and every instance of `NSURLConnection` or `NSURLSession` delegates. However, it should only be enabled for simple Apps, as it may not work properly in several scenarios including: - + * Apps with complex connection delegates, for example to handle client authentication via certificates or basic authentication. * Apps where method swizzling of the connection delegates is already performed by another module or library (such as Analytics SDKs). * Apps that do no use `NSURLSession` or `NSURLConnection` for their connections. - + In such scenarios or if the developer wants a tigher control on the App's networking behavior, `kTSKSwizzleNetworkDelegates` should be set to `NO`; the developer should then manually add pinning validation to the App's authentication handlers. - + See the `TSKPinningValidator` class for instructions on how to do so. */ FOUNDATION_EXPORT const TSKGlobalConfigurationKey kTSKSwizzleNetworkDelegates; - /** A dictionary with domains (such as _www.domain.com_) as keys and dictionaries as values. - + Keys can also be IPv4 or IPv6 addresses. Internationalized domain names must be written in punycode. Matching ignores case. - + Each entry should contain domain-specific settings for performing pinning validation when connecting to the domain, including for example the domain's public key hashes. A list of all domain-specific keys is available in the "Domain-specific Keys" sections. */ FOUNDATION_EXPORT const TSKGlobalConfigurationKey kTSKPinnedDomains; - - #pragma mark Global Configuration Keys - Optional - /** A boolean. If set to `YES`, pinning validation will be skipped if the server's certificate chain terminates at a user-defined trust anchor (such as a root CA that isn't part of macOS's default trust store) and no pin failure reports will be sent; default value is `YES`. - + This is useful for allowing SSL connections through corporate proxies or firewalls. See "How does key pinning interact with local proxies and filters?" within the Chromium security FAQ at https://www.chromium.org/Home/chromium-security/security-faq for more information. - + Only available on macOS; not available to Mac Catalyst apps. */ FOUNDATION_EXPORT const TSKGlobalConfigurationKey kTSKIgnorePinningForUserDefinedTrustAnchors API_UNAVAILABLE(ios, tvos, watchos, visionos); - #pragma mark Domain-Specific Configuration Keys - Required /** An array of SSL pins, where each pin is the base64-encoded SHA-256 hash of a certificate's Subject Public Key Info. - + TrustKit will verify that at least one of the specified pins is found in the server's evaluated certificate chain. */ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKPublicKeyHashes; - /** DEPRECATED since 1.6.0: this configuration key is no longer needed. - + An array of `TSKSupportedAlgorithm` constants to specify the public key algorithms for the keys to be pinned. - + TrustKit requires this information in order to compute SSL pins when validating a server's certificate chain, because the `Security` framework does not provide APIs to extract the key's algorithm from an SSL certificate. To minimize the performance impact of Trustkit, only one algorithm should be enabled. */ -FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKPublicKeyAlgorithms __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); - +FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKPublicKeyAlgorithms + __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); #pragma mark Domain-Specific Configuration Keys - Optional /** A boolean. If set to `NO`, TrustKit will not block SSL connections that caused a pin or certificate validation error; default value is `YES`. - + When a pinning failure occurs, pin failure reports will always be sent to the configured report URIs regardless of the value of `kTSKEnforcePinning`. */ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKEnforcePinning; - /** A boolean. If set to `YES`, also pin all the subdomains of the specified domain; default value is `NO`. It cannot be set for an IP address or a single-label domain such as _com_. */ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKIncludeSubdomains; - /** A boolean. If set to `YES`, TrustKit will not pin this specific domain if `kTSKIncludeSubdomains` was set for this domain's parent domain. - + This allows excluding specific subdomains from a pinning policy that was applied to a parent domain. */ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKExcludeSubdomainFromParentPolicy; - /** An array of URLs to which pin validation failures should be reported. - + To minimize the performance impact of sending reports on each validation failure, the reports are uploaded using the background transfer service and are also rate-limited to one per day and per type of failure. For HTTPS report URLs, the HTTPS connections will ignore the SSL pinning policy and use the default certificate validation mechanisms, in order to maximize the chance of the reports reaching the server. The format of the reports is similar to the one described in RFC 7469 for the HPKP specification: - + { "app-bundle-id": "com.datatheorem.testtrustkit2", "app-version": "1", @@ -182,11 +168,10 @@ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKExcludeSubdomainFromParent */ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKReportUris; - /** A boolean. If set to `YES`, the default report URL for sending pin failure reports will be disabled; default value is `NO`. - + By default, pin failure reports are sent to a report server hosted by Data Theorem, for detecting potential CA compromises and man-in-the-middle attacks, as well as providing a free dashboard for developers; email info@datatheorem.com if you'd like a dashboard for @@ -195,48 +180,46 @@ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKReportUris; */ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKDisableDefaultReportUri; - /** A string containing the date, in yyyy-MM-dd format, on which the domain's configured SSL pins expire, thus disabling pinning validation. If the key is not set, then the pins do not expire. - + Expiration helps prevent connectivity issues in Apps which do not get updates to their pin set, such as when the user disables App updates. */ FOUNDATION_EXPORT const TSKDomainConfigurationKey kTSKExpirationDate; - #pragma mark DEPRECATED - Supported Public Key Algorithm Keys - /** DEPRECATED since 1.6.0: this configuration key is no longer needed. - + A public key algorithm supported by TrustKit for generating the SSL pin for a certificate. */ -typedef NSString *TSKSupportedAlgorithm __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKSupportedAlgorithm from your configuration."); - +typedef NSString * + TSKSupportedAlgorithm __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKSupportedAlgorithm from your configuration."); /** RSA 2048. */ -FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmRsa2048 __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); - +FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmRsa2048 + __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); /** RSA 4096. */ -FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmRsa4096 __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); - +FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmRsa4096 + __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); /** ECDSA with secp256r1 curve. */ -FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmEcDsaSecp256r1 __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); - +FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmEcDsaSecp256r1 + __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); /** ECDSA with secp384r1 curve. */ -FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmEcDsaSecp384r1 __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); +FOUNDATION_EXPORT const TSKSupportedAlgorithm kTSKAlgorithmEcDsaSecp384r1 + __deprecated_msg("Starting with TrustKit 1.6.0, key algorithms no longer need to be specified; remove TSKPublicKeyAlgorithms from your configuration."); diff --git a/TrustKit/public/TrustKit/TrustKit.h b/TrustKit/public/TrustKit/TrustKit.h index 56e847fa..fa1b8267 100644 --- a/TrustKit/public/TrustKit/TrustKit.h +++ b/TrustKit/public/TrustKit/TrustKit.h @@ -1,12 +1,12 @@ /* - + TrustKit.h 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 @@ -14,45 +14,44 @@ #ifndef _TRUSTKIT_ #define _TRUSTKIT_ #if __has_include() - #import - #import - #import - #import - #import +#import +#import +#import +#import +#import #else - #import "TSKTrustKitConfig.h" - #import "TSKPinningValidatorResult.h" - #import "TSKPinningValidatorCallback.h" - #import "TSKPinningValidator.h" - #import "TSKTrustDecision.h" +#import "TSKPinningValidator.h" +#import "TSKPinningValidatorCallback.h" +#import "TSKPinningValidatorResult.h" +#import "TSKTrustDecision.h" +#import "TSKTrustKitConfig.h" #endif #endif /* _TRUSTKIT_ */ NS_ASSUME_NONNULL_BEGIN - /** `TrustKit` is the main class for configuring an SSL pinning policy within an App. - + For most Apps, TrustKit should be used as a singleton, where a global SSL pinning policy is configured for the App. In singleton mode, the policy can be set either: - - * By adding it to the App's _Info.plist_ under the `TSKConfiguration` key, or + + * By adding it to the App's _Info.plist_ under the `TSKConfiguration` key, or * By programmatically supplying it using the `+initSharedInstanceWithConfiguration:` method. - - In singleton mode, TrustKit can only be initialized once so only one of the two techniques + + In singleton mode, TrustKit can only be initialized once so only one of the two techniques should be used. - - For more complex Apps where multiple SSL pinning policies need to be used independently + + For more complex Apps where multiple SSL pinning policies need to be used independently (for example within different frameworks), TrustKit can be used in "multi-instance" mode by leveraging the `-initWithConfiguration:` method described at the end of this page. - + A TrustKit pinning policy is a dictionary which contains some global, App-wide settings (of type `TSKGlobalConfigurationKey`) as well as domain-specific configuration keys - (of type `TSKDomainConfigurationKey`) to be defined under the `kTSKPinnedDomains` entry. + (of type `TSKDomainConfigurationKey`) to be defined under the `kTSKPinnedDomains` entry. The following table shows the keys and the types of the corresponding values, and uses indentation to indicate structure: - + ``` | Key | Type | |----------------------------------------------|------------| @@ -67,11 +66,11 @@ NS_ASSUME_NONNULL_BEGIN | ____ TSKReportUris | Array | | ____ TSKDisableDefaultReportUri | Boolean | ``` - + When setting the pinning policy programmatically, it has to be supplied to the - `initSharedInstanceWithConfiguration:` method as a dictionary in order to initialize + `initSharedInstanceWithConfiguration:` method as a dictionary in order to initialize TrustKit. For example: - + ``` NSDictionary *trustKitConfig = @{ @@ -93,13 +92,13 @@ NS_ASSUME_NONNULL_BEGIN kTSKIncludeSubdomains : @YES } }}; - + [TrustKit initSharedInstanceWithConfiguration:trustKitConfig]; trustKit = [TrustKit sharedInstance]; ``` - + Similarly, the TrustKit singleton can be initialized in Swift: - + ``` let trustKitConfig = [ kTSKSwizzleNetworkDelegates: false, @@ -110,66 +109,60 @@ NS_ASSUME_NONNULL_BEGIN "JbQbUG5JMJUoI6brnx0x3vZF6jilxsapbXGVfjhN8Fg=", "WoiWRyIOVNa9ihaBciRSC7XHjliYS9VwUGOIud4PB18=" ],]]] as [String : Any] - + TrustKit.initSharedInstance(withConfiguration:trustKitConfig) ``` - + After initialization, the `TrustKit` instance's `pinningValidator` should be used to implement pinning validation within the App's network authentication handlers. */ @interface TrustKit : NSObject - #pragma mark Usage in Singleton Mode /** See `+initSharedInstanceWithConfiguration:sharedContainerIdentifier:` - + @param trustKitConfig A dictionary containing various keys for configuring the SSL pinning policy. @exception NSException Thrown when the supplied configuration is invalid or TrustKit has already been initialized. - + */ + (void)initSharedInstanceWithConfiguration:(NSDictionary *)trustKitConfig; /** Initialize the global TrustKit singleton with the supplied pinning policy. - + @param trustKitConfig A dictionary containing various keys for configuring the SSL pinning policy. @param sharedContainerIdentifier The container identifier for an app extension. This must be set in order for reports to be sent from an app extension. See https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1409450-sharedcontaineridentifier @exception NSException Thrown when the supplied configuration is invalid or TrustKit has already been initialized. - + */ -+ (void)initSharedInstanceWithConfiguration:(NSDictionary *)trustKitConfig - sharedContainerIdentifier:(nullable NSString *)sharedContainerIdentifier; ++ (void)initSharedInstanceWithConfiguration:(NSDictionary *)trustKitConfig sharedContainerIdentifier:(nullable NSString *)sharedContainerIdentifier; /** Retrieve the global TrustKit singleton instance. Raises an exception if `+initSharedInstanceWithConfiguration:` has not yet been invoked. - + @return the shared TrustKit singleton */ + (instancetype)sharedInstance; - #pragma mark Implementing Pinning Validation - /** Retrieve the validator instance conforming to the pinning policy of this TrustKit instance. - + The validator should be used to implement pinning validation within the App's network authentication handlers. */ @property (nonatomic, nonnull) TSKPinningValidator *pinningValidator; - #pragma mark Configuring a Validation Callback - /** Register a block to be invoked for every request that is going through TrustKit's pinning validation mechanism. See `TSKPinningValidatorCallback` for more information. @@ -181,31 +174,29 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nonatomic, null_resettable) dispatch_queue_t pinningValidatorCallbackQueue; - #pragma mark Usage in Multi-Instance Mode /** See `-initWithConfiguration:sharedContainerIdentifier:` - + @param trustKitConfig A dictionary containing various keys for configuring the SSL pinning policy. */ - (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig; /** Initialize a local TrustKit instance with the supplied SSL pinning policy configuration. - + This method is useful in scenarios where the TrustKit singleton cannot be used, for example within larger Apps that have split some of their functionality into multiple framework/SDK. Each framework can initialize its own instance of TrustKit and use it for pinning validation independently of the App's other components. - + @param trustKitConfig A dictionary containing various keys for configuring the SSL pinning policy. @param sharedContainerIdentifier The container identifier for an app extension. This must be set in order for reports to be sent from an app extension. See https://developer.apple.com/documentation/foundation/nsurlsessionconfiguration/1409450-sharedcontaineridentifier */ -- (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig - sharedContainerIdentifier:(nullable NSString *)sharedContainerIdentifier; +- (instancetype)initWithConfiguration:(NSDictionary *)trustKitConfig sharedContainerIdentifier:(nullable NSString *)sharedContainerIdentifier; @end NS_ASSUME_NONNULL_END diff --git a/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.h b/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.h index af384c33..fda4d464 100644 --- a/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.h +++ b/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.h @@ -1,12 +1,12 @@ /* - + AppDelegate.h TrustKitDemo - + Copyright 2017 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 @@ -16,4 +16,3 @@ @property (nonatomic, readonly) NSDictionary *trustKitConfig; @end - diff --git a/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.m b/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.m index 8f180ca8..2819d741 100644 --- a/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.m +++ b/TrustKitDemo/TrustKitDemo-ObjC/AppDelegate.m @@ -1,12 +1,12 @@ /* - + AppDelegate.m TrustKitDemo - + Copyright 2017 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 "AppDelegate.h" @@ -18,57 +18,54 @@ @interface AppDelegate () @implementation AppDelegate - -- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions -{ +- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Initialize TrustKit - _trustKitConfig = - @{ - // Do not auto-swizzle NSURLSession delegates - kTSKSwizzleNetworkDelegates: @NO, - - kTSKPinnedDomains: @{ - - // Pin invalid SPKI hashes to *.google.com to demonstrate pinning failures - @"google.com": @{ - kTSKEnforcePinning: @YES, - kTSKIncludeSubdomains: @YES, - - // Wrong SPKI hashes to demonstrate pinning failure - kTSKPublicKeyHashes: @[ - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" - ], - - // Send reports for pinning failures - // Email info@datatheorem.com if you need a free dashboard to see your App's reports - kTSKReportUris: @[@"https://overmind.datatheorem.com/trustkit/report"] - }, - - - // Pin valid SPKI hashes to www.datatheorem.com to demonstrate success - @"www.datatheorem.com" : @{ - kTSKEnforcePinning:@YES, - - // Valid SPKI hashes to demonstrate success - kTSKPublicKeyHashes : @[ - @"hxqRlPTu1bMS/0DITB1SSu0vd4u/8l8TjPgfaAp63Gc=", // GTS Root R1 - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key but 2 pins need to be provided - ] - }}}; - + _trustKitConfig = @{ + // Do not auto-swizzle NSURLSession delegates + kTSKSwizzleNetworkDelegates: @NO, + + kTSKPinnedDomains: @ { + + // Pin invalid SPKI hashes to *.google.com to demonstrate pinning failures + @"google.com": @ { + kTSKEnforcePinning: @YES, + kTSKIncludeSubdomains: @YES, + + // Wrong SPKI hashes to demonstrate pinning failure + kTSKPublicKeyHashes: @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="], + + // Send reports for pinning failures + // Email info@datatheorem.com if you need a free dashboard to see your App's reports + kTSKReportUris: @[@"https://overmind.datatheorem.com/trustkit/report"] + }, + + // Pin valid SPKI hashes to www.datatheorem.com to demonstrate success + @"www.datatheorem.com": @ { + kTSKEnforcePinning: @YES, + + // Valid SPKI hashes to demonstrate success + kTSKPublicKeyHashes: @[ + @"hxqRlPTu1bMS/0DITB1SSu0vd4u/8l8TjPgfaAp63Gc=", // GTS Root R1 + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key but 2 pins need to be provided + ] + } + } + }; + [TrustKit initSharedInstanceWithConfiguration:_trustKitConfig]; - + // Demonstrate how to receive pin validation notifications (only useful for performance/metrics) [TrustKit sharedInstance].pinningValidatorCallbackQueue = dispatch_get_main_queue(); [TrustKit sharedInstance].pinningValidatorCallback = ^(TSKPinningValidatorResult *result, NSString *notedHostname, TKSDomainPinningPolicy *policy) { - NSLog(@"Received pinning validation notification:\n\tDuration: %0.4f\n\tDecision: %ld\n\tResult: %ld\n\tHostname: %@", - result.validationDuration, - (long)result.finalTrustDecision, - (long)result.evaluationResult, - result.serverHostname); + NSLog( + @"Received pinning validation notification:\n\tDuration: %0.4f\n\tDecision: %ld\n\tResult: %ld\n\tHostname: %@", + result.validationDuration, + (long)result.finalTrustDecision, + (long)result.evaluationResult, + result.serverHostname + ); }; - + return YES; } diff --git a/TrustKitDemo/TrustKitDemo-ObjC/ViewController.h b/TrustKitDemo/TrustKitDemo-ObjC/ViewController.h index f36bb4ce..55bfbe58 100644 --- a/TrustKitDemo/TrustKitDemo-ObjC/ViewController.h +++ b/TrustKitDemo/TrustKitDemo-ObjC/ViewController.h @@ -1,12 +1,12 @@ /* - + ViewController.h TrustKitDemo - + Copyright 2017 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 @@ -21,4 +21,3 @@ @property (weak, nonatomic) IBOutlet UIActivityIndicatorView *activityIndicator; @end - diff --git a/TrustKitDemo/TrustKitDemo-ObjC/ViewController.m b/TrustKitDemo/TrustKitDemo-ObjC/ViewController.m index 48267b3d..d5595553 100644 --- a/TrustKitDemo/TrustKitDemo-ObjC/ViewController.m +++ b/TrustKitDemo/TrustKitDemo-ObjC/ViewController.m @@ -1,12 +1,12 @@ /* - + ViewController.m TrustKitDemo - + Copyright 2017 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 "ViewController.h" @@ -27,28 +27,25 @@ - (void)displayAlertWithTitle:(NSString *)title messageFormat:(NSString *)format @implementation ViewController -- (void)viewDidLoad -{ +- (void)viewDidLoad { [super viewDidLoad]; - + self.invalidPinBtn.layer.cornerRadius = 4; self.validPinBtn.layer.cornerRadius = 4; self.notPinnedBtn.layer.cornerRadius = 4; self.webViewInvalidPinBtn.layer.cornerRadius = 4; self.webViewValidPinBtn.layer.cornerRadius = 4; - - self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] - delegate:self - delegateQueue:nil]; + + self.session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration ephemeralSessionConfiguration] delegate:self delegateQueue:nil]; } #pragma mark TrustKit Pinning Reference -- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler -{ +- (void)URLSession:(NSURLSession *)session + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler { // Call into TrustKit here to do pinning validation - if (![TrustKit.sharedInstance.pinningValidator handleChallenge:challenge completionHandler:completionHandler]) - { + if (![TrustKit.sharedInstance.pinningValidator handleChallenge:challenge completionHandler:completionHandler]) { // TrustKit did not handle this challenge: perhaps it was not for server trust // or the domain was not pinned. Fall back to the default behavior completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); @@ -57,78 +54,67 @@ - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticat #pragma mark Test Control -- (void)loadUrl:(NSURL *)url -{ +- (void)loadUrl:(NSURL *)url { // Show loading view [self showActivityIndicatorInCurrentViewController]; - + // Load a URL with a good pinning configuration - NSURLSessionDataTask *task = [self.session dataTaskWithURL:url completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) { - dispatch_async(dispatch_get_main_queue(), ^{ - if (!error) { - [self displayAlertWithTitle:@"Connected" messageFormat:@"Connected to %@", url.absoluteString]; - } - else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { - [self displayAlertWithTitle:@"Blocked" messageFormat:@"TrustKit blocked the connection to %@", url.absoluteString]; - } - else { - [self displayAlertWithTitle:@"Failed" messageFormat:@"Connection to %@ failed\n\n%@", url.absoluteString, error.description]; - } - }); - }]; + NSURLSessionDataTask *task = [self.session dataTaskWithURL:url + completionHandler:^(NSData *_Nullable data, NSURLResponse *_Nullable response, NSError *_Nullable error) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (!error) { + [self displayAlertWithTitle:@"Connected" messageFormat:@"Connected to %@", url.absoluteString]; + } else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + [self displayAlertWithTitle:@"Blocked" messageFormat:@"TrustKit blocked the connection to %@", url.absoluteString]; + } else { + [self displayAlertWithTitle:@"Failed" + messageFormat:@"Connection to %@ failed\n\n%@", url.absoluteString, error.description]; + } + }); + }]; [task resume]; } -- (IBAction)testInvalidPinning:(UIButton *)sender -{ +- (IBAction)testInvalidPinning:(UIButton *)sender { [self loadUrl:[NSURL URLWithString:baseURLGoogle]]; } -- (IBAction)testValidPinning:(UIButton *)sender -{ +- (IBAction)testValidPinning:(UIButton *)sender { [self loadUrl:[NSURL URLWithString:baseURLDT]]; } -- (IBAction)testNotPinned:(UIButton *)sender -{ +- (IBAction)testNotPinned:(UIButton *)sender { [self loadUrl:[NSURL URLWithString:baseURLYahoo]]; } -- (IBAction)testWebViewInvalidPinning:(UIButton *)sender -{ +- (IBAction)testWebViewInvalidPinning:(UIButton *)sender { [self presentViewController:[[WebViewController alloc] initWithURL:[NSURL URLWithString:baseURLGoogle]] animated:YES completion:nil]; } -- (IBAction)testWebViewValidPinning:(UIButton *)sender -{ +- (IBAction)testWebViewValidPinning:(UIButton *)sender { [self presentViewController:[[WebViewController alloc] initWithURL:[NSURL URLWithString:baseURLDT]] animated:YES completion:nil]; } -- (void)displayAlertWithTitle:(NSString *)title messageFormat:(NSString *)format, ... -{ +- (void)displayAlertWithTitle:(NSString *)title messageFormat:(NSString *)format, ... { // Hide Activity Indicator [self hideActivityIndicator]; - + va_list args; va_start(args, format); NSString *message = [[NSString alloc] initWithFormat:format arguments:args]; va_end(args); - - UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title - message:message - preferredStyle:UIAlertControllerStyleAlert]; + + UIAlertController *alertController = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert]; [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil]]; [self presentViewController:alertController animated:YES completion:nil]; } -- (void)showActivityIndicatorInCurrentViewController -{ +- (void)showActivityIndicatorInCurrentViewController { self.view.userInteractionEnabled = NO; [self.activityIndicator startAnimating]; } -- (void)hideActivityIndicator -{ +- (void)hideActivityIndicator { self.view.userInteractionEnabled = YES; [self.activityIndicator stopAnimating]; } diff --git a/TrustKitDemo/TrustKitDemo-ObjC/WebViewController.m b/TrustKitDemo/TrustKitDemo-ObjC/WebViewController.m index 60d3b77b..b1fccb43 100644 --- a/TrustKitDemo/TrustKitDemo-ObjC/WebViewController.m +++ b/TrustKitDemo/TrustKitDemo-ObjC/WebViewController.m @@ -11,18 +11,15 @@ @interface WebViewController () @implementation WebViewController -- (instancetype)initWithURL:(NSURL *)url -{ +- (instancetype)initWithURL:(NSURL *)url { self = [super initWithNibName:nil bundle:nil]; - if (self) - { + if (self) { _url = url; } return self; } -- (void)loadView -{ +- (void)loadView { WKWebView *webView = [[WKWebView alloc] init]; webView.navigationDelegate = self; self.view = webView; @@ -30,9 +27,8 @@ - (void)loadView } - (void)webView:(WKWebView *)webView -didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge -completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler -{ + didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler { NSString *host = challenge.protectionSpace.host; void (^completeOnMainQueue)(NSURLSessionAuthChallengeDisposition, NSURLCredential *) = ^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) { dispatch_async(dispatch_get_main_queue(), ^{ @@ -44,26 +40,22 @@ - (void)webView:(WKWebView *)webView }; dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{ TSKPinningValidator *pinningValidator = [[TrustKit sharedInstance] pinningValidator]; - if (![pinningValidator handleChallenge:challenge completionHandler:completeOnMainQueue]) - { + if (![pinningValidator handleChallenge:challenge completionHandler:completeOnMainQueue]) { completeOnMainQueue(NSURLSessionAuthChallengePerformDefaultHandling, nil); } }); } -- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error -{ +- (void)webView:(WKWebView *)webView didFailProvisionalNavigation:(WKNavigation *)navigation withError:(NSError *)error { NSURL *failingURL = error.userInfo[NSURLErrorFailingURLErrorKey] ?: webView.URL; UIAlertController *alertController; if (self.blockedHost && [failingURL.host isEqualToString:self.blockedHost]) { alertController = [UIAlertController alertControllerWithTitle:@"Blocked" message:[NSString stringWithFormat:@"TrustKit blocked the connection to %@", failingURL.absoluteString] preferredStyle:UIAlertControllerStyleAlert]; - } - else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { + } else if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled) { return; - } - else { + } else { alertController = [UIAlertController alertControllerWithTitle:@"Failed" message:[NSString stringWithFormat:@"Loading %@ failed\n\n%@", failingURL.absoluteString, error.description] preferredStyle:UIAlertControllerStyleAlert]; diff --git a/TrustKitDemo/TrustKitDemo-ObjC/main.m b/TrustKitDemo/TrustKitDemo-ObjC/main.m index 5cbb4215..76b29915 100644 --- a/TrustKitDemo/TrustKitDemo-ObjC/main.m +++ b/TrustKitDemo/TrustKitDemo-ObjC/main.m @@ -1,18 +1,18 @@ /* - + main.m TrustKitDemo - + Copyright 2017 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 #import "AppDelegate.h" +#import -int main(int argc, char * argv[]) { +int main(int argc, char *argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } diff --git a/TrustKitDemo/TrustKitDemo-Swift/AppDelegate.swift b/TrustKitDemo/TrustKitDemo-Swift/AppDelegate.swift index 97929cbb..5fd4c73c 100644 --- a/TrustKitDemo/TrustKitDemo-Swift/AppDelegate.swift +++ b/TrustKitDemo/TrustKitDemo-Swift/AppDelegate.swift @@ -1,51 +1,50 @@ -/* - - AppDelegate.swift - TrustKitDemoInSwift - - Copyright 2017 The TrustKit Project Authors - Licensed under the MIT license, see associated LICENSE file for terms. - See AUTHORS file for the list of project authors. - - */ +// AppDelegate.swift +// TrustKitDemoInSwift +// +// Copyright 2017 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 UIKit import TrustKit +import UIKit @UIApplicationMain class AppDelegate: UIResponder, UIApplicationDelegate { - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - + func application( + _ application: UIApplication, + didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? + ) -> Bool { + let trustKitConfig: [String: Any] = [ kTSKSwizzleNetworkDelegates: false, kTSKPinnedDomains: [ "google.com": [ kTSKEnforcePinning: true, kTSKIncludeSubdomains: true, - + // Invalid pins to demonstrate a pinning failure kTSKPublicKeyHashes: [ - "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" + "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" ], - kTSKReportUris:["https://overmind.datatheorem.com/trustkit/report"], + kTSKReportUris: ["https://overmind.datatheorem.com/trustkit/report"] ], "www.datatheorem.com": [ kTSKEnforcePinning: true, - + // Valid pin and backup pin kTSKPublicKeyHashes: [ "hxqRlPTu1bMS/0DITB1SSu0vd4u/8l8TjPgfaAp63Gc=", "BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" ], - kTSKReportUris:["https://overmind.datatheorem.com/trustkit/report"], + kTSKReportUris: ["https://overmind.datatheorem.com/trustkit/report"] ] - ]] - + ] + ] + TrustKit.initSharedInstance(withConfiguration: trustKitConfig) - + return true } } - diff --git a/TrustKitDemo/TrustKitDemo-Swift/DTViewController.swift b/TrustKitDemo/TrustKitDemo-Swift/DTViewController.swift index 86653203..ae795d06 100644 --- a/TrustKitDemo/TrustKitDemo-Swift/DTViewController.swift +++ b/TrustKitDemo/TrustKitDemo-Swift/DTViewController.swift @@ -1,57 +1,59 @@ -/* - - DTViewController.swift - TrustKitDemoInSwift - - Copyright 2017 The TrustKit Project Authors - Licensed under the MIT license, see associated LICENSE file for terms. - See AUTHORS file for the list of project authors. - - */ +// DTViewController.swift +// TrustKitDemoInSwift +// +// Copyright 2017 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 UIKit import TrustKit +import UIKit class DTViewController: UIViewController, URLSessionDelegate { @IBOutlet weak var testInvalidPinBtn: UIButton? { - didSet { testInvalidPinBtn?.layer.cornerRadius = 4; } // add rounded corners + didSet { testInvalidPinBtn?.layer.cornerRadius = 4 } // add rounded corners } - + @IBOutlet weak var testValidPinBtn: UIButton? { - didSet { testValidPinBtn?.layer.cornerRadius = 4; } // add rounded corners + didSet { testValidPinBtn?.layer.cornerRadius = 4 } // add rounded corners } - + @IBOutlet weak var testNotPinnedBtn: UIButton? { - didSet { testNotPinnedBtn?.layer.cornerRadius = 4; } + didSet { testNotPinnedBtn?.layer.cornerRadius = 4 } } @IBOutlet weak var testWebViewInvalidPinBtn: UIButton? { - didSet { testWebViewInvalidPinBtn?.layer.cornerRadius = 4; } + didSet { testWebViewInvalidPinBtn?.layer.cornerRadius = 4 } } @IBOutlet weak var testWebViewValidPinBtn: UIButton? { - didSet { testWebViewValidPinBtn?.layer.cornerRadius = 4; } + didSet { testWebViewValidPinBtn?.layer.cornerRadius = 4 } } - + @IBOutlet weak var activityIndicator: UIActivityIndicatorView! - + lazy var session: URLSession = { - URLSession(configuration: URLSessionConfiguration.ephemeral, - delegate: self, - delegateQueue: nil) + URLSession( + configuration: URLSessionConfiguration.ephemeral, + delegate: self, + delegateQueue: nil + ) }() - + let baseURLGoogle = "https://www.google.com/" - + let baseURLDT = "https://www.datatheorem.com/" - + let baseURLYahoo = "https://www.yahoo.com/" - + // MARK: TrustKit Pinning Reference - - nonisolated func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { - + + nonisolated func urlSession( + _ session: URLSession, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) { + // Call into TrustKit here to do pinning validation if TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completionHandler) == false { // TrustKit did not handle this challenge: perhaps it was not for server trust @@ -59,38 +61,46 @@ class DTViewController: UIViewController, URLSessionDelegate { completionHandler(.performDefaultHandling, nil) } } - + // MARK: Test Control - + func loadURL(url: URL) { // Show loading view showActivityIndicatorInCurrentViewController() - + // Load a URL with a good pinning configuration - let task = session.dataTask(with: url) { [weak self] (data, response, error) in + let task = session.dataTask(with: url) { [weak self] (_, _, error) in DispatchQueue.main.async { switch error { case nil: self?.displayAlert(withTitle: "Connected", message: "Connected to \(url.absoluteString)") case let error as URLError where error.code == .cancelled: - self?.displayAlert(withTitle: "Blocked", message: "TrustKit blocked the connection to \(url.absoluteString)") + self? + .displayAlert( + withTitle: "Blocked", + message: "TrustKit blocked the connection to \(url.absoluteString)" + ) case let error?: - self?.displayAlert(withTitle: "Failed", message: "Connection to \(url.absoluteString) failed\n\n\(error)") + self? + .displayAlert( + withTitle: "Failed", + message: "Connection to \(url.absoluteString) failed\n\n\(error)" + ) } } } - + task.resume() } - + @IBAction func testInvalidPinning(_ sender: UIButton) { self.loadURL(url: URL(string: baseURLGoogle)!) } - + @IBAction func testValidPinning(_ sender: UIButton) { self.loadURL(url: URL(string: baseURLDT)!) } - + @IBAction func testNotPinned(_ sender: UIButton) { self.loadURL(url: URL(string: baseURLYahoo)!) } @@ -102,24 +112,23 @@ class DTViewController: UIViewController, URLSessionDelegate { @IBAction func testWebViewValidPinning(_ sender: UIButton) { present(WebViewController(url: URL(string: baseURLDT)!), animated: true) } - + func displayAlert(withTitle title: String, message: String) { // Hide Activity Indicator hideActivityIndicator() - + let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert) alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) present(alertController, animated: true, completion: nil) } - + func showActivityIndicatorInCurrentViewController() { view.isUserInteractionEnabled = false activityIndicator.startAnimating() } - + func hideActivityIndicator() { view.isUserInteractionEnabled = true activityIndicator.stopAnimating() } } - diff --git a/TrustKitDemo/TrustKitDemo-Swift/WebViewController.swift b/TrustKitDemo/TrustKitDemo-Swift/WebViewController.swift index 61da287a..4fad281a 100644 --- a/TrustKitDemo/TrustKitDemo-Swift/WebViewController.swift +++ b/TrustKitDemo/TrustKitDemo-Swift/WebViewController.swift @@ -1,6 +1,6 @@ +import TrustKit import UIKit import WebKit -import TrustKit class WebViewController: UIViewController, WKNavigationDelegate { @@ -23,23 +23,28 @@ class WebViewController: UIViewController, WKNavigationDelegate { webView.load(URLRequest(url: url)) } - func webView(_ webView: WKWebView, - didReceive challenge: URLAuthenticationChallenge, - completionHandler: @escaping @MainActor @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) { + func webView( + _ webView: WKWebView, + didReceive challenge: URLAuthenticationChallenge, + completionHandler: @escaping @MainActor @Sendable (URLSession.AuthChallengeDisposition, URLCredential?) -> Void + ) { let host = challenge.protectionSpace.host - DispatchQueue.global(qos: .userInitiated).async { - let completeOnMainQueue = { (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?) in - DispatchQueue.main.async { - if disposition == .cancelAuthenticationChallenge { - self.blockedHost = host + DispatchQueue.global(qos: .userInitiated) + .async { + let completeOnMainQueue = { + (disposition: URLSession.AuthChallengeDisposition, credential: URLCredential?) in + DispatchQueue.main.async { + if disposition == .cancelAuthenticationChallenge { + self.blockedHost = host + } + completionHandler(disposition, credential) } - completionHandler(disposition, credential) + } + if !TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completeOnMainQueue) + { + completeOnMainQueue(.performDefaultHandling, nil) } } - if !TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completeOnMainQueue) { - completeOnMainQueue(.performDefaultHandling, nil) - } - } } func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) { @@ -47,11 +52,19 @@ class WebViewController: UIViewController, WKNavigationDelegate { let failingAddress = failingURL?.absoluteString ?? url.absoluteString let alertController: UIAlertController if let blockedHost, failingURL?.host == blockedHost { - alertController = UIAlertController(title: "Blocked", message: "TrustKit blocked the connection to \(failingAddress)", preferredStyle: .alert) + alertController = UIAlertController( + title: "Blocked", + message: "TrustKit blocked the connection to \(failingAddress)", + preferredStyle: .alert + ) } else if let error = error as? URLError, error.code == .cancelled { return } else { - alertController = UIAlertController(title: "Failed", message: "Loading \(failingAddress) failed\n\n\(error)", preferredStyle: .alert) + alertController = UIAlertController( + title: "Failed", + message: "Loading \(failingAddress) failed\n\n\(error)", + preferredStyle: .alert + ) } alertController.addAction(UIAlertAction(title: "OK", style: .default, handler: nil)) present(alertController, animated: true, completion: nil) diff --git a/TrustKitTests/TSKCertificateUtils.h b/TrustKitTests/TSKCertificateUtils.h index 837aeba0..74b6a420 100644 --- a/TrustKitTests/TSKCertificateUtils.h +++ b/TrustKitTests/TSKCertificateUtils.h @@ -1,12 +1,12 @@ /* - + TSKCertificateUtils.h 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. - + */ #if __has_feature(modules) diff --git a/TrustKitTests/TSKCertificateUtils.m b/TrustKitTests/TSKCertificateUtils.m index 9e2d5119..256f8306 100644 --- a/TrustKitTests/TSKCertificateUtils.m +++ b/TrustKitTests/TSKCertificateUtils.m @@ -1,43 +1,39 @@ /* - + TSKCertificateUtils.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 "TSKCertificateUtils.h" @implementation TSKCertificateUtils -+ (SecCertificateRef)createCertificateFromPem:(NSString *)pemFilename -{ ++ (SecCertificateRef)createCertificateFromPem:(NSString *)pemFilename { NSBundle *bundle = [NSBundle bundleForClass:self.class]; NSString *pemFilepath = [bundle pathForResource:pemFilename ofType:@"pem"]; NSString *pemString = [NSString stringWithContentsOfFile:pemFilepath usedEncoding:nil error:nil]; pemString ?: [NSException raise:@"Test error" format:@"Could not open PEM file %@", pemFilename]; - + pemString = [pemString stringByTrimmingCharactersInSet:NSCharacterSet.whitespaceAndNewlineCharacterSet]; pemString = [pemString stringByReplacingOccurrencesOfString:@"-----BEGIN CERTIFICATE-----" withString:@""]; pemString = [pemString stringByReplacingOccurrencesOfString:@"-----END CERTIFICATE-----" withString:@""]; - + NSData *der = [[NSData alloc] initWithBase64EncodedString:pemString options:NSDataBase64DecodingIgnoreUnknownCharacters]; der ?: [NSException raise:@"Test error" format:@"Could not convert PEM to DER from %@", pemFilename]; - + SecCertificateRef certificate = SecCertificateCreateWithData(kCFAllocatorDefault, (__bridge CFDataRef)der); return certificate; } - -+ (SecCertificateRef)createCertificateFromDer:(NSString *)derFilename -{ ++ (SecCertificateRef)createCertificateFromDer:(NSString *)derFilename { NSBundle *bundle = [NSBundle bundleForClass:[self class]]; CFDataRef certData = (__bridge_retained CFDataRef)[NSData dataWithContentsOfFile:[bundle pathForResource:derFilename ofType:@"der"]]; - if (!certData) - { + if (!certData) { [NSException raise:@"Test error" format:@"Could not open certificate at path %@", derFilename]; } SecCertificateRef certificate = SecCertificateCreateWithData(kCFAllocatorDefault, certData); @@ -45,30 +41,24 @@ + (SecCertificateRef)createCertificateFromDer:(NSString *)derFilename return certificate; } - + (SecTrustRef)createTrustWithCertificates:(const void **)certArray arrayLength:(NSInteger)certArrayLength anchorCertificates:(const void **)anchorCertificates - arrayLength:(NSInteger)anchorArrayLength -{ + arrayLength:(NSInteger)anchorArrayLength { CFArrayRef certificateChain = CFArrayCreate(NULL, (const void **)certArray, certArrayLength, &kCFTypeArrayCallBacks); SecPolicyRef policy = SecPolicyCreateSSL(true, NULL); SecTrustRef trust; - + OSStatus result = SecTrustCreateWithCertificates(certificateChain, policy, &trust); CFRelease(certificateChain); CFRelease(policy); - if (result != errSecSuccess) - { + if (result != errSecSuccess) { [NSException raise:@"Test error" format:@"SecTrustCreateWithCertificates did not return errSecSuccess"]; } - - - if (anchorCertificates) - { + + if (anchorCertificates) { CFArrayRef trustStore = CFArrayCreate(NULL, (const void **)anchorCertificates, anchorArrayLength, NULL); - if (SecTrustSetAnchorCertificates(trust, trustStore) != errSecSuccess) - { + if (SecTrustSetAnchorCertificates(trust, trustStore) != errSecSuccess) { CFRelease(trust); [NSException raise:@"Test error" format:@"SecTrustCreateWithCertificates did not return errSecSuccess"]; } diff --git a/TrustKitTests/TSKEndToEndNSURLSessionTests.m b/TrustKitTests/TSKEndToEndNSURLSessionTests.m index 26f1b640..0d23a43d 100644 --- a/TrustKitTests/TSKEndToEndNSURLSessionTests.m +++ b/TrustKitTests/TSKEndToEndNSURLSessionTests.m @@ -6,17 +6,15 @@ // Copyright © 2015 TrustKit. All rights reserved. // -#import -#import "../TrustKit/public/TrustKit/TrustKit.h" -#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" #import "../TrustKit/configuration_utils.h" +#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" #import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" - +#import "../TrustKit/public/TrustKit/TrustKit.h" +#import #pragma mark Test NSURLSession delegate -@interface TestNSURLSessionDelegate : NSObject -{ +@interface TestNSURLSessionDelegate : NSObject { XCTestExpectation *testExpectation; } @property TSKPinningValidator *validator; @@ -25,99 +23,80 @@ @interface TestNSURLSessionDelegate : NSObject -#import "../TrustKit/public/TrustKit/TrustKit.h" #import "../TrustKit/configuration_utils.h" - - +#import "../TrustKit/public/TrustKit/TrustKit.h" +#import #pragma mark Test NSURLSession delegate -@interface TestNSURLSessionDelegateSwizzling : NSObject -{ +@interface TestNSURLSessionDelegateSwizzling : NSObject { XCTestExpectation *testExpectation; - + BOOL _completedConnectionToCloudflare; BOOL _completedConnectionToFacebook; } @property TSKPinningValidator *validator; +- (instancetype)initWithValidator:(TSKPinningValidator *)validator expectation:(XCTestExpectation *)expectation; -- (instancetype)initWithValidator:(TSKPinningValidator *)validator - expectation:(XCTestExpectation *)expectation; +- (void)URLSession:(NSURLSession *_Nonnull)session task:(NSURLSessionTask *_Nonnull)task didCompleteWithError:(NSError *_Nullable)error; -- (void)URLSession:(NSURLSession * _Nonnull)session - task:(NSURLSessionTask * _Nonnull)task -didCompleteWithError:(NSError * _Nullable)error; - -- (void)URLSession:(NSURLSession * _Nonnull)session - dataTask:(NSURLSessionDataTask * _Nonnull)dataTask -didReceiveResponse:(NSURLResponse * _Nonnull)response - completionHandler:(void (^ _Nonnull)(NSURLSessionResponseDisposition disposition))completionHandler; +- (void)URLSession:(NSURLSession *_Nonnull)session + dataTask:(NSURLSessionDataTask *_Nonnull)dataTask + didReceiveResponse:(NSURLResponse *_Nonnull)response + completionHandler:(void (^_Nonnull)(NSURLSessionResponseDisposition disposition))completionHandler; - (void)URLSession:(NSURLSession *)session - task:(NSURLSessionTask *)task -willPerformHTTPRedirection:(NSHTTPURLResponse *)response - newRequest:(NSURLRequest *)request - completionHandler:(void (^)(NSURLRequest *))completionHandler; - + task:(NSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler; -- (void)URLSession:(NSURLSession * _Nonnull)session - task:(NSURLSessionTask * _Nonnull)task -didReceiveChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge - completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * _Nullable credential))completionHandler; +- (void)URLSession:(NSURLSession *_Nonnull)session + task:(NSURLSessionTask *_Nonnull)task + didReceiveChallenge:(NSURLAuthenticationChallenge *_Nonnull)challenge + completionHandler:(void (^_Nonnull)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential))completionHandler; @end - @implementation TestNSURLSessionDelegateSwizzling -- (instancetype)initWithValidator:(TSKPinningValidator *)validator - expectation:(XCTestExpectation *)expectation -{ +- (instancetype)initWithValidator:(TSKPinningValidator *)validator expectation:(XCTestExpectation *)expectation { self = [super init]; - if (self) - { + if (self) { testExpectation = expectation; _validator = validator; _completedConnectionToCloudflare = NO; @@ -68,86 +55,66 @@ - (instancetype)initWithValidator:(TSKPinningValidator *)validator return self; } -- (void)URLSession:(NSURLSession * _Nonnull)session - task:(NSURLSessionTask * _Nonnull)task -didCompleteWithError:(NSError * _Nullable)error -{ +- (void)URLSession:(NSURLSession *_Nonnull)session task:(NSURLSessionTask *_Nonnull)task didCompleteWithError:(NSError *_Nullable)error { NSLog(@"Received error, %@", error); - if ([task.originalRequest.URL.host isEqualToString:@"www.facebook.com"]) - { + if ([task.originalRequest.URL.host isEqualToString:@"www.facebook.com"]) { _completedConnectionToFacebook = YES; - } - else if ([task.originalRequest.URL.host isEqualToString:@"www.datatheorem.com"]) - { + } else if ([task.originalRequest.URL.host isEqualToString:@"www.datatheorem.com"]) { _completedConnectionToCloudflare = YES; } - - if (_completedConnectionToCloudflare && _completedConnectionToFacebook) - { + + if (_completedConnectionToCloudflare && _completedConnectionToFacebook) { [testExpectation fulfill]; } } -- (void)URLSession:(NSURLSession * _Nonnull)session - dataTask:(NSURLSessionDataTask * _Nonnull)dataTask -didReceiveResponse:(NSURLResponse * _Nonnull)response - completionHandler:(void (^ _Nonnull)(NSURLSessionResponseDisposition disposition))completionHandler -{ - if ([dataTask.originalRequest.URL.host isEqualToString:@"www.facebook.com"]) - { +- (void)URLSession:(NSURLSession *_Nonnull)session + dataTask:(NSURLSessionDataTask *_Nonnull)dataTask + didReceiveResponse:(NSURLResponse *_Nonnull)response + completionHandler:(void (^_Nonnull)(NSURLSessionResponseDisposition disposition))completionHandler { + if ([dataTask.originalRequest.URL.host isEqualToString:@"www.facebook.com"]) { _completedConnectionToFacebook = YES; - } - else if ([dataTask.originalRequest.URL.host isEqualToString:@"www.datatheorem.com"]) - { + } else if ([dataTask.originalRequest.URL.host isEqualToString:@"www.datatheorem.com"]) { _completedConnectionToCloudflare = YES; } - - if (_completedConnectionToCloudflare && _completedConnectionToFacebook) - { + + if (_completedConnectionToCloudflare && _completedConnectionToFacebook) { [testExpectation fulfill]; } } -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task willPerformHTTPRedirection:(NSHTTPURLResponse *)response newRequest:(NSURLRequest *)request completionHandler:(void (^)(NSURLRequest *))completionHandler -{ - +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + willPerformHTTPRedirection:(NSHTTPURLResponse *)response + newRequest:(NSURLRequest *)request + completionHandler:(void (^)(NSURLRequest *))completionHandler { + NSLog(@"Received redirection"); - if ([task.originalRequest.URL.host isEqualToString:@"www.facebook.com"]) - { + if ([task.originalRequest.URL.host isEqualToString:@"www.facebook.com"]) { _completedConnectionToFacebook = YES; - } - else if ([task.originalRequest.URL.host isEqualToString:@"www.datatheorem.com"]) - { + } else if ([task.originalRequest.URL.host isEqualToString:@"www.datatheorem.com"]) { _completedConnectionToCloudflare = YES; } - - if (_completedConnectionToCloudflare && _completedConnectionToFacebook) - { + + if (_completedConnectionToCloudflare && _completedConnectionToFacebook) { [testExpectation fulfill]; } - + // Do not follow redirections as they cause two pinning validations - if (completionHandler) - { + if (completionHandler) { completionHandler(nil); } } - -- (void)URLSession:(NSURLSession * _Nonnull)session - task:(NSURLSessionTask * _Nonnull)task -didReceiveChallenge:(NSURLAuthenticationChallenge * _Nonnull)challenge - completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * _Nullable credential))completionHandler -{ +- (void)URLSession:(NSURLSession *_Nonnull)session + task:(NSURLSessionTask *_Nonnull)task + didReceiveChallenge:(NSURLAuthenticationChallenge *_Nonnull)challenge + completionHandler:(void (^_Nonnull)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential))completionHandler { completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil); } - @end - - #pragma mark Test suite @interface TSKEndToEndSwizzlingTests : XCTestCase @@ -155,28 +122,30 @@ @interface TSKEndToEndSwizzlingTests : XCTestCase @implementation TSKEndToEndSwizzlingTests - -- (void)test -{ +- (void)test { // We can only intialize the shared instance once so we run both tests here - NSDictionary *trustKitConfig = - @{ - kTSKSwizzleNetworkDelegates: @YES, - kTSKPinnedDomains : - @{ - // Valid pinning configuration - @"www.datatheorem.com" : @{ - kTSKEnforcePinning : @YES, - kTSKPublicKeyHashes : @[@"OdSlmQD9NWJh4EbcOHBxkhygPwNSwA9Q91eounfbcoE=", // CA key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}, - // Invalid pinning configuration - @"www.facebook.com" : @{ - kTSKEnforcePinning : @YES, - kTSKPublicKeyHashes : @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Fake key 2 - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @YES, + kTSKPinnedDomains: @ { + // Valid pinning configuration + @"www.datatheorem.com": @ { + kTSKEnforcePinning: @YES, + kTSKPublicKeyHashes: @[ + @"OdSlmQD9NWJh4EbcOHBxkhygPwNSwA9Q91eounfbcoE=", // CA key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + }, + // Invalid pinning configuration + @"www.facebook.com": @ { + kTSKEnforcePinning: @YES, + kTSKPublicKeyHashes: @[ + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Fake key 2 + ] + } + } + }; + [TrustKit initSharedInstanceWithConfiguration:trustKitConfig]; // Configure a validation callback @@ -184,64 +153,57 @@ - (void)test notifReceivedExpectation.assertForOverFulfill = NO; __block BOOL receivedCallForCloudflare = NO; __block BOOL receivedCallForFacebook = NO; - TrustKit.sharedInstance.pinningValidatorCallback = ^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, TKSDomainPinningPolicy *_Nonnull notedHostnamePinningPolicy) { - // Check the received values - if ([result.serverHostname isEqualToString:@"www.facebook.com"]) - { - receivedCallForFacebook = YES; - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); - - XCTAssertGreaterThan([result.certificateChain count], (unsigned long)1); - XCTAssertGreaterThan(result.validationDuration, 0); - - XCTAssertEqualObjects(notedHostname, @"www.facebook.com"); - XCTAssertNotNil(notedHostnamePinningPolicy); - } - else if ([result.serverHostname isEqualToString:@"www.datatheorem.com"]) - { - receivedCallForCloudflare = YES; - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); - - XCTAssertEqualObjects(result.serverHostname, @"www.datatheorem.com"); - XCTAssertGreaterThan([result.certificateChain count], (unsigned long)1); - XCTAssertGreaterThan(result.validationDuration, 0); - - XCTAssertNotNil(notedHostnamePinningPolicy); - } - - if (receivedCallForCloudflare && receivedCallForFacebook) - { - [notifReceivedExpectation fulfill]; - } - }; - + TrustKit.sharedInstance.pinningValidatorCallback + = ^(TSKPinningValidatorResult *_Nonnull result, NSString *_Nonnull notedHostname, TKSDomainPinningPolicy *_Nonnull notedHostnamePinningPolicy) { + // Check the received values + if ([result.serverHostname isEqualToString:@"www.facebook.com"]) { + receivedCallForFacebook = YES; + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); + + XCTAssertGreaterThan([result.certificateChain count], (unsigned long)1); + XCTAssertGreaterThan(result.validationDuration, 0); + + XCTAssertEqualObjects(notedHostname, @"www.facebook.com"); + XCTAssertNotNil(notedHostnamePinningPolicy); + } else if ([result.serverHostname isEqualToString:@"www.datatheorem.com"]) { + receivedCallForCloudflare = YES; + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); + + XCTAssertEqualObjects(result.serverHostname, @"www.datatheorem.com"); + XCTAssertGreaterThan([result.certificateChain count], (unsigned long)1); + XCTAssertGreaterThan(result.validationDuration, 0); + + XCTAssertNotNil(notedHostnamePinningPolicy); + } + + if (receivedCallForCloudflare && receivedCallForFacebook) { + [notifReceivedExpectation fulfill]; + } + }; + XCTestExpectation *expectation = [self expectationWithDescription:@"TestNSURLSessionTaskDelegate"]; - TestNSURLSessionDelegateSwizzling* delegate = [[TestNSURLSessionDelegateSwizzling alloc] initWithValidator:TrustKit.sharedInstance.pinningValidator - expectation:expectation]; - - NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration() - delegate:delegate - delegateQueue:nil]; - + TestNSURLSessionDelegateSwizzling *delegate = [[TestNSURLSessionDelegateSwizzling alloc] initWithValidator:TrustKit.sharedInstance.pinningValidator expectation:expectation]; + + NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration() delegate:delegate delegateQueue:nil]; + // Start two connections // One should fail NSURLSessionDataTask *task = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.facebook.com/"]]; [task resume]; - + // One should succeed NSURLSessionDataTask *task2 = [session dataTaskWithURL:[NSURL URLWithString:@"https://www.datatheorem.com/"]]; [task2 resume]; - + // Wait for the connection to succeed and ensure a notification was posted - [self waitForExpectationsWithTimeout:30.0 handler:^(NSError *error) - { - if (error) - { - NSLog(@"Timeout Error: %@", error); - } - }]; + [self waitForExpectationsWithTimeout:30.0 + handler:^(NSError *error) { + if (error) { + NSLog(@"Timeout Error: %@", error); + } + }]; } @end diff --git a/TrustKitTests/TSKLoggerTests.m b/TrustKitTests/TSKLoggerTests.m index 5d0b8bab..77c91c55 100644 --- a/TrustKitTests/TSKLoggerTests.m +++ b/TrustKitTests/TSKLoggerTests.m @@ -6,17 +6,16 @@ // Copyright © 2016 TrustKit. All rights reserved. // -#import -#import -#import "../TrustKit/public/TrustKit/TrustKit.h" -#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" -#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" +#import "../TrustKit/Pinning/TSKSPKIHashCache.h" +#import "../TrustKit/TSKPinningValidator_Private.h" #import "../TrustKit/parse_configuration.h" +#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" #import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" -#import "../TrustKit/TSKPinningValidator_Private.h" -#import "../TrustKit/Pinning/TSKSPKIHashCache.h" +#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" +#import "../TrustKit/public/TrustKit/TrustKit.h" #import "TSKCertificateUtils.h" - +#import +#import @interface TSKLoggerTests : XCTestCase @@ -24,28 +23,29 @@ @interface TSKLoggerTests : XCTestCase @implementation TSKLoggerTests -- (void)testPinValidationFailureIsLoggedAsError -{ +- (void)testPinValidationFailureIsLoggedAsError { NSDate *startDate = [NSDate date]; SecCertificateRef rootCertificate = [TSKCertificateUtils createCertificateFromDer:@"GoodRootCA"]; SecCertificateRef leafCertificate = [TSKCertificateUtils createCertificateFromDer:@"www.good.com"]; - SecCertificateRef certChainArray[1] = {leafCertificate}; - SecCertificateRef trustStoreArray[1] = {rootCertificate}; + SecCertificateRef certChainArray[1] = { leafCertificate }; + SecCertificateRef trustStoreArray[1] = { rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray arrayLength:1 anchorCertificates:(const void **)trustStoreArray arrayLength:1]; - NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(@{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains: @{@"www.good.com": @{ - kTSKPublicKeyHashes: @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="]}}}); - TSKPinningValidator *validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:[[TSKSPKIHashCache alloc] initWithIdentifier:@"test"] - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult *result, NSString *notedHostname, NSDictionary *notedHostnamePinningPolicy) {}]; + NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(@{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { @"www.good.com": @ { kTSKPublicKeyHashes: @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB="] } } + }); + TSKPinningValidator *validator = [[TSKPinningValidator alloc] + initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] + hashCache:[[TSKSPKIHashCache alloc] initWithIdentifier:@"test"] + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^(TSKPinningValidatorResult *result, NSString *notedHostname, NSDictionary *notedHostnamePinningPolicy) { + }]; XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], TSKTrustDecisionShouldBlockConnection); CFRelease(trust); @@ -59,10 +59,7 @@ - (void)testPinValidationFailureIsLoggedAsError NSDate *deadline = [NSDate dateWithTimeIntervalSinceNow:30]; while ([deadline timeIntervalSinceNow] > 0) { OSLogStore *store = [OSLogStore storeWithScope:OSLogStoreCurrentProcessIdentifier error:nil]; - OSLogEnumerator *entries = [store entriesEnumeratorWithOptions:0 - position:[store positionWithDate:startDate] - predicate:trustKitEntries - error:nil]; + OSLogEnumerator *entries = [store entriesEnumeratorWithOptions:0 position:[store positionWithDate:startDate] predicate:trustKitEntries error:nil]; for (OSLogEntry *entry in entries) { if ([entry isKindOfClass:[OSLogEntryLog class]] && [entry.composedMessage isEqualToString:@"Pin validation failed for www.good.com"]) { failureEntryLevel = ((OSLogEntryLog *)entry).level; diff --git a/TrustKitTests/TSKNSURLConnectionTests.m b/TrustKitTests/TSKNSURLConnectionTests.m index a66f43e7..0364d9fa 100644 --- a/TrustKitTests/TSKNSURLConnectionTests.m +++ b/TrustKitTests/TSKNSURLConnectionTests.m @@ -6,16 +6,16 @@ // Copyright © 2015 TrustKit. All rights reserved. // -#import -#import "../TrustKit/public/TrustKit/TrustKit.h" +#import "../TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.h" #import "../TrustKit/public/TrustKit/TSKPinningValidator.h" #import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" -#import "../TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.h" +#import "../TrustKit/public/TrustKit/TrustKit.h" #import +#import @interface TSKNSURLConnectionDelegateProxy (TestSupport) @property (nonatomic) TSKTrustEvaluationResult lastTrustDecision; --(BOOL)forwardToOriginalDelegateAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge forConnection:(NSURLConnection *)connection; +- (BOOL)forwardToOriginalDelegateAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge forConnection:(NSURLConnection *)connection; @end @interface TrustKit (TestSupport) @@ -25,21 +25,27 @@ + (void)resetConfiguration; ///// These are classes that respond to specific combinations of methods. Mock the methods ///// as needed for the tests. -@interface TestModeADelegate : NSObject +@interface TestModeADelegate : NSObject @end @implementation TestModeADelegate -- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { } -- (void)fakeMethod { } // used in some tests +- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)fakeMethod { +} // used in some tests // Protocol requirements -- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} @end -@interface TestModeBDelegate : NSObject +@interface TestModeBDelegate : NSObject @property (nonatomic) BOOL shouldAuthenticate; // the value returned by `canAuthenticate...:` @end @@ -49,19 +55,23 @@ @implementation TestModeBDelegate - (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace { return self.shouldAuthenticate; } -- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { } +- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} #pragma clang diagnostic pop // Protocol requirements -- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)performDefaultHandlingForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} @end ///// - #pragma mark Test suite // WARNING: For NSURLConnection tests, whenever we connect to a real endpoint as a test, the TLS Session Cache @@ -93,77 +103,66 @@ - (void)tearDown { #pragma mark - respondsToSelector override -- (void)test_respondsToSelector_alwaysTrueForWillSendRequest -{ +- (void)test_respondsToSelector_alwaysTrueForWillSendRequest { TSKNSURLConnectionDelegateProxy *proxy; - - proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:[TestModeADelegate new]]; + + proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:[TestModeADelegate new]]; XCTAssertTrue([proxy respondsToSelector:@selector(connection:willSendRequestForAuthenticationChallenge:)]); - proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:[TestModeBDelegate new]]; + proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:[TestModeBDelegate new]]; XCTAssertTrue([proxy respondsToSelector:@selector(connection:willSendRequestForAuthenticationChallenge:)]); } -- (void)respondsToSelector_trueForOriginalMethods -{ +- (void)respondsToSelector_trueForOriginalMethods { TSKNSURLConnectionDelegateProxy *proxy; - - proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:[TestModeADelegate new]]; + + proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:[TestModeADelegate new]]; XCTAssertTrue([proxy respondsToSelector:@selector(fakeMethod)]); - proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:[TestModeBDelegate new]]; + proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:[TestModeBDelegate new]]; XCTAssertFalse([proxy respondsToSelector:@selector(fakeMethod)]); } -- (void)test_respondsToSelector_falseForUnimplementedMethods -{ +- (void)test_respondsToSelector_falseForUnimplementedMethods { TSKNSURLConnectionDelegateProxy *proxy; - proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:[TestModeADelegate new]]; + proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:[TestModeADelegate new]]; XCTAssertFalse([proxy respondsToSelector:NSSelectorFromString(@"argle:bargle:")]); } -//#pragma mark - forwardingTargetForSelector override +// #pragma mark - forwardingTargetForSelector override // //- (void)test_respondsToSelector_forwardsTargetForSelector //{ -// TestModeADelegate *delegate = OCMStrictClassMock([TestModeADelegate class]); -// OCMExpect([delegate fakeMethod]); +// TestModeADelegate *delegate = OCMStrictClassMock([TestModeADelegate class]); +// OCMExpect([delegate fakeMethod]); // -// TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithDelegate:delegate]; -// [(id)proxy fakeMethod]; +// TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithDelegate:delegate]; +// [(id)proxy fakeMethod]; // -// OCMVerifyAll((id)delegate); -// [(id)delegate stopMocking]; -//} +// OCMVerifyAll((id)delegate); +// [(id)delegate stopMocking]; +// } #pragma mark - forwardToOriginalDelegateAuthenticationChallenge -- (void)test_forwardToOriginalDelegateAuthenticationChallenge_respondsToWillSend -{ +- (void)test_forwardToOriginalDelegateAuthenticationChallenge_respondsToWillSend { NSURLConnection *cnxn = [[NSURLConnection alloc] init]; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] init]; - + TestModeADelegate *delegate = OCMStrictClassMock([TestModeADelegate class]); OCMExpect([delegate connection:cnxn willSendRequestForAuthenticationChallenge:challenge]); - - TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:delegate]; + + TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:delegate]; [(id)proxy forwardToOriginalDelegateAuthenticationChallenge:challenge forConnection:cnxn]; OCMVerifyAll((id)delegate); [(id)delegate stopMocking]; } -- (void)test_forwardToOriginalDelegateAuthenticationChallenge_respondsToCanAuthenticate -{ +- (void)test_forwardToOriginalDelegateAuthenticationChallenge_respondsToCanAuthenticate { TestModeBDelegate *delegate = OCMStrictClassMock([TestModeBDelegate class]); - + NSURLConnection *cnxn = [[NSURLConnection alloc] init]; NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] init]; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space @@ -174,26 +173,22 @@ - (void)test_forwardToOriginalDelegateAuthenticationChallenge_respondsToCanAuthe sender:delegate]; OCMExpect([delegate connection:cnxn canAuthenticateAgainstProtectionSpace:space]).andReturn(YES); OCMExpect([delegate connection:cnxn didReceiveAuthenticationChallenge:challenge]); - - TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:delegate]; + + TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:delegate]; [(id)proxy forwardToOriginalDelegateAuthenticationChallenge:challenge forConnection:cnxn]; - + OCMVerifyAll((id)delegate); [(id)delegate stopMocking]; } #pragma mark - connection:willSendRequestForAuthenticationChallenge: -- (void)test_connectionWillSendRequestForAuthenticationChallenge_notServerTrust -{ +- (void)test_connectionWillSendRequestForAuthenticationChallenge_notServerTrust { TestModeADelegate *delegate = OCMStrictClassMock([TestModeADelegate class]); - TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:delegate]; - + TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:delegate]; + NSURLConnection *cnxn = [[NSURLConnection alloc] init]; - NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"host" port:0 protocol:nil realm:nil - authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; + NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"host" port:0 protocol:nil realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 @@ -206,149 +201,137 @@ - (void)test_connectionWillSendRequestForAuthenticationChallenge_notServerTrust OCMVerifyAll((id)delegate); } -- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustA_allow -{ +- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustA_allow { TestModeADelegate *delegate = OCMStrictClassMock([TestModeADelegate class]); TSKPinningValidator *validator = OCMStrictClassMock([TSKPinningValidator class]); self.trustKit.pinningValidator = validator; - + NSURLConnection *cnxn = [[NSURLConnection alloc] init]; - NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil - authenticationMethod:NSURLAuthenticationMethodServerTrust]; + NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; - - TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:delegate]; + + TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:delegate]; // Ensure the proxy mirrors the methods of the delegate XCTAssertTrue([proxy respondsToSelector:@selector(connection:willSendRequestForAuthenticationChallenge:)]); - + // In this case the challenge was seen as valid by TrustKit and then gets forwarded to the original delegate OCMExpect([validator evaluateTrust:space.serverTrust forHostname:@"hostname"]).andReturn(TSKTrustDecisionShouldAllowConnection); OCMExpect([delegate connection:cnxn willSendRequestForAuthenticationChallenge:challenge]); - + [proxy connection:cnxn willSendRequestForAuthenticationChallenge:challenge]; - + OCMVerifyAll((id)delegate); OCMVerifyAll((id)validator); - + [(id)validator stopMocking]; [(id)delegate stopMocking]; } -- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustB_allow_noDelegateMethod -{ +- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustB_allow_noDelegateMethod { TestModeBDelegate *delegate = OCMPartialMock([TestModeBDelegate new]); - + TSKPinningValidator *validator = OCMStrictClassMock([TSKPinningValidator class]); self.trustKit.pinningValidator = validator; - + NSURLConnection *cnxn = [[NSURLConnection alloc] init]; - NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil - authenticationMethod:NSURLAuthenticationMethodServerTrust]; + NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; - - TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:delegate]; - + + TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:delegate]; + // Ensure the proxy forces the usage of connection:willSendRequestForAuthenticationChallenge: VS the legacy connection:canAuthenticateAgainstProtectionSpace: XCTAssertTrue([proxy respondsToSelector:@selector(connection:willSendRequestForAuthenticationChallenge:)]); - + // The delegate is NOT able to handle this challenge delegate.shouldAuthenticate = NO; // In this case the challenge was seen as valid by TrustKit and then the default handler gets called since the delegate has no method OCMExpect([validator evaluateTrust:space.serverTrust forHostname:@"hostname"]).andReturn(TSKTrustDecisionShouldAllowConnection); OCMExpect([delegate performDefaultHandlingForAuthenticationChallenge:challenge]); - + [proxy connection:cnxn willSendRequestForAuthenticationChallenge:challenge]; - + OCMVerifyAll((id)delegate); OCMVerifyAll((id)validator); - + [(id)validator stopMocking]; [(id)delegate stopMocking]; } -- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustB_allow -{ +- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustB_allow { TestModeBDelegate *delegate = OCMPartialMock([TestModeBDelegate new]); - + TSKPinningValidator *validator = OCMStrictClassMock([TSKPinningValidator class]); self.trustKit.pinningValidator = validator; - + NSURLConnection *cnxn = [[NSURLConnection alloc] init]; - NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil - authenticationMethod:NSURLAuthenticationMethodServerTrust]; + NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; - - TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:delegate]; - + + TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:delegate]; + // Ensure the proxy forces the usage of connection:willSendRequestForAuthenticationChallenge: VS the legacy connection:canAuthenticateAgainstProtectionSpace: XCTAssertTrue([proxy respondsToSelector:@selector(connection:willSendRequestForAuthenticationChallenge:)]); - + // The delegate is able to handle this challenge delegate.shouldAuthenticate = YES; // In this case the challenge was seen as valid by TrustKit and then gets forwarded to the original delegate's connection:didReceiveAuthenticationChallenge: method OCMExpect([validator evaluateTrust:space.serverTrust forHostname:@"hostname"]).andReturn(TSKTrustDecisionShouldAllowConnection); OCMExpect([delegate connection:cnxn didReceiveAuthenticationChallenge:challenge]); - + [proxy connection:cnxn willSendRequestForAuthenticationChallenge:challenge]; - + OCMVerifyAll((id)delegate); OCMVerifyAll((id)validator); - + [(id)validator stopMocking]; [(id)delegate stopMocking]; } // Test the block case: only need to test for one because the failure handling is identical -- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustB_block -{ +- (void)test_connectionWillSendRequestForAuthenticationChallenge_serverTrustB_block { TestModeBDelegate *delegate = OCMPartialMock([TestModeBDelegate new]); - + TSKPinningValidator *validator = OCMStrictClassMock([TSKPinningValidator class]); self.trustKit.pinningValidator = validator; - + NSURLConnection *cnxn = [[NSURLConnection alloc] init]; - NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil - authenticationMethod:NSURLAuthenticationMethodServerTrust]; + NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" port:0 protocol:nil realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]; NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; - - TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit - connectionDelegate:delegate]; - + + TSKNSURLConnectionDelegateProxy *proxy = [[TSKNSURLConnectionDelegateProxy alloc] initWithTrustKit:self.trustKit connectionDelegate:delegate]; + OCMExpect([validator evaluateTrust:space.serverTrust forHostname:@"hostname"]).andReturn(TSKTrustDecisionShouldBlockConnection); - + // In this case the challenge is directly handled by the proxy and should cancel the connection OCMExpect([delegate cancelAuthenticationChallenge:challenge]); - + [proxy connection:cnxn willSendRequestForAuthenticationChallenge:challenge]; - + OCMVerifyAll((id)delegate); OCMVerifyAll((id)validator); - + [(id)validator stopMocking]; [(id)delegate stopMocking]; } diff --git a/TrustKitTests/TSKNSURLSessionTests.m b/TrustKitTests/TSKNSURLSessionTests.m index 5985c988..3fe6e51f 100644 --- a/TrustKitTests/TSKNSURLSessionTests.m +++ b/TrustKitTests/TSKNSURLSessionTests.m @@ -6,12 +6,12 @@ // Copyright © 2015 TrustKit. All rights reserved. // -#import -#import "../TrustKit/public/TrustKit/TrustKit.h" -#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" -#import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" #import "../TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.h" #import "../TrustKit/configuration_utils.h" +#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" +#import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" +#import "../TrustKit/public/TrustKit/TrustKit.h" +#import #import @@ -25,63 +25,77 @@ @interface TSKNSURLSessionDelegateProxy (TestSupport) - (BOOL)forwardToOriginalDelegateAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(TSKURLSessionAuthChallengeCallback)completionHandler - forSession:(NSURLSession * _Nonnull)session; + forSession:(NSURLSession *_Nonnull)session; -- (void)common_URLSession:(NSURLSession * _Nonnull)session - challenge:(NSURLAuthenticationChallenge * _Nonnull)challenge - completionHandler:(void (^ _Nonnull)(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential * _Nullable credential))completionHandler; +- (void)common_URLSession:(NSURLSession *_Nonnull)session + challenge:(NSURLAuthenticationChallenge *_Nonnull)challenge + completionHandler:(void (^_Nonnull)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential))completionHandler; @end - // An NSURLSessionDelegate -@interface SessionDelegate : NSObject +@interface SessionDelegate : NSObject @end @implementation SessionDelegate -- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler -{ +- (void)URLSession:(NSURLSession *)session + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler { completionHandler(NSURLSessionAuthChallengeUseCredential, challenge.proposedCredential); } -- (void)fakeMethod { } +- (void)fakeMethod { +} -- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} -- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} -- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} @end // An NSURLSession and Task delegate -@interface TaskAndSessionDelegate : SessionDelegate +@interface TaskAndSessionDelegate : SessionDelegate @end @implementation TaskAndSessionDelegate -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler -{ } +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler { +} -- (void)fakeMethod { } +- (void)fakeMethod { +} @end // An NSURLSessionTask delegate (only, no NSURLSessionDelegate methods) -@interface TaskDelegate : NSObject @end +@interface TaskDelegate : NSObject +@end @implementation TaskDelegate -- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler -{ } +- (void)URLSession:(NSURLSession *)session + task:(NSURLSessionTask *)task + didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge + completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable))completionHandler { +} -- (void)fakeMethod { } +- (void)fakeMethod { +} @end // A delegate that doesn't actually implement any of the optional handlers -@interface NoOptionalsDelegate : NSObject @end +@interface NoOptionalsDelegate : NSObject +@end @implementation NoOptionalsDelegate -- (void)fakeMethod { } +- (void)fakeMethod { +} @end #pragma mark - Test suite @@ -103,108 +117,81 @@ - (void)tearDown { #pragma mark respondsToSelector override -- (void)test_respondsToSelector_sessionDelegate -{ - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:[SessionDelegate new]]; - +- (void)test_respondsToSelector_sessionDelegate { + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:[SessionDelegate new]]; + XCTAssertTrue([proxy respondsToSelector:@selector(URLSession:didReceiveChallenge:completionHandler:)]); XCTAssertFalse([proxy respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]); - + XCTAssertTrue([proxy respondsToSelector:@selector(fakeMethod)]); XCTAssertFalse([proxy respondsToSelector:NSSelectorFromString(@"unimplementedMethod")]); } -- (void)test_respondsToSelector_taskDelegate -{ - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:[TaskDelegate new]]; - +- (void)test_respondsToSelector_taskDelegate { + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:[TaskDelegate new]]; + XCTAssertFalse([proxy respondsToSelector:@selector(URLSession:didReceiveChallenge:completionHandler:)]); - + XCTAssertTrue([proxy respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]); - + XCTAssertTrue([proxy respondsToSelector:@selector(fakeMethod)]); - + XCTAssertFalse([proxy respondsToSelector:NSSelectorFromString(@"unimplementedMethod")]); } -- (void)test_respondsToSelector_taskAndSessionDelegate -{ - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:[TaskAndSessionDelegate new]]; - +- (void)test_respondsToSelector_taskAndSessionDelegate { + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:[TaskAndSessionDelegate new]]; + XCTAssertTrue([proxy respondsToSelector:@selector(URLSession:didReceiveChallenge:completionHandler:)]); - + XCTAssertTrue([proxy respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]); - + XCTAssertTrue([proxy respondsToSelector:@selector(fakeMethod)]); - + XCTAssertFalse([proxy respondsToSelector:NSSelectorFromString(@"unimplementedMethod")]); } -- (void)test_respondsToSelector_noOptionalsDelegate -{ - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:[NoOptionalsDelegate new]]; - +- (void)test_respondsToSelector_noOptionalsDelegate { + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:[NoOptionalsDelegate new]]; + XCTAssertTrue([proxy respondsToSelector:@selector(URLSession:didReceiveChallenge:completionHandler:)]); - + XCTAssertFalse([proxy respondsToSelector:@selector(URLSession:task:didReceiveChallenge:completionHandler:)]); - + XCTAssertTrue([proxy respondsToSelector:@selector(fakeMethod)]); - + XCTAssertFalse([proxy respondsToSelector:NSSelectorFromString(@"unimplementedMethod")]); } - #pragma mark common_URLSession:challenge:challenge:completionHandler: -- (void)test_common_URLSession_invalidAuthMethod_session -{ +- (void)test_common_URLSession_invalidAuthMethod_session { SessionDelegate *delegate = [SessionDelegate new]; - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:delegate]; - + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:delegate]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration()]; NSURLAuthenticationChallenge *challenge = ({ - NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"" - port:443 - protocol:@"" - realm:@"" - authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; - [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space - proposedCredential:nil - previousFailureCount:0 - failureResponse:nil - error:nil - sender:delegate]; + NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"" port:443 protocol:@"" realm:@"" authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; + [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; }); - + [proxy common_URLSession:session challenge:challenge - completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential *credential) { + completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) { XCTAssertEqual(disposition, NSURLSessionAuthChallengeUseCredential); XCTAssertEqual(credential, challenge.proposedCredential); }]; } -- (void)test_common_URLSession_invalidAuthMethod_task -{ +- (void)test_common_URLSession_invalidAuthMethod_task { TaskDelegate *delegate = [TaskDelegate new]; - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:delegate]; - + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:delegate]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration()]; NSURLAuthenticationChallenge *challenge = ({ - NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"" - port:443 - protocol:@"" - realm:@"" - authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; + NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"" port:443 protocol:@"" realm:@"" authenticationMethod:NSURLAuthenticationMethodHTTPBasic]; [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 @@ -212,22 +199,19 @@ - (void)test_common_URLSession_invalidAuthMethod_task error:nil sender:[SessionDelegate new]]; }); - + [proxy common_URLSession:session challenge:challenge - completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential *credential) { + completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) { XCTAssertEqual(disposition, NSURLSessionAuthChallengePerformDefaultHandling); XCTAssertEqual(credential, challenge.proposedCredential); }]; } -- (void)test_common_URLSession_session_pinFailed -{ +- (void)test_common_URLSession_session_pinFailed { SessionDelegate *delegate = [SessionDelegate new]; - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:delegate]; - + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:delegate]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration()]; NSURLAuthenticationChallenge *challenge = ({ NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" @@ -235,36 +219,28 @@ - (void)test_common_URLSession_session_pinFailed protocol:@"" realm:@"" authenticationMethod:NSURLAuthenticationMethodServerTrust]; - [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space - proposedCredential:nil - previousFailureCount:0 - failureResponse:nil - error:nil - sender:delegate]; + [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; }); - + TSKPinningValidator *validator = OCMStrictClassMock([TSKPinningValidator class]); self.trustKit.pinningValidator = validator; - + OCMExpect([validator evaluateTrust:challenge.protectionSpace.serverTrust forHostname:@"hostname"]).andReturn(TSKTrustDecisionShouldBlockConnection); - + [proxy common_URLSession:session challenge:challenge - completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential *credential) { + completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) { XCTAssertEqual(disposition, NSURLSessionAuthChallengeCancelAuthenticationChallenge); XCTAssertEqual(credential, challenge.proposedCredential); }]; - + [(id)validator stopMocking]; } -- (void)test_common_URLSession_session_pinSuccess -{ +- (void)test_common_URLSession_session_pinSuccess { SessionDelegate *delegate = [SessionDelegate new]; - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:delegate]; - + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:delegate]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration()]; NSURLAuthenticationChallenge *challenge = ({ NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" @@ -272,38 +248,30 @@ - (void)test_common_URLSession_session_pinSuccess protocol:@"" realm:@"" authenticationMethod:NSURLAuthenticationMethodServerTrust]; - [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space - proposedCredential:nil - previousFailureCount:0 - failureResponse:nil - error:nil - sender:delegate]; + [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; }); - + TSKPinningValidator *validator = OCMStrictClassMock([TSKPinningValidator class]); self.trustKit.pinningValidator = validator; - + OCMExpect([validator evaluateTrust:challenge.protectionSpace.serverTrust forHostname:@"hostname"]).andReturn(TSKTrustDecisionShouldAllowConnection); - + [proxy common_URLSession:session challenge:challenge - completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, - NSURLCredential *credential) { + completionHandler:^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential) { XCTAssertEqual(disposition, NSURLSessionAuthChallengeUseCredential); XCTAssertEqual(credential, challenge.proposedCredential); }]; - + [(id)validator stopMocking]; } #pragma mark URLSession:didReceiveChallenge:challenge:completionHandler: -- (void)test_urlSessionChallengeDelegate -{ +- (void)test_urlSessionChallengeDelegate { SessionDelegate *delegate = OCMPartialMock([SessionDelegate new]); - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:delegate]; - + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:delegate]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration()]; NSURLAuthenticationChallenge *challenge = ({ NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" @@ -311,35 +279,29 @@ - (void)test_urlSessionChallengeDelegate protocol:@"" realm:@"" authenticationMethod:NSURLAuthenticationMethodServerTrust]; - [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space - proposedCredential:nil - previousFailureCount:0 - failureResponse:nil - error:nil - sender:delegate]; + [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; }); - - TSKURLSessionAuthChallengeCallback completionHandler = ^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential) {}; - + + TSKURLSessionAuthChallengeCallback completionHandler = ^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential) { + }; + // Ensure that the challenge is forwarded to the delegate to session-based handler - OCMExpect([delegate URLSession:session - didReceiveChallenge:challenge - completionHandler:completionHandler]).andDo(^(NSInvocation *i){}); - + OCMExpect([delegate URLSession:session didReceiveChallenge:challenge completionHandler:completionHandler]) + .andDo(^(NSInvocation *i) { + }); + [proxy URLSession:session didReceiveChallenge:challenge completionHandler:completionHandler]; - + OCMVerifyAll((id)delegate); [(id)delegate stopMocking]; } #pragma mark URLSession:task:didReceiveChallenge:completionHandler: -- (void)test_urlTaskAndSessionChallengeDelegate -{ +- (void)test_urlTaskAndSessionChallengeDelegate { TaskAndSessionDelegate *delegate = OCMPartialMock([TaskAndSessionDelegate new]); - TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit - sessionDelegate:delegate]; - + TSKNSURLSessionDelegateProxy *proxy = [[TSKNSURLSessionDelegateProxy alloc] initWithTrustKit:self.trustKit sessionDelegate:delegate]; + NSURLSession *session = [NSURLSession sessionWithConfiguration:ephemeralNSURLSessionConfiguration()]; NSURLAuthenticationChallenge *challenge = ({ NSURLProtectionSpace *space = [[NSURLProtectionSpace alloc] initWithHost:@"hostname" @@ -347,28 +309,20 @@ - (void)test_urlTaskAndSessionChallengeDelegate protocol:@"" realm:@"" authenticationMethod:NSURLAuthenticationMethodServerTrust]; - [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space - proposedCredential:nil - previousFailureCount:0 - failureResponse:nil - error:nil - sender:delegate]; + [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:space proposedCredential:nil previousFailureCount:0 failureResponse:nil error:nil sender:delegate]; }); - - TSKURLSessionAuthChallengeCallback completionHandler = ^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential) {}; - + + TSKURLSessionAuthChallengeCallback completionHandler = ^(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential) { + }; + // Ensure that the challenge is forwarded to the delegate to the task-based handler NSURLSessionTask *task = [session downloadTaskWithURL:[NSURL URLWithString:@"www.example.com"]]; - OCMExpect([delegate URLSession:session - task:task - didReceiveChallenge:challenge - completionHandler:completionHandler]).andDo(^(NSInvocation *i){}); - - [proxy URLSession:session - task:task - didReceiveChallenge:challenge - completionHandler:completionHandler]; - + OCMExpect([delegate URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]) + .andDo(^(NSInvocation *i) { + }); + + [proxy URLSession:session task:task didReceiveChallenge:challenge completionHandler:completionHandler]; + OCMVerifyAll((id)delegate); [(id)delegate stopMocking]; } diff --git a/TrustKitTests/TSKPinConfigurationTests.m b/TrustKitTests/TSKPinConfigurationTests.m index a94f6af7..d3949210 100644 --- a/TrustKitTests/TSKPinConfigurationTests.m +++ b/TrustKitTests/TSKPinConfigurationTests.m @@ -1,21 +1,21 @@ /* - + TSKPinConfigurationTests.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 -#import "../TrustKit/public/TrustKit/TrustKit.h" -#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" #import "../TrustKit/Pinning/ssl_pin_verifier.h" -#import "../TrustKit/parse_configuration.h" #import "../TrustKit/configuration_utils.h" +#import "../TrustKit/parse_configuration.h" +#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" +#import "../TrustKit/public/TrustKit/TrustKit.h" #import "TSKCertificateUtils.h" #import @@ -24,538 +24,656 @@ @interface TSKPinConfigurationTests : XCTestCase @implementation TSKPinConfigurationTests -- (void)setUp -{ +- (void)setUp { [super setUp]; } -- (void)tearDown -{ +- (void)tearDown { [super tearDown]; } - // Pin to only one key and ensure it fails; TrustKit requires at least two pins (which should include a backup pin) -- (void)testPinOnePublicKey -{ - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - ]}}}), - @"Configuration with one pin only must be rejected"); -} - - -- (void)testDisablePinningForSubdomainAndNoPublicKey -{ +- (void)testPinOnePublicKey { + XCTAssertThrows( + parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + ] + } + } + }), + @"Configuration with one pin only must be rejected" + ); +} + +- (void)testDisablePinningForSubdomainAndNoPublicKey { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ], - kTSKIncludeSubdomains: @YES}, - @"unsecured.good.com": @{ - // When using this option, TrustKit should accept an empty policy for the domain - kTSKExcludeSubdomainFromParentPolicy: @YES - } - } - }); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ], + kTSKIncludeSubdomains: @YES + }, + @"unsecured.good.com": @ { + // When using this option, TrustKit should accept an empty policy for the domain + kTSKExcludeSubdomainFromParentPolicy: @YES + } + } + }); + NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"unsecured.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"unsecured.good.com", @"Did not receive a configuration for pinned subdomain"); } -- (void)testExplicitNotDisablePinningForSubdomainAdditionalDomainKeys -{ +- (void)testExplicitNotDisablePinningForSubdomainAdditionalDomainKeys { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ], - kTSKIncludeSubdomains: @YES}, - @"unsecured.good.com": @{ - // When using this option, TrustKit should allow/require a policy for the subdomain - kTSKExcludeSubdomainFromParentPolicy: @NO, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ], - } - } - }); + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ], + kTSKIncludeSubdomains: @YES + }, + @"unsecured.good.com": @ { + // When using this option, TrustKit should allow/require a policy for the subdomain + kTSKExcludeSubdomainFromParentPolicy: @NO, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ], + } + } + }); NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"unsecured.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"unsecured.good.com", @"Did not receive a configuration for pinned subdomain"); } -- (void)testDisablePinningForSubdomainWithoutParentAndNoPublicKey -{ - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ], - // IncludeSubdomains set to NO so the configuration here is invalid - kTSKIncludeSubdomains: @NO}, - @"unsecured.good.com": @{ - kTSKExcludeSubdomainFromParentPolicy: @YES - } - } - }), - @"Configuration with kTSKExcludeSubdomainFromParentPolicy must have a parent"); -} - - -- (void)testDisablePinningForSubdomainAdditionalDomainKeys -{ - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ], - kTSKIncludeSubdomains: @YES}, - @"unsecured.good.com": @{ - // When using this option, TrustKit should reject additional keys for the domain - kTSKExcludeSubdomainFromParentPolicy: @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ], - } - } - }), - @"Configuration with kTSKExcludeSubdomainFromParentPolicy must reject additional domain keys"); -} - -- (void)testNokTSKSwizzleNetworkDelegates -{ - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - ]}}}), - @"Configuration that does not specify kTSKSwizzleNetworkDelegates must be rejected"); -} - - -- (void)testGetConfigurationPinningEnabled -{ +- (void)testDisablePinningForSubdomainWithoutParentAndNoPublicKey { + XCTAssertThrows( + parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ], + // IncludeSubdomains set to NO so the configuration here is invalid + kTSKIncludeSubdomains: @NO + }, + @"unsecured.good.com": @ { kTSKExcludeSubdomainFromParentPolicy: @YES } + } + }), + @"Configuration with kTSKExcludeSubdomainFromParentPolicy must have a parent" + ); +} + +- (void)testDisablePinningForSubdomainAdditionalDomainKeys { + XCTAssertThrows( + parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ], + kTSKIncludeSubdomains: @YES + }, + @"unsecured.good.com": @ { + // When using this option, TrustKit should reject additional keys for the domain + kTSKExcludeSubdomainFromParentPolicy: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ], + } + } + }), + @"Configuration with kTSKExcludeSubdomainFromParentPolicy must reject additional domain keys" + ); +} + +- (void)testNokTSKSwizzleNetworkDelegates { + XCTAssertThrows( + parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + ] + } + } + }), + @"Configuration that does not specify kTSKSwizzleNetworkDelegates must be rejected" + ); +} + +- (void)testGetConfigurationPinningEnabled { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"www.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"www.good.com", @"Did not receive a configuration for a pinned domain"); - + // Validate the content of the config NSDictionary *serverConfig = trustKitConfig[kTSKPinnedDomains][serverConfigKey]; XCTAssertNil(serverConfig[kTSKExpirationDate]); - XCTAssertEqual([serverConfig[kTSKPublicKeyHashes] count], (unsigned long) 2); + XCTAssertEqual([serverConfig[kTSKPublicKeyHashes] count], (unsigned long)2); } - -- (void)testGetConfigurationPinningEnabledWithExpirationDate -{ +- (void)testGetConfigurationPinningEnabledWithExpirationDate { NSString *expirationDateStr = @"2015-01-01"; NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"www.good.com" : @{ - kTSKExpirationDate: expirationDateStr, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKExpirationDate: expirationDateStr, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"www.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"www.good.com", @"Did not receive a configuration for a pinned domain"); - + // Validate the content of the config NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; dateFormat.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; dateFormat.dateFormat = @"yyyy-MM-dd"; dateFormat.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; NSDate *expirationDate = [dateFormat dateFromString:expirationDateStr]; - + NSDictionary *serverConfig = trustKitConfig[kTSKPinnedDomains][serverConfigKey]; XCTAssertEqualObjects(expirationDate, serverConfig[kTSKExpirationDate]); XCTAssertEqual([serverConfig[kTSKPublicKeyHashes] count], (unsigned long)2); } - -- (void)testExpirationDateIgnoresUserCalendar -{ +- (void)testExpirationDateIgnoresUserCalendar { NSLocale *thaiLocale = [NSLocale localeWithLocaleIdentifier:@"th_TH"]; id localeMock = OCMClassMock([NSLocale class]); OCMStub(ClassMethod([localeMock currentLocale])).andReturn(thaiLocale); OCMStub(ClassMethod([localeMock autoupdatingCurrentLocale])).andReturn(thaiLocale); - XCTAssertEqualObjects([[NSDateFormatter alloc] init].calendar.calendarIdentifier, NSCalendarIdentifierBuddhist, - @"Stubbing NSLocale no longer sets NSDateFormatter's default calendar, so this test cannot detect the bug"); - - NSDictionary *trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"www.good.com" : @{ - kTSKExpirationDate: @"2027-06-01", - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); + XCTAssertEqualObjects( + [[NSDateFormatter alloc] init].calendar.calendarIdentifier, + NSCalendarIdentifierBuddhist, + @"Stubbing NSLocale no longer sets NSDateFormatter's default calendar, so this test cannot detect the bug" + ); + + NSDictionary *trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKExpirationDate: @"2027-06-01", + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); [localeMock stopMocking]; - + NSCalendar *gregorian = [NSCalendar calendarWithIdentifier:NSCalendarIdentifierGregorian]; gregorian.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; NSDateComponents *components = [[NSDateComponents alloc] init]; components.year = 2027; components.month = 6; components.day = 1; - - XCTAssertEqualObjects(trustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKExpirationDate], - [gregorian dateFromComponents:components]); -} + XCTAssertEqualObjects(trustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKExpirationDate], [gregorian dateFromComponents:components]); +} -- (void)testGetConfigurationPinningDisabled -{ +- (void)testGetConfigurationPinningDisabled { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Ensure www.datatheorem.com gets no configuration NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"www.datatheorem.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertNil(serverConfigKey, @"Received a configuration a non-pinned domain"); } - -- (void)testIncludeSubdomainsEnabled -{ +- (void)testIncludeSubdomainsEnabled { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Ensure www.good.com gets the configuration set for good.com as includeSubdomains is enabled NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"www.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"good.com", @"IncludeSubdomains did not work"); } - -- (void)testIncludeSubdomainsEnabledSameDomain -{ +- (void)testIncludeSubdomainsEnabledSameDomain { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{@"good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Ensure good.com gets the configuration set for good.com as includeSubdomains is enabled NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"good.com", @"IncludeSubdomains did not work"); } - -- (void)testIncludeSubdomainsEnabledSubSubDomain -{ +- (void)testIncludeSubdomainsEnabledSubSubDomain { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"sub.www.good.com.www.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"www.good.com", @"IncludeSubdomains did not work"); } - -- (void)testIncludeSubdomainsEnabledNotSubdomain -{ +- (void)testIncludeSubdomainsEnabledNotSubdomain { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : - @{@"good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Corner case to ensure two different domains with similar strings don't get returned as subdomains NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"good.com.otherdomaingood.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertNil(serverConfigKey); } - -- (void)testIncludeSubdomainsEnabledNotSubdomainDifferentTld -{ +- (void)testIncludeSubdomainsEnabledNotSubdomainDifferentTld { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : - @{@"good.net" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.net": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Corner case to ensure two different domains (because different TLD) with similar strings don't get returned as subdomains NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"test.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertNil(serverConfigKey); } - -- (void)testIncludeSubdomainsEnabledForSuffix -{ - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}), - @"Configuration that pins *.com must be rejected"); -} - - -- (void)testIncludeSubdomainsDisabled -{ +- (void)testIncludeSubdomainsEnabledForSuffix { + XCTAssertThrows( + parseTrustKitConfiguration(@{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }), + @"Configuration that pins *.com must be rejected" + ); +} + +- (void)testIncludeSubdomainsDisabled { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : - @{@"good.com" : @{ - kTSKIncludeSubdomains : @NO, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @NO, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Ensure www.good.com does not get the configuration set for good.com NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"www.good.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertNil(serverConfigKey, @"IncludeSubdomains did not work"); } - -- (void)testIncludeSubdomainsEnabledAndSpecificConfiguration -{ +- (void)testIncludeSubdomainsEnabledAndSpecificConfiguration { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : - @{@"good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}, - @"www.good.com": @{ - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + }, + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Ensure the configuration specific to www.good.com takes precedence over the more general config for good.com NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"www.good.com", trustKitConfig[kTSKPinnedDomains]); - XCTAssertEqualObjects(serverConfigKey, @"www.good.com", - @"IncludeSubdomains took precedence over a more specialized configuration"); + XCTAssertEqualObjects(serverConfigKey, @"www.good.com", @"IncludeSubdomains took precedence over a more specialized configuration"); } - -- (void)testIncludeSubdomainsEnabledAndOverlap -{ +- (void)testIncludeSubdomainsEnabledAndOverlap { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : - @{@"good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}, - @"www.good.com": @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + }, + @"www.good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); // Ensure the configuration of www.good.com with a longer match takes precedence over the more general config for good.com NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"foo.www.good.com", trustKitConfig[kTSKPinnedDomains]); - XCTAssertEqualObjects(serverConfigKey, @"www.good.com", - @"Overlapping configurations with IncludeSubdomains did not use the most specific (longest) matching configuration"); + XCTAssertEqualObjects(serverConfigKey, @"www.good.com", @"Overlapping configurations with IncludeSubdomains did not use the most specific (longest) matching configuration"); } - -- (void)testNoPinnedDomains -{ - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKSwizzleNetworkDelegates : @YES}), - @"Configuration with no pinned domains must be rejected"); +- (void)testNoPinnedDomains { + XCTAssertThrows(parseTrustKitConfiguration(@{ kTSKSwizzleNetworkDelegates: @YES }), @"Configuration with no pinned domains must be rejected"); } - -- (void)testGlobalSettings -{ +- (void)testGlobalSettings { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : - @{@"good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }); + // Ensure the kTSKSwizzleNetworkDelegates setting was saved - XCTAssertFalse([trustKitConfig[kTSKSwizzleNetworkDelegates] boolValue], - @"kTSKSwizzleNetworkDelegates was not saved in the configuration"); + XCTAssertFalse([trustKitConfig[kTSKSwizzleNetworkDelegates] boolValue], @"kTSKSwizzleNetworkDelegates was not saved in the configuration"); } - -- (void)testSwizzleNetworkDelegatesInLocalInstance -{ +- (void)testSwizzleNetworkDelegatesInLocalInstance { NSDictionary *trustKitConfig; // Swizzling can only be enabled in the shared instance - trustKitConfig = @{kTSKSwizzleNetworkDelegates: @YES, - kTSKPinnedDomains : - @{@"good.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}; - - + trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @YES, + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }; + XCTAssertThrows([[TrustKit alloc] initWithConfiguration:trustKitConfig]); } - -- (void)testDomainTldAndParentFromServerConfigDoesNotCrash -{ +- (void)testDomainTldAndParentFromServerConfigDoesNotCrash { // For https://github.com/datatheorem/TrustKit/issues/210 // Given a TrustKit config for a domain NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ], - kTSKIncludeSubdomains: @YES}, - } - }); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ], + kTSKIncludeSubdomains: @YES + }, + } + }); + // When trying to connect to an invalid domain that is a TLD and also a parent domain of the configured domain // TrustKit does not crash and does not return the subdomain's configuration NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"com", trustKitConfig[kTSKPinnedDomains]); XCTAssertNil(serverConfigKey, @"IncludeSubdomains did not work"); } - -- (void)testIPv4Address -{ +- (void)testIPv4Address { // For https://github.com/datatheorem/TrustKit/issues/341 NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"192.0.2.10" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"192.0.2.10": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + } + } + }); + XCTAssertEqualObjects(getPinningConfigurationKeyForDomain(@"192.0.2.10", trustKitConfig[kTSKPinnedDomains]), @"192.0.2.10"); XCTAssertNil(getPinningConfigurationKeyForDomain(@"192.0.2.11", trustKitConfig[kTSKPinnedDomains])); } - -- (void)testIPv6AddressIsCanonicalized -{ +- (void)testIPv6AddressIsCanonicalized { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"2001:DB8:0:0:0:0:0:1" : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"2001:DB8:0:0:0:0:0:1": @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + } + } + }); + XCTAssertEqualObjects([trustKitConfig[kTSKPinnedDomains] allKeys], @[@"2001:db8::1"]); XCTAssertEqualObjects(getPinningConfigurationKeyForDomain(@"2001:db8::1", trustKitConfig[kTSKPinnedDomains]), @"2001:db8::1"); XCTAssertEqualObjects(getPinningConfigurationKeyForDomain(@"[2001:db8::1]", trustKitConfig[kTSKPinnedDomains]), @"2001:db8::1"); } - -- (void)testIncludeSubdomainsForIPAddressOrSingleLabelDomain -{ - for (NSString *domain in @[@"192.0.2.10", @"2001:db8::1", @"localhost"]) - { - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - domain : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}}}), - @"Configuration with includeSubdomains for %@ must be rejected", domain); +- (void)testIncludeSubdomainsForIPAddressOrSingleLabelDomain { + for (NSString *domain in @[@"192.0.2.10", @"2001:db8::1", @"localhost"]) { + XCTAssertThrows( + parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + domain: @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + } + } + }), + @"Configuration with includeSubdomains for %@ must be rejected", + domain + ); } } - -- (void)testDomainsOutsideThePublicSuffixList -{ +- (void)testDomainsOutsideThePublicSuffixList { // For https://github.com/datatheorem/TrustKit/issues/333 - for (NSString *domain in @[@"api.internal", @"api.corp", @"printer.local", @"router.home.arpa", @"localhost", - @"good.xyz", @"good.music", @"www.fastly.net", @"amazonaws.com", @"_service.good.com"]) - { - XCTAssertNoThrow(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - domain : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}}}), - @"Configuration for %@ must be accepted", domain); + for (NSString *domain in @[ + @"api.internal", + @"api.corp", + @"printer.local", + @"router.home.arpa", + @"localhost", + @"good.xyz", + @"good.music", + @"www.fastly.net", + @"amazonaws.com", + @"_service.good.com" + ]) { + XCTAssertNoThrow( + parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + domain: @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + } + } + }), + @"Configuration for %@ must be accepted", + domain + ); } } - -- (void)testIncludeSubdomainsForMultiLabelSuffix -{ +- (void)testIncludeSubdomainsForMultiLabelSuffix { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"execute-api.us-east-1.amazonaws.com" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}}}); - + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"execute-api.us-east-1.amazonaws.com": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + } + } + }); + NSString *serverConfigKey = getPinningConfigurationKeyForDomain(@"abc123.execute-api.us-east-1.amazonaws.com", trustKitConfig[kTSKPinnedDomains]); XCTAssertEqualObjects(serverConfigKey, @"execute-api.us-east-1.amazonaws.com"); } - -- (void)testMalformedDomainsAreRejected -{ - for (NSString *domain in @[@"", @"*.good.com", @"https://good.com", @"good.com:443", @"good.com/path", @"user@good.com", - @"good com", @"good..com", @"..good.com", @"bücher.de", @"10.0.0.256", @"1.2.3", @"fe80::1%en0"]) - { - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - domain : @{ - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}}}), - @"Configuration for %@ must be rejected", domain); +- (void)testMalformedDomainsAreRejected { + for (NSString *domain in @[ + @"", + @"*.good.com", + @"https://good.com", + @"good.com:443", + @"good.com/path", + @"user@good.com", + @"good com", + @"good..com", + @"..good.com", + @"bücher.de", + @"10.0.0.256", + @"1.2.3", + @"fe80::1%en0" + ]) { + XCTAssertThrows( + parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + domain: @ { + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + } + } + }), + @"Configuration for %@ must be rejected", + domain + ); } } - -- (void)testDomainMatchingIgnoresCaseAndTrailingDot -{ +- (void)testDomainMatchingIgnoresCaseAndTrailingDot { NSDictionary *trustKitConfig; - trustKitConfig = parseTrustKitConfiguration(@{kTSKPinnedDomains : @{ - @"Good.COM" : @{ - kTSKIncludeSubdomains : @YES, - kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}}}); - - for (NSString *hostname in @[@"good.com", @"GOOD.com", @"WWW.good.com", @"www.good.com."]) - { + trustKitConfig = parseTrustKitConfiguration(@{ + kTSKPinnedDomains: @ { + @"Good.COM": @ { + kTSKIncludeSubdomains: @YES, + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + } + } + }); + + for (NSString *hostname in @[@"good.com", @"GOOD.com", @"WWW.good.com", @"www.good.com."]) { XCTAssertEqualObjects(getPinningConfigurationKeyForDomain(hostname, trustKitConfig[kTSKPinnedDomains]), @"good.com", @"%@", hostname); } } - -- (void)testDuplicateDomainsAreRejected -{ - NSDictionary *domainPolicy = @{kTSKPublicKeyHashes : @[@"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", - ]}; - XCTAssertThrows(parseTrustKitConfiguration(@{kTSKPinnedDomains : @{@"good.com" : domainPolicy, - @"GOOD.com." : domainPolicy}}), - @"Configuration with the same domain twice must be rejected"); +- (void)testDuplicateDomainsAreRejected { + NSDictionary *domainPolicy = @{ + kTSKPublicKeyHashes: @[ + @"TQEtdMbmwFgYUifM4LDF+xgEtd0z69mPGmkp014d6ZY=", + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", + ] + }; + XCTAssertThrows( + parseTrustKitConfiguration(@{ kTSKPinnedDomains: @ { @"good.com": domainPolicy, @"GOOD.com.": domainPolicy } }), + @"Configuration with the same domain twice must be rejected" + ); } @end diff --git a/TrustKitTests/TSKPinningValidatorTests.m b/TrustKitTests/TSKPinningValidatorTests.m index 68c58960..35972b6d 100644 --- a/TrustKitTests/TSKPinningValidatorTests.m +++ b/TrustKitTests/TSKPinningValidatorTests.m @@ -1,37 +1,39 @@ /* - + TSKPinningValidatorTests.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 -#import "../TrustKit/public/TrustKit/TrustKit.h" -#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" -#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" +#import "../TrustKit/Pinning/TSKSPKIHashCache.h" +#import "../TrustKit/TSKPinningValidator_Private.h" #import "../TrustKit/parse_configuration.h" +#import "../TrustKit/public/TrustKit/TSKPinningValidator.h" #import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" -#import "../TrustKit/TSKPinningValidator_Private.h" -#import "../TrustKit/Pinning/TSKSPKIHashCache.h" +#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" +#import "../TrustKit/public/TrustKit/TrustKit.h" +#import -#import "../TrustKit/Pinning/ssl_pin_verifier.h" #import "../TrustKit/Pinning/TSKSPKIHashCache.h" +#import "../TrustKit/Pinning/ssl_pin_verifier.h" #import "../TrustKit/Reporting/reporting_utils.h" - #import "TSKCertificateUtils.h" #import -@interface TestAuthSender : NSObject +@interface TestAuthSender : NSObject @end @implementation TestAuthSender -- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} -- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {} +- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)continueWithoutCredentialForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} +- (void)cancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { +} @end @interface TSKSPKIHashCache (TestSupport) @@ -42,918 +44,948 @@ - (void)waitForPendingSubjectPublicKeyInfoDiskWrites; @end static BOOL AllowsAdditionalTrustAnchors = YES; // toggle in tests if needed -@interface TestPinningValidator: TSKPinningValidator +@interface TestPinningValidator : TSKPinningValidator @end @implementation TestPinningValidator -+ (BOOL)allowsAdditionalTrustAnchors -{ ++ (BOOL)allowsAdditionalTrustAnchors { return AllowsAdditionalTrustAnchors; } @end - @interface TSKPinningValidatorTests : XCTestCase @end -@implementation TSKPinningValidatorTests -{ +@implementation TSKPinningValidatorTests { SecCertificateRef _rootCertificate; SecCertificateRef _selfSignedCertificate; SecCertificateRef _leafCertificate; SecCertificateRef _globalsignRootCertificate; - + TSKSPKIHashCache *spkiCache; } - -- (void)setUp -{ +- (void)setUp { [super setUp]; - + // Create our certificate objects _rootCertificate = [TSKCertificateUtils createCertificateFromDer:@"GoodRootCA"]; _leafCertificate = [TSKCertificateUtils createCertificateFromDer:@"www.good.com"]; _selfSignedCertificate = [TSKCertificateUtils createCertificateFromDer:@"www.good.com.selfsigned"]; _globalsignRootCertificate = [TSKCertificateUtils createCertificateFromDer:@"GlobalSignRootCA"]; - + [spkiCache resetSubjectPublicKeyInfoDiskCache]; spkiCache = [[TSKSPKIHashCache alloc] initWithIdentifier:@"test"]; } - -- (void)tearDown -{ +- (void)tearDown { CFRelease(_rootCertificate); CFRelease(_selfSignedCertificate); CFRelease(_leafCertificate); CFRelease(_globalsignRootCertificate); - + _rootCertificate = nil; _leafCertificate = nil; _selfSignedCertificate = nil; _globalsignRootCertificate = nil; - + [spkiCache resetSubjectPublicKeyInfoDiskCache]; spkiCache = nil; [super tearDown]; } - #pragma mark Tests for evaluateTrust:forHostname: // Pin to any of CA, Intermediate CA and Leaf certificates public keys (all valid) and ensure it succeeds -- (void)testVerifyAgainstAnyPublicKey -{ +- (void)testVerifyAgainstAnyPublicKey { #if TARGET_OS_IPHONE && !TARGET_OS_WATCH id mockApplication = OCMClassMock([UIApplication class]); OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); - + // Mock isProtectedDataAvailable to return YES OCMStub([mockApplication isProtectedDataAvailable]).andReturn(YES); #endif // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=", // Leaf key - @"S5z3Fz5ZfZAGJOBZjK6TYBquyLLKO+BndKXBlL3nPjo=" // CA key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=", // Leaf key + @"S5z3Fz5ZfZAGJOBZjK6TYBquyLLKO+BndKXBlL3nPjo=" // CA key + ] + } + } + }; + // Ensure the SPKI cache was on the filesystem is empty NSDictionary *fsCache = [spkiCache loadSPKICacheFromFileSystem]; XCTAssert([fsCache[@1] count] == 0, @"SPKI cache for RSA 4096 must be empty before the test"); - + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); NSDictionary *domainConfig = parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"]; - - TSKTrustEvaluationResult verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - domainConfig[kTSKPublicKeyHashes], - spkiCache); - - XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, - @"Validation must pass against valid public key pins"); - + + TSKTrustEvaluationResult verificationResult = verifyPublicKeyPin(trust, @"www.good.com", domainConfig[kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, @"Validation must pass against valid public key pins"); + XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + // Call TSKPinningValidator TSKTrustDecision result = [validator evaluateTrust:trust forHostname:@"www.good.com"]; XCTAssertEqual(result, TSKTrustDecisionShouldAllowConnection); - + // Ensure a validation notification was posted [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + // Ensure the SPKI cache was persisted to the filesystem [spkiCache waitForPendingSubjectPublicKeyInfoDiskWrites]; fsCache = [spkiCache loadSPKICacheFromFileSystem]; XCTAssertEqual([fsCache count], 1UL, @"SPKI cache for RSA 4096 must be persisted to the file system"); - + CFRelease(trust); - + #if TARGET_OS_IPHONE && !TARGET_OS_WATCH OCMVerify([mockApplication isProtectedDataAvailable]); [mockApplication stopMocking]; #endif } - // Pin only to the CA certificate public key and ensure it succeeds -- (void)testVerifyAgainstCAPublicKey -{ +- (void)testVerifyAgainstCAPublicKey { // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"S5z3Fz5ZfZAGJOBZjK6TYBquyLLKO+BndKXBlL3nPjo=", // CA Key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"S5z3Fz5ZfZAGJOBZjK6TYBquyLLKO+BndKXBlL3nPjo=", // CA Key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationFailedNoMatchingPin; - verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], - spkiCache); - - - XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, - @"Validation must pass against valid public key pins"); - - + verificationResult = verifyPublicKeyPin(trust, @"www.good.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, @"Validation must pass against valid public key pins"); + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - - XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], - TSKTrustDecisionShouldAllowConnection); - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + + XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], TSKTrustDecisionShouldAllowConnection); + [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - // Pin only to the leaf certificate public key and ensure it succeeds -- (void)testVerifyAgainstLeafPublicKey -{ +- (void)testVerifyAgainstLeafPublicKey { // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=", // Leaf Key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=", // Leaf Key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationFailedNoMatchingPin; - verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], - spkiCache); - - - XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, - @"Validation must pass against valid public key pins"); - - + verificationResult = verifyPublicKeyPin(trust, @"www.good.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, @"Validation must pass against valid public key pins"); + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - - XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], - TSKTrustDecisionShouldAllowConnection); - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + + XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], TSKTrustDecisionShouldAllowConnection); + [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - // Pin a bad key and ensure validation fails -- (void)testVerifyAgainstBadPublicKey -{ +- (void)testVerifyAgainstBadPublicKey { // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad Key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Bad key 2 - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad Key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Bad key 2 + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationErrorInvalidParameters; - verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], - spkiCache); - - - XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedNoMatchingPin, - @"Validation must fail against bad public key pins"); - - + verificationResult = verifyPublicKeyPin(trust, @"www.good.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedNoMatchingPin, @"Validation must fail against bad public key pins"); + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + // Call TSKPinningValidator TSKTrustDecision result = [validator evaluateTrust:trust forHostname:@"www.good.com"]; XCTAssertEqual(result, TSKTrustDecisionShouldBlockConnection); - + // Ensure a validation notification was posted [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - // Pin a bad key but the pinning policy expired and ensure the connection is left untouched -- (void)testVerifyAgainstBadPublicKeyPinsExpired -{ +- (void)testVerifyAgainstBadPublicKeyPinsExpired { // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - // Totally expired - kTSKExpirationDate: @"2015-01-01", - kTSKEnforcePinning: @YES, - kTSKPublicKeyHashes : @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad Key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Bad key 2 - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + // Totally expired + kTSKExpirationDate: @"2015-01-01", + kTSKEnforcePinning: @YES, + kTSKPublicKeyHashes: @[ + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad Key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Bad key 2 + ] + } + } + }; + NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + // Test TSKPinningValidator TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTFail(@"Should not be invoked"); - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTFail(@"Should not be invoked"); + }]; + // Call TSKPinningValidator TSKTrustDecision result = [validator evaluateTrust:trust forHostname:@"www.good.com"]; XCTAssertEqual(result, TSKTrustDecisionDomainNotPinned); - + CFRelease(trust); } // Pin a bad key but do not enforce pinning and ensure the connection is allowed -- (void)testVerifyAgainstBadPublicKeyPinningNotEnforced -{ +- (void)testVerifyAgainstBadPublicKeyPinningNotEnforced { // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKEnforcePinning: @NO, - kTSKPublicKeyHashes : @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad Key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Bad key 2 - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKEnforcePinning: @NO, + kTSKPublicKeyHashes: @[ + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad Key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Bad key 2 + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationErrorInvalidParameters; - verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], - spkiCache); - + verificationResult = verifyPublicKeyPin(trust, @"www.good.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], spkiCache); + XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedNoMatchingPin, @"Validation must fail against bad public key pins"); - + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + // Call TSKPinningValidator TSKTrustDecision result = [validator evaluateTrust:trust forHostname:@"www.good.com"]; XCTAssertEqual(result, TSKTrustDecisionShouldAllowConnection); - + // Ensure a validation notification was posted [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - // Pin a bad key and a good key and ensure validation succeeds -- (void)testVerifyAgainstLeafPublicKeyAndBadPublicKey -{ +- (void)testVerifyAgainstLeafPublicKeyAndBadPublicKey { // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad key - @"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=" // Leaf key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Bad key + @"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=" // Leaf key + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationErrorInvalidParameters; - verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], - spkiCache); - - - XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, - @"Validation must pass against valid public key pins"); - - + verificationResult = verifyPublicKeyPin(trust, @"www.good.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationSuccess, @"Validation must pass against valid public key pins"); + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - - XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], - TSKTrustDecisionShouldAllowConnection); - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldAllowConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationSuccess); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + + XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], TSKTrustDecisionShouldAllowConnection); + // Ensure a validation notification was posted [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - // Pin the valid CA key with an invalid certificate chain and ensure validation fails -- (void)testVerifyAgainstCaPublicKeyAndBadCertificateChain -{ +- (void)testVerifyAgainstCaPublicKeyAndBadCertificateChain { // The leaf certificate is self-signed - SecCertificateRef certChainArray[1] = {_selfSignedCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _selfSignedCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKEnforcePinning: @NO, // Should fail even if pinning is not enforced - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKEnforcePinning: @NO, // Should fail even if pinning is not enforced + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationErrorInvalidParameters; - verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], - spkiCache); - - - XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedInvalidCertificateChain, - @"Validation must fail against bad certificate chain"); - - + verificationResult = verifyPublicKeyPin(trust, @"www.good.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedInvalidCertificateChain, @"Validation must fail against bad certificate chain"); + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedInvalidCertificateChain); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedInvalidCertificateChain); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + // Call TSKPinningValidator - XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], - TSKTrustDecisionShouldBlockConnection); - + XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], TSKTrustDecisionShouldBlockConnection); + // Ensure a validation notification was posted [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - // Pin the valid CA key with an valid certificate chain but a wrong hostname and ensure validation fails -- (void)testVerifyAgainstCaPublicKeyAndBadHostname -{ +- (void)testVerifyAgainstCaPublicKeyAndBadHostname { // The certificate chain is valid for www.good.com but we are connecting to www.bad.com - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.bad.com" : @{ - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.bad.com": @ { + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationErrorInvalidParameters; - verificationResult = verifyPublicKeyPin(trust, - @"www.bad.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.bad.com"][kTSKPublicKeyHashes], - spkiCache); - - - XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedInvalidCertificateChain, - @"Validation must fail against bad hostname"); - - + verificationResult = verifyPublicKeyPin(trust, @"www.bad.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.bad.com"][kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedInvalidCertificateChain, @"Validation must fail against bad hostname"); + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedInvalidCertificateChain); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.bad.com"); - - [expectation fulfill]; - }]; - - XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.bad.com"], - TSKTrustDecisionShouldBlockConnection); - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedInvalidCertificateChain); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.bad.com"); + + [expectation fulfill]; + }]; + + XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.bad.com"], TSKTrustDecisionShouldBlockConnection); + // Ensure a validation notification was posted [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - // Pin the valid CA key but serve a different valid chain with the (unrelared) pinned CA certificate injected at the end -- (void)testVerifyAgainstInjectedCaPublicKey -{ +- (void)testVerifyAgainstInjectedCaPublicKey { // The certificate chain is valid for www.good.com but does not contain the pinned CA certificate, which we inject as an additional certificate - SecCertificateRef certChainArray[2] = {_leafCertificate, _globalsignRootCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[2] = { _leafCertificate, _globalsignRootCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A=", // Globalsign CA - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"cGuxAXyFXFkWm61cF4HPWX8S0srS9j0aSqN0k4AP+4A=", // Globalsign CA + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ] + } + } + }; + // First test the verifyPublicKeyPin() function NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKTrustEvaluationResult verificationResult = TSKTrustEvaluationErrorInvalidParameters; - verificationResult = verifyPublicKeyPin(trust, - @"www.good.com", - parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], - spkiCache); - - - XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedNoMatchingPin, - @"Validation must fail against injected pinned CA"); - - + verificationResult = verifyPublicKeyPin(trust, @"www.good.com", parsedTrustKitConfig[kTSKPinnedDomains][@"www.good.com"][kTSKPublicKeyHashes], spkiCache); + + XCTAssertEqual(verificationResult, TSKTrustEvaluationFailedNoMatchingPin, @"Validation must fail against injected pinned CA"); + // Then test TSKPinningValidator XCTestExpectation *expectation = [self expectationWithDescription:@"ValidationResultHandler"]; TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); - - XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); - - XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); - - XCTAssertEqualObjects(notedHostname, @"www.good.com"); - - [expectation fulfill]; - }]; - - XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], - TSKTrustDecisionShouldBlockConnection); - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTAssertEqual(result.finalTrustDecision, TSKTrustDecisionShouldBlockConnection); + + XCTAssertEqual(result.evaluationResult, TSKTrustEvaluationFailedNoMatchingPin); + + XCTAssertEqualObjects(result.certificateChain, convertTrustToPemArray(trust)); + + XCTAssertEqualObjects(notedHostname, @"www.good.com"); + + [expectation fulfill]; + }]; + + XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.good.com"], TSKTrustDecisionShouldBlockConnection); + // Ensure a validation notification was posted [self waitForExpectationsWithTimeout:2.0 handler:nil]; - + CFRelease(trust); } - -- (void)testVerifyIPAddress -{ +- (void)testVerifyIPAddress { // For https://github.com/datatheorem/TrustKit/issues/341 // The leaf certificate is valid for 192.0.2.10 and 2001:db8::10 SecCertificateRef rootCertificate = [TSKCertificateUtils createCertificateFromDer:@"IPAddressRootCA"]; SecCertificateRef leafCertificate = [TSKCertificateUtils createCertificateFromDer:@"192.0.2.10"]; - SecCertificateRef certChainArray[1] = {leafCertificate}; - SecCertificateRef trustStoreArray[1] = {rootCertificate}; - - NSArray *pins = @[@"kORWsi+P7EkQBmUG9EEmEWDxQ+pbyqy6xSH+sVtGoJQ=", // Leaf key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]; - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : @{@"192.0.2.10" : @{kTSKPublicKeyHashes : pins}, - @"2001:DB8:0:0:0:0:0:10" : @{kTSKPublicKeyHashes : pins}, - @"192.0.2.11" : @{kTSKPublicKeyHashes : pins}}}; + SecCertificateRef certChainArray[1] = { leafCertificate }; + SecCertificateRef trustStoreArray[1] = { rootCertificate }; + + NSArray *pins = @[ + @"kORWsi+P7EkQBmUG9EEmEWDxQ+pbyqy6xSH+sVtGoJQ=", // Leaf key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ]; + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: + @ { @"192.0.2.10": @ { kTSKPublicKeyHashes: pins }, @"2001:DB8:0:0:0:0:0:10": @ { kTSKPublicKeyHashes: pins }, @"192.0.2.11": @ { kTSKPublicKeyHashes: pins } } + }; NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) {}]; - - NSDictionary *expectedDecisions = @{@"192.0.2.10" : @(TSKTrustDecisionShouldAllowConnection), - @"[2001:db8::10]" : @(TSKTrustDecisionShouldAllowConnection), - @"192.0.2.11" : @(TSKTrustDecisionShouldBlockConnection)}; - for (NSString *hostname in expectedDecisions) - { + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + }]; + + NSDictionary *expectedDecisions = @{ + @"192.0.2.10": @(TSKTrustDecisionShouldAllowConnection), + @"[2001:db8::10]": @(TSKTrustDecisionShouldAllowConnection), + @"192.0.2.11": @(TSKTrustDecisionShouldBlockConnection) + }; + for (NSString *hostname in expectedDecisions) { SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; XCTAssertEqual([validator evaluateTrust:trust forHostname:hostname], [expectedDecisions[hostname] integerValue], @"%@", hostname); CFRelease(trust); } - + CFRelease(rootCertificate); CFRelease(leafCertificate); } - -- (void)testDomainNotPinned -{ +- (void)testDomainNotPinned { // The certificate chain is valid for www.good.com but we are connecting to www.nonpinned.com - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ] + } + } + }; + NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + // Then test TSKPinningValidator TSKPinningValidator *validator; validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:NO - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - XCTFail(@"Should not invoke callback"); - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:NO + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + XCTFail(@"Should not invoke callback"); + }]; + // Call TSKPinningValidator - XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.nonpinned.com"], - TSKTrustDecisionDomainNotPinned); - + XCTAssertEqual([validator evaluateTrust:trust forHostname:@"www.nonpinned.com"], TSKTrustDecisionDomainNotPinned); + CFRelease(trust); } - #pragma mark Tests for handleChallenge:completionHandler: // Ensure handleChallenge:completionHandler: properly calls evaluateTrust:forHostname: --(void) testHandleChallengeCompletionHandlerDomainNotPinned -{ +- (void)testHandleChallengeCompletionHandlerDomainNotPinned { // The certificate chain is valid for www.good.com but we are connecting to www.nonpinned.com - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - ]}}}; + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + ] + } + } + }; TrustKit *tk = [[TrustKit alloc] initWithConfiguration:trustKitConfig]; - + __block BOOL wasHandlerCalled = NO; - void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable) = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential) - { - // For a non-pinned domain, we expect the default SSL validation to be called - XCTAssertEqual(disposition, NSURLSessionAuthChallengePerformDefaultHandling); - XCTAssertNil(credential); - wasHandlerCalled = YES; - }; - + void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable) + = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential) { + // For a non-pinned domain, we expect the default SSL validation to be called + XCTAssertEqual(disposition, NSURLSessionAuthChallengePerformDefaultHandling); + XCTAssertNil(credential); + wasHandlerCalled = YES; + }; + // Mock a protection space id protectionSpaceMock = [OCMockObject mockForClass:[NSURLProtectionSpace class]]; OCMStub([protectionSpaceMock authenticationMethod]).andReturn(NSURLAuthenticationMethodServerTrust); OCMStub([protectionSpaceMock host]).andReturn(@"www.nonpinned.com"); OCMStub([protectionSpaceMock serverTrust]).andReturn(trust); - + // Mock an authentication challenge id challengeMock = [OCMockObject mockForClass:[NSURLAuthenticationChallenge class]]; OCMStub([challengeMock protectionSpace]).andReturn(protectionSpaceMock); - + // Test the helper method BOOL wasChallengeHandled = [tk.pinningValidator handleChallenge:challengeMock completionHandler:completionHandler]; - + XCTAssertTrue(wasChallengeHandled); XCTAssertTrue(wasHandlerCalled); - + CFRelease(trust); } - --(void) testHandleChallengeCompletionHandlerPinningFailed -{ - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; +- (void)testHandleChallengeCompletionHandlerPinningFailed { + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKEnforcePinning: @YES, - kTSKPublicKeyHashes : @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", //Fake Key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKEnforcePinning: @YES, + kTSKPublicKeyHashes: @[ + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake Key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key + ] + } + } + }; + NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKPinningValidator *validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:YES - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - // - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:YES + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + // + }]; + // Mock a protection space id protectionSpaceMock = [OCMockObject mockForClass:[NSURLProtectionSpace class]]; OCMStub([protectionSpaceMock authenticationMethod]).andReturn(NSURLAuthenticationMethodServerTrust); OCMStub([protectionSpaceMock host]).andReturn(@"www.good.com"); OCMStub([protectionSpaceMock serverTrust]).andReturn(trust); - + // Mock an authentication challenge id challengeMock = [OCMockObject mockForClass:[NSURLAuthenticationChallenge class]]; OCMStub([challengeMock protectionSpace]).andReturn(protectionSpaceMock); - - + __block BOOL wasHandlerCalled = NO; - void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable) = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential) - { - // For a pinning failure, we expect the authentication challenge to be cancelled - XCTAssertEqual(disposition, NSURLSessionAuthChallengeCancelAuthenticationChallenge); - XCTAssertNil(credential); - wasHandlerCalled = YES; - }; - + void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable) + = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential) { + // For a pinning failure, we expect the authentication challenge to be cancelled + XCTAssertEqual(disposition, NSURLSessionAuthChallengeCancelAuthenticationChallenge); + XCTAssertNil(credential); + wasHandlerCalled = YES; + }; + // Test the helper method BOOL wasChallengeHandled = [validator handleChallenge:challengeMock completionHandler:completionHandler]; - + XCTAssertTrue(wasChallengeHandled); XCTAssertTrue(wasHandlerCalled); - + CFRelease(trust); } - --(void) testHandleChallengeCompletionHandlerPinningSuccessful -{ - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; +- (void)testHandleChallengeCompletionHandlerPinningSuccessful { + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=", // CA Key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key - ]}}}; - - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw=", // CA Key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key + ] + } + } + }; + NSDictionary *parsedTrustKitConfig = parseTrustKitConfiguration(trustKitConfig); - + TSKPinningValidator *validator = [[TSKPinningValidator alloc] initWithDomainPinningPolicies:parsedTrustKitConfig[kTSKPinnedDomains] - hashCache:spkiCache - ignorePinsForUserTrustAnchors:YES - validationCallbackQueue:dispatch_get_main_queue() - validationCallback:^(TSKPinningValidatorResult * _Nonnull result, NSString * _Nonnull notedHostname, NSDictionary *_Nonnull notedHostnamePinningPolicy) { - // - }]; - + hashCache:spkiCache + ignorePinsForUserTrustAnchors:YES + validationCallbackQueue:dispatch_get_main_queue() + validationCallback:^( + TSKPinningValidatorResult *_Nonnull result, + NSString *_Nonnull notedHostname, + NSDictionary *_Nonnull notedHostnamePinningPolicy + ) { + // + }]; + // Mock a protection space NSURLProtectionSpace *protectionSpace = OCMPartialMock([[NSURLProtectionSpace alloc] initWithHost:@"www.good.com" port:443 protocol:NSURLProtectionSpaceHTTPS realm:nil authenticationMethod:NSURLAuthenticationMethodServerTrust]); - + NSURLCredential *credential = [NSURLCredential credentialForTrust:trust]; OCMStub([protectionSpace serverTrust]).andReturn(trust); - + // Mock an authentication challenge NSURLAuthenticationChallenge *challenge = [[NSURLAuthenticationChallenge alloc] initWithProtectionSpace:protectionSpace proposedCredential:credential @@ -961,101 +993,103 @@ -(void) testHandleChallengeCompletionHandlerPinningSuccessful failureResponse:nil error:nil sender:[TestAuthSender new]]; - + __block BOOL wasHandlerCalled = NO; - void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable) = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable gotCredential) - { - // For a pinning success, we expect the authentication challenge to use the supplied credential - XCTAssertEqual(disposition, NSURLSessionAuthChallengeUseCredential); - XCTAssertEqualObjects(gotCredential, credential); - wasHandlerCalled = YES; - }; - + void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable) + = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable gotCredential) { + // For a pinning success, we expect the authentication challenge to use the supplied credential + XCTAssertEqual(disposition, NSURLSessionAuthChallengeUseCredential); + XCTAssertEqualObjects(gotCredential, credential); + wasHandlerCalled = YES; + }; + // Test the helper method BOOL wasChallengeHandled = [validator handleChallenge:challenge completionHandler:completionHandler]; - + XCTAssertTrue(wasChallengeHandled); XCTAssertTrue(wasHandlerCalled); - + CFRelease(trust); } - --(void) testHandleChallengeCompletionHandlerNotServerTrustAuthenticationMethod -{ - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; +- (void)testHandleChallengeCompletionHandlerNotServerTrustAuthenticationMethod { + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{@"www.good.com" : @{ - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key - ]}}}; + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.good.com": @ { + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key + ] + } + } + }; TrustKit *tk = [[TrustKit alloc] initWithConfiguration:trustKitConfig]; - + __block BOOL wasHandlerCalled = NO; - void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable) = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential) - { - // This should not be called when the challenge is not for server trust - wasHandlerCalled = YES; - }; - + void (^completionHandler)(NSURLSessionAuthChallengeDisposition, NSURLCredential *_Nullable) + = ^void(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *_Nullable credential) { + // This should not be called when the challenge is not for server trust + wasHandlerCalled = YES; + }; + // Mock a protection space id protectionSpaceMock = [OCMockObject mockForClass:[NSURLProtectionSpace class]]; // Not a server trust challenge OCMStub([protectionSpaceMock authenticationMethod]).andReturn(NSURLAuthenticationMethodNTLM); OCMStub([protectionSpaceMock host]).andReturn(@"www.good.com"); OCMStub([protectionSpaceMock serverTrust]).andReturn(trust); - + // Mock an authentication challenge id challengeMock = [OCMockObject mockForClass:[NSURLAuthenticationChallenge class]]; OCMStub([challengeMock protectionSpace]).andReturn(protectionSpaceMock); - + // Test the helper method BOOL wasChallengeHandled = [tk.pinningValidator handleChallenge:challengeMock completionHandler:completionHandler]; - + XCTAssertFalse(wasChallengeHandled); XCTAssertFalse(wasHandlerCalled); - + CFRelease(trust); } -- (void)testExcludedSubdomain -{ +- (void)testExcludedSubdomain { // Create a valid server trust - SecCertificateRef certChainArray[1] = {_leafCertificate}; - SecCertificateRef trustStoreArray[1] = {_rootCertificate}; + SecCertificateRef certChainArray[1] = { _leafCertificate }; + SecCertificateRef trustStoreArray[1] = { _rootCertificate }; SecTrustRef trust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; - + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; + // Create a configuration - NSDictionary *trustKitConfig = @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : @{ - @"good.com" : @{ - kTSKPublicKeyHashes : @[@"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key - ], - kTSKIncludeSubdomains: @YES}, - @"unsecured.good.com": @{ - kTSKExcludeSubdomainFromParentPolicy: @YES - } - }}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"good.com": @ { + kTSKPublicKeyHashes: @[ + @"iQMk4onrJJz/nwW1wCUR0Ycsh3omhbM+PqMEwNof/K0=", // CA Key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=", // Fake key + ], + kTSKIncludeSubdomains: @YES + }, + @"unsecured.good.com": @ { kTSKExcludeSubdomainFromParentPolicy: @YES } + } + }; + // Then test TSKPinningValidator TrustKit *tk = [[TrustKit alloc] initWithConfiguration:trustKitConfig]; - - XCTAssertEqual([tk.pinningValidator evaluateTrust:trust forHostname:@"unsecured.good.com"], - TSKTrustDecisionDomainNotPinned); - + + XCTAssertEqual([tk.pinningValidator evaluateTrust:trust forHostname:@"unsecured.good.com"], TSKTrustDecisionDomainNotPinned); + CFRelease(trust); } diff --git a/TrustKitTests/TSKPublicKeyAlgorithmTests.m b/TrustKitTests/TSKPublicKeyAlgorithmTests.m index 355e57f8..149b4eb4 100644 --- a/TrustKitTests/TSKPublicKeyAlgorithmTests.m +++ b/TrustKitTests/TSKPublicKeyAlgorithmTests.m @@ -1,58 +1,51 @@ /* - + TSKPublicKeyAlgorithmTests.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 -#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" #import "../TrustKit/parse_configuration.h" +#import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" +#import -#import "../TrustKit/Pinning/ssl_pin_verifier.h" #import "../TrustKit/Pinning/TSKSPKIHashCache.h" -#import +#import "../TrustKit/Pinning/ssl_pin_verifier.h" #import "../TrustKit/Reporting/reporting_utils.h" +#import #import "TSKCertificateUtils.h" #import - @interface TSKSPKIHashCache (TestSupport) - (void)resetSubjectPublicKeyInfoDiskCache; - (NSMutableDictionary *)getSubjectPublicKeyInfoHashesCache; @end - @interface TSKPublicKeyAlgorithmTests : XCTestCase @end -@implementation TSKPublicKeyAlgorithmTests -{ +@implementation TSKPublicKeyAlgorithmTests { TSKSPKIHashCache *spkiCache; } -- (void)setUp -{ +- (void)setUp { [super setUp]; [spkiCache resetSubjectPublicKeyInfoDiskCache]; spkiCache = [[TSKSPKIHashCache alloc] initWithIdentifier:@"test"]; } -- (void)tearDown -{ +- (void)tearDown { [spkiCache resetSubjectPublicKeyInfoDiskCache]; spkiCache = nil; [super tearDown]; } - -- (void)testExtractRsa2048 -{ +- (void)testExtractRsa2048 { // Ensure a RSA 2048 key is properly extracted from its certificate SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.globalsign.com"]; @@ -63,8 +56,7 @@ - (void)testExtractRsa2048 CFRelease(certificate); } -- (void)testExtractRsa3072 -{ +- (void)testExtractRsa3072 { // Ensure a RSA 2048 key is properly extracted from its certificate SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"Corporation Service Company RSA OV SSL CA"]; @@ -75,81 +67,69 @@ - (void)testExtractRsa3072 CFRelease(certificate); } -- (void)testExtractRsa4096 -{ +- (void)testExtractRsa4096 { // Ensure a RSA 4096 key is properly extracted from its certificate SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.good.com"]; - + NSData *spkiHash = [spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; NSString *spkiPin = [spkiHash base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; - + XCTAssertEqualObjects(spkiPin, @"TwyNzy19zZi7cKfPsucs1E+h8ODOCPMrT8681sFWJvw="); CFRelease(certificate); } - -- (void)testExtractEcDsaSecp256r1 -{ +- (void)testExtractEcDsaSecp256r1 { // Ensure a secp256r1 key is properly extracted from its certificate SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.cloudflare.com"]; - + NSData *spkiHash = [spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; NSString *spkiPin = [spkiHash base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; - + XCTAssertEqualObjects(spkiPin, @"Gc7EN2acfkbE0dUOAd34tr1XLr+JdkTiTrMAfhESQHI="); CFRelease(certificate); } - -- (void)testExtractEcDsaSecp384r1 -{ +- (void)testExtractEcDsaSecp384r1 { // Ensure a secp384r1 key is properly extracted from its certificate SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"GeoTrust_Primary_CA_G2_ECC"]; - + NSData *spkiHash = [spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; NSString *spkiPin = [spkiHash base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; - + XCTAssertEqualObjects(spkiPin, @"vPtEqrmtAhAVcGtBIep2HIHJ6IlnWQ9vlK50TciLePs="); CFRelease(certificate); } - -- (void)testExtractEcDsaSecp521r1 -{ +- (void)testExtractEcDsaSecp521r1 { // Ensure a secp521r1 key is properly extracted from its certificate SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"Secp521r1TestCA"]; - + NSData *spkiHash = [spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; NSString *spkiPin = [spkiHash base64EncodedStringWithOptions:NSDataBase64Encoding64CharacterLineLength]; - + XCTAssertEqualObjects(spkiPin, @"JDbjp5T1MJ6GUN/fY/voVivkg/5a+kiWCTF7yBFL4DM="); CFRelease(certificate); } - -- (void)testSubjectPublicKeyInfoFromMalformedCertificate -{ +- (void)testSubjectPublicKeyInfoFromMalformedCertificate { SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.good.com"]; NSData *certificateData = (__bridge_transfer NSData *)SecCertificateCopyData(certificate); CFRelease(certificate); - + XCTAssertNotNil([TSKSubjectPublicKeyInfo dataFromCertificate:certificateData]); - for (NSUInteger length = 0; length < [certificateData length]; length++) - { + for (NSUInteger length = 0; length < [certificateData length]; length++) { XCTAssertNil([TSKSubjectPublicKeyInfo dataFromCertificate:[certificateData subdataWithRange:NSMakeRange(0, length)]], @"Truncated to %lu bytes", (unsigned long)length); } - - const uint8_t indefiniteLength[] = {0x30, 0x80, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00}; - const uint8_t fiveByteLength[] = {0x30, 0x85, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, 0x00}; - const uint8_t notASequence[] = {0x31, 0x02, 0x30, 0x00}; + + const uint8_t indefiniteLength[] = { 0x30, 0x80, 0x30, 0x80, 0x00, 0x00, 0x00, 0x00 }; + const uint8_t fiveByteLength[] = { 0x30, 0x85, 0x00, 0x00, 0x00, 0x00, 0x02, 0x30, 0x00 }; + const uint8_t notASequence[] = { 0x31, 0x02, 0x30, 0x00 }; XCTAssertNil([TSKSubjectPublicKeyInfo dataFromCertificate:[NSData dataWithBytes:indefiniteLength length:sizeof(indefiniteLength)]]); XCTAssertNil([TSKSubjectPublicKeyInfo dataFromCertificate:[NSData dataWithBytes:fiveByteLength length:sizeof(fiveByteLength)]]); XCTAssertNil([TSKSubjectPublicKeyInfo dataFromCertificate:[NSData dataWithBytes:notASequence length:sizeof(notASequence)]]); } - -- (void)testSPKICacheMissOffMainThreadDoesNotWaitForMainThread -{ +- (void)testSPKICacheMissOffMainThreadDoesNotWaitForMainThread { SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.globalsign.com"]; dispatch_semaphore_t hashed = dispatch_semaphore_create(0); dispatch_async(dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ @@ -157,10 +137,10 @@ - (void)testSPKICacheMissOffMainThreadDoesNotWaitForMainThread CFRelease(certificate); dispatch_semaphore_signal(hashed); }); - + long timedOut = dispatch_semaphore_wait(hashed, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC))); XCTAssertEqual(timedOut, 0L, @"Hashing an uncached certificate must not wait on the main thread"); - + XCTestExpectation *mainQueueDrained = [self expectationWithDescription:@"Main queue drained"]; dispatch_async(dispatch_get_main_queue(), ^{ [mainQueueDrained fulfill]; @@ -168,18 +148,15 @@ - (void)testSPKICacheMissOffMainThreadDoesNotWaitForMainThread [self waitForExpectations:@[mainQueueDrained] timeout:5.0]; } - #if TARGET_OS_IPHONE && !TARGET_OS_WATCH -- (void)testSPKICacheThreadSafetyAndProtectedData -{ +- (void)testSPKICacheThreadSafetyAndProtectedData { XCTestExpectation *expectation = [self expectationWithDescription:@"Cache operations completed"]; - + id mockApplication = OCMClassMock([UIApplication class]); OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); // Simulate protected data being unavailable OCMStub([mockApplication isProtectedDataAvailable]).andReturn(NO); - - + // Perform multiple cache operations in parallel on a background queue SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.globalsign.com"]; dispatch_group_t group = dispatch_group_create(); @@ -188,40 +165,38 @@ - (void)testSPKICacheThreadSafetyAndProtectedData [self->spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; }); } - - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSDictionary *cache = [self->spkiCache getSubjectPublicKeyInfoHashesCache]; XCTAssertEqual(cache.count, 1, @"Cache should contain one entry"); - + // Simulate protected data becoming available OCMStub([mockApplication isProtectedDataAvailable]).andReturn(YES); - + // Perform one more cache operation to trigger filesystem write [self->spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; - + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSDictionary *finalCache = [self->spkiCache getSubjectPublicKeyInfoHashesCache]; XCTAssertEqual(finalCache.count, 1, @"Cache should still contain one entry"); - + CFRelease(certificate); [mockApplication stopMocking]; [expectation fulfill]; }); }); - + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } -// This test hardly manages to reproduce the crash, but it does reproduce it sometimes. -- (void)testSPKICacheThreadSafetyAndProtectedDataDoesntCrash -{ +// This test hardly manages to reproduce the crash, but it does reproduce it sometimes. +- (void)testSPKICacheThreadSafetyAndProtectedDataDoesntCrash { XCTestExpectation *expectation = [self expectationWithDescription:@"Cache operations completed"]; - + id mockApplication = OCMClassMock([UIApplication class]); OCMStub([mockApplication sharedApplication]).andReturn(mockApplication); OCMStub([mockApplication isProtectedDataAvailable]).andReturn(YES); - + // Perform multiple cache operations in parallel on a background queue SecCertificateRef certificate = [TSKCertificateUtils createCertificateFromDer:@"www.globalsign.com"]; dispatch_group_t group = dispatch_group_create(); @@ -230,28 +205,27 @@ - (void)testSPKICacheThreadSafetyAndProtectedDataDoesntCrash [self->spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; }); } - - dispatch_group_notify(group, dispatch_get_main_queue(), ^{ + dispatch_group_notify(group, dispatch_get_main_queue(), ^{ NSDictionary *cache = [self->spkiCache getSubjectPublicKeyInfoHashesCache]; XCTAssertEqual(cache.count, 1, @"Cache should contain one entry"); - + BOOL yes = YES; OCMStub([mockApplication isProtectedDataAvailable]).andReturn(YES).andDo(^(NSInvocation *invocation) { self->spkiCache = nil; [invocation setReturnValue:(void *)&yes]; }); - + // Perform one more cache operation to trigger filesystem write [self->spkiCache hashSubjectPublicKeyInfoFromCertificate:certificate]; - + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ CFRelease(certificate); [mockApplication stopMocking]; [expectation fulfill]; }); }); - + [self waitForExpectationsWithTimeout:5.0 handler:nil]; } #endif diff --git a/TrustKitTests/TSKReporterTests.m b/TrustKitTests/TSKReporterTests.m index 40d59097..3f46874f 100644 --- a/TrustKitTests/TSKReporterTests.m +++ b/TrustKitTests/TSKReporterTests.m @@ -1,28 +1,28 @@ /* - + TSKReporterTests.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 -#import "../TrustKit/public/TrustKit/TrustKit.h" #import "../TrustKit/public/TrustKit/TSKTrustKitConfig.h" +#import "../TrustKit/public/TrustKit/TrustKit.h" -#import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" -#import "../TrustKit/TSKPinningValidator_Private.h" #import "../TrustKit/Reporting/TSKBackgroundReporter.h" #import "../TrustKit/Reporting/TSKPinFailureReport.h" #import "../TrustKit/Reporting/reporting_utils.h" +#import "../TrustKit/TSKPinningValidator_Private.h" +#import "../TrustKit/public/TrustKit/TSKPinningValidatorResult.h" -#import #import "../TrustKit/Reporting/vendor_identifier.h" #import "TSKCertificateUtils.h" +#import #pragma mark Test suite @@ -30,94 +30,92 @@ @interface TrustKit (TestSupport) @property (nonatomic) TSKBackgroundReporter *pinFailureReporter; @property (nonatomic, readonly, nullable) NSDictionary *configuration; -- (void)sendValidationReport:(TSKPinningValidatorResult *)result notedHostname:(NSString *)notedHostname pinningPolicy:(NSDictionary *)notedHostnamePinningPolicy; +- (void)sendValidationReport:(TSKPinningValidatorResult *)result + notedHostname:(NSString *)notedHostname + pinningPolicy:(NSDictionary *)notedHostnamePinningPolicy; @end - -static NSString * const kTSKDefaultReportUri = @"https://overmind.datatheorem.com/trustkit/report"; - +static NSString *const kTSKDefaultReportUri = @"https://overmind.datatheorem.com/trustkit/report"; @interface TSKReporterTests : XCTestCase @end -@implementation TSKReporterTests -{ +@implementation TSKReporterTests { TrustKit *_trustKit; - //TSKPinFailureReport *_testReporter; + // TSKPinFailureReport *_testReporter; SecTrustRef _testTrust; SecCertificateRef _rootCertificate; SecCertificateRef _leafCertificate; NSArray *_testCertificateChain; } - - (void)setUp { [super setUp]; - + _rootCertificate = [TSKCertificateUtils createCertificateFromDer:@"GoodRootCA"]; _leafCertificate = [TSKCertificateUtils createCertificateFromDer:@"www.good.com"]; - + SecCertificateRef certChainArray[1] = { _leafCertificate }; SecCertificateRef trustStoreArray[1] = { _rootCertificate }; - + _testTrust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; _testCertificateChain = convertTrustToPemArray(_testTrust); } -- (void)tearDown -{ +- (void)tearDown { CFRelease(_rootCertificate); CFRelease(_leafCertificate); CFRelease(_testTrust); _trustKit = nil; //_testReporter = nil; - + [super tearDown]; } // NOTE: this is more of a test of TrustKit.m -- (void)testSendReportFromValidationReport -{ +- (void)testSendReportFromValidationReport { // Ensure that a pin validation notification triggers the upload of a report if the validation failed // Initialize TrustKit so the reporter block is ready to receive notifications NSString *expirationDateStr = @"2019-01-01"; - NSDictionary *trustKitConfig = - @{kTSKSwizzleNetworkDelegates: @NO, - kTSKPinnedDomains : - @{ - @"www.test.com" : @{ - kTSKEnforcePinning : @YES, - kTSKExpirationDate : expirationDateStr, - kTSKPublicKeyHashes : @[@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key - @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Fake key 2 - ]}}}; - + NSDictionary *trustKitConfig = @{ + kTSKSwizzleNetworkDelegates: @NO, + kTSKPinnedDomains: @ { + @"www.test.com": @ { + kTSKEnforcePinning: @YES, + kTSKExpirationDate: expirationDateStr, + kTSKPublicKeyHashes: @[ + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=", // Fake key + @"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" // Fake key 2 + ] + } + } + }; + _trustKit = [[TrustKit alloc] initWithConfiguration:trustKitConfig]; - + // Expect a report to be sent out when a notification is posted - NSSet *knownPins = [NSSet setWithArray:@[[[NSData alloc]initWithBase64EncodedString:@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" - options:(NSDataBase64DecodingOptions)0], - [[NSData alloc]initWithBase64EncodedString:@"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" - options:(NSDataBase64DecodingOptions)0]]]; - - + NSSet *knownPins = [NSSet setWithArray:@[ + [[NSData alloc] initWithBase64EncodedString:@"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" options:(NSDataBase64DecodingOptions)0], + [[NSData alloc] initWithBase64EncodedString:@"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=" options:(NSDataBase64DecodingOptions)0] + ]]; + NSDateFormatter *dateFormat = [[NSDateFormatter alloc] init]; dateFormat.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; dateFormat.dateFormat = @"yyyy-MM-dd"; dateFormat.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0]; NSDate *expirationDate = [dateFormat dateFromString:expirationDateStr]; - + TSKPinningValidatorResult *res; - + // TEST FAILURE // Setup mocking of the reporter id pinReporterMock = OCMClassMock([TSKBackgroundReporter class]); _trustKit.pinFailureReporter = pinReporterMock; - + OCMExpect([pinReporterMock pinValidationFailedForHostname:@"www.test.com" port:nil certificateChain:_testCertificateChain @@ -128,25 +126,25 @@ - (void)testSendReportFromValidationReport knownPins:knownPins validationResult:TSKTrustEvaluationErrorCouldNotGenerateSpkiHash expirationDate:expirationDate]); - + res = [[TSKPinningValidatorResult alloc] initWithServerHostname:@"www.test.com" serverTrust:_testTrust validationResult:TSKTrustEvaluationErrorCouldNotGenerateSpkiHash finalTrustDecision:TSKTrustDecisionShouldBlockConnection validationDuration:1.0]; [_trustKit sendValidationReport:res notedHostname:@"www.test.com" pinningPolicy:_trustKit.configuration[kTSKPinnedDomains][@"www.test.com"]]; - + // Ensure that the reporter was called [pinReporterMock verify]; - + [pinReporterMock stopMocking]; _trustKit.pinFailureReporter = nil; - + // TEST CA SUCCESS // Send a notification for a successful validation and ensure no report gets sent pinReporterMock = OCMClassMock([TSKBackgroundReporter class]); _trustKit.pinFailureReporter = pinReporterMock; - + res = [[TSKPinningValidatorResult alloc] initWithServerHostname:@"www.test.com" serverTrust:_testTrust validationResult:TSKTrustEvaluationSuccess @@ -156,22 +154,22 @@ - (void)testSendReportFromValidationReport // Ensure that the reporter was NOT called [_trustKit sendValidationReport:res notedHostname:@"www.test.com" pinningPolicy:_trustKit.configuration[kTSKPinnedDomains][@"www.test.com"]]; [pinReporterMock verify]; - + [pinReporterMock stopMocking]; _trustKit.pinFailureReporter = nil; - + #if !TARGET_OS_IPHONE // TEST USER-DEFINED SUCCESS // Send a notification for a successful validation and ensure no report gets sent pinReporterMock = OCMClassMock([TSKBackgroundReporter class]); _trustKit.pinFailureReporter = pinReporterMock; - + res = [[TSKPinningValidatorResult alloc] initWithServerHostname:@"www.test.com" serverTrust:_testTrust validationResult:TSKTrustEvaluationFailedUserDefinedTrustAnchor finalTrustDecision:TSKTrustDecisionShouldAllowConnection validationDuration:1.0]; - + // Ensure that the reporter was NOT called [_trustKit sendValidationReport:res notedHostname:@"www.test.com" pinningPolicy:_trustKit.configuration[kTSKPinnedDomains][@"www.test.com"]]; [pinReporterMock verify]; @@ -180,12 +178,9 @@ - (void)testSendReportFromValidationReport #endif } - -- (void)testReporter -{ +- (void)testReporter { // Just try a simple valid case to see if we can post this to the default report URL - TSKBackgroundReporter *reporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:NO - sharedContainerIdentifier:nil]; + TSKBackgroundReporter *reporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:NO sharedContainerIdentifier:nil]; [reporter pinValidationFailedForHostname:@"mail.example.com" port:[NSNumber numberWithInt:443] certificateChain:_testCertificateChain @@ -194,21 +189,18 @@ - (void)testReporter includeSubdomains:YES enforcePinning:YES knownPins:[NSSet setWithArray:@[ - [[NSData alloc]initWithBase64EncodedString:@"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" - options:(NSDataBase64DecodingOptions)0], - [[NSData alloc]initWithBase64EncodedString:@"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" - options:(NSDataBase64DecodingOptions)0]]] + [[NSData alloc] initWithBase64EncodedString:@"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" options:(NSDataBase64DecodingOptions)0], + [[NSData alloc] initWithBase64EncodedString:@"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" options:(NSDataBase64DecodingOptions)0] + ]] validationResult:TSKTrustEvaluationFailedNoMatchingPin expirationDate:[NSDate date]]; - + [NSThread sleepForTimeInterval:0.1]; } -- (void)testReporterNilExpirationDate -{ +- (void)testReporterNilExpirationDate { // Just try a simple valid case to see if we can post this to the default report URL - TSKBackgroundReporter *reporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:NO - sharedContainerIdentifier:nil]; + TSKBackgroundReporter *reporter = [[TSKBackgroundReporter alloc] initAndRateLimitReports:NO sharedContainerIdentifier:nil]; [reporter pinValidationFailedForHostname:@"mail.example.com" port:[NSNumber numberWithInt:443] certificateChain:_testCertificateChain @@ -217,18 +209,16 @@ - (void)testReporterNilExpirationDate includeSubdomains:YES enforcePinning:YES knownPins:[NSSet setWithArray:@[ - [[NSData alloc]initWithBase64EncodedString:@"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" - options:(NSDataBase64DecodingOptions)0], - [[NSData alloc]initWithBase64EncodedString:@"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" - options:(NSDataBase64DecodingOptions)0]]] + [[NSData alloc] initWithBase64EncodedString:@"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" options:(NSDataBase64DecodingOptions)0], + [[NSData alloc] initWithBase64EncodedString:@"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" options:(NSDataBase64DecodingOptions)0] + ]] validationResult:TSKTrustEvaluationFailedNoMatchingPin expirationDate:nil]; - + [NSThread sleepForTimeInterval:0.1]; } -- (void)testIdentifierForVendor -{ +- (void)testIdentifierForVendor { NSString *idfv = identifier_for_vendor(); XCTAssertNotNil(idfv); } diff --git a/TrustKitTests/TSKReportsRateLimiterTests.m b/TrustKitTests/TSKReportsRateLimiterTests.m index a807ee90..45768ad8 100644 --- a/TrustKitTests/TSKReportsRateLimiterTests.m +++ b/TrustKitTests/TSKReportsRateLimiterTests.m @@ -31,27 +31,26 @@ - (void)setUp { [super setUp]; // Put setup code here. This method is called before the invocation of each test method in the class. _rateLimiter = [TSKReportsRateLimiter new]; - + SecCertificateRef rootCertificate = [TSKCertificateUtils createCertificateFromDer:@"GoodRootCA"]; SecCertificateRef leafCertificate = [TSKCertificateUtils createCertificateFromDer:@"www.good.com"]; - + SecCertificateRef certChainArray[1] = { leafCertificate }; SecCertificateRef trustStoreArray[1] = { rootCertificate }; - + _testTrust = [TSKCertificateUtils createTrustWithCertificates:(const void **)certChainArray - arrayLength:sizeof(certChainArray)/sizeof(certChainArray[0]) + arrayLength:sizeof(certChainArray) / sizeof(certChainArray[0]) anchorCertificates:(const void **)trustStoreArray - arrayLength:sizeof(trustStoreArray)/sizeof(trustStoreArray[0])]; + arrayLength:sizeof(trustStoreArray) / sizeof(trustStoreArray[0])]; _testCertificateChain = convertTrustToPemArray(self.testTrust); - + // Create the pin validation failure report NSArray *certificateChain = convertTrustToPemArray(self.testTrust); - NSArray *formattedPins = convertPinsToHpkpPins([NSSet setWithArray:@[[[NSData alloc]initWithBase64EncodedString:@"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" - options:(NSDataBase64DecodingOptions)0], - [[NSData alloc]initWithBase64EncodedString:@"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" - options:(NSDataBase64DecodingOptions)0]] - ]); - + NSArray *formattedPins = convertPinsToHpkpPins([NSSet setWithArray:@[ + [[NSData alloc] initWithBase64EncodedString:@"d6qzRu9zOECb90Uez27xWltNsj0e1Md7GkYYkVoZWmM=" options:(NSDataBase64DecodingOptions)0], + [[NSData alloc] initWithBase64EncodedString:@"E9CZ9INDbd+2eRQozYqqbQ2yXLVKB9+xcprMF+44U1g=" options:(NSDataBase64DecodingOptions)0] + ]]); + _testReport = [[TSKPinFailureReport alloc] initWithAppBundleId:@"test" appVersion:@"1.2.3" appPlatform:@"IOS" @@ -68,7 +67,6 @@ - (void)setUp { knownPins:formattedPins validationResult:TSKTrustEvaluationFailedInvalidCertificateChain expirationDate:[NSDate dateWithTimeIntervalSinceReferenceDate:0]]; - } - (void)tearDown { @@ -77,14 +75,12 @@ - (void)tearDown { } // Ensure a new report will be sent -- (void)test_noRateLimitOnFirstReport -{ +- (void)test_noRateLimitOnFirstReport { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); } // Ensure the same report will not be sent twice in a row -- (void)test_rateLimitDuplicateReport -{ +- (void)test_rateLimitDuplicateReport { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); XCTAssertTrue([self.rateLimiter shouldRateLimitReport:self.testReport], @"Failed to rate-limit a repeated report"); // And sanity check, why not? @@ -92,93 +88,86 @@ - (void)test_rateLimitDuplicateReport } // Ensure the same report will not be sent twice in a row -- (void)test_rateLimitAllowDuplicateAfter24Hours -{ +- (void)test_rateLimitAllowDuplicateAfter24Hours { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); XCTAssertTrue([self.rateLimiter shouldRateLimitReport:self.testReport], @"Failed to rate-limit a repeated report"); - + // Force update the cache timer to replicate a day passing, then re-run the tests self.rateLimiter.lastReportsCacheResetDate = [NSDate dateWithTimeIntervalSinceNow:-3601 * 24]; - + XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a duplicate report after 24 hours"); XCTAssertTrue([self.rateLimiter shouldRateLimitReport:self.testReport], @"Failed to rate-limit a repeated report"); } // Hashing test: noted hostname -- (void)test_rateLimitHashing_notedHostname -{ +- (void)test_rateLimitHashing_notedHostname { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); - + TSKPinFailureReport *mockReport = OCMPartialMock(self.testReport); OCMStub(mockReport.notedHostname).andReturn(@"flargle.blargle.com"); - + XCTAssertFalse([self.rateLimiter shouldRateLimitReport:mockReport], @"Wrongly rate-limited a new report"); - + [(id)mockReport stopMocking]; } // Hashing test: hostname -- (void)test_rateLimitHashing_hostname -{ +- (void)test_rateLimitHashing_hostname { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); - + TSKPinFailureReport *mockReport = OCMPartialMock(self.testReport); OCMStub(mockReport.hostname).andReturn(@"flargle.blargle.com"); - + XCTAssertFalse([self.rateLimiter shouldRateLimitReport:mockReport], @"Wrongly rate-limited a new report"); - + [(id)mockReport stopMocking]; } // Hashing test: noted port -- (void)test_rateLimitHashing_port -{ +- (void)test_rateLimitHashing_port { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); - + TSKPinFailureReport *mockReport = OCMPartialMock(self.testReport); OCMStub(mockReport.port).andReturn(@9090); - + XCTAssertFalse([self.rateLimiter shouldRateLimitReport:mockReport], @"Wrongly rate-limited a new report"); - + [(id)mockReport stopMocking]; } // Hashing test: certificate chain -- (void)test_rateLimitHashing_validatedCertificateChain -{ +- (void)test_rateLimitHashing_validatedCertificateChain { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); - + TSKPinFailureReport *mockReport = OCMPartialMock(self.testReport); OCMStub(mockReport.validatedCertificateChain).andReturn(@[]); - + XCTAssertFalse([self.rateLimiter shouldRateLimitReport:mockReport], @"Wrongly rate-limited a new report"); - + [(id)mockReport stopMocking]; } // Hashing test: known pins -- (void)test_rateLimitHashing_knownPins -{ +- (void)test_rateLimitHashing_knownPins { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); - + TSKPinFailureReport *mockReport = OCMPartialMock(self.testReport); OCMStub(mockReport.knownPins).andReturn(@[]); - + XCTAssertFalse([self.rateLimiter shouldRateLimitReport:mockReport], @"Wrongly rate-limited a new report"); - + [(id)mockReport stopMocking]; } // Hashing test: validation result -- (void)test_rateLimitHashing_validationResult -{ +- (void)test_rateLimitHashing_validationResult { XCTAssertFalse([self.rateLimiter shouldRateLimitReport:self.testReport], @"Wrongly rate-limited a new report"); - + TSKPinFailureReport *mockReport = OCMPartialMock(self.testReport); OCMStub(mockReport.validationResult).andReturn(TSKTrustEvaluationErrorCouldNotGenerateSpkiHash); - + XCTAssertFalse([self.rateLimiter shouldRateLimitReport:mockReport], @"Wrongly rate-limited a new report"); - + [(id)mockReport stopMocking]; } diff --git a/TrustKitTests/TSKSwizzlingTests.m b/TrustKitTests/TSKSwizzlingTests.m index b5b2262e..ee96c84b 100644 --- a/TrustKitTests/TSKSwizzlingTests.m +++ b/TrustKitTests/TSKSwizzlingTests.m @@ -6,15 +6,14 @@ // Copyright © 2017 TrustKit. All rights reserved. // -#import -#import "../TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.h" #import "../TrustKit/Swizzling/TSKNSURLConnectionDelegateProxy.m" +#import "../TrustKit/Swizzling/TSKNSURLSessionDelegateProxy.h" +#import @interface TSKSwizzlingTests : XCTestCase @end - /* Basic tests to ensure RSSwizzle does not trigger a crash because a method we are hooking has changed */ @implementation TSKSwizzlingTests @@ -27,42 +26,40 @@ - (void)tearDown { [super tearDown]; } +- (void)testNSURLSession { + NSDictionary *trustKitConfig = @{ + kTSKPinnedDomains: @ { + @"www.datatheorem.com": @ { + kTSKEnforcePinning: @YES, + kTSKPublicKeyHashes: @[ + @"58qRu/uxh4gFezqAcERupSkRYBlBAvfcw7mEjGPLnNU=", // CA key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }; -- (void)testNSURLSession -{ - NSDictionary *trustKitConfig = - @{ - kTSKPinnedDomains : - @{ - @"www.datatheorem.com" : @{ - kTSKEnforcePinning : @YES, - kTSKPublicKeyHashes : @[@"58qRu/uxh4gFezqAcERupSkRYBlBAvfcw7mEjGPLnNU=", // CA key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}; - TrustKit *trustKit = [[TrustKit alloc] initWithConfiguration:trustKitConfig]; - + [TSKNSURLSessionDelegateProxy swizzleNSURLSessionConstructors:trustKit]; - } +- (void)testNSURLConnection { + NSDictionary *trustKitConfig = @{ + kTSKPinnedDomains: @ { + @"www.datatheorem.com": @ { + kTSKEnforcePinning: @YES, + kTSKPublicKeyHashes: @[ + @"58qRu/uxh4gFezqAcERupSkRYBlBAvfcw7mEjGPLnNU=", // CA key + @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key + ] + } + } + }; -- (void)testNSURLConnection -{ - NSDictionary *trustKitConfig = - @{ - kTSKPinnedDomains : - @{ - @"www.datatheorem.com" : @{ - kTSKEnforcePinning : @YES, - kTSKPublicKeyHashes : @[@"58qRu/uxh4gFezqAcERupSkRYBlBAvfcw7mEjGPLnNU=", // CA key - @"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" // Fake key - ]}}}; - TrustKit *trustKit = [[TrustKit alloc] initWithConfiguration:trustKitConfig]; - + [TSKNSURLConnectionDelegateProxy swizzleNSURLConnectionConstructors:trustKit]; - } @end