├── .gitignore ├── LICENSE ├── README.md ├── ios-pinning-demo.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ ├── IDEWorkspaceChecks.plist │ │ └── swiftpm │ │ └── Package.resolved └── xcuserdata │ └── tim.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── ios-pinning-demo ├── Assets.xcassets │ ├── AccentColor.colorset │ │ └── Contents.json │ ├── AppIcon.appiconset │ │ └── Contents.json │ └── Contents.json ├── Info.plist ├── Model │ ├── Certificates.swift │ ├── HTTPRequestModels.swift │ ├── PinningURLSessionDelegate.swift │ ├── RequestViewModel.swift │ └── TrustKitURLSessionDelegate.swift ├── Preview Content │ └── Preview Assets.xcassets │ │ └── Contents.json ├── View │ ├── ContentView.swift │ └── PinningApp.swift └── isrg-root.der └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## User settings 6 | xcuserdata/ 7 | 8 | ## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) 9 | *.xcscmblueprint 10 | *.xccheckout 11 | 12 | ## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) 13 | build/ 14 | DerivedData/ 15 | *.moved-aside 16 | *.pbxuser 17 | !default.pbxuser 18 | *.mode1v3 19 | !default.mode1v3 20 | *.mode2v3 21 | !default.mode2v3 22 | *.perspectivev3 23 | !default.perspectivev3 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | 28 | ## App packaging 29 | *.ipa 30 | *.dSYM.zip 31 | *.dSYM 32 | 33 | ## Playgrounds 34 | timeline.xctimeline 35 | playground.xcworkspace 36 | 37 | # Swift Package Manager 38 | # 39 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 40 | # Packages/ 41 | # Package.pins 42 | # Package.resolved 43 | # *.xcodeproj 44 | # 45 | # Xcode automatically generates this directory with a .xcworkspacedata file and xcuserdata 46 | # hence it is not needed unless you have added a package configuration file to your project 47 | # .swiftpm 48 | 49 | .build/ 50 | 51 | # CocoaPods 52 | # 53 | # We recommend against adding the Pods directory to your .gitignore. However 54 | # you should judge for yourself, the pros and cons are mentioned at: 55 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 56 | # 57 | # Pods/ 58 | # 59 | # Add this line if you want to avoid checking in source code from the Xcode workspace 60 | # *.xcworkspace 61 | 62 | # Carthage 63 | # 64 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 65 | # Carthage/Checkouts 66 | 67 | Carthage/Build/ 68 | 69 | # Accio dependency management 70 | Dependencies/ 71 | .accio/ 72 | 73 | # fastlane 74 | # 75 | # It is recommended to not store the screenshots in the git repo. 76 | # Instead, use fastlane to re-generate the screenshots whenever they are needed. 77 | # For more information about the recommended setup visit: 78 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 79 | 80 | fastlane/report.xml 81 | fastlane/Preview.html 82 | fastlane/screenshots/**/*.png 83 | fastlane/test_output 84 | 85 | # Code Injection 86 | # 87 | # After new code Injection tools there's a generated folder /iOSInjectionProject 88 | # https://github.com/johnno1962/injectionforxcode 89 | 90 | iOSInjectionProject/ 91 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ios-ssl-pinning-demo 2 | 3 | > _Part of [HTTP Toolkit](https://httptoolkit.com): powerful tools for building, testing & debugging HTTP(S)_ 4 | 5 | A tiny demo iOS app using SSL pinning to block HTTPS MitM interception. 6 | 7 | ## Try it out 8 | 9 | To test this out, clone this repo and build it yourself in XCode, then install it on your simulator or device. 10 | 11 | Pressing each button will send an HTTP request with the corresponding configuration. The buttons are purple initially or while a request is in flight, and then turn green or red (with corresponding icons) when the request succeeds/fails. Error details for failures are available in the console. 12 | 13 | On a normal unintercepted device, every button should always immediately go green. On a device whose HTTPS is being intercepted (e.g. by [HTTP Toolkit](https://httptoolkit.com/)) all 'pinning' buttons will go red and fail, unless you've used Frida or similar to successfully disable certificate pinning. 14 | 15 | A screenshot of the app in action 16 | -------------------------------------------------------------------------------- /ios-pinning-demo.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 56; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 226A678C2B32016F0060DF1A /* PinningApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A678B2B32016F0060DF1A /* PinningApp.swift */; }; 11 | 226A678E2B32016F0060DF1A /* ContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A678D2B32016F0060DF1A /* ContentView.swift */; }; 12 | 226A67902B3201720060DF1A /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 226A678F2B3201720060DF1A /* Assets.xcassets */; }; 13 | 226A67932B3201720060DF1A /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 226A67922B3201720060DF1A /* Preview Assets.xcassets */; }; 14 | 226A679B2B32082C0060DF1A /* RequestViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A679A2B32082C0060DF1A /* RequestViewModel.swift */; }; 15 | 226A679D2B320A680060DF1A /* PinningURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 226A679C2B320A680060DF1A /* PinningURLSessionDelegate.swift */; }; 16 | 22B46E632B330D300031C568 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 22B46E622B330D300031C568 /* Alamofire */; }; 17 | 22B46E652B330E070031C568 /* HTTPRequestModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22B46E642B330E070031C568 /* HTTPRequestModels.swift */; }; 18 | 22F4323B2B33380300866A21 /* Certificates.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F4323A2B33380300866A21 /* Certificates.swift */; }; 19 | 22F4323F2B3346A100866A21 /* isrg-root.der in Resources */ = {isa = PBXBuildFile; fileRef = 22F4323E2B3346A100866A21 /* isrg-root.der */; }; 20 | 22F432482B335E3600866A21 /* TrustKit in Frameworks */ = {isa = PBXBuildFile; productRef = 22F432472B335E3600866A21 /* TrustKit */; }; 21 | 22F4324C2B335E3600866A21 /* TrustKitStatic in Frameworks */ = {isa = PBXBuildFile; productRef = 22F4324B2B335E3600866A21 /* TrustKitStatic */; }; 22 | 22F432502B33675000866A21 /* TrustKitURLSessionDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 22F4324F2B33675000866A21 /* TrustKitURLSessionDelegate.swift */; }; 23 | 22F432532B336F2500866A21 /* AFNetworking in Frameworks */ = {isa = PBXBuildFile; productRef = 22F432522B336F2500866A21 /* AFNetworking */; }; 24 | /* End PBXBuildFile section */ 25 | 26 | /* Begin PBXCopyFilesBuildPhase section */ 27 | 22F64AA42B6025F90093AC10 /* Embed Frameworks */ = { 28 | isa = PBXCopyFilesBuildPhase; 29 | buildActionMask = 2147483647; 30 | dstPath = ""; 31 | dstSubfolderSpec = 10; 32 | files = ( 33 | ); 34 | name = "Embed Frameworks"; 35 | runOnlyForDeploymentPostprocessing = 0; 36 | }; 37 | /* End PBXCopyFilesBuildPhase section */ 38 | 39 | /* Begin PBXFileReference section */ 40 | 226A67882B32016F0060DF1A /* ios-pinning-demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "ios-pinning-demo.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 226A678B2B32016F0060DF1A /* PinningApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinningApp.swift; sourceTree = ""; }; 42 | 226A678D2B32016F0060DF1A /* ContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentView.swift; sourceTree = ""; }; 43 | 226A678F2B3201720060DF1A /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 44 | 226A67922B3201720060DF1A /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; 45 | 226A67992B3206C70060DF1A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 46 | 226A679A2B32082C0060DF1A /* RequestViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RequestViewModel.swift; sourceTree = ""; }; 47 | 226A679C2B320A680060DF1A /* PinningURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PinningURLSessionDelegate.swift; sourceTree = ""; }; 48 | 22B46E642B330E070031C568 /* HTTPRequestModels.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HTTPRequestModels.swift; sourceTree = ""; }; 49 | 22F4323A2B33380300866A21 /* Certificates.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Certificates.swift; sourceTree = ""; }; 50 | 22F4323E2B3346A100866A21 /* isrg-root.der */ = {isa = PBXFileReference; lastKnownFileType = file; path = "isrg-root.der"; sourceTree = ""; }; 51 | 22F4324F2B33675000866A21 /* TrustKitURLSessionDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustKitURLSessionDelegate.swift; sourceTree = ""; }; 52 | /* End PBXFileReference section */ 53 | 54 | /* Begin PBXFrameworksBuildPhase section */ 55 | 226A67852B32016F0060DF1A /* Frameworks */ = { 56 | isa = PBXFrameworksBuildPhase; 57 | buildActionMask = 2147483647; 58 | files = ( 59 | 22F4324C2B335E3600866A21 /* TrustKitStatic in Frameworks */, 60 | 22F432482B335E3600866A21 /* TrustKit in Frameworks */, 61 | 22F432532B336F2500866A21 /* AFNetworking in Frameworks */, 62 | 22B46E632B330D300031C568 /* Alamofire in Frameworks */, 63 | ); 64 | runOnlyForDeploymentPostprocessing = 0; 65 | }; 66 | /* End PBXFrameworksBuildPhase section */ 67 | 68 | /* Begin PBXGroup section */ 69 | 226A677F2B32016F0060DF1A = { 70 | isa = PBXGroup; 71 | children = ( 72 | 226A678A2B32016F0060DF1A /* ios-pinning-demo */, 73 | 226A67892B32016F0060DF1A /* Products */, 74 | ); 75 | sourceTree = ""; 76 | }; 77 | 226A67892B32016F0060DF1A /* Products */ = { 78 | isa = PBXGroup; 79 | children = ( 80 | 226A67882B32016F0060DF1A /* ios-pinning-demo.app */, 81 | ); 82 | name = Products; 83 | sourceTree = ""; 84 | }; 85 | 226A678A2B32016F0060DF1A /* ios-pinning-demo */ = { 86 | isa = PBXGroup; 87 | children = ( 88 | 22F432412B334EC000866A21 /* View */, 89 | 22F432402B334EBB00866A21 /* Model */, 90 | 226A67992B3206C70060DF1A /* Info.plist */, 91 | 22F4323E2B3346A100866A21 /* isrg-root.der */, 92 | 226A678F2B3201720060DF1A /* Assets.xcassets */, 93 | 226A67912B3201720060DF1A /* Preview Content */, 94 | ); 95 | path = "ios-pinning-demo"; 96 | sourceTree = ""; 97 | }; 98 | 226A67912B3201720060DF1A /* Preview Content */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 226A67922B3201720060DF1A /* Preview Assets.xcassets */, 102 | ); 103 | path = "Preview Content"; 104 | sourceTree = ""; 105 | }; 106 | 22F432402B334EBB00866A21 /* Model */ = { 107 | isa = PBXGroup; 108 | children = ( 109 | 226A679C2B320A680060DF1A /* PinningURLSessionDelegate.swift */, 110 | 226A679A2B32082C0060DF1A /* RequestViewModel.swift */, 111 | 22B46E642B330E070031C568 /* HTTPRequestModels.swift */, 112 | 22F4323A2B33380300866A21 /* Certificates.swift */, 113 | 22F4324F2B33675000866A21 /* TrustKitURLSessionDelegate.swift */, 114 | ); 115 | path = Model; 116 | sourceTree = ""; 117 | }; 118 | 22F432412B334EC000866A21 /* View */ = { 119 | isa = PBXGroup; 120 | children = ( 121 | 226A678B2B32016F0060DF1A /* PinningApp.swift */, 122 | 226A678D2B32016F0060DF1A /* ContentView.swift */, 123 | ); 124 | path = View; 125 | sourceTree = ""; 126 | }; 127 | /* End PBXGroup section */ 128 | 129 | /* Begin PBXNativeTarget section */ 130 | 226A67872B32016F0060DF1A /* ios-pinning-demo */ = { 131 | isa = PBXNativeTarget; 132 | buildConfigurationList = 226A67962B3201720060DF1A /* Build configuration list for PBXNativeTarget "ios-pinning-demo" */; 133 | buildPhases = ( 134 | 226A67842B32016F0060DF1A /* Sources */, 135 | 226A67852B32016F0060DF1A /* Frameworks */, 136 | 226A67862B32016F0060DF1A /* Resources */, 137 | 22F64AA42B6025F90093AC10 /* Embed Frameworks */, 138 | ); 139 | buildRules = ( 140 | ); 141 | dependencies = ( 142 | ); 143 | name = "ios-pinning-demo"; 144 | packageProductDependencies = ( 145 | 22B46E622B330D300031C568 /* Alamofire */, 146 | 22F432472B335E3600866A21 /* TrustKit */, 147 | 22F4324B2B335E3600866A21 /* TrustKitStatic */, 148 | 22F432522B336F2500866A21 /* AFNetworking */, 149 | ); 150 | productName = "ios-pinning-demo"; 151 | productReference = 226A67882B32016F0060DF1A /* ios-pinning-demo.app */; 152 | productType = "com.apple.product-type.application"; 153 | }; 154 | /* End PBXNativeTarget section */ 155 | 156 | /* Begin PBXProject section */ 157 | 226A67802B32016F0060DF1A /* Project object */ = { 158 | isa = PBXProject; 159 | attributes = { 160 | BuildIndependentTargetsInParallel = 1; 161 | LastSwiftUpdateCheck = 1500; 162 | LastUpgradeCheck = 1500; 163 | TargetAttributes = { 164 | 226A67872B32016F0060DF1A = { 165 | CreatedOnToolsVersion = 15.0.1; 166 | }; 167 | }; 168 | }; 169 | buildConfigurationList = 226A67832B32016F0060DF1A /* Build configuration list for PBXProject "ios-pinning-demo" */; 170 | compatibilityVersion = "Xcode 14.0"; 171 | developmentRegion = en; 172 | hasScannedForEncodings = 0; 173 | knownRegions = ( 174 | en, 175 | Base, 176 | ); 177 | mainGroup = 226A677F2B32016F0060DF1A; 178 | packageReferences = ( 179 | 22B46E612B330D300031C568 /* XCRemoteSwiftPackageReference "Alamofire" */, 180 | 22F432462B335E3600866A21 /* XCRemoteSwiftPackageReference "TrustKit" */, 181 | 22F432512B336F2500866A21 /* XCRemoteSwiftPackageReference "AFNetworking" */, 182 | ); 183 | productRefGroup = 226A67892B32016F0060DF1A /* Products */; 184 | projectDirPath = ""; 185 | projectRoot = ""; 186 | targets = ( 187 | 226A67872B32016F0060DF1A /* ios-pinning-demo */, 188 | ); 189 | }; 190 | /* End PBXProject section */ 191 | 192 | /* Begin PBXResourcesBuildPhase section */ 193 | 226A67862B32016F0060DF1A /* Resources */ = { 194 | isa = PBXResourcesBuildPhase; 195 | buildActionMask = 2147483647; 196 | files = ( 197 | 226A67932B3201720060DF1A /* Preview Assets.xcassets in Resources */, 198 | 226A67902B3201720060DF1A /* Assets.xcassets in Resources */, 199 | 22F4323F2B3346A100866A21 /* isrg-root.der in Resources */, 200 | ); 201 | runOnlyForDeploymentPostprocessing = 0; 202 | }; 203 | /* End PBXResourcesBuildPhase section */ 204 | 205 | /* Begin PBXSourcesBuildPhase section */ 206 | 226A67842B32016F0060DF1A /* Sources */ = { 207 | isa = PBXSourcesBuildPhase; 208 | buildActionMask = 2147483647; 209 | files = ( 210 | 226A679B2B32082C0060DF1A /* RequestViewModel.swift in Sources */, 211 | 22B46E652B330E070031C568 /* HTTPRequestModels.swift in Sources */, 212 | 226A678E2B32016F0060DF1A /* ContentView.swift in Sources */, 213 | 226A678C2B32016F0060DF1A /* PinningApp.swift in Sources */, 214 | 22F432502B33675000866A21 /* TrustKitURLSessionDelegate.swift in Sources */, 215 | 226A679D2B320A680060DF1A /* PinningURLSessionDelegate.swift in Sources */, 216 | 22F4323B2B33380300866A21 /* Certificates.swift in Sources */, 217 | ); 218 | runOnlyForDeploymentPostprocessing = 0; 219 | }; 220 | /* End PBXSourcesBuildPhase section */ 221 | 222 | /* Begin XCBuildConfiguration section */ 223 | 226A67942B3201720060DF1A /* Debug */ = { 224 | isa = XCBuildConfiguration; 225 | buildSettings = { 226 | ALWAYS_SEARCH_USER_PATHS = NO; 227 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 228 | CLANG_ANALYZER_NONNULL = YES; 229 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 230 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 231 | CLANG_ENABLE_MODULES = YES; 232 | CLANG_ENABLE_OBJC_ARC = YES; 233 | CLANG_ENABLE_OBJC_WEAK = YES; 234 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 235 | CLANG_WARN_BOOL_CONVERSION = YES; 236 | CLANG_WARN_COMMA = YES; 237 | CLANG_WARN_CONSTANT_CONVERSION = YES; 238 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 239 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 240 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 241 | CLANG_WARN_EMPTY_BODY = YES; 242 | CLANG_WARN_ENUM_CONVERSION = YES; 243 | CLANG_WARN_INFINITE_RECURSION = YES; 244 | CLANG_WARN_INT_CONVERSION = YES; 245 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 246 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 247 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 248 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 249 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 250 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 251 | CLANG_WARN_STRICT_PROTOTYPES = YES; 252 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 253 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 254 | CLANG_WARN_UNREACHABLE_CODE = YES; 255 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 256 | COPY_PHASE_STRIP = NO; 257 | DEBUG_INFORMATION_FORMAT = dwarf; 258 | ENABLE_STRICT_OBJC_MSGSEND = YES; 259 | ENABLE_TESTABILITY = YES; 260 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 261 | GCC_C_LANGUAGE_STANDARD = gnu17; 262 | GCC_DYNAMIC_NO_PIC = NO; 263 | GCC_NO_COMMON_BLOCKS = YES; 264 | GCC_OPTIMIZATION_LEVEL = 0; 265 | GCC_PREPROCESSOR_DEFINITIONS = ( 266 | "DEBUG=1", 267 | "$(inherited)", 268 | ); 269 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 270 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 271 | GCC_WARN_UNDECLARED_SELECTOR = YES; 272 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 273 | GCC_WARN_UNUSED_FUNCTION = YES; 274 | GCC_WARN_UNUSED_VARIABLE = YES; 275 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 276 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 277 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 278 | MTL_FAST_MATH = YES; 279 | ONLY_ACTIVE_ARCH = YES; 280 | SDKROOT = iphoneos; 281 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; 282 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 283 | }; 284 | name = Debug; 285 | }; 286 | 226A67952B3201720060DF1A /* Release */ = { 287 | isa = XCBuildConfiguration; 288 | buildSettings = { 289 | ALWAYS_SEARCH_USER_PATHS = NO; 290 | ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; 291 | CLANG_ANALYZER_NONNULL = YES; 292 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 293 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; 294 | CLANG_ENABLE_MODULES = YES; 295 | CLANG_ENABLE_OBJC_ARC = YES; 296 | CLANG_ENABLE_OBJC_WEAK = YES; 297 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 298 | CLANG_WARN_BOOL_CONVERSION = YES; 299 | CLANG_WARN_COMMA = YES; 300 | CLANG_WARN_CONSTANT_CONVERSION = YES; 301 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 302 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 303 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 304 | CLANG_WARN_EMPTY_BODY = YES; 305 | CLANG_WARN_ENUM_CONVERSION = YES; 306 | CLANG_WARN_INFINITE_RECURSION = YES; 307 | CLANG_WARN_INT_CONVERSION = YES; 308 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 309 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 310 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 311 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 312 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = YES; 313 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 314 | CLANG_WARN_STRICT_PROTOTYPES = YES; 315 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 316 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 317 | CLANG_WARN_UNREACHABLE_CODE = YES; 318 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 319 | COPY_PHASE_STRIP = NO; 320 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 321 | ENABLE_NS_ASSERTIONS = NO; 322 | ENABLE_STRICT_OBJC_MSGSEND = YES; 323 | ENABLE_USER_SCRIPT_SANDBOXING = YES; 324 | GCC_C_LANGUAGE_STANDARD = gnu17; 325 | GCC_NO_COMMON_BLOCKS = YES; 326 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 327 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 328 | GCC_WARN_UNDECLARED_SELECTOR = YES; 329 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 330 | GCC_WARN_UNUSED_FUNCTION = YES; 331 | GCC_WARN_UNUSED_VARIABLE = YES; 332 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 333 | LOCALIZATION_PREFERS_STRING_CATALOGS = YES; 334 | MTL_ENABLE_DEBUG_INFO = NO; 335 | MTL_FAST_MATH = YES; 336 | SDKROOT = iphoneos; 337 | SWIFT_COMPILATION_MODE = wholemodule; 338 | VALIDATE_PRODUCT = YES; 339 | }; 340 | name = Release; 341 | }; 342 | 226A67972B3201720060DF1A /* Debug */ = { 343 | isa = XCBuildConfiguration; 344 | buildSettings = { 345 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 346 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 347 | CODE_SIGN_STYLE = Automatic; 348 | CURRENT_PROJECT_VERSION = 1; 349 | DEVELOPMENT_ASSET_PATHS = "\"ios-pinning-demo/Preview Content\""; 350 | DEVELOPMENT_TEAM = 8LCZSXKVZY; 351 | ENABLE_PREVIEWS = YES; 352 | GENERATE_INFOPLIST_FILE = YES; 353 | INFOPLIST_FILE = "ios-pinning-demo/Info.plist"; 354 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 355 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 356 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 357 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 358 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 359 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 360 | LD_RUNPATH_SEARCH_PATHS = ( 361 | "$(inherited)", 362 | "@executable_path/Frameworks", 363 | ); 364 | MARKETING_VERSION = 1.0; 365 | PRODUCT_BUNDLE_IDENTIFIER = "com.httptoolkit.ios-pinning-demo"; 366 | PRODUCT_NAME = "$(TARGET_NAME)"; 367 | SWIFT_EMIT_LOC_STRINGS = YES; 368 | SWIFT_VERSION = 5.0; 369 | TARGETED_DEVICE_FAMILY = "1,2"; 370 | }; 371 | name = Debug; 372 | }; 373 | 226A67982B3201720060DF1A /* Release */ = { 374 | isa = XCBuildConfiguration; 375 | buildSettings = { 376 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 377 | ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; 378 | CODE_SIGN_STYLE = Automatic; 379 | CURRENT_PROJECT_VERSION = 1; 380 | DEVELOPMENT_ASSET_PATHS = "\"ios-pinning-demo/Preview Content\""; 381 | DEVELOPMENT_TEAM = 8LCZSXKVZY; 382 | ENABLE_PREVIEWS = YES; 383 | GENERATE_INFOPLIST_FILE = YES; 384 | INFOPLIST_FILE = "ios-pinning-demo/Info.plist"; 385 | INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.developer-tools"; 386 | INFOPLIST_KEY_UIApplicationSceneManifest_Generation = YES; 387 | INFOPLIST_KEY_UIApplicationSupportsIndirectInputEvents = YES; 388 | INFOPLIST_KEY_UILaunchScreen_Generation = YES; 389 | INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; 390 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 391 | LD_RUNPATH_SEARCH_PATHS = ( 392 | "$(inherited)", 393 | "@executable_path/Frameworks", 394 | ); 395 | MARKETING_VERSION = 1.0; 396 | PRODUCT_BUNDLE_IDENTIFIER = "com.httptoolkit.ios-pinning-demo"; 397 | PRODUCT_NAME = "$(TARGET_NAME)"; 398 | SWIFT_EMIT_LOC_STRINGS = YES; 399 | SWIFT_VERSION = 5.0; 400 | TARGETED_DEVICE_FAMILY = "1,2"; 401 | }; 402 | name = Release; 403 | }; 404 | /* End XCBuildConfiguration section */ 405 | 406 | /* Begin XCConfigurationList section */ 407 | 226A67832B32016F0060DF1A /* Build configuration list for PBXProject "ios-pinning-demo" */ = { 408 | isa = XCConfigurationList; 409 | buildConfigurations = ( 410 | 226A67942B3201720060DF1A /* Debug */, 411 | 226A67952B3201720060DF1A /* Release */, 412 | ); 413 | defaultConfigurationIsVisible = 0; 414 | defaultConfigurationName = Release; 415 | }; 416 | 226A67962B3201720060DF1A /* Build configuration list for PBXNativeTarget "ios-pinning-demo" */ = { 417 | isa = XCConfigurationList; 418 | buildConfigurations = ( 419 | 226A67972B3201720060DF1A /* Debug */, 420 | 226A67982B3201720060DF1A /* Release */, 421 | ); 422 | defaultConfigurationIsVisible = 0; 423 | defaultConfigurationName = Release; 424 | }; 425 | /* End XCConfigurationList section */ 426 | 427 | /* Begin XCRemoteSwiftPackageReference section */ 428 | 22B46E612B330D300031C568 /* XCRemoteSwiftPackageReference "Alamofire" */ = { 429 | isa = XCRemoteSwiftPackageReference; 430 | repositoryURL = "https://github.com/Alamofire/Alamofire.git"; 431 | requirement = { 432 | kind = upToNextMajorVersion; 433 | minimumVersion = 5.8.1; 434 | }; 435 | }; 436 | 22F432462B335E3600866A21 /* XCRemoteSwiftPackageReference "TrustKit" */ = { 437 | isa = XCRemoteSwiftPackageReference; 438 | repositoryURL = "https://github.com/datatheorem/TrustKit"; 439 | requirement = { 440 | kind = upToNextMajorVersion; 441 | minimumVersion = 3.0.3; 442 | }; 443 | }; 444 | 22F432512B336F2500866A21 /* XCRemoteSwiftPackageReference "AFNetworking" */ = { 445 | isa = XCRemoteSwiftPackageReference; 446 | repositoryURL = "https://github.com/AFNetworking/AFNetworking.git"; 447 | requirement = { 448 | kind = upToNextMajorVersion; 449 | minimumVersion = 4.0.1; 450 | }; 451 | }; 452 | /* End XCRemoteSwiftPackageReference section */ 453 | 454 | /* Begin XCSwiftPackageProductDependency section */ 455 | 22B46E622B330D300031C568 /* Alamofire */ = { 456 | isa = XCSwiftPackageProductDependency; 457 | package = 22B46E612B330D300031C568 /* XCRemoteSwiftPackageReference "Alamofire" */; 458 | productName = Alamofire; 459 | }; 460 | 22F432472B335E3600866A21 /* TrustKit */ = { 461 | isa = XCSwiftPackageProductDependency; 462 | package = 22F432462B335E3600866A21 /* XCRemoteSwiftPackageReference "TrustKit" */; 463 | productName = TrustKit; 464 | }; 465 | 22F4324B2B335E3600866A21 /* TrustKitStatic */ = { 466 | isa = XCSwiftPackageProductDependency; 467 | package = 22F432462B335E3600866A21 /* XCRemoteSwiftPackageReference "TrustKit" */; 468 | productName = TrustKitStatic; 469 | }; 470 | 22F432522B336F2500866A21 /* AFNetworking */ = { 471 | isa = XCSwiftPackageProductDependency; 472 | package = 22F432512B336F2500866A21 /* XCRemoteSwiftPackageReference "AFNetworking" */; 473 | productName = AFNetworking; 474 | }; 475 | /* End XCSwiftPackageProductDependency section */ 476 | }; 477 | rootObject = 226A67802B32016F0060DF1A /* Project object */; 478 | } 479 | -------------------------------------------------------------------------------- /ios-pinning-demo.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios-pinning-demo.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios-pinning-demo.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved: -------------------------------------------------------------------------------- 1 | { 2 | "pins" : [ 3 | { 4 | "identity" : "afnetworking", 5 | "kind" : "remoteSourceControl", 6 | "location" : "https://github.com/AFNetworking/AFNetworking.git", 7 | "state" : { 8 | "revision" : "ffae2391ab0c29dc88eb0a58d2f5b2c2c27cadbf", 9 | "version" : "4.0.1" 10 | } 11 | }, 12 | { 13 | "identity" : "alamofire", 14 | "kind" : "remoteSourceControl", 15 | "location" : "https://github.com/Alamofire/Alamofire.git", 16 | "state" : { 17 | "revision" : "3dc6a42c7727c49bf26508e29b0a0b35f9c7e1ad", 18 | "version" : "5.8.1" 19 | } 20 | }, 21 | { 22 | "identity" : "trustkit", 23 | "kind" : "remoteSourceControl", 24 | "location" : "https://github.com/datatheorem/TrustKit", 25 | "state" : { 26 | "revision" : "5718ba8720b4dafc5360aebfbc9762e2d5f2615e", 27 | "version" : "3.0.3" 28 | } 29 | } 30 | ], 31 | "version" : 2 32 | } 33 | -------------------------------------------------------------------------------- /ios-pinning-demo.xcodeproj/xcuserdata/tim.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | ios-pinning-demo.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 0 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /ios-pinning-demo/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /ios-pinning-demo/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "platform" : "ios", 6 | "size" : "1024x1024" 7 | } 8 | ], 9 | "info" : { 10 | "author" : "xcode", 11 | "version" : 1 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios-pinning-demo/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios-pinning-demo/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSExceptionDomains 8 | 9 | amiusing.httptoolkit.tech 10 | 11 | NSExceptionAllowsInsecureHTTPLoads 12 | 13 | 14 | 15 | NSPinnedDomains 16 | 17 | sha256.badssl.com 18 | 19 | NSIncludesSubdomains 20 | 21 | NSPinnedCAIdentities 22 | 23 | 24 | SPKI-SHA256-BASE64 25 | C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M= 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /ios-pinning-demo/Model/Certificates.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | struct BundledCertificates { 4 | 5 | static let isrgRootCert: SecCertificate = BundledCertificates.loadCertificate(filename: "isrg-root") 6 | 7 | private static func loadCertificate(filename: String) -> SecCertificate { 8 | let filePath = Bundle.main.path(forResource: filename, ofType: "der")! 9 | let data = try! Data(contentsOf: URL(fileURLWithPath: filePath)) 10 | let certificate = SecCertificateCreateWithData(nil, data as CFData)! 11 | return certificate 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /ios-pinning-demo/Model/HTTPRequestModels.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Alamofire 3 | import TrustKit 4 | import AFNetworking 5 | 6 | class BaseHTTPRequest: Identifiable, ObservableObject { 7 | 8 | let id = UUID() 9 | let name: String 10 | let url: String 11 | 12 | @Published var isLoading = false 13 | @Published var status: RequestStatus = .none 14 | 15 | init(name: String, url: String) { 16 | self.name = name 17 | self.url = url 18 | } 19 | 20 | func run() async { 21 | URLCache.shared.removeAllCachedResponses() 22 | 23 | DispatchQueue.main.async { 24 | self.isLoading = true 25 | self.status = .none 26 | } 27 | 28 | do { 29 | let status = try await performRequest() 30 | 31 | if (status != 200) { 32 | throw URLError(.badServerResponse) 33 | } 34 | 35 | DispatchQueue.main.async { 36 | self.status = .success 37 | self.isLoading = false 38 | } 39 | } catch { 40 | print("\(name) failed with: \(error)") 41 | 42 | DispatchQueue.main.async { 43 | self.isLoading = false 44 | self.status = .failure 45 | } 46 | } 47 | } 48 | 49 | func isAvailable() -> Bool { 50 | return true 51 | } 52 | 53 | func performRequest() async throws -> Int { 54 | preconditionFailure("performRequest must be overloaded for each case") 55 | } 56 | } 57 | 58 | class SimpleHTTPRequest: BaseHTTPRequest { 59 | override func performRequest() async throws -> Int { 60 | let url = URL(string: url)! 61 | 62 | var urlRequest = URLRequest(url: url) 63 | urlRequest.timeoutInterval = 10 64 | urlRequest.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringLocalAndRemoteCacheData 65 | 66 | let session = buildSession() 67 | 68 | let (_, response) = try await session.data(for: urlRequest) 69 | return (response as! HTTPURLResponse).statusCode 70 | } 71 | 72 | func buildSession() -> URLSession { 73 | return URLSession(configuration: .default) 74 | } 75 | } 76 | 77 | class URLSessionPinnedRequest: SimpleHTTPRequest { 78 | 79 | let pinnedCertificate: String 80 | 81 | init(name: String, url: String, pinnedCertificate: String) { 82 | self.pinnedCertificate = pinnedCertificate 83 | super.init(name: name, url: url) 84 | } 85 | 86 | override func isAvailable() -> Bool { 87 | if #available(iOS 15.0, *) { 88 | return true 89 | } else { 90 | return false 91 | } 92 | } 93 | 94 | override func buildSession() -> URLSession { 95 | if #available(iOS 15.0, *) { 96 | let delegate = PinningURLSessionDelegate(pinnedCertificate: pinnedCertificate) 97 | return URLSession( 98 | configuration: .default, 99 | delegate: delegate, 100 | delegateQueue: nil 101 | ) 102 | } else { 103 | fatalError("URLSessionPinnedRequest is not available before iOS 15") 104 | } 105 | } 106 | 107 | } 108 | 109 | class AlamofireBaseHTTPRequest: BaseHTTPRequest { 110 | 111 | let evaluators: [String: ServerTrustEvaluating] 112 | 113 | init(name: String, url: String, evaluators: [String: ServerTrustEvaluating]) { 114 | self.evaluators = evaluators 115 | super.init(name: name, url: url) 116 | } 117 | 118 | override func performRequest() async throws -> Int { 119 | // Disable all caching: 120 | let configuration = URLSessionConfiguration.af.default 121 | configuration.urlCache = nil 122 | 123 | let session = Session( 124 | configuration: configuration, 125 | serverTrustManager: ServerTrustManager( 126 | allHostsMustBeEvaluated: !self.evaluators.isEmpty, 127 | evaluators: self.evaluators 128 | ) 129 | ) 130 | 131 | return try await withCheckedThrowingContinuation { continuation in 132 | session.request(self.url).response { response in 133 | switch response.result { 134 | case .success: 135 | continuation.resume(returning: response.response!.statusCode) 136 | case .failure(let error): 137 | continuation.resume(throwing: error) 138 | } 139 | } 140 | } 141 | } 142 | } 143 | 144 | class AlamofireSimpleHTTPRequest: AlamofireBaseHTTPRequest { 145 | 146 | init(name: String, url: String) { 147 | super.init(name: name, url: url, evaluators: [:]) 148 | } 149 | 150 | } 151 | 152 | class AlamofirePinnedCertHTTPRequest: AlamofireBaseHTTPRequest { 153 | 154 | init(name: String, url: String, pinnedCertificate: SecCertificate) { 155 | let evaluators = [URL(string: url)!.host!: PinnedCertificatesTrustEvaluator( 156 | certificates: [pinnedCertificate], 157 | acceptSelfSignedCertificates: false, 158 | performDefaultValidation: true, 159 | validateHost: true 160 | )] 161 | 162 | super.init(name: name, url: url, evaluators: evaluators) 163 | } 164 | 165 | } 166 | 167 | class AlamofirePinnedPKHTTPRequest: AlamofireBaseHTTPRequest { 168 | 169 | init(name: String, url: String, pinnedKey: SecKey) { 170 | let evaluators = [URL(string: url)!.host!: PublicKeysTrustEvaluator( 171 | keys: [pinnedKey], 172 | performDefaultValidation: true, 173 | validateHost: true 174 | )] 175 | 176 | super.init(name: name, url: url, evaluators: evaluators) 177 | } 178 | 179 | } 180 | 181 | var trustKitInitialized = false 182 | 183 | class TrustKitPinnedHTTPRequest: SimpleHTTPRequest { 184 | 185 | init(name: String) { 186 | super.init(name: name, url: "https://ecc384.badssl.com") 187 | } 188 | 189 | override func buildSession() -> URLSession { 190 | // Initialize when first clicked: 191 | if (!trustKitInitialized) { 192 | TrustKit.initSharedInstance(withConfiguration: [ 193 | kTSKSwizzleNetworkDelegates: false, 194 | kTSKEnforcePinning: true, 195 | kTSKPinnedDomains: [ 196 | "ecc384.badssl.com": [ 197 | kTSKPublicKeyHashes: [ 198 | "C5+lpZ7tcVwmwQIMcRtPbsQtWLABXhQzejna0wHFr8M=", 199 | // A backup pin is required, so we add a dud: 200 | "ABCABCABCABCABCABCABCABCABCABCABCABCABCABCA=" 201 | ] 202 | ] 203 | ] 204 | ]) 205 | trustKitInitialized = true 206 | } 207 | 208 | return URLSession( 209 | configuration: .default, 210 | delegate: TrustKitURLSessionDelegate(), 211 | delegateQueue: nil 212 | ) 213 | } 214 | 215 | } 216 | 217 | class AFNetworkingSimpleHTTPRequest: BaseHTTPRequest { 218 | 219 | override func performRequest() async throws -> Int { 220 | let manager = buildManager() 221 | 222 | return try await withCheckedThrowingContinuation { continuation in 223 | manager.get("/", parameters: nil, headers: nil, progress: nil, success: { (task, responseObject) in 224 | let httpResponse = task.response as! HTTPURLResponse 225 | continuation.resume(returning: httpResponse.statusCode) 226 | }, failure: { (task, error) in 227 | continuation.resume(throwing: error) 228 | }) 229 | } 230 | } 231 | 232 | func buildManager() -> AFHTTPSessionManager { 233 | let manager = AFHTTPSessionManager( 234 | baseURL: URL(string: self.url) 235 | ) 236 | manager.responseSerializer = AFHTTPResponseSerializer() 237 | return manager 238 | } 239 | 240 | } 241 | 242 | class AFNetworkingPinnedHTTPRequest: AFNetworkingSimpleHTTPRequest { 243 | 244 | let pinnedCertificate: SecCertificate 245 | 246 | init(name: String, url: String, pinnedCertificate: SecCertificate) { 247 | self.pinnedCertificate = pinnedCertificate 248 | super.init(name: name, url: url) 249 | } 250 | 251 | override func buildManager() -> AFHTTPSessionManager { 252 | let manager = super.buildManager() 253 | 254 | let securityPolicy = AFSecurityPolicy(pinningMode: .certificate) 255 | securityPolicy.pinnedCertificates = Set( 256 | [SecCertificateCopyData(self.pinnedCertificate)] as! [Data] 257 | ) 258 | securityPolicy.validatesDomainName = true 259 | manager.securityPolicy = securityPolicy 260 | 261 | return manager 262 | } 263 | 264 | } 265 | -------------------------------------------------------------------------------- /ios-pinning-demo/Model/PinningURLSessionDelegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import CryptoKit 3 | 4 | @available(iOS 15.0, *) 5 | class PinningURLSessionDelegate: NSObject, URLSessionDelegate { 6 | var pinnedCertificate: String 7 | 8 | init(pinnedCertificate: String) { 9 | self.pinnedCertificate = pinnedCertificate 10 | } 11 | 12 | func urlSession( 13 | _ session: URLSession, 14 | didReceive challenge: URLAuthenticationChallenge, 15 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential? 16 | ) -> Void) { 17 | guard let serverTrust = challenge.protectionSpace.serverTrust else { 18 | completionHandler(.cancelAuthenticationChallenge, nil) 19 | return 20 | } 21 | 22 | if SecTrustEvaluateWithError(serverTrust, nil) { 23 | if let certificateChain = SecTrustCopyCertificateChain(serverTrust) as? [SecCertificate] { 24 | for serverCertificate in certificateChain { 25 | guard let publicKey = SecCertificateCopyKey(serverCertificate) else { 26 | print("Error reading public key from certificate") 27 | continue 28 | } 29 | 30 | var error: Unmanaged? 31 | guard let publicKeyData = SecKeyCopyExternalRepresentation(publicKey, &error) as Data? else { 32 | print("Error retrieving public key data: \(error!.takeRetainedValue() as Error)") 33 | return 34 | } 35 | 36 | let publicKeyHash = SHA256.hash(data: publicKeyData) 37 | let publicKeyHashBase64 = Data(publicKeyHash).base64EncodedString() 38 | 39 | if publicKeyHashBase64 == pinnedCertificate { 40 | completionHandler(.useCredential, URLCredential(trust: serverTrust)) 41 | return 42 | } 43 | } 44 | } 45 | } 46 | 47 | completionHandler(.cancelAuthenticationChallenge, nil) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /ios-pinning-demo/Model/RequestViewModel.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | 3 | class RequestViewModel: ObservableObject { 4 | @Published var unpinnedRequests: [BaseHTTPRequest] = [ 5 | // We use amiusing.httptoolkit.tech for unpinned requests: 6 | SimpleHTTPRequest(name: "Plain HTTP", url: "http://amiusing.httptoolkit.tech"), 7 | SimpleHTTPRequest(name: "HTTPS", url: "https://amiusing.httptoolkit.tech"), 8 | AlamofireSimpleHTTPRequest(name: "Alamofire HTTPS", url: "https://amiusing.httptoolkit.tech"), 9 | AFNetworkingSimpleHTTPRequest(name: "AFNetworking HTTPS", url: "https://amiusing.httptoolkit.tech") 10 | ] 11 | 12 | @Published var pinnedRequests: [BaseHTTPRequest] = [ 13 | // We use sha256.badssl.com for config-pinned (in Info.plist NSPinnedDomains) requests: 14 | SimpleHTTPRequest(name: "Config-based pinning", url: "https://sha256.badssl.com"), 15 | 16 | // We use ecc384.badssl.com for all manually-pinned requests: 17 | URLSessionPinnedRequest( 18 | name: "URLSession pinning", 19 | url: "https://ecc384.badssl.com", 20 | // Pinned on hash of raw PK - fiddly to format to match pins elsewhere: 21 | pinnedCertificate: "9Fk6HgfMnM7/vtnBHcUhg1b3gU2bIpSd50XmKZkMbGA=" 22 | ), 23 | 24 | AlamofirePinnedCertHTTPRequest( 25 | name: "Alamofire cert pinning", 26 | url: "https://ecc384.badssl.com", 27 | pinnedCertificate: BundledCertificates.isrgRootCert 28 | ), 29 | 30 | AlamofirePinnedPKHTTPRequest( 31 | name: "Alamofire PK pinning", 32 | url: "https://ecc384.badssl.com", 33 | pinnedKey: SecCertificateCopyKey(BundledCertificates.isrgRootCert)! 34 | ), 35 | 36 | AFNetworkingPinnedHTTPRequest( 37 | name: "AFNetworking cert pinning", 38 | url: "https://ecc384.badssl.com", 39 | pinnedCertificate: BundledCertificates.isrgRootCert 40 | ), 41 | 42 | TrustKitPinnedHTTPRequest( 43 | name: "TrustKit pinning" 44 | // TrustKit uses global configuration, configured to pin ecc384.badssl.com 45 | ) 46 | ] 47 | 48 | func sendRequest(_ httpRequest: BaseHTTPRequest) { 49 | Task { 50 | await httpRequest.run() 51 | } 52 | } 53 | } 54 | 55 | enum RequestStatus { 56 | case none, success, failure 57 | } 58 | -------------------------------------------------------------------------------- /ios-pinning-demo/Model/TrustKitURLSessionDelegate.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import TrustKit 3 | 4 | class TrustKitURLSessionDelegate: NSObject, URLSessionDelegate { 5 | 6 | func urlSession( 7 | _ session: URLSession, 8 | didReceive challenge: URLAuthenticationChallenge, 9 | completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void 10 | ) { 11 | let pinningValidator = TrustKit.sharedInstance().pinningValidator 12 | if pinningValidator.handle(challenge, completionHandler: completionHandler) { 13 | // Challenge handled by TrustKit 14 | return 15 | } 16 | 17 | // Not handled - we fail in this case, this delegate should only be used with TrustKit 18 | completionHandler(.cancelAuthenticationChallenge, nil) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /ios-pinning-demo/Preview Content/Preview Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /ios-pinning-demo/View/ContentView.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | struct ContentView: View { 4 | 5 | @StateObject var viewModel = RequestViewModel() 6 | 7 | let SPACING = 15.0 8 | 9 | var body: some View { 10 | VStack { 11 | Text("SSL Pinning Demo") 12 | .font(.largeTitle) 13 | .padding(.top) 14 | 15 | GeometryReader { geometry in 16 | ScrollView(.vertical) { 17 | VStack(spacing: SPACING) { 18 | ForEach(viewModel.unpinnedRequests) { request in 19 | RequestButtonView(request: request, viewModel: viewModel) 20 | } 21 | 22 | Divider() 23 | .background(Color.gray) 24 | .padding(.horizontal) 25 | 26 | ForEach(viewModel.pinnedRequests) { request in 27 | RequestButtonView(request: request, viewModel: viewModel) 28 | } 29 | } 30 | .frame( 31 | minWidth: geometry.size.width - SPACING*2, 32 | minHeight: geometry.size.height - SPACING*2, 33 | alignment: .center 34 | ) 35 | .padding(.horizontal) 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | struct RequestButtonView: View { 43 | 44 | @ObservedObject var request: BaseHTTPRequest 45 | var viewModel: RequestViewModel 46 | 47 | var body: some View { 48 | Button(action: { 49 | if (request.isAvailable()) { 50 | viewModel.sendRequest(request) 51 | } 52 | }) { 53 | HStack { 54 | Spacer().frame(width: 16) 55 | 56 | if request.status == .success { 57 | Image(systemName: "checkmark.circle") 58 | } else if request.status == .failure { 59 | Image(systemName: "xmark.circle") 60 | } else { 61 | // Invisible placeholder to maintain alignment 62 | Image(systemName: "circle").opacity(0) 63 | } 64 | 65 | Spacer() 66 | 67 | if request.isLoading { 68 | ProgressView() 69 | } else if !request.isAvailable() { 70 | VStack { 71 | Text(request.name) 72 | Text("(Not available)") 73 | } 74 | } else { 75 | Text(request.name) 76 | } 77 | 78 | Spacer() 79 | 80 | // Matching placeholder, so we end up centered 81 | Image(systemName: "circle").opacity(0) 82 | Spacer().frame(width: 16) 83 | } 84 | .frame(maxWidth: .infinity, minHeight: 44) 85 | .disabled(!request.isAvailable()) 86 | } 87 | .background( 88 | request.isAvailable() == false 89 | ? Color.gray 90 | : request.status == .none 91 | ? Color.purple 92 | : request.status == .success 93 | ? Color.green 94 | // Failure: 95 | : Color.red 96 | ) 97 | .foregroundColor(.white) 98 | .cornerRadius(8) 99 | } 100 | } 101 | 102 | 103 | struct ContentView_Previews: PreviewProvider { 104 | static var previews: some View { 105 | ContentView() 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /ios-pinning-demo/View/PinningApp.swift: -------------------------------------------------------------------------------- 1 | import SwiftUI 2 | 3 | @main 4 | struct PinningApp: App { 5 | var body: some Scene { 6 | WindowGroup { 7 | ContentView() 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /ios-pinning-demo/isrg-root.der: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httptoolkit/ios-ssl-pinning-demo/4ee845482b202946627009df5d795fa0add21a8a/ios-pinning-demo/isrg-root.der -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/httptoolkit/ios-ssl-pinning-demo/4ee845482b202946627009df5d795fa0add21a8a/screenshot.png --------------------------------------------------------------------------------