├── .gitignore ├── .travis.yml ├── GoldenKey.podspec ├── GoldenKey.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcshareddata │ └── xcschemes │ ├── GoldenKey.xcscheme │ └── GoldenKeyTests.xcscheme ├── GoldenKey ├── CommonCrypto │ ├── Cryptor.swift │ ├── CryptorAlgorithm.swift │ ├── CryptorError.swift │ ├── CryptorOptions.swift │ ├── Digest.swift │ ├── Digest.swift.gyb │ ├── HMAC.swift │ ├── PBKDF2.swift │ ├── RandomBytes.swift │ └── SecurityError.swift └── Info.plist ├── GoldenKeyTests ├── CommonCrypto │ ├── CryptorTests.swift │ ├── DigestTests.swift │ ├── GoldenKeyTests.swift │ ├── HMACTests.swift │ ├── PBKDF2Tests.swift │ └── RandomBytesTests.swift └── Info.plist ├── LICENSE ├── Package.swift ├── README.md ├── gyb ├── gyb ├── gyb.py └── gyb.pyc ├── instruments_screenshot.png └── logo.png /.gitignore: -------------------------------------------------------------------------------- 1 | # Xcode 2 | # 3 | # gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore 4 | 5 | ## Build generated 6 | build/ 7 | DerivedData/ 8 | 9 | ## Various settings 10 | *.pbxuser 11 | !default.pbxuser 12 | *.mode1v3 13 | !default.mode1v3 14 | *.mode2v3 15 | !default.mode2v3 16 | *.perspectivev3 17 | !default.perspectivev3 18 | xcuserdata/ 19 | 20 | ## Other 21 | *.moved-aside 22 | *.xccheckout 23 | *.xcscmblueprint 24 | 25 | ## Obj-C/Swift specific 26 | *.hmap 27 | *.ipa 28 | *.dSYM.zip 29 | *.dSYM 30 | 31 | ## Playgrounds 32 | timeline.xctimeline 33 | playground.xcworkspace 34 | 35 | # Swift Package Manager 36 | # 37 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 38 | # Packages/ 39 | # Package.pins 40 | # Package.resolved 41 | .build/ 42 | 43 | # CocoaPods 44 | # 45 | # We recommend against adding the Pods directory to your .gitignore. However 46 | # you should judge for yourself, the pros and cons are mentioned at: 47 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 48 | # 49 | # Pods/ 50 | 51 | # Carthage 52 | # 53 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 54 | # Carthage/Checkouts 55 | 56 | Carthage/Build 57 | 58 | # fastlane 59 | # 60 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 61 | # screenshots whenever they are needed. 62 | # For more information about the recommended setup visit: 63 | # https://docs.fastlane.tools/best-practices/source-control/#source-control 64 | 65 | fastlane/report.xml 66 | fastlane/Preview.html 67 | fastlane/screenshots/**/*.png 68 | fastlane/test_output 69 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: objective-c 2 | osx_image: xcode10.2 3 | xcode_project: GoldenKey.xcodeproj 4 | xcode_scheme: GoldenKey 5 | xcode_sdk: iphonesimulator12.2 6 | xcode_destination: platform=iOS Simulator,OS=12.2,name=iPhone 8 -------------------------------------------------------------------------------- /GoldenKey.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | s.name = "GoldenKey" 3 | s.version = "1.0.0" 4 | s.summary = "CommonCrypto and Security wrapper for iOS" 5 | 6 | s.description = <<-DESC 7 | Swift wrapper around CommonCrypto and Security frameworks. 8 | DESC 9 | 10 | s.homepage = "https://github.com/RedMadRobot/golden-key" 11 | s.license = { :type => "MIT"} 12 | s.author = { "Alexander Ignatiev" => "ai@redmadrobot.com", "Anton Glezman" => "a.glezman@redmadrobot.com" } 13 | s.source = { :git => "https://github.com/RedMadRobot/golden-key.git", :tag => "#{s.version}" } 14 | 15 | s.ios.deployment_target = "10.0" 16 | s.tvos.deployment_target = "10.0" 17 | s.osx.deployment_target = "10.10" 18 | s.watchos.deployment_target = "4.0" 19 | 20 | s.swift_version = "5.0" 21 | s.source_files = "GoldenKey/**/*.swift" 22 | end 23 | -------------------------------------------------------------------------------- /GoldenKey.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 0113AB71233A3A770060801C /* DigestTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0113AB70233A3A770060801C /* DigestTests.swift */; }; 11 | 01EAFF8C21E8C31100089590 /* GoldenKey.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 01EAFF8221E8C31100089590 /* GoldenKey.framework */; }; 12 | 01EAFF9121E8C31100089590 /* GoldenKeyTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFF9021E8C31100089590 /* GoldenKeyTests.swift */; }; 13 | 01EAFF9D21E8C39800089590 /* SecurityError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFF9C21E8C39800089590 /* SecurityError.swift */; }; 14 | 01EAFF9F21E8C3AB00089590 /* CryptorError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFF9E21E8C3AB00089590 /* CryptorError.swift */; }; 15 | 01EAFFA121E8C3DC00089590 /* CryptorAlgorithm.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFA021E8C3DC00089590 /* CryptorAlgorithm.swift */; }; 16 | 01EAFFA321E8C3FB00089590 /* Cryptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFA221E8C3FB00089590 /* Cryptor.swift */; }; 17 | 01EAFFA521E8C41000089590 /* PBKDF2.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFA421E8C41000089590 /* PBKDF2.swift */; }; 18 | 01EAFFA721E8C44200089590 /* HMAC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFA621E8C44200089590 /* HMAC.swift */; }; 19 | 01EAFFA921E8C46B00089590 /* Digest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFA821E8C46B00089590 /* Digest.swift */; }; 20 | 01EAFFB021E8C5CF00089590 /* HMACTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFAF21E8C5CF00089590 /* HMACTests.swift */; }; 21 | 01EAFFB221E8C5FE00089590 /* PBKDF2Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFB121E8C5FE00089590 /* PBKDF2Tests.swift */; }; 22 | 01EAFFB421E8C62600089590 /* CryptorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 01EAFFB321E8C62600089590 /* CryptorTests.swift */; }; 23 | 0226FC7D2276D8F5008E60F3 /* CryptorOptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0226FC7C2276D8F5008E60F3 /* CryptorOptions.swift */; }; 24 | 02573DF72268A078004BD894 /* RandomBytes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02573DF62268A078004BD894 /* RandomBytes.swift */; }; 25 | 02573DF92268A491004BD894 /* RandomBytesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 02573DF82268A491004BD894 /* RandomBytesTests.swift */; }; 26 | /* End PBXBuildFile section */ 27 | 28 | /* Begin PBXContainerItemProxy section */ 29 | 01EAFF8D21E8C31100089590 /* PBXContainerItemProxy */ = { 30 | isa = PBXContainerItemProxy; 31 | containerPortal = 01EAFF7921E8C31100089590 /* Project object */; 32 | proxyType = 1; 33 | remoteGlobalIDString = 01EAFF8121E8C31100089590; 34 | remoteInfo = GoldenKey; 35 | }; 36 | /* End PBXContainerItemProxy section */ 37 | 38 | /* Begin PBXFileReference section */ 39 | 0113AB70233A3A770060801C /* DigestTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DigestTests.swift; sourceTree = ""; }; 40 | 01C1E9F6229BF1A0008C65E4 /* GoldenKey.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = GoldenKey.podspec; sourceTree = ""; }; 41 | 01EAFF8221E8C31100089590 /* GoldenKey.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = GoldenKey.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 42 | 01EAFF8621E8C31100089590 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 43 | 01EAFF8B21E8C31100089590 /* GoldenKeyTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = GoldenKeyTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 44 | 01EAFF9021E8C31100089590 /* GoldenKeyTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoldenKeyTests.swift; sourceTree = ""; }; 45 | 01EAFF9221E8C31100089590 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 46 | 01EAFF9C21E8C39800089590 /* SecurityError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecurityError.swift; sourceTree = ""; }; 47 | 01EAFF9E21E8C3AB00089590 /* CryptorError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorError.swift; sourceTree = ""; }; 48 | 01EAFFA021E8C3DC00089590 /* CryptorAlgorithm.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorAlgorithm.swift; sourceTree = ""; }; 49 | 01EAFFA221E8C3FB00089590 /* Cryptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Cryptor.swift; sourceTree = ""; }; 50 | 01EAFFA421E8C41000089590 /* PBKDF2.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PBKDF2.swift; sourceTree = ""; }; 51 | 01EAFFA621E8C44200089590 /* HMAC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMAC.swift; sourceTree = ""; }; 52 | 01EAFFA821E8C46B00089590 /* Digest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Digest.swift; sourceTree = ""; }; 53 | 01EAFFAA21E8C4AD00089590 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; 54 | 01EAFFAC21E8C57800089590 /* Digest.swift.gyb */ = {isa = PBXFileReference; lastKnownFileType = text; path = Digest.swift.gyb; sourceTree = ""; }; 55 | 01EAFFAF21E8C5CF00089590 /* HMACTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HMACTests.swift; sourceTree = ""; }; 56 | 01EAFFB121E8C5FE00089590 /* PBKDF2Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PBKDF2Tests.swift; sourceTree = ""; }; 57 | 01EAFFB321E8C62600089590 /* CryptorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorTests.swift; sourceTree = ""; }; 58 | 0226FC7C2276D8F5008E60F3 /* CryptorOptions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptorOptions.swift; sourceTree = ""; }; 59 | 02573DF62268A078004BD894 /* RandomBytes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomBytes.swift; sourceTree = ""; }; 60 | 02573DF82268A491004BD894 /* RandomBytesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RandomBytesTests.swift; sourceTree = ""; }; 61 | 02900A7B22A5106400FFD3A9 /* .travis.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .travis.yml; sourceTree = ""; }; 62 | /* End PBXFileReference section */ 63 | 64 | /* Begin PBXFrameworksBuildPhase section */ 65 | 01EAFF7F21E8C31100089590 /* Frameworks */ = { 66 | isa = PBXFrameworksBuildPhase; 67 | buildActionMask = 2147483647; 68 | files = ( 69 | ); 70 | runOnlyForDeploymentPostprocessing = 0; 71 | }; 72 | 01EAFF8821E8C31100089590 /* Frameworks */ = { 73 | isa = PBXFrameworksBuildPhase; 74 | buildActionMask = 2147483647; 75 | files = ( 76 | 01EAFF8C21E8C31100089590 /* GoldenKey.framework in Frameworks */, 77 | ); 78 | runOnlyForDeploymentPostprocessing = 0; 79 | }; 80 | /* End PBXFrameworksBuildPhase section */ 81 | 82 | /* Begin PBXGroup section */ 83 | 01C1E9F7229C1EF1008C65E4 /* CommonCrypto */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 01EAFFA221E8C3FB00089590 /* Cryptor.swift */, 87 | 0226FC7C2276D8F5008E60F3 /* CryptorOptions.swift */, 88 | 01EAFFA021E8C3DC00089590 /* CryptorAlgorithm.swift */, 89 | 01EAFF9E21E8C3AB00089590 /* CryptorError.swift */, 90 | 01EAFFA821E8C46B00089590 /* Digest.swift */, 91 | 01EAFFAC21E8C57800089590 /* Digest.swift.gyb */, 92 | 01EAFFA621E8C44200089590 /* HMAC.swift */, 93 | 01EAFFA421E8C41000089590 /* PBKDF2.swift */, 94 | 02573DF62268A078004BD894 /* RandomBytes.swift */, 95 | 01EAFF9C21E8C39800089590 /* SecurityError.swift */, 96 | ); 97 | path = CommonCrypto; 98 | sourceTree = ""; 99 | }; 100 | 01C1E9F8229C1F03008C65E4 /* CommonCrypto */ = { 101 | isa = PBXGroup; 102 | children = ( 103 | 01EAFF9021E8C31100089590 /* GoldenKeyTests.swift */, 104 | 0113AB70233A3A770060801C /* DigestTests.swift */, 105 | 01EAFFAF21E8C5CF00089590 /* HMACTests.swift */, 106 | 01EAFFB121E8C5FE00089590 /* PBKDF2Tests.swift */, 107 | 01EAFFB321E8C62600089590 /* CryptorTests.swift */, 108 | 02573DF82268A491004BD894 /* RandomBytesTests.swift */, 109 | ); 110 | path = CommonCrypto; 111 | sourceTree = ""; 112 | }; 113 | 01EAFF7821E8C31100089590 = { 114 | isa = PBXGroup; 115 | children = ( 116 | 01EAFFAA21E8C4AD00089590 /* README.md */, 117 | 01C1E9F6229BF1A0008C65E4 /* GoldenKey.podspec */, 118 | 02900A7B22A5106400FFD3A9 /* .travis.yml */, 119 | 01EAFF8421E8C31100089590 /* GoldenKey */, 120 | 01EAFF8F21E8C31100089590 /* GoldenKeyTests */, 121 | 01EAFF8321E8C31100089590 /* Products */, 122 | ); 123 | sourceTree = ""; 124 | }; 125 | 01EAFF8321E8C31100089590 /* Products */ = { 126 | isa = PBXGroup; 127 | children = ( 128 | 01EAFF8221E8C31100089590 /* GoldenKey.framework */, 129 | 01EAFF8B21E8C31100089590 /* GoldenKeyTests.xctest */, 130 | ); 131 | name = Products; 132 | sourceTree = ""; 133 | }; 134 | 01EAFF8421E8C31100089590 /* GoldenKey */ = { 135 | isa = PBXGroup; 136 | children = ( 137 | 01EAFF8621E8C31100089590 /* Info.plist */, 138 | 01C1E9F7229C1EF1008C65E4 /* CommonCrypto */, 139 | ); 140 | path = GoldenKey; 141 | sourceTree = ""; 142 | }; 143 | 01EAFF8F21E8C31100089590 /* GoldenKeyTests */ = { 144 | isa = PBXGroup; 145 | children = ( 146 | 01C1E9F8229C1F03008C65E4 /* CommonCrypto */, 147 | 01EAFF9221E8C31100089590 /* Info.plist */, 148 | ); 149 | path = GoldenKeyTests; 150 | sourceTree = ""; 151 | }; 152 | /* End PBXGroup section */ 153 | 154 | /* Begin PBXHeadersBuildPhase section */ 155 | 01EAFF7D21E8C31100089590 /* Headers */ = { 156 | isa = PBXHeadersBuildPhase; 157 | buildActionMask = 2147483647; 158 | files = ( 159 | ); 160 | runOnlyForDeploymentPostprocessing = 0; 161 | }; 162 | /* End PBXHeadersBuildPhase section */ 163 | 164 | /* Begin PBXNativeTarget section */ 165 | 01EAFF8121E8C31100089590 /* GoldenKey */ = { 166 | isa = PBXNativeTarget; 167 | buildConfigurationList = 01EAFF9621E8C31100089590 /* Build configuration list for PBXNativeTarget "GoldenKey" */; 168 | buildPhases = ( 169 | 01EAFF7D21E8C31100089590 /* Headers */, 170 | 01EAFFAB21E8C52E00089590 /* Swift GYB */, 171 | 01EAFF7E21E8C31100089590 /* Sources */, 172 | 01EAFF7F21E8C31100089590 /* Frameworks */, 173 | 01EAFF8021E8C31100089590 /* Resources */, 174 | ); 175 | buildRules = ( 176 | ); 177 | dependencies = ( 178 | ); 179 | name = GoldenKey; 180 | productName = GoldenKey; 181 | productReference = 01EAFF8221E8C31100089590 /* GoldenKey.framework */; 182 | productType = "com.apple.product-type.framework"; 183 | }; 184 | 01EAFF8A21E8C31100089590 /* GoldenKeyTests */ = { 185 | isa = PBXNativeTarget; 186 | buildConfigurationList = 01EAFF9921E8C31100089590 /* Build configuration list for PBXNativeTarget "GoldenKeyTests" */; 187 | buildPhases = ( 188 | 01EAFF8721E8C31100089590 /* Sources */, 189 | 01EAFF8821E8C31100089590 /* Frameworks */, 190 | 01EAFF8921E8C31100089590 /* Resources */, 191 | ); 192 | buildRules = ( 193 | ); 194 | dependencies = ( 195 | 01EAFF8E21E8C31100089590 /* PBXTargetDependency */, 196 | ); 197 | name = GoldenKeyTests; 198 | productName = GoldenKeyTests; 199 | productReference = 01EAFF8B21E8C31100089590 /* GoldenKeyTests.xctest */; 200 | productType = "com.apple.product-type.bundle.unit-test"; 201 | }; 202 | /* End PBXNativeTarget section */ 203 | 204 | /* Begin PBXProject section */ 205 | 01EAFF7921E8C31100089590 /* Project object */ = { 206 | isa = PBXProject; 207 | attributes = { 208 | LastSwiftUpdateCheck = 1010; 209 | LastUpgradeCheck = 1010; 210 | ORGANIZATIONNAME = RedMadRobot; 211 | TargetAttributes = { 212 | 01EAFF8121E8C31100089590 = { 213 | CreatedOnToolsVersion = 10.1; 214 | LastSwiftMigration = 1010; 215 | }; 216 | 01EAFF8A21E8C31100089590 = { 217 | CreatedOnToolsVersion = 10.1; 218 | LastSwiftMigration = 1020; 219 | }; 220 | }; 221 | }; 222 | buildConfigurationList = 01EAFF7C21E8C31100089590 /* Build configuration list for PBXProject "GoldenKey" */; 223 | compatibilityVersion = "Xcode 9.3"; 224 | developmentRegion = en; 225 | hasScannedForEncodings = 0; 226 | knownRegions = ( 227 | en, 228 | Base, 229 | ); 230 | mainGroup = 01EAFF7821E8C31100089590; 231 | productRefGroup = 01EAFF8321E8C31100089590 /* Products */; 232 | projectDirPath = ""; 233 | projectRoot = ""; 234 | targets = ( 235 | 01EAFF8121E8C31100089590 /* GoldenKey */, 236 | 01EAFF8A21E8C31100089590 /* GoldenKeyTests */, 237 | ); 238 | }; 239 | /* End PBXProject section */ 240 | 241 | /* Begin PBXResourcesBuildPhase section */ 242 | 01EAFF8021E8C31100089590 /* Resources */ = { 243 | isa = PBXResourcesBuildPhase; 244 | buildActionMask = 2147483647; 245 | files = ( 246 | ); 247 | runOnlyForDeploymentPostprocessing = 0; 248 | }; 249 | 01EAFF8921E8C31100089590 /* Resources */ = { 250 | isa = PBXResourcesBuildPhase; 251 | buildActionMask = 2147483647; 252 | files = ( 253 | ); 254 | runOnlyForDeploymentPostprocessing = 0; 255 | }; 256 | /* End PBXResourcesBuildPhase section */ 257 | 258 | /* Begin PBXShellScriptBuildPhase section */ 259 | 01EAFFAB21E8C52E00089590 /* Swift GYB */ = { 260 | isa = PBXShellScriptBuildPhase; 261 | buildActionMask = 2147483647; 262 | files = ( 263 | ); 264 | inputFileListPaths = ( 265 | ); 266 | inputPaths = ( 267 | ); 268 | name = "Swift GYB"; 269 | outputFileListPaths = ( 270 | ); 271 | outputPaths = ( 272 | ); 273 | runOnlyForDeploymentPostprocessing = 0; 274 | shellPath = /bin/sh; 275 | shellScript = "find . -name '*.gyb' | \\\n while read file; do \\\n ./gyb/gyb --line-directive '' -o \"${file%.gyb}\" \"$file\"; \\\n done\n"; 276 | }; 277 | /* End PBXShellScriptBuildPhase section */ 278 | 279 | /* Begin PBXSourcesBuildPhase section */ 280 | 01EAFF7E21E8C31100089590 /* Sources */ = { 281 | isa = PBXSourcesBuildPhase; 282 | buildActionMask = 2147483647; 283 | files = ( 284 | 01EAFFA121E8C3DC00089590 /* CryptorAlgorithm.swift in Sources */, 285 | 02573DF72268A078004BD894 /* RandomBytes.swift in Sources */, 286 | 01EAFF9F21E8C3AB00089590 /* CryptorError.swift in Sources */, 287 | 01EAFFA721E8C44200089590 /* HMAC.swift in Sources */, 288 | 01EAFF9D21E8C39800089590 /* SecurityError.swift in Sources */, 289 | 01EAFFA921E8C46B00089590 /* Digest.swift in Sources */, 290 | 0226FC7D2276D8F5008E60F3 /* CryptorOptions.swift in Sources */, 291 | 01EAFFA321E8C3FB00089590 /* Cryptor.swift in Sources */, 292 | 01EAFFA521E8C41000089590 /* PBKDF2.swift in Sources */, 293 | ); 294 | runOnlyForDeploymentPostprocessing = 0; 295 | }; 296 | 01EAFF8721E8C31100089590 /* Sources */ = { 297 | isa = PBXSourcesBuildPhase; 298 | buildActionMask = 2147483647; 299 | files = ( 300 | 01EAFFB421E8C62600089590 /* CryptorTests.swift in Sources */, 301 | 0113AB71233A3A770060801C /* DigestTests.swift in Sources */, 302 | 02573DF92268A491004BD894 /* RandomBytesTests.swift in Sources */, 303 | 01EAFFB221E8C5FE00089590 /* PBKDF2Tests.swift in Sources */, 304 | 01EAFF9121E8C31100089590 /* GoldenKeyTests.swift in Sources */, 305 | 01EAFFB021E8C5CF00089590 /* HMACTests.swift in Sources */, 306 | ); 307 | runOnlyForDeploymentPostprocessing = 0; 308 | }; 309 | /* End PBXSourcesBuildPhase section */ 310 | 311 | /* Begin PBXTargetDependency section */ 312 | 01EAFF8E21E8C31100089590 /* PBXTargetDependency */ = { 313 | isa = PBXTargetDependency; 314 | target = 01EAFF8121E8C31100089590 /* GoldenKey */; 315 | targetProxy = 01EAFF8D21E8C31100089590 /* PBXContainerItemProxy */; 316 | }; 317 | /* End PBXTargetDependency section */ 318 | 319 | /* Begin XCBuildConfiguration section */ 320 | 01EAFF9421E8C31100089590 /* Debug */ = { 321 | isa = XCBuildConfiguration; 322 | buildSettings = { 323 | ALWAYS_SEARCH_USER_PATHS = NO; 324 | CLANG_ANALYZER_NONNULL = YES; 325 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 326 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 327 | CLANG_CXX_LIBRARY = "libc++"; 328 | CLANG_ENABLE_MODULES = YES; 329 | CLANG_ENABLE_OBJC_ARC = YES; 330 | CLANG_ENABLE_OBJC_WEAK = YES; 331 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 332 | CLANG_WARN_BOOL_CONVERSION = YES; 333 | CLANG_WARN_COMMA = YES; 334 | CLANG_WARN_CONSTANT_CONVERSION = YES; 335 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 336 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 337 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 338 | CLANG_WARN_EMPTY_BODY = YES; 339 | CLANG_WARN_ENUM_CONVERSION = YES; 340 | CLANG_WARN_INFINITE_RECURSION = YES; 341 | CLANG_WARN_INT_CONVERSION = YES; 342 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 343 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 344 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 345 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 346 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 347 | CLANG_WARN_STRICT_PROTOTYPES = YES; 348 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 349 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 350 | CLANG_WARN_UNREACHABLE_CODE = YES; 351 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 352 | CODE_SIGN_IDENTITY = "iPhone Developer"; 353 | COPY_PHASE_STRIP = NO; 354 | CURRENT_PROJECT_VERSION = 1; 355 | DEBUG_INFORMATION_FORMAT = dwarf; 356 | ENABLE_STRICT_OBJC_MSGSEND = YES; 357 | ENABLE_TESTABILITY = YES; 358 | GCC_C_LANGUAGE_STANDARD = gnu11; 359 | GCC_DYNAMIC_NO_PIC = NO; 360 | GCC_NO_COMMON_BLOCKS = YES; 361 | GCC_OPTIMIZATION_LEVEL = 0; 362 | GCC_PREPROCESSOR_DEFINITIONS = ( 363 | "DEBUG=1", 364 | "$(inherited)", 365 | ); 366 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 367 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 368 | GCC_WARN_UNDECLARED_SELECTOR = YES; 369 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 370 | GCC_WARN_UNUSED_FUNCTION = YES; 371 | GCC_WARN_UNUSED_VARIABLE = YES; 372 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 373 | MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; 374 | MTL_FAST_MATH = YES; 375 | ONLY_ACTIVE_ARCH = YES; 376 | SDKROOT = iphoneos; 377 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 378 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 379 | VERSIONING_SYSTEM = "apple-generic"; 380 | VERSION_INFO_PREFIX = ""; 381 | }; 382 | name = Debug; 383 | }; 384 | 01EAFF9521E8C31100089590 /* Release */ = { 385 | isa = XCBuildConfiguration; 386 | buildSettings = { 387 | ALWAYS_SEARCH_USER_PATHS = NO; 388 | CLANG_ANALYZER_NONNULL = YES; 389 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 390 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 391 | CLANG_CXX_LIBRARY = "libc++"; 392 | CLANG_ENABLE_MODULES = YES; 393 | CLANG_ENABLE_OBJC_ARC = YES; 394 | CLANG_ENABLE_OBJC_WEAK = YES; 395 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 396 | CLANG_WARN_BOOL_CONVERSION = YES; 397 | CLANG_WARN_COMMA = YES; 398 | CLANG_WARN_CONSTANT_CONVERSION = YES; 399 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 400 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 401 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 402 | CLANG_WARN_EMPTY_BODY = YES; 403 | CLANG_WARN_ENUM_CONVERSION = YES; 404 | CLANG_WARN_INFINITE_RECURSION = YES; 405 | CLANG_WARN_INT_CONVERSION = YES; 406 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 407 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 408 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 409 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 410 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 411 | CLANG_WARN_STRICT_PROTOTYPES = YES; 412 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 413 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 414 | CLANG_WARN_UNREACHABLE_CODE = YES; 415 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 416 | CODE_SIGN_IDENTITY = "iPhone Developer"; 417 | COPY_PHASE_STRIP = NO; 418 | CURRENT_PROJECT_VERSION = 1; 419 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 420 | ENABLE_NS_ASSERTIONS = NO; 421 | ENABLE_STRICT_OBJC_MSGSEND = YES; 422 | GCC_C_LANGUAGE_STANDARD = gnu11; 423 | GCC_NO_COMMON_BLOCKS = YES; 424 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 425 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 426 | GCC_WARN_UNDECLARED_SELECTOR = YES; 427 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 428 | GCC_WARN_UNUSED_FUNCTION = YES; 429 | GCC_WARN_UNUSED_VARIABLE = YES; 430 | IPHONEOS_DEPLOYMENT_TARGET = 10.0; 431 | MTL_ENABLE_DEBUG_INFO = NO; 432 | MTL_FAST_MATH = YES; 433 | SDKROOT = iphoneos; 434 | SWIFT_COMPILATION_MODE = wholemodule; 435 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 436 | VALIDATE_PRODUCT = YES; 437 | VERSIONING_SYSTEM = "apple-generic"; 438 | VERSION_INFO_PREFIX = ""; 439 | }; 440 | name = Release; 441 | }; 442 | 01EAFF9721E8C31100089590 /* Debug */ = { 443 | isa = XCBuildConfiguration; 444 | buildSettings = { 445 | CLANG_ENABLE_MODULES = YES; 446 | CODE_SIGN_IDENTITY = ""; 447 | CODE_SIGN_STYLE = Automatic; 448 | DEFINES_MODULE = YES; 449 | DYLIB_COMPATIBILITY_VERSION = 1; 450 | DYLIB_CURRENT_VERSION = 1; 451 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 452 | INFOPLIST_FILE = GoldenKey/Info.plist; 453 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 454 | LD_RUNPATH_SEARCH_PATHS = ( 455 | "$(inherited)", 456 | "@executable_path/Frameworks", 457 | "@loader_path/Frameworks", 458 | ); 459 | PRODUCT_BUNDLE_IDENTIFIER = com.redmadrobot.GoldenKey; 460 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 461 | SKIP_INSTALL = YES; 462 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 463 | SWIFT_VERSION = 5.0; 464 | TARGETED_DEVICE_FAMILY = "1,2"; 465 | }; 466 | name = Debug; 467 | }; 468 | 01EAFF9821E8C31100089590 /* Release */ = { 469 | isa = XCBuildConfiguration; 470 | buildSettings = { 471 | CLANG_ENABLE_MODULES = YES; 472 | CODE_SIGN_IDENTITY = ""; 473 | CODE_SIGN_STYLE = Automatic; 474 | DEFINES_MODULE = YES; 475 | DYLIB_COMPATIBILITY_VERSION = 1; 476 | DYLIB_CURRENT_VERSION = 1; 477 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 478 | INFOPLIST_FILE = GoldenKey/Info.plist; 479 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 480 | LD_RUNPATH_SEARCH_PATHS = ( 481 | "$(inherited)", 482 | "@executable_path/Frameworks", 483 | "@loader_path/Frameworks", 484 | ); 485 | PRODUCT_BUNDLE_IDENTIFIER = com.redmadrobot.GoldenKey; 486 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 487 | SKIP_INSTALL = YES; 488 | SWIFT_VERSION = 5.0; 489 | TARGETED_DEVICE_FAMILY = "1,2"; 490 | }; 491 | name = Release; 492 | }; 493 | 01EAFF9A21E8C31100089590 /* Debug */ = { 494 | isa = XCBuildConfiguration; 495 | buildSettings = { 496 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 497 | CODE_SIGN_STYLE = Automatic; 498 | INFOPLIST_FILE = GoldenKeyTests/Info.plist; 499 | LD_RUNPATH_SEARCH_PATHS = ( 500 | "$(inherited)", 501 | "@executable_path/Frameworks", 502 | "@loader_path/Frameworks", 503 | ); 504 | PRODUCT_BUNDLE_IDENTIFIER = com.redmadrobot.GoldenKeyTests; 505 | PRODUCT_NAME = "$(TARGET_NAME)"; 506 | SWIFT_VERSION = 5.0; 507 | TARGETED_DEVICE_FAMILY = "1,2"; 508 | }; 509 | name = Debug; 510 | }; 511 | 01EAFF9B21E8C31100089590 /* Release */ = { 512 | isa = XCBuildConfiguration; 513 | buildSettings = { 514 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 515 | CODE_SIGN_STYLE = Automatic; 516 | INFOPLIST_FILE = GoldenKeyTests/Info.plist; 517 | LD_RUNPATH_SEARCH_PATHS = ( 518 | "$(inherited)", 519 | "@executable_path/Frameworks", 520 | "@loader_path/Frameworks", 521 | ); 522 | PRODUCT_BUNDLE_IDENTIFIER = com.redmadrobot.GoldenKeyTests; 523 | PRODUCT_NAME = "$(TARGET_NAME)"; 524 | SWIFT_VERSION = 5.0; 525 | TARGETED_DEVICE_FAMILY = "1,2"; 526 | }; 527 | name = Release; 528 | }; 529 | /* End XCBuildConfiguration section */ 530 | 531 | /* Begin XCConfigurationList section */ 532 | 01EAFF7C21E8C31100089590 /* Build configuration list for PBXProject "GoldenKey" */ = { 533 | isa = XCConfigurationList; 534 | buildConfigurations = ( 535 | 01EAFF9421E8C31100089590 /* Debug */, 536 | 01EAFF9521E8C31100089590 /* Release */, 537 | ); 538 | defaultConfigurationIsVisible = 0; 539 | defaultConfigurationName = Release; 540 | }; 541 | 01EAFF9621E8C31100089590 /* Build configuration list for PBXNativeTarget "GoldenKey" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 01EAFF9721E8C31100089590 /* Debug */, 545 | 01EAFF9821E8C31100089590 /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | 01EAFF9921E8C31100089590 /* Build configuration list for PBXNativeTarget "GoldenKeyTests" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | 01EAFF9A21E8C31100089590 /* Debug */, 554 | 01EAFF9B21E8C31100089590 /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | /* End XCConfigurationList section */ 560 | }; 561 | rootObject = 01EAFF7921E8C31100089590 /* Project object */; 562 | } 563 | -------------------------------------------------------------------------------- /GoldenKey.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /GoldenKey.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /GoldenKey.xcodeproj/xcshareddata/xcschemes/GoldenKey.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 32 | 33 | 39 | 40 | 41 | 42 | 44 | 50 | 51 | 52 | 53 | 54 | 60 | 61 | 62 | 63 | 64 | 65 | 75 | 76 | 82 | 83 | 84 | 85 | 86 | 87 | 93 | 94 | 100 | 101 | 102 | 103 | 105 | 106 | 109 | 110 | 111 | -------------------------------------------------------------------------------- /GoldenKey.xcodeproj/xcshareddata/xcschemes/GoldenKeyTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 14 | 15 | 17 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 39 | 40 | 41 | 42 | 48 | 49 | 51 | 52 | 55 | 56 | 57 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/Cryptor.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Cryptor.swift 3 | // GoldenKey 4 | // 5 | // Created by Alexander Ignatev on 11/01/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.CommonCryptor 11 | 12 | /// Generic class for symmetric encryption. 13 | public final class Cryptor { 14 | 15 | /// Operations that an `Cryptor` can perform. 16 | /// 17 | /// - encrypt: Symmetric encryption. 18 | /// - decrypt: Symmetric decryption. 19 | public enum Operation { 20 | case encrypt 21 | case decrypt 22 | 23 | var rawValue: CCOperation { 24 | switch self { 25 | case .encrypt: 26 | return CCOperation(kCCEncrypt) 27 | case .decrypt: 28 | return CCOperation(kCCDecrypt) 29 | } 30 | } 31 | } 32 | 33 | 34 | /// Basic operation. 35 | public let operation: Operation 36 | 37 | /// Encryption algorithm. 38 | public let algorithm: CryptorAlgorithm 39 | 40 | /// Options flags. 41 | public let options: CryptorOptions 42 | 43 | /// Opaque reference to a CCCryptor object. 44 | private var cryptor: CCCryptorRef? 45 | 46 | /// Create a symmetric cryptor. 47 | /// 48 | /// - Parameters: 49 | /// - operation: Basic operation. 50 | /// - algorithm: Encryption algorithm. 51 | /// - options: A struct of flags defining options. 52 | /// - key: Symmetric encryption key. 53 | /// 54 | /// - Throws: `CryptorError`. 55 | public init( 56 | operation: Operation, 57 | algorithm: CryptorAlgorithm, 58 | options: CryptorOptions, 59 | key: Data 60 | ) throws { 61 | 62 | try algorithm.validate(key: key) 63 | self.operation = operation 64 | self.algorithm = algorithm 65 | self.options = options 66 | var initializationVector: Data 67 | if case .cbc(let cbcIv) = options.blockMode, let iv = cbcIv { 68 | try algorithm.validate(iv: iv) 69 | initializationVector = iv 70 | } else { 71 | // If CBC mode is selected (by the absence of any mode bits in the options flags) and no IV is present, 72 | // a NULL (all zeroes) IV will be used. 73 | let zeroBytes = [UInt8](repeating: 0, count: algorithm.blockSize) 74 | initializationVector = Data(bytes: zeroBytes, count: algorithm.blockSize) 75 | } 76 | 77 | let status: CCCryptorStatus = key.withUnsafeBytes { (keyBuffer: UnsafeRawBufferPointer) -> CCCryptorStatus in 78 | initializationVector.withUnsafeBytes { (ivBuffer: UnsafeRawBufferPointer) -> CCCryptorStatus in 79 | return CCCryptorCreate( 80 | operation.rawValue, 81 | algorithm.rawValue, 82 | options.rawValue, 83 | keyBuffer.baseAddress, 84 | key.count, 85 | ivBuffer.baseAddress, 86 | &cryptor) 87 | } 88 | } 89 | try CryptorError.verify(status) 90 | } 91 | 92 | deinit { 93 | guard let cryptor = cryptor else { return } 94 | let status = CCCryptorRelease(cryptor) 95 | do { 96 | try CryptorError.verify(status) 97 | } catch { 98 | assertionFailure("\(error)") 99 | } 100 | } 101 | 102 | /// Process (encrypt, decrypt) some data. 103 | /// 104 | /// - Parameter data: Data to process. 105 | /// - Returns: Processed data. 106 | /// - Throws: `CryptorError`. 107 | @discardableResult 108 | public func process(_ data: Data) throws -> Data { 109 | var outLength = Int(0) 110 | var outBytes = [UInt8](repeating: 0, count: outputLength(for: data.count, final: false)) 111 | 112 | let status: CCCryptorStatus = data.withUnsafeBytes { 113 | CCCryptorUpdate(cryptor, $0.baseAddress, data.count, &outBytes, outBytes.count, &outLength) 114 | } 115 | try CryptorError.verify(status) 116 | 117 | return Data(outBytes[.. Data { 125 | var outLength = Int(0) 126 | var outBytes = [UInt8](repeating: 0, count: outputLength(for: 0, final: true)) 127 | 128 | let status = CCCryptorFinal(cryptor, &outBytes, outBytes.count, &outLength) 129 | try CryptorError.verify(status) 130 | 131 | return Data(outBytes[.. Int { 144 | return CCCryptorGetOutputLength(cryptor, inputLength, final) 145 | } 146 | 147 | /// Reset an existing `Cryptor` with a (possibly) new initialization vector. 148 | /// The Cryptor's key is unchanged. 149 | /// 150 | /// - Precondition: Use only for CBC mode. 151 | /// - Parameter iv: Optional initialization vector; 152 | /// if present, must be the same size as the current algorithm's block size. 153 | /// For sound encryption, always initialize iv with random data. 154 | /// - Throws: `CryptorError`. 155 | public func reset(iv: Data? = nil) throws { 156 | var vector = iv 157 | let status = CCCryptorReset(cryptor, &vector) 158 | try CryptorError.verify(status) 159 | } 160 | 161 | /// Stateless, one-shot encrypt operation. 162 | /// 163 | /// - Parameters: 164 | /// - algorithm: Encryption algorithm. 165 | /// - options: A word of flags defining options. 166 | /// - key: Symmetric encryption key. 167 | /// - data: Data to encrypt or decrypt. 168 | /// - iv: Initialization vector, optional. 169 | /// - Returns: Result data. 170 | /// - Throws: `CryptorError`. 171 | public static func encrypt( 172 | algorithm: CryptorAlgorithm, 173 | options: CryptorOptions, 174 | key: Data, 175 | data: Data 176 | ) throws -> Data { 177 | return try crypt(.encrypt, algorithm: algorithm, options: options, key: key, data: data) 178 | } 179 | 180 | /// Stateless, one-shot decrypt operation. 181 | /// 182 | /// - Parameters: 183 | /// - algorithm: Encryption algorithm. 184 | /// - options: A word of flags defining options. 185 | /// - key: Symmetric encryption key. 186 | /// - data: Data to encrypt or decrypt. 187 | /// - iv: Initialization vector, optional. 188 | /// - Returns: Result data. 189 | /// - Throws: `CryptorError`. 190 | public static func decrypt( 191 | algorithm: CryptorAlgorithm, 192 | options: CryptorOptions, 193 | key: Data, 194 | data: Data 195 | ) throws -> Data { 196 | return try crypt(.decrypt, algorithm: algorithm, options: options, key: key, data: data) 197 | } 198 | 199 | /// Stateless, one-shot encrypt or decrypt operation. 200 | /// 201 | /// - Parameters: 202 | /// - operation: Basic operation. 203 | /// - algorithm: Encryption algorithm. 204 | /// - options: A word of flags defining options. 205 | /// - key: Symmetric encryption key. 206 | /// - data: Data to encrypt or decrypt. 207 | /// - iv: Initialization vector, optional. 208 | /// - Returns: Result data. 209 | /// - Throws: `CryptorError`. 210 | internal static func crypt( 211 | _ operation: Operation, 212 | algorithm: CryptorAlgorithm, 213 | options: CryptorOptions, 214 | key: Data, 215 | data: Data 216 | ) throws -> Data { 217 | 218 | try algorithm.validate(key: key) 219 | var outLength = Int(0) 220 | var outBytes = [UInt8](repeating: 0, count: data.count + algorithm.blockSize) 221 | var initializationVector: Data 222 | if case .cbc(let cbcIv) = options.blockMode, let iv = cbcIv { 223 | try algorithm.validate(iv: iv) 224 | initializationVector = iv 225 | } else { 226 | // If CBC mode is selected (by the absence of any mode bits in the options flags) and no IV is present, 227 | // a NULL (all zeroes) IV will be used. 228 | let zeroBytes = [UInt8](repeating: 0, count: algorithm.blockSize) 229 | initializationVector = Data(bytes: zeroBytes, count: algorithm.blockSize) 230 | } 231 | 232 | let status: CCCryptorStatus = data.withUnsafeBytes { (inputBuffer: UnsafeRawBufferPointer) -> CCCryptorStatus in 233 | return key.withUnsafeBytes { (keyBuffer: UnsafeRawBufferPointer) -> CCCryptorStatus in 234 | return initializationVector.withUnsafeBytes { (ivBuffer: UnsafeRawBufferPointer) -> CCCryptorStatus in 235 | return CCCrypt( 236 | operation.rawValue, algorithm.rawValue, options.rawValue, 237 | keyBuffer.baseAddress, 238 | key.count, 239 | ivBuffer.baseAddress, 240 | inputBuffer.baseAddress, 241 | data.count, 242 | &outBytes, outBytes.count, &outLength) 243 | } 244 | } 245 | } 246 | try CryptorError.verify(status) 247 | 248 | return Data(outBytes[.. { 92 | switch self { 93 | case .cast: 94 | return kCCKeySizeMinCAST...kCCKeySizeMaxCAST 95 | case .rc2: 96 | return kCCKeySizeMinRC2...kCCKeySizeMaxRC2 97 | case .rc4: 98 | return kCCKeySizeMinRC4...kCCKeySizeMaxRC4 99 | case .blowfish: 100 | return kCCKeySizeMinBlowfish...kCCKeySizeMaxBlowfish 101 | default: 102 | return keySize...keySize 103 | } 104 | } 105 | 106 | func validate(key: Data) throws { 107 | guard availableKeySize.contains(key.count) else { 108 | throw CryptorError.paramError 109 | } 110 | } 111 | 112 | func validate(iv: Data) throws { 113 | guard iv.count == blockSize else { 114 | throw CryptorError.paramError 115 | } 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/CryptorError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CryptorError.swift 3 | // GoldenKey 4 | // 5 | // Created by Alexander Ignatev on 11/01/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.Error 11 | 12 | /// Errors from `Cryptor` operations. 13 | /// 14 | /// - SeeAlso: CommonCryptoError.h 15 | public struct CryptorError: Error, RawRepresentable, Hashable { 16 | public let rawValue: CCStatus 17 | 18 | public init(rawValue: CCStatus) { 19 | self.rawValue = rawValue 20 | } 21 | 22 | init(_ rawValue: Int) { 23 | self.rawValue = CCStatus(rawValue) 24 | } 25 | 26 | /// Verify `Cryptor` operation status. 27 | /// 28 | /// - Parameter status: `Cryptor` operation status. 29 | /// - Throws: `CryptorError`. 30 | static func verify(_ status: CCCryptorStatus) throws { 31 | if status == kCCSuccess { return } 32 | throw CryptorError(rawValue: status) 33 | } 34 | } 35 | 36 | extension CryptorError { 37 | 38 | /// Illegal parameter value. 39 | public static var paramError: CryptorError { return CryptorError(kCCParamError) } 40 | 41 | /// Insufficent buffer provided for specified operation. 42 | public static var bufferTooSmall: CryptorError { return CryptorError(kCCBufferTooSmall) } 43 | 44 | /// Memory allocation failure. 45 | public static var memoryFailure: CryptorError { return CryptorError(kCCMemoryFailure) } 46 | 47 | /// Input size was not aligned properly. 48 | public static var alignmentError: CryptorError { return CryptorError(kCCAlignmentError) } 49 | 50 | /// Input data did not decode or decrypt properly. 51 | public static var decodeError: CryptorError { return CryptorError(kCCDecodeError) } 52 | 53 | /// Function not implemented for the current algorithm. 54 | public static var unimplemented: CryptorError { return CryptorError(kCCUnimplemented) } 55 | 56 | /// Key is not valid. 57 | public static var invalidKey: CryptorError { return CryptorError(kCCInvalidKey) } 58 | } 59 | 60 | extension CryptorError: LocalizedError { 61 | 62 | /// A localized message describing what error occurred. 63 | public var errorDescription: String? { 64 | switch self { 65 | case .paramError: 66 | return "Illegal parameter value" 67 | case .bufferTooSmall: 68 | return "Insufficent buffer provided for specified operation" 69 | case .memoryFailure: 70 | return "Memory allocation failure" 71 | case .alignmentError: 72 | return "Input size was not aligned properly" 73 | case .decodeError: 74 | return "Input data did not decode or decrypt properly" 75 | case .unimplemented: 76 | return "Function not implemented for the current algorithm" 77 | case .invalidKey: 78 | return "Key is not valid" 79 | default: 80 | return "Unknown status: \(rawValue)" 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/CryptorOptions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CryptorOptions.swift 3 | // GoldenKey 4 | // 5 | // Created by Anton Glezman on 29/04/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.CommonCryptor 11 | 12 | public struct CryptorOptions { 13 | 14 | public enum BlockMode { 15 | /// Cipher Block Chaining. 16 | /// - iv: Initialization Vector. Must be the same length as the AES algorithm block size, 128 bits (16 bytes). 17 | /// If CBC mode is selected and no IV is present, a NULL (all zeroes) IV will be used. 18 | case cbc(iv: Data?) 19 | 20 | /// Electronic Codebook. 21 | /// Not recommended for use because ECB has known cryptography weakness. 22 | case ecb 23 | } 24 | 25 | public enum Padding { 26 | /// PKCS#7 padding algorithm. 27 | case pkcs7 28 | 29 | /// In this case, the message length must be multiple of the block size. 30 | /// You can padded message manually before encryption. 31 | case noPadding 32 | } 33 | 34 | 35 | // MARK: - Public properties 36 | 37 | public let blockMode: BlockMode 38 | public let padding: Padding 39 | 40 | public var iv: Data? { 41 | switch blockMode { 42 | case .cbc(let iv): 43 | return iv 44 | case .ecb: 45 | return nil 46 | } 47 | } 48 | 49 | 50 | // MARK: - Init 51 | 52 | public init(blockMode: BlockMode, padding: Padding = .pkcs7) { 53 | self.blockMode = blockMode 54 | self.padding = padding 55 | } 56 | 57 | 58 | // MARK: - Internal properties 59 | 60 | internal var rawValue: CCOptions { 61 | // Default is CBC mode and no padding 62 | var options: CCOptions = 0 63 | switch blockMode { 64 | case .ecb: 65 | options |= CCOptions(kCCOptionECBMode) 66 | case .cbc: 67 | break 68 | } 69 | 70 | switch padding { 71 | case .pkcs7: 72 | options |= CCOptions(kCCOptionPKCS7Padding) 73 | case .noPadding: 74 | break 75 | } 76 | return options 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/Digest.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CommonDigest.swift 3 | // GoldenKey 4 | // 5 | // Created by Alexander Ignatev on 02/01/2019. 6 | // Copyright © 2019 Alexander Ignition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.CommonDigest 11 | 12 | public protocol Digest { 13 | 14 | init() 15 | 16 | /// Updates the digest with another data chunk. This can be called multiple times. 17 | /// Use this method for streaming digests. 18 | /// 19 | /// - Parameters: bytes: Data chunk to digest. `Data` or `[UInt8]`. 20 | func update(data: T) where T: ContiguousBytes 21 | 22 | /// Return the digest of the data passed to the `update(data:)` method so far. 23 | func finalize() -> Data 24 | 25 | static func hash(data: T) -> Data where T: ContiguousBytes 26 | } 27 | 28 | /// Class for MD2 cryptographic hash generation 29 | public final class MD2: Digest { 30 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 31 | 32 | /// Initializes a MD2_CTX structure 33 | public init() { 34 | CC_MD2_Init(context) 35 | } 36 | 37 | deinit { 38 | context.deallocate() 39 | } 40 | 41 | /// Combines data to be hashed. 42 | /// Can be called repeatedly with chunks of the message. 43 | /// 44 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 45 | public func update(data: T) where T: ContiguousBytes { 46 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 47 | _ = CC_MD2_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 48 | } 49 | } 50 | 51 | /// Computes the MD2 data digest (cryptographic hash). 52 | /// Erases the MD2_CTX structure. 53 | /// 54 | /// - Returns: data digest (cryptographic hash) in MD2. 55 | public func finalize() -> Data { 56 | var data = Data(repeating: 0, count: Int(CC_MD2_DIGEST_LENGTH)) 57 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 58 | _ = CC_MD2_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 59 | } 60 | return data 61 | } 62 | 63 | /// Computes the MD2 data digest. 64 | /// Command line analog: 65 | /// $ openssl MD2 <<< "string_to_be_hashed" 66 | /// 67 | /// - Returns: data digest (cryptographic hash) in MD2. 68 | public static func hash(data: T) -> Data where T: ContiguousBytes { 69 | var result = [UInt8](repeating: 0, count: Int(CC_MD2_DIGEST_LENGTH)) 70 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 71 | _ = CC_MD2(buffer.baseAddress, CC_LONG(buffer.count), &result) 72 | } 73 | return Data(result) 74 | } 75 | } 76 | 77 | /// Class for MD4 cryptographic hash generation 78 | public final class MD4: Digest { 79 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 80 | 81 | /// Initializes a MD4_CTX structure 82 | public init() { 83 | CC_MD4_Init(context) 84 | } 85 | 86 | deinit { 87 | context.deallocate() 88 | } 89 | 90 | /// Combines data to be hashed. 91 | /// Can be called repeatedly with chunks of the message. 92 | /// 93 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 94 | public func update(data: T) where T: ContiguousBytes { 95 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 96 | _ = CC_MD4_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 97 | } 98 | } 99 | 100 | /// Computes the MD4 data digest (cryptographic hash). 101 | /// Erases the MD4_CTX structure. 102 | /// 103 | /// - Returns: data digest (cryptographic hash) in MD4. 104 | public func finalize() -> Data { 105 | var data = Data(repeating: 0, count: Int(CC_MD4_DIGEST_LENGTH)) 106 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 107 | _ = CC_MD4_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 108 | } 109 | return data 110 | } 111 | 112 | /// Computes the MD4 data digest. 113 | /// Command line analog: 114 | /// $ openssl MD4 <<< "string_to_be_hashed" 115 | /// 116 | /// - Returns: data digest (cryptographic hash) in MD4. 117 | public static func hash(data: T) -> Data where T: ContiguousBytes { 118 | var result = [UInt8](repeating: 0, count: Int(CC_MD4_DIGEST_LENGTH)) 119 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 120 | _ = CC_MD4(buffer.baseAddress, CC_LONG(buffer.count), &result) 121 | } 122 | return Data(result) 123 | } 124 | } 125 | 126 | /// Class for MD5 cryptographic hash generation 127 | public final class MD5: Digest { 128 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 129 | 130 | /// Initializes a MD5_CTX structure 131 | public init() { 132 | CC_MD5_Init(context) 133 | } 134 | 135 | deinit { 136 | context.deallocate() 137 | } 138 | 139 | /// Combines data to be hashed. 140 | /// Can be called repeatedly with chunks of the message. 141 | /// 142 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 143 | public func update(data: T) where T: ContiguousBytes { 144 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 145 | _ = CC_MD5_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 146 | } 147 | } 148 | 149 | /// Computes the MD5 data digest (cryptographic hash). 150 | /// Erases the MD5_CTX structure. 151 | /// 152 | /// - Returns: data digest (cryptographic hash) in MD5. 153 | public func finalize() -> Data { 154 | var data = Data(repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) 155 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 156 | _ = CC_MD5_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 157 | } 158 | return data 159 | } 160 | 161 | /// Computes the MD5 data digest. 162 | /// Command line analog: 163 | /// $ openssl MD5 <<< "string_to_be_hashed" 164 | /// 165 | /// - Returns: data digest (cryptographic hash) in MD5. 166 | public static func hash(data: T) -> Data where T: ContiguousBytes { 167 | var result = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH)) 168 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 169 | _ = CC_MD5(buffer.baseAddress, CC_LONG(buffer.count), &result) 170 | } 171 | return Data(result) 172 | } 173 | } 174 | 175 | /// Class for SHA1 cryptographic hash generation 176 | public final class SHA1: Digest { 177 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 178 | 179 | /// Initializes a SHA1_CTX structure 180 | public init() { 181 | CC_SHA1_Init(context) 182 | } 183 | 184 | deinit { 185 | context.deallocate() 186 | } 187 | 188 | /// Combines data to be hashed. 189 | /// Can be called repeatedly with chunks of the message. 190 | /// 191 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 192 | public func update(data: T) where T: ContiguousBytes { 193 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 194 | _ = CC_SHA1_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 195 | } 196 | } 197 | 198 | /// Computes the SHA1 data digest (cryptographic hash). 199 | /// Erases the SHA1_CTX structure. 200 | /// 201 | /// - Returns: data digest (cryptographic hash) in SHA1. 202 | public func finalize() -> Data { 203 | var data = Data(repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) 204 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 205 | _ = CC_SHA1_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 206 | } 207 | return data 208 | } 209 | 210 | /// Computes the SHA1 data digest. 211 | /// Command line analog: 212 | /// $ openssl SHA1 <<< "string_to_be_hashed" 213 | /// 214 | /// - Returns: data digest (cryptographic hash) in SHA1. 215 | public static func hash(data: T) -> Data where T: ContiguousBytes { 216 | var result = [UInt8](repeating: 0, count: Int(CC_SHA1_DIGEST_LENGTH)) 217 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 218 | _ = CC_SHA1(buffer.baseAddress, CC_LONG(buffer.count), &result) 219 | } 220 | return Data(result) 221 | } 222 | } 223 | 224 | /// Class for SHA224 cryptographic hash generation 225 | public final class SHA224: Digest { 226 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 227 | 228 | /// Initializes a SHA224_CTX structure 229 | public init() { 230 | CC_SHA224_Init(context) 231 | } 232 | 233 | deinit { 234 | context.deallocate() 235 | } 236 | 237 | /// Combines data to be hashed. 238 | /// Can be called repeatedly with chunks of the message. 239 | /// 240 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 241 | public func update(data: T) where T: ContiguousBytes { 242 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 243 | _ = CC_SHA224_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 244 | } 245 | } 246 | 247 | /// Computes the SHA224 data digest (cryptographic hash). 248 | /// Erases the SHA224_CTX structure. 249 | /// 250 | /// - Returns: data digest (cryptographic hash) in SHA224. 251 | public func finalize() -> Data { 252 | var data = Data(repeating: 0, count: Int(CC_SHA224_DIGEST_LENGTH)) 253 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 254 | _ = CC_SHA224_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 255 | } 256 | return data 257 | } 258 | 259 | /// Computes the SHA224 data digest. 260 | /// Command line analog: 261 | /// $ openssl SHA224 <<< "string_to_be_hashed" 262 | /// 263 | /// - Returns: data digest (cryptographic hash) in SHA224. 264 | public static func hash(data: T) -> Data where T: ContiguousBytes { 265 | var result = [UInt8](repeating: 0, count: Int(CC_SHA224_DIGEST_LENGTH)) 266 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 267 | _ = CC_SHA224(buffer.baseAddress, CC_LONG(buffer.count), &result) 268 | } 269 | return Data(result) 270 | } 271 | } 272 | 273 | /// Class for SHA256 cryptographic hash generation 274 | public final class SHA256: Digest { 275 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 276 | 277 | /// Initializes a SHA256_CTX structure 278 | public init() { 279 | CC_SHA256_Init(context) 280 | } 281 | 282 | deinit { 283 | context.deallocate() 284 | } 285 | 286 | /// Combines data to be hashed. 287 | /// Can be called repeatedly with chunks of the message. 288 | /// 289 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 290 | public func update(data: T) where T: ContiguousBytes { 291 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 292 | _ = CC_SHA256_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 293 | } 294 | } 295 | 296 | /// Computes the SHA256 data digest (cryptographic hash). 297 | /// Erases the SHA256_CTX structure. 298 | /// 299 | /// - Returns: data digest (cryptographic hash) in SHA256. 300 | public func finalize() -> Data { 301 | var data = Data(repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 302 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 303 | _ = CC_SHA256_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 304 | } 305 | return data 306 | } 307 | 308 | /// Computes the SHA256 data digest. 309 | /// Command line analog: 310 | /// $ openssl SHA256 <<< "string_to_be_hashed" 311 | /// 312 | /// - Returns: data digest (cryptographic hash) in SHA256. 313 | public static func hash(data: T) -> Data where T: ContiguousBytes { 314 | var result = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH)) 315 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 316 | _ = CC_SHA256(buffer.baseAddress, CC_LONG(buffer.count), &result) 317 | } 318 | return Data(result) 319 | } 320 | } 321 | 322 | /// Class for SHA384 cryptographic hash generation 323 | public final class SHA384: Digest { 324 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 325 | 326 | /// Initializes a SHA384_CTX structure 327 | public init() { 328 | CC_SHA384_Init(context) 329 | } 330 | 331 | deinit { 332 | context.deallocate() 333 | } 334 | 335 | /// Combines data to be hashed. 336 | /// Can be called repeatedly with chunks of the message. 337 | /// 338 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 339 | public func update(data: T) where T: ContiguousBytes { 340 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 341 | _ = CC_SHA384_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 342 | } 343 | } 344 | 345 | /// Computes the SHA384 data digest (cryptographic hash). 346 | /// Erases the SHA384_CTX structure. 347 | /// 348 | /// - Returns: data digest (cryptographic hash) in SHA384. 349 | public func finalize() -> Data { 350 | var data = Data(repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH)) 351 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 352 | _ = CC_SHA384_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 353 | } 354 | return data 355 | } 356 | 357 | /// Computes the SHA384 data digest. 358 | /// Command line analog: 359 | /// $ openssl SHA384 <<< "string_to_be_hashed" 360 | /// 361 | /// - Returns: data digest (cryptographic hash) in SHA384. 362 | public static func hash(data: T) -> Data where T: ContiguousBytes { 363 | var result = [UInt8](repeating: 0, count: Int(CC_SHA384_DIGEST_LENGTH)) 364 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 365 | _ = CC_SHA384(buffer.baseAddress, CC_LONG(buffer.count), &result) 366 | } 367 | return Data(result) 368 | } 369 | } 370 | 371 | /// Class for SHA512 cryptographic hash generation 372 | public final class SHA512: Digest { 373 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 374 | 375 | /// Initializes a SHA512_CTX structure 376 | public init() { 377 | CC_SHA512_Init(context) 378 | } 379 | 380 | deinit { 381 | context.deallocate() 382 | } 383 | 384 | /// Combines data to be hashed. 385 | /// Can be called repeatedly with chunks of the message. 386 | /// 387 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 388 | public func update(data: T) where T: ContiguousBytes { 389 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 390 | _ = CC_SHA512_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 391 | } 392 | } 393 | 394 | /// Computes the SHA512 data digest (cryptographic hash). 395 | /// Erases the SHA512_CTX structure. 396 | /// 397 | /// - Returns: data digest (cryptographic hash) in SHA512. 398 | public func finalize() -> Data { 399 | var data = Data(repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH)) 400 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 401 | _ = CC_SHA512_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 402 | } 403 | return data 404 | } 405 | 406 | /// Computes the SHA512 data digest. 407 | /// Command line analog: 408 | /// $ openssl SHA512 <<< "string_to_be_hashed" 409 | /// 410 | /// - Returns: data digest (cryptographic hash) in SHA512. 411 | public static func hash(data: T) -> Data where T: ContiguousBytes { 412 | var result = [UInt8](repeating: 0, count: Int(CC_SHA512_DIGEST_LENGTH)) 413 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 414 | _ = CC_SHA512(buffer.baseAddress, CC_LONG(buffer.count), &result) 415 | } 416 | return Data(result) 417 | } 418 | } 419 | 420 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/Digest.swift.gyb: -------------------------------------------------------------------------------- 1 | // 2 | // CommonDigest.swift 3 | // GoldenKey 4 | // 5 | // Created by Alexander Ignatev on 02/01/2019. 6 | // Copyright © 2019 Alexander Ignition. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.CommonDigest 11 | 12 | public protocol Digest { 13 | 14 | init() 15 | 16 | /// Updates the digest with another data chunk. This can be called multiple times. 17 | /// Use this method for streaming digests. 18 | /// 19 | /// - Parameters: bytes: Data chunk to digest. `Data` or `[UInt8]`. 20 | func update(data: T) where T: ContiguousBytes 21 | 22 | /// Return the digest of the data passed to the `update(data:)` method so far. 23 | func finalize() -> Data 24 | 25 | static func hash(data: T) -> Data where T: ContiguousBytes 26 | } 27 | %{ 28 | DIGESTS = [('MD2', 'MD2'), 29 | ('MD4', 'MD4'), 30 | ('MD5', 'MD5'), 31 | ('SHA1', 'SHA1'), 32 | ('SHA224', 'SHA256'), 33 | ('SHA256', 'SHA256'), 34 | ('SHA384', 'SHA512'), 35 | ('SHA512', 'SHA512')] 36 | }% 37 | 38 | % for (name, context) in DIGESTS: 39 | /// Class for ${name} cryptographic hash generation 40 | public final class ${name}: Digest { 41 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 42 | 43 | /// Initializes a ${name}_CTX structure 44 | public init() { 45 | CC_${name}_Init(context) 46 | } 47 | 48 | deinit { 49 | context.deallocate() 50 | } 51 | 52 | /// Combines data to be hashed. 53 | /// Can be called repeatedly with chunks of the message. 54 | /// 55 | /// - Parameters: data: Data chunk to digest. `Data` or `[UInt8]`. 56 | public func update(data: T) where T: ContiguousBytes { 57 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 58 | _ = CC_${name}_Update(context, buffer.baseAddress, CC_LONG(buffer.count)) 59 | } 60 | } 61 | 62 | /// Computes the ${name} data digest (cryptographic hash). 63 | /// Erases the ${name}_CTX structure. 64 | /// 65 | /// - Returns: data digest (cryptographic hash) in ${name}. 66 | public func finalize() -> Data { 67 | var data = Data(repeating: 0, count: Int(CC_${name}_DIGEST_LENGTH)) 68 | data.withUnsafeMutableBytes { (buffer: UnsafeMutableRawBufferPointer) -> Void in 69 | _ = CC_${name}_Final(buffer.bindMemory(to: UInt8.self).baseAddress, context) 70 | } 71 | return data 72 | } 73 | 74 | /// Computes the ${name} data digest. 75 | /// Command line analog: 76 | /// $ openssl ${name} <<< "string_to_be_hashed" 77 | /// 78 | /// - Returns: data digest (cryptographic hash) in ${name}. 79 | public static func hash(data: T) -> Data where T: ContiguousBytes { 80 | var result = [UInt8](repeating: 0, count: Int(CC_${name}_DIGEST_LENGTH)) 81 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 82 | _ = CC_${name}(buffer.baseAddress, CC_LONG(buffer.count), &result) 83 | } 84 | return Data(result) 85 | } 86 | } 87 | 88 | % end 89 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/HMAC.swift: -------------------------------------------------------------------------------- 1 | // 2 | // HMAC.swift 3 | // GoldenKey 4 | // 5 | // Created by Alexander Ignatev on 11/01/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.CommonHMAC 11 | 12 | /// hash-based message authentication code 13 | public final class HMAC { 14 | 15 | /// HMAC algorithm. 16 | public enum Algorithm { 17 | case md5 18 | case sha1 19 | case sha224 20 | case sha256 21 | case sha384 22 | case sha512 23 | 24 | var rawValue: CCHmacAlgorithm { 25 | switch self { 26 | case .md5: 27 | return CCHmacAlgorithm(kCCHmacAlgMD5) 28 | case .sha1: 29 | return CCHmacAlgorithm(kCCHmacAlgSHA1) 30 | case .sha224: 31 | return CCHmacAlgorithm(kCCHmacAlgSHA224) 32 | case .sha256: 33 | return CCHmacAlgorithm(kCCHmacAlgSHA256) 34 | case .sha384: 35 | return CCHmacAlgorithm(kCCHmacAlgSHA384) 36 | case .sha512: 37 | return CCHmacAlgorithm(kCCHmacAlgSHA512) 38 | } 39 | } 40 | 41 | var digestLength: Int { 42 | switch self { 43 | case .md5: 44 | return Int(CC_MD5_DIGEST_LENGTH) 45 | case .sha1: 46 | return Int(CC_SHA1_DIGEST_LENGTH) 47 | case .sha224: 48 | return Int(CC_SHA224_DIGEST_LENGTH) 49 | case .sha256: 50 | return Int(CC_SHA256_DIGEST_LENGTH) 51 | case .sha384: 52 | return Int(CC_SHA384_DIGEST_LENGTH) 53 | case .sha512: 54 | return Int(CC_SHA512_DIGEST_LENGTH) 55 | } 56 | } 57 | } 58 | 59 | /// HMAC algorithm. 60 | public let algorithm: Algorithm 61 | 62 | /// HMAC context. 63 | private var context = UnsafeMutablePointer.allocate(capacity: 1) 64 | 65 | public init(algorithm: Algorithm, key: T) where T: ContiguousBytes { 66 | self.algorithm = algorithm 67 | key.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 68 | CCHmacInit(context, algorithm.rawValue, buffer.baseAddress, buffer.count) 69 | } 70 | } 71 | 72 | deinit { 73 | context.deallocate() 74 | } 75 | 76 | /// Process some bytes. 77 | /// 78 | /// - Parameter data: Bytes to process. `Data` or `[UInt8]`. 79 | public func update(data: T) where T: ContiguousBytes { 80 | data.withUnsafeBytes { (buffer: UnsafeRawBufferPointer) -> Void in 81 | CCHmacUpdate(context, buffer.baseAddress, buffer.count) 82 | } 83 | } 84 | 85 | /// Obtain the final Message Authentication Code. 86 | public func finalize() -> Data { 87 | var data = Data(repeating: 0, count: algorithm.digestLength) 88 | data.withUnsafeMutableBytes { 89 | CCHmacFinal(context, $0.baseAddress) 90 | } 91 | return data 92 | } 93 | 94 | public static func hash(algorithm: Algorithm, data: T, key: T) -> Data where T: ContiguousBytes { 95 | var bytes = [UInt8](repeating: 0, count: algorithm.digestLength) 96 | key.withUnsafeBytes { (keyBytes: UnsafeRawBufferPointer) -> Void in 97 | data.withUnsafeBytes { (dataBytes: UnsafeRawBufferPointer) -> Void in 98 | CCHmac(algorithm.rawValue, 99 | keyBytes.baseAddress, keyBytes.count, 100 | dataBytes.baseAddress, dataBytes.count, 101 | &bytes) 102 | } 103 | } 104 | return Data(bytes) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/PBKDF2.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PBKDF2.swift 3 | // GoldenKey 4 | // 5 | // Created by Alexander Ignatev on 11/01/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.CommonKeyDerivation 11 | 12 | /// Password Based Key Derivation 2. 13 | public struct PBKDF2 { 14 | 15 | /// The Pseudo Random Algorithm to use for the derivation iterations. 16 | public enum PseudoRandomAlgorithm { 17 | case sha1 18 | case sha224 19 | case sha256 20 | case sha384 21 | case sha1512 22 | } 23 | 24 | /// The text password used as input to the derivation function. 25 | private let password: String 26 | 27 | /// The salt byte values used as input to the derivation function. 28 | private let salt: Data 29 | 30 | /// Password Based Key Derivation 2. 31 | /// 32 | /// - Parameters: 33 | /// - password: The text password used as input to the derivation function. 34 | /// - salt: The salt byte values used as input to the derivation function. 35 | public init(password: String, salt: Data) { 36 | self.password = password 37 | self.salt = salt 38 | } 39 | 40 | /// Derive a key from a text password/passphrase. 41 | /// 42 | /// - Parameters: 43 | /// - algorithm: The Pseudo Random Algorithm to use for the derivation iterations. 44 | /// - keyCount: The expected length of the derived key in bytes. It cannot be zero. 45 | /// - rounds: The number of rounds of the Pseudo Random Algorithm to use. It cannot be zero. 46 | /// - Returns: Derived key. 47 | /// - Throws: `CryptorError`. 48 | public func keyDerivation( 49 | _ pseudoRandomAlgorithm: PseudoRandomAlgorithm, 50 | keyCount: Int, 51 | rounds: UInt32 52 | ) throws -> Data { 53 | 54 | var derivedKey = [UInt8](repeating: 0, count: keyCount) 55 | 56 | let status = salt.withUnsafeBytes { (saltBuffer: UnsafeRawBufferPointer) -> CCStatus in 57 | return CCKeyDerivationPBKDF( 58 | CCPBKDFAlgorithm(kCCPBKDF2), 59 | password, password.utf8.count, 60 | saltBuffer.bindMemory(to: UInt8.self).baseAddress, 61 | salt.count, 62 | pseudoRandomAlgorithm.rawValue, 63 | rounds, 64 | &derivedKey, derivedKey.count) 65 | } 66 | try CryptorError.verify(status) 67 | 68 | return Data(derivedKey) 69 | } 70 | 71 | /// Calibrate PBKDF. 72 | /// 73 | /// Determine the number of PRF rounds to use for a specific delay on the current platform. 74 | /// 75 | /// - Parameters: 76 | /// - pseudoRandomAlgorithm: The Pseudo Random Algorithm to use for the derivation iterations. 77 | /// - keyCount: The expected length of the derived key in bytes. 78 | /// - milliseconds: The targetted duration we want to achieve for a key derivation with these parameters. 79 | /// - Returns: The number of iterations to use for the desired processing time. 80 | /// Returns a minimum of 10000 iterations (safety net, not a particularly recommended value) 81 | /// The number of iterations is a trade-off of usability and security. If there is an error 82 | /// the function returns (unsigned)(-1). The minimum return value is set to 10000. 83 | public func calibrate( 84 | _ pseudoRandomAlgorithm: PseudoRandomAlgorithm, 85 | keyCount: Int, 86 | milliseconds: UInt32 87 | ) -> UInt32 { 88 | 89 | return CCCalibratePBKDF( 90 | CCPBKDFAlgorithm(kCCPBKDF2), 91 | password.utf8.count, 92 | salt.count, 93 | pseudoRandomAlgorithm.rawValue, 94 | keyCount, 95 | milliseconds) 96 | } 97 | } 98 | 99 | private extension PBKDF2.PseudoRandomAlgorithm { 100 | 101 | var rawValue: CCPBKDFAlgorithm { 102 | switch self { 103 | case .sha1: 104 | return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA1) 105 | case .sha224: 106 | return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA224) 107 | case .sha256: 108 | return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA256) 109 | case .sha384: 110 | return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA384) 111 | case .sha1512: 112 | return CCPBKDFAlgorithm(kCCPRFHmacAlgSHA512) 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/RandomBytes.swift: -------------------------------------------------------------------------------- 1 | // 2 | // RandomBytes.swift 3 | // GoldenKey 4 | // 5 | // Created by Anton Glezman on 18/04/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import CommonCrypto.Random 11 | 12 | /// The functions provided in RandomBytes implement high-level accessors 13 | /// to cryptographically secure random numbers. 14 | public enum RandomBytes { 15 | 16 | /// Returns cryptographically strong random bits suitable for use as cryptographic keys, IVs, nonces etc. 17 | /// 18 | /// - Parameter count: Number of random bytes to return. 19 | /// - Returns: random `Data`. 20 | /// - Throws: `CryptorError`. 21 | public static func generate(count: Int) throws -> Data { 22 | var bytes = [Int8](repeating: 0, count: count) 23 | let status = CCRandomGenerateBytes(&bytes, bytes.count) 24 | try CryptorError.verify(status) 25 | return Data(bytes: bytes, count: bytes.count) 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /GoldenKey/CommonCrypto/SecurityError.swift: -------------------------------------------------------------------------------- 1 | // 2 | // SecurityError.swift 3 | // GoldenKey 4 | // 5 | // Created by Alexander Ignatev on 11/01/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | 11 | /// Security Error. 12 | /// 13 | /// - SeeAlso: SecBase.h 14 | public struct SecurityError: Error, RawRepresentable, Hashable { 15 | 16 | /// Security Error Code. 17 | public let rawValue: OSStatus 18 | 19 | /// Create Security Error. 20 | /// 21 | /// - Parameter rawValue: Raw security error code. 22 | public init(rawValue: OSStatus) { 23 | self.rawValue = rawValue 24 | } 25 | 26 | static func verify(_ status: OSStatus) throws { 27 | if status == errSecSuccess { return } 28 | throw SecurityError(rawValue: status) 29 | } 30 | } 31 | 32 | extension SecurityError: LocalizedError { 33 | 34 | /// A human-readable string describing the error. 35 | public var errorDescription: String? { 36 | if #available(iOS 11.3, watchOSApplicationExtension 4.3, tvOS 11.3, *) { 37 | return SecCopyErrorMessageString(rawValue, nil) as String? 38 | } else { 39 | return "OSStatus: \(rawValue)" 40 | } 41 | } 42 | } 43 | 44 | extension SecurityError: CustomNSError { 45 | 46 | /// The domain of the error. 47 | public static var errorDomain = NSOSStatusErrorDomain 48 | 49 | /// The error code within the given domain. 50 | public var errorCode: Int { return Int(rawValue) } 51 | 52 | /// The user-info dictionary. 53 | public var errorUserInfo: [String: Any] { 54 | var userInfo: [String: Any] = [:] 55 | userInfo[NSLocalizedDescriptionKey] = errorDescription 56 | return userInfo 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /GoldenKey/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | 22 | 23 | -------------------------------------------------------------------------------- /GoldenKeyTests/CommonCrypto/CryptorTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CryptorTests.swift 3 | // GoldenKeyTests 4 | // 5 | // Created by Alexander Ignatev on 11/01/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import GoldenKey 11 | 12 | final class CryptorTests: XCTestCase { 13 | 14 | func testCrypt() throws { 15 | do { 16 | let token = "user_token" 17 | let algorithm = CryptorAlgorithm.aes128 18 | let options = CryptorOptions(blockMode: .ecb) 19 | 20 | let pbkdf = PBKDF2(password: "1111", salt: Data("12345678".utf8)) 21 | let key = try pbkdf.keyDerivation(.sha256, keyCount: algorithm.keySize, rounds: 1024) 22 | XCTAssertEqual(key.base64EncodedString(), "Hp+8lUfauUgxo4CGIa8PGw==") 23 | 24 | let encryptedToken = try Cryptor.encrypt(algorithm: algorithm, options: options, key: key, data: Data(token.utf8)) 25 | let decryptedToken = try Cryptor.decrypt(algorithm: algorithm, options: options, key: key, data: encryptedToken) 26 | XCTAssertEqual(String(data: decryptedToken, encoding: .utf8), token) 27 | 28 | let cryptor = try Cryptor(operation: .encrypt, algorithm: algorithm, options: options, key: key) 29 | try cryptor.process(Data("user".utf8)) 30 | try cryptor.process(Data("_token".utf8)) 31 | let result = try cryptor.finalize() 32 | XCTAssertEqual(result, encryptedToken) 33 | } catch { 34 | XCTFail(error.localizedDescription) 35 | } 36 | } 37 | 38 | func testEncrypt_AES256_CBC_PKCS7() { 39 | do { 40 | let openText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " + 41 | "do eiusmod tempor incididunt ut labore et dolore magna aliqua" 42 | let key = Data(base64Encoded: "NqFOyr0w3T6I29eieqFh8aKxAu9PnrgYMKeiuVWRYyE=")! 43 | let iv = Data(base64Encoded: "ztwCgLfy5ADD1Lx+ovq09w==") // random generated 44 | 45 | let encryptedData = try Cryptor.encrypt( 46 | algorithm: .aes256, 47 | options: CryptorOptions(blockMode: .cbc(iv: iv)), 48 | key: key, 49 | data: Data(openText.utf8)) 50 | 51 | let referenceEncryptedText = "bcq961xmUYsJoMVoJhi6NKasa8s/C0gRloefkxPpN86EwKAxsJgjjDtBLVdifiCj" + 52 | "zBWrkdBVIR+YvJLnKrXWzxdK61Q84bCWvmiCFAZ4YsWDFMZIyOtQwIxcl7aKLQzt" + 53 | "d7jTPqR/Q9LS7Q5M0iULQ2NqAW/UHRzFcZ11gV9GghY=" 54 | let referenceEncryptedData = Data(base64Encoded: referenceEncryptedText)! 55 | 56 | XCTAssertEqual(encryptedData, referenceEncryptedData) 57 | } catch { 58 | XCTFail(error.localizedDescription) 59 | } 60 | } 61 | 62 | func testDecrypt_AES256_CBC_PKCS7() { 63 | do { 64 | let encryptedText = "bcq961xmUYsJoMVoJhi6NKasa8s/C0gRloefkxPpN86EwKAxsJgjjDtBLVdifiCj" + 65 | "zBWrkdBVIR+YvJLnKrXWzxdK61Q84bCWvmiCFAZ4YsWDFMZIyOtQwIxcl7aKLQzt" + 66 | "d7jTPqR/Q9LS7Q5M0iULQ2NqAW/UHRzFcZ11gV9GghY=" 67 | let encryptedData = Data(base64Encoded: encryptedText)! 68 | let key = Data(base64Encoded: "NqFOyr0w3T6I29eieqFh8aKxAu9PnrgYMKeiuVWRYyE=")! 69 | let iv = Data(base64Encoded: "ztwCgLfy5ADD1Lx+ovq09w==") 70 | 71 | let decryptedData = try Cryptor.decrypt( 72 | algorithm: .aes256, 73 | options: CryptorOptions(blockMode: .cbc(iv: iv)), 74 | key: key, 75 | data: encryptedData) 76 | let openText = String(data: decryptedData, encoding: .utf8) 77 | 78 | let referenceOpenText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " + 79 | "do eiusmod tempor incididunt ut labore et dolore magna aliqua" 80 | 81 | XCTAssertEqual(openText, referenceOpenText) 82 | } catch { 83 | XCTFail(error.localizedDescription) 84 | } 85 | } 86 | 87 | func testEncrypt_AES256_ECB_PKCS7() { 88 | do { 89 | let openText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " + 90 | "do eiusmod tempor incididunt ut labore et dolore magna aliqua" 91 | let key = Data(base64Encoded: "NqFOyr0w3T6I29eieqFh8aKxAu9PnrgYMKeiuVWRYyE=")! 92 | 93 | let encryptedData = try Cryptor.encrypt( 94 | algorithm: .aes256, 95 | options: CryptorOptions(blockMode: .ecb), 96 | key: key, 97 | data: Data(openText.utf8)) 98 | 99 | let referenceEncryptedText = "djKuuzlvuHkMFupINpfKSCoJMiiskTadgcjdfVO+rTmmF3/Jo63VcbB3dFyo/gHJ" + 100 | "ZRb7lxlnnJj3tMo7HHhTlpeuBvlXM1m6tO4D2ozdR9L/Sp8RJvhdfJdyRS6DdFs1" + 101 | "uWQ9poL3HZIvUM7IQ9rHqjUiGy5e9VNQzWGle7uahFg=" 102 | let referenceEncryptedData = Data(base64Encoded: referenceEncryptedText)! 103 | 104 | XCTAssertEqual(encryptedData, referenceEncryptedData) 105 | } catch { 106 | XCTFail(error.localizedDescription) 107 | } 108 | } 109 | 110 | func testDecrypt_AES256_ECB_PKCS7() { 111 | do { 112 | let encryptedText = "djKuuzlvuHkMFupINpfKSCoJMiiskTadgcjdfVO+rTmmF3/Jo63VcbB3dFyo/gHJ" + 113 | "ZRb7lxlnnJj3tMo7HHhTlpeuBvlXM1m6tO4D2ozdR9L/Sp8RJvhdfJdyRS6DdFs1" + 114 | "uWQ9poL3HZIvUM7IQ9rHqjUiGy5e9VNQzWGle7uahFg=" 115 | let encryptedData = Data(base64Encoded: encryptedText)! 116 | let key = Data(base64Encoded: "NqFOyr0w3T6I29eieqFh8aKxAu9PnrgYMKeiuVWRYyE=")! 117 | 118 | let decryptedData = try Cryptor.decrypt( 119 | algorithm: .aes256, 120 | options: CryptorOptions(blockMode: .ecb), 121 | key: key, 122 | data: encryptedData) 123 | let openText = String(data: decryptedData, encoding: .utf8) 124 | 125 | let referenceOpenText = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed " + 126 | "do eiusmod tempor incididunt ut labore et dolore magna aliqua" 127 | 128 | XCTAssertEqual(openText, referenceOpenText) 129 | } catch { 130 | XCTFail(error.localizedDescription) 131 | } 132 | } 133 | 134 | } 135 | -------------------------------------------------------------------------------- /GoldenKeyTests/CommonCrypto/DigestTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DigestTests.swift 3 | // GoldenKeyTests 4 | // 5 | // Created by Alexander Ignatev on 24/09/2019. 6 | // Copyright © 2019 RedMadRobot. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | import GoldenKey 11 | 12 | final class DigestTests: XCTestCase { 13 | 14 | private final class Message { 15 | let string: String 16 | let count: Int 17 | 18 | private(set) lazy var chunk = Data(string.utf8) 19 | private(set) lazy var data = Data(Array(repeating: chunk, count: count).joined()) 20 | 21 | init(string: String, count: Int = 1) { 22 | self.string = string 23 | self.count = count 24 | } 25 | } 26 | 27 | func testAbc() { 28 | let input = Message(string: "abc") 29 | 30 | assert(input, MD2(), "da853b0d 3f88d99b 30283a69 e6ded6bb") 31 | assert(input, MD4(), "a448017a af21d852 5fc10ae8 7aa6729d") 32 | assert(input, MD5(), "90015098 3cd24fb0 d6963f7d 28e17f72") 33 | assert(input, SHA1(), "a9993e36 4706816a ba3e2571 7850c26c 9cd0d89d") 34 | assert(input, SHA224(), "23097d22 3405d822 8642a477 bda255b3 2aadbce4 bda0b3f7 e36c9da7") 35 | assert(input, SHA256(), "ba7816bf 8f01cfea 414140de 5dae2223 b00361a3 96177a9c b410ff61 f20015ad") 36 | assert(input, SHA384(), "cb00753f 45a35e8b b5a03d69 9ac65007 272c32ab 0eded163 1a8b605a 43ff5bed 8086072b a1e7cc23 58baeca1 34c825a7") 37 | assert(input, SHA512(), "ddaf35a1 93617aba cc417349 ae204131 12e6fa4e 89a97ea2 0a9eeee6 4b55d39a 2192992a 274fc1a8 36ba3c23 a3feebbd 454d4423 643ce80e 2a9ac94f a54ca49f") 38 | } 39 | 40 | func testEmpty() { 41 | let input = Message(string: "") 42 | 43 | assert(input, MD2(), "8350e5a3 e24c153d f2275c9f 80692773") 44 | assert(input, MD4(), "31d6cfe0 d16ae931 b73c59d7 e0c089c0") 45 | assert(input, MD5(), "d41d8cd9 8f00b204 e9800998 ecf8427e") 46 | assert(input, SHA1(), "da39a3ee 5e6b4b0d 3255bfef 95601890 afd80709") 47 | assert(input, SHA224(), "d14a028c 2a3a2bc9 476102bb 288234c4 15a2b01f 828ea62a c5b3e42f") 48 | assert(input, SHA256(), "e3b0c442 98fc1c14 9afbf4c8 996fb924 27ae41e4 649b934c a495991b 7852b855") 49 | assert(input, SHA384(), "38b060a7 51ac9638 4cd9327e b1b1e36a 21fdb711 14be0743 4c0cc7bf 63f6e1da 274edebf e76f65fb d51ad2f1 4898b95b") 50 | assert(input, SHA512(), "cf83e135 7eefb8bd f1542850 d66d8007 d620e405 0b5715dc 83f4a921 d36ce9ce 47d0d13c 5d85f2b0 ff8318d2 877eec2f 63b931bd 47417a81 a538327a f927da3e") 51 | } 52 | 53 | func testMessage448() { 54 | let input = Message(string: "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq") 55 | 56 | assert(input, MD2(), "0dff6b39 8ad5a62a c8d97566 b80c3a7f") 57 | assert(input, MD4(), "4691a9ec 81b1a6bd 1ab85572 40b245c5") 58 | assert(input, MD5(), "8215ef07 96a20bca aae116d3 876c664a") 59 | assert(input, SHA1(), "84983e44 1c3bd26e baae4aa1 f95129e5 e54670f1") 60 | assert(input, SHA224(), "75388b16 512776cc 5dba5da1 fd890150 b0c6455c b4f58b19 52522525") 61 | assert(input, SHA256(), "248d6a61 d20638b8 e5c02693 0c3e6039 a33ce459 64ff2167 f6ecedd4 19db06c1") 62 | assert(input, SHA384(), "3391fddd fc8dc739 3707a65b 1b470939 7cf8b1d1 62af05ab fe8f450d e5f36bc6 b0455a85 20bc4e6f 5fe95b1f e3c8452b") 63 | assert(input, SHA512(), "204a8fc6 dda82f0a 0ced7beb 8e08a416 57c16ef4 68b228a8 279be331 a703c335 96fd15c1 3b1b07f9 aa1d3bea 57789ca0 31ad85c7 a71dd703 54ec6312 38ca3445") 64 | } 65 | 66 | func testMessage896() { 67 | let input = Message(string: "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmnoijklmnopjklmnopqklmnopqrlmnopqrsmnopqrstnopqrstu") 68 | 69 | assert(input, MD2(), "2c194d03 76411dc0 b8485d3a be2a4b6b") 70 | assert(input, MD4(), "2102d1d9 4bd58ebf 5aa25c30 5bb783ad") 71 | assert(input, MD5(), "03dd8807 a93175fb 062dfb55 dc7d359c") 72 | assert(input, SHA1(), "a49b2446 a02c645b f419f995 b6709125 3a04a259") 73 | assert(input, SHA224(), "c97ca9a5 59850ce9 7a04a96d ef6d99a9 e0e0e2ab 14e6b8df 265fc0b3") 74 | assert(input, SHA256(), "cf5b16a7 78af8380 036ce59e 7b049237 0b249b11 e8f07a51 afac4503 7afee9d1") 75 | assert(input, SHA384(), "09330c33 f71147e8 3d192fc7 82cd1b47 53111b17 3b3b05d2 2fa08086 e3b0f712 fcc7c71a 557e2db9 66c3e9fa 91746039") 76 | assert(input, SHA512(), "8e959b75 dae313da 8cf4f728 14fc143f 8f7779c6 eb9f7fa1 7299aead b6889018 501d289e 4900f7e4 331b99de c4b5433a c7d329ee b6dd2654 5e96e55b 874be909") 77 | } 78 | 79 | func testMessageOneMillionA() { 80 | let input = Message(string: "a", count: 1_000_000) 81 | 82 | assert(input, MD2(), "8c0a09ff 1216ecaf 95c81309 53c62efd") 83 | assert(input, MD4(), "bbce80cc 6bb65e5c 6745e30d 4eeca9a4") 84 | assert(input, MD5(), "7707d6ae 4e027c70 eea2a935 c2296f21") 85 | assert(input, SHA1(), "34aa973c d4c4daa4 f61eeb2b dbad2731 6534016f") 86 | assert(input, SHA224(), "20794655 980c91d8 bbb4c1ea 97618a4b f03f4258 1948b2ee 4ee7ad67") 87 | assert(input, SHA256(), "cdc76e5c 9914fb92 81a1c7e2 84d73e67 f1809a48 a497200e 046d39cc c7112cd0") 88 | assert(input, SHA384(), "9d0e1809 716474cb 086e834e 310a4a1c ed149e9c 00f24852 7972cec5 704c2a5b 07b8b3dc 38ecc4eb ae97ddd8 7f3d8985") 89 | assert(input, SHA512(), "e718483d 0ce76964 4e2e42c7 bc15b463 8e1f98b1 3b204428 5632a803 afa973eb de0ff244 877ea60a 4cb0432c e577c31b eb009c5c 2c49aa2e 4eadb217 ad8cc09b") 90 | } 91 | 92 | func _testExtremelyLong() { 93 | let input = Message(string: "abcdefghbcdefghicdefghijdefghijkefghijklfghijklmghijklmnhijklmno", count: 16_777_216) 94 | 95 | assert(input, SHA1(), "7789f0c9 ef7bfc40 d9331114 3dfbe69e 2017f592") 96 | assert(input, SHA224(), "b5989713 ca4fe47a 009f8621 980b34e6 d63ed306 3b2a0a2c 867d8a85") 97 | assert(input, SHA256(), "50e72a0e 26442fe2 552dc393 8ac58658 228c0cbf b1d2ca87 2ae43526 6fcd055e") 98 | assert(input, SHA384(), "5441235c c0235341 ed806a64 fb354742 b5e5c02a 3c5cb71b 5f63fb79 3458d8fd ae599c8c d8884943 c04f11b3 1b89f023") 99 | assert(input, SHA512(), "b47c9334 21ea2db1 49ad6e10 fce6c7f9 3d075238 0180ffd7 f4629a71 2134831d 77be6091 b819ed35 2c2967a2 e2d4fa50 50723c96 30691f1a 05a7281d be6c1086") 100 | } 101 | 102 | private func hex(data: Data) -> String { 103 | var result: [String] = [] 104 | var start = data.startIndex 105 | while let end = data.index(start, offsetBy: 4, limitedBy: data.endIndex) { 106 | let chunk = data[start.. 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Redmadrobot 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.0 2 | // 3 | // Package.swift 4 | // GoldenKey 5 | // 6 | // Created by Ivan Vavilov on 11/10/2019. 7 | // Copyright © 2019 RedMadRobot. All rights reserved. 8 | // 9 | 10 | import PackageDescription 11 | 12 | let package = Package( 13 | name: "GoldenKey", 14 | platforms: [ 15 | .macOS(.v10_12), 16 | .iOS(.v10), 17 | .tvOS(.v10), 18 | .watchOS(.v3) 19 | ], 20 | products: [ 21 | .library( 22 | name: "GoldenKey", 23 | targets: ["GoldenKey"]) 24 | ], 25 | targets: [ 26 | .target( 27 | name: "GoldenKey", 28 | path: "GoldenKey"), 29 | .testTarget( 30 | name: "GoldenKeyTests", 31 | dependencies: ["GoldenKey"], 32 | path: "GoldenKeyTests") 33 | ], 34 | swiftLanguageVersions: [ 35 | .v5 36 | ] 37 | ) -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | GoldenKey 3 |

4 | 5 | # GoldenKey 6 | 7 | Swift wrapper around CommonCrypto and Security frameworks 8 | 9 | ## Common Digest 10 | 11 | Supported algorithms: MD2, MD4, MD5, SHA1, SHA224, SHA256, SHA384, SHA512. 12 | 13 | Stream hasher. 14 | 15 | ```swift 16 | let sha = SHA256() 17 | sha.update(data: Data("12".utf8)) 18 | sha.update(data: [1, 2]) 19 | 20 | let degest = sha.finalize() 21 | ``` 22 | 23 | One shot. 24 | 25 | ```swift 26 | let digest = SHA256.hash(data: Data("123".utf8)) 27 | ``` 28 | 29 | All hash functions return type conform to `Digest` protocol . 30 | You can convert digest to common types like a `Data` and `[UInt8]`. 31 | 32 | ```swift 33 | let data = Data(digest) 34 | let bytes: [UInt8] = Array(digest) 35 | ``` 36 | 37 | ## HMAC 38 | hash-based message authentication code 39 | 40 | Stream hasher. 41 | 42 | ```swift 43 | let key = Data("secret_key".utf8) 44 | let hmac = HMAC(algorithm: .md5, key: key) 45 | 46 | hmac.update(data: Data("ab".utf8)) 47 | hmac.update(data: Data("cd".utf8)) 48 | let hash = hmac.finalize() 49 | ``` 50 | 51 | One shot. 52 | 53 | ```swift 54 | let key = Data("secret_key".utf8) 55 | let data = Data("abcd".utf8) 56 | 57 | let hash = HMAC.hash(algorithm: .sha224, data: data, key: key) 58 | ``` 59 | 60 | ## Setup for development 61 | 62 | ```bash 63 | $ mkdir gyb 64 | $ cd gyb 65 | $ wget https://github.com/apple/swift/raw/master/utils/gyb 66 | $ wget https://github.com/apple/swift/raw/master/utils/gyb.py 67 | $ chmod +x gyb 68 | ``` 69 | 70 | # Efficient way to calculate hash of a large file 71 | 72 | To calculate hash of a large file use `DispatchIO`. 73 | 74 | In the example below `DispatchIO` reads a file by chunks and process every chunk by calling update method of `SHA256` class. 75 | 76 | The default chunk size is set to 128 MB to limit maximum memory usage. 77 | 78 | ![Screenshot](./scr.png) 79 | 80 | ```swift 81 | import Foundation 82 | import GoldenKey 83 | 84 | final class FileHash { 85 | 86 | private let workQueue: DispatchQueue 87 | private let dispatchIO: DispatchIO 88 | 89 | /// Opens and prepares a file for reading. 90 | /// - Parameter fileURL: URL of the file. 91 | /// - Parameter workQueue: DispatchQueue on which to perform work (read and calculating hash). 92 | /// - Parameter queue: DispatchQueue of the completion handler. 93 | /// - Parameter completion: Calls when the file closed. Useful when you want to calculate hash of multiple files sequentially. 94 | init( 95 | fileURL: URL, 96 | workQueue: DispatchQueue = .init(label: "work", qos: .userInitiated), 97 | queue: DispatchQueue = .main, 98 | completion: (() -> Void)? = nil) throws { 99 | 100 | self.workQueue = workQueue 101 | 102 | let fileHandle = try FileHandle(forReadingFrom: fileURL) 103 | 104 | dispatchIO = DispatchIO( 105 | type: .stream, 106 | fileDescriptor: fileHandle.fileDescriptor, 107 | queue: queue, 108 | cleanupHandler: { _ in 109 | fileHandle.closeFile() 110 | queue.async { completion?() } 111 | } 112 | ) 113 | dispatchIO.setLimit(lowWater: Int.max) 114 | } 115 | 116 | /// Calculates hash of the file 117 | /// - Parameter hashFunctionType: Hash function type. SHA256.self for example. 118 | /// - Parameter chunkSize: Max memory usage. 128 MB by default. 119 | /// - Parameter queue: DispatchQueue of the completion handler. 120 | /// - Parameter completion: Completion handler. 121 | func calculateHash( 122 | hashFunctionType: Digest.Type, 123 | chunkSize: Int = 128 * 1024 * 1024, 124 | queue: DispatchQueue = .main, 125 | completion: @escaping (Result) -> Void) { 126 | 127 | let hashFunction = hashFunctionType.init() 128 | 129 | func readNextChunk() { 130 | dispatchIO.read(offset: 0, length: chunkSize, queue: workQueue) { [weak self] (done, data, error) in 131 | guard let self = self else { return } 132 | 133 | guard error == 0 else { 134 | let error = POSIXError(POSIXErrorCode(rawValue: error)!) 135 | self.dispatchIO.close(flags: .stop) 136 | queue.async { 137 | completion(.failure(error)) 138 | } 139 | return 140 | } 141 | 142 | guard let data = data else { return } 143 | 144 | if data.isEmpty == false { 145 | data.regions.forEach { hashFunction.update(data: $0) } 146 | } 147 | 148 | if done, data.isEmpty { 149 | self.dispatchIO.close() 150 | 151 | let digest = hashFunction.finalize() 152 | queue.async { 153 | completion(.success(digest)) 154 | } 155 | } 156 | 157 | if done, data.isEmpty == false { 158 | readNextChunk() 159 | } 160 | } 161 | } 162 | 163 | readNextChunk() 164 | } 165 | 166 | } 167 | ``` 168 | 169 | ## Usage example 170 | ```swift 171 | 172 | extension Data { 173 | /// Presents Data in hex format 174 | var hexDescription: String { 175 | return reduce("") {$0 + String(format: "%02x", $1)} 176 | } 177 | } 178 | 179 | do { 180 | let fileURL = URL(string: "absolute_path_to_file")! 181 | let fileHash = try FileHash(fileURL: fileURL) 182 | fileHash.calculateHash(hashFunctionType: SHA256.self) { result in 183 | switch result { 184 | case .success(let hash): 185 | print(hash.hexDescription) 186 | case .failure(let error): 187 | print(error.localizedDescription) 188 | } 189 | } 190 | } catch (let error) { 191 | print(error.localizedDescription) 192 | } 193 | ``` -------------------------------------------------------------------------------- /gyb/gyb: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2.7 2 | import gyb 3 | gyb.main() 4 | -------------------------------------------------------------------------------- /gyb/gyb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # GYB: Generate Your Boilerplate (improved names welcome; at least 3 | # this one's short). See -h output for instructions 4 | 5 | from __future__ import print_function 6 | 7 | import os 8 | import re 9 | import sys 10 | try: 11 | from cStringIO import StringIO 12 | except ImportError: 13 | from io import StringIO 14 | import textwrap 15 | import tokenize 16 | from bisect import bisect 17 | 18 | try: 19 | basestring 20 | except NameError: 21 | basestring = str 22 | 23 | 24 | def get_line_starts(s): 25 | """Return a list containing the start index of each line in s. 26 | 27 | The list also contains a sentinel index for the end of the string, 28 | so there will be one more element in the list than there are lines 29 | in the string 30 | """ 31 | starts = [0] 32 | 33 | for line in s.split('\n'): 34 | starts.append(starts[-1] + len(line) + 1) 35 | 36 | starts[-1] -= 1 37 | return starts 38 | 39 | 40 | def strip_trailing_nl(s): 41 | """If s ends with a newline, drop it; else return s intact""" 42 | return s[:-1] if s.endswith('\n') else s 43 | 44 | 45 | def split_lines(s): 46 | """Split s into a list of lines, each of which has a trailing newline 47 | 48 | If the lines are later concatenated, the result is s, possibly 49 | with a single appended newline. 50 | """ 51 | return [l + '\n' for l in s.split('\n')] 52 | 53 | 54 | # text on a line up to the first '$$', '${', or '%%' 55 | literalText = r'(?: [^$\n%] | \$(?![${]) | %(?!%) )*' 56 | 57 | # The part of an '%end' line that follows the '%' sign 58 | linesClose = r'[\ \t]* end [\ \t]* (?: \# .* )? $' 59 | 60 | # Note: Where "# Absorb" appears below, the regexp attempts to eat up 61 | # through the end of ${...} and %{...}% constructs. In reality we 62 | # handle this with the Python tokenizer, which avoids mis-detections 63 | # due to nesting, comments and strings. This extra absorption in the 64 | # regexp facilitates testing the regexp on its own, by preventing the 65 | # interior of some of these constructs from being treated as literal 66 | # text. 67 | tokenize_re = re.compile( 68 | r''' 69 | # %-lines and %{...}-blocks 70 | # \n? # absorb one preceding newline 71 | ^ 72 | (?: 73 | (?P 74 | (?P<_indent> [\ \t]* % (?! [{%] ) [\ \t]* ) (?! [\ \t] | ''' + 75 | linesClose + r''' ) .* 76 | ( \n (?P=_indent) (?! ''' + linesClose + r''' ) .* ) * 77 | ) 78 | | (?P [\ \t]* % [ \t]* ''' + linesClose + r''' ) 79 | | [\ \t]* (?P %\{ ) 80 | (?: [^}]| \} (?!%) )* \}% # Absorb 81 | ) 82 | \n? # absorb one trailing newline 83 | 84 | # Substitutions 85 | | (?P \$\{ ) 86 | [^}]* \} # Absorb 87 | 88 | # %% and $$ are literal % and $ respectively 89 | | (?P[$%]) (?P=symbol) 90 | 91 | # Literal text 92 | | (?P ''' + literalText + r''' 93 | (?: 94 | # newline that doesn't precede space+% 95 | (?: \n (?! [\ \t]* %[^%] ) ) 96 | ''' + literalText + r''' 97 | )* 98 | \n? 99 | ) 100 | ''', re.VERBOSE | re.MULTILINE) 101 | 102 | gyb_block_close = re.compile(r'\}%[ \t]*\n?') 103 | 104 | 105 | def token_pos_to_index(token_pos, start, line_starts): 106 | """Translate a tokenize (line, column) pair into an absolute 107 | position in source text given the position where we started 108 | tokenizing and a list that maps lines onto their starting 109 | character indexes. 110 | """ 111 | relative_token_line_plus1, token_col = token_pos 112 | 113 | # line number where we started tokenizing 114 | start_line_num = bisect(line_starts, start) - 1 115 | 116 | # line number of the token in the whole text 117 | abs_token_line = relative_token_line_plus1 - 1 + start_line_num 118 | 119 | # if found in the first line, adjust the end column to account 120 | # for the extra text 121 | if relative_token_line_plus1 == 1: 122 | token_col += start - line_starts[start_line_num] 123 | 124 | # Sometimes tokenizer errors report a line beyond the last one 125 | if abs_token_line >= len(line_starts): 126 | return line_starts[-1] 127 | 128 | return line_starts[abs_token_line] + token_col 129 | 130 | 131 | def tokenize_python_to_unmatched_close_curly(source_text, start, line_starts): 132 | """Apply Python's tokenize to source_text starting at index start 133 | while matching open and close curly braces. When an unmatched 134 | close curly brace is found, return its index. If not found, 135 | return len(source_text). If there's a tokenization error, return 136 | the position of the error. 137 | """ 138 | stream = StringIO(source_text) 139 | stream.seek(start) 140 | nesting = 0 141 | 142 | try: 143 | for kind, text, token_start, token_end, line_text \ 144 | in tokenize.generate_tokens(stream.readline): 145 | 146 | if text == '{': 147 | nesting += 1 148 | elif text == '}': 149 | nesting -= 1 150 | if nesting < 0: 151 | return token_pos_to_index(token_start, start, line_starts) 152 | 153 | except tokenize.TokenError as error: 154 | (message, error_pos) = error.args 155 | return token_pos_to_index(error_pos, start, line_starts) 156 | 157 | return len(source_text) 158 | 159 | 160 | def tokenize_template(template_text): 161 | r"""Given the text of a template, returns an iterator over 162 | (tokenType, token, match) tuples. 163 | 164 | **Note**: this is template syntax tokenization, not Python 165 | tokenization. 166 | 167 | When a non-literal token is matched, a client may call 168 | iter.send(pos) on the iterator to reset the position in 169 | template_text at which scanning will resume. 170 | 171 | This function provides a base level of tokenization which is 172 | then refined by ParseContext.token_generator. 173 | 174 | >>> from pprint import * 175 | >>> pprint(list((kind, text) for kind, text, _ in tokenize_template( 176 | ... '%for x in range(10):\n% print x\n%end\njuicebox'))) 177 | [('gybLines', '%for x in range(10):\n% print x'), 178 | ('gybLinesClose', '%end'), 179 | ('literal', 'juicebox')] 180 | 181 | >>> pprint(list((kind, text) for kind, text, _ in tokenize_template( 182 | ... '''Nothing 183 | ... % if x: 184 | ... % for i in range(3): 185 | ... ${i} 186 | ... % end 187 | ... % else: 188 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT 189 | ... '''))) 190 | [('literal', 'Nothing\n'), 191 | ('gybLines', '% if x:\n% for i in range(3):'), 192 | ('substitutionOpen', '${'), 193 | ('literal', '\n'), 194 | ('gybLinesClose', '% end'), 195 | ('gybLines', '% else:'), 196 | ('literal', 'THIS SHOULD NOT APPEAR IN THE OUTPUT\n')] 197 | 198 | >>> for kind, text, _ in tokenize_template(''' 199 | ... This is $some$ literal stuff containing a ${substitution} 200 | ... followed by a %{...} block: 201 | ... %{ 202 | ... # Python code 203 | ... }% 204 | ... and here $${are} some %-lines: 205 | ... % x = 1 206 | ... % y = 2 207 | ... % if z == 3: 208 | ... % print '${hello}' 209 | ... % end 210 | ... % for x in zz: 211 | ... % print x 212 | ... % # different indentation 213 | ... % twice 214 | ... and some lines that literally start with a %% token 215 | ... %% first line 216 | ... %% second line 217 | ... '''): 218 | ... print((kind, text.strip().split('\n',1)[0])) 219 | ('literal', 'This is $some$ literal stuff containing a') 220 | ('substitutionOpen', '${') 221 | ('literal', 'followed by a %{...} block:') 222 | ('gybBlockOpen', '%{') 223 | ('literal', 'and here ${are} some %-lines:') 224 | ('gybLines', '% x = 1') 225 | ('gybLinesClose', '% end') 226 | ('gybLines', '% for x in zz:') 227 | ('gybLines', '% # different indentation') 228 | ('gybLines', '% twice') 229 | ('literal', 'and some lines that literally start with a % token') 230 | """ 231 | pos = 0 232 | end = len(template_text) 233 | 234 | saved_literal = [] 235 | literal_first_match = None 236 | 237 | while pos < end: 238 | m = tokenize_re.match(template_text, pos, end) 239 | 240 | # pull out the one matched key (ignoring internal patterns starting 241 | # with _) 242 | ((kind, text), ) = ( 243 | (kind, text) for (kind, text) in m.groupdict().items() 244 | if text is not None and kind[0] != '_') 245 | 246 | if kind in ('literal', 'symbol'): 247 | if len(saved_literal) == 0: 248 | literal_first_match = m 249 | # literals and symbols get batched together 250 | saved_literal.append(text) 251 | pos = None 252 | else: 253 | # found a non-literal. First yield any literal we've accumulated 254 | if saved_literal != []: 255 | yield 'literal', ''.join(saved_literal), literal_first_match 256 | saved_literal = [] 257 | 258 | # Then yield the thing we found. If we get a reply, it's 259 | # the place to resume tokenizing 260 | pos = yield kind, text, m 261 | 262 | # If we were not sent a new position by our client, resume 263 | # tokenizing at the end of this match. 264 | if pos is None: 265 | pos = m.end(0) 266 | else: 267 | # Client is not yet ready to process next token 268 | yield 269 | 270 | if saved_literal != []: 271 | yield 'literal', ''.join(saved_literal), literal_first_match 272 | 273 | 274 | def split_gyb_lines(source_lines): 275 | r"""Return a list of lines at which to split the incoming source 276 | 277 | These positions represent the beginnings of python line groups that 278 | will require a matching %end construct if they are to be closed. 279 | 280 | >>> src = split_lines('''\ 281 | ... if x: 282 | ... print x 283 | ... if y: # trailing comment 284 | ... print z 285 | ... if z: # another comment\ 286 | ... ''') 287 | >>> s = split_gyb_lines(src) 288 | >>> len(s) 289 | 2 290 | >>> src[s[0]] 291 | ' print z\n' 292 | >>> s[1] - len(src) 293 | 0 294 | 295 | >>> src = split_lines('''\ 296 | ... if x: 297 | ... if y: print 1 298 | ... if z: 299 | ... print 2 300 | ... pass\ 301 | ... ''') 302 | >>> s = split_gyb_lines(src) 303 | >>> len(s) 304 | 1 305 | >>> src[s[0]] 306 | ' if y: print 1\n' 307 | 308 | >>> src = split_lines('''\ 309 | ... if x: 310 | ... if y: 311 | ... print 1 312 | ... print 2 313 | ... ''') 314 | >>> s = split_gyb_lines(src) 315 | >>> len(s) 316 | 2 317 | >>> src[s[0]] 318 | ' if y:\n' 319 | >>> src[s[1]] 320 | ' print 1\n' 321 | """ 322 | last_token_text, last_token_kind = None, None 323 | unmatched_indents = [] 324 | 325 | dedents = 0 326 | try: 327 | for token_kind, token_text, token_start, \ 328 | (token_end_line, token_end_col), line_text \ 329 | in tokenize.generate_tokens(lambda i=iter(source_lines): 330 | next(i)): 331 | 332 | if token_kind in (tokenize.COMMENT, tokenize.ENDMARKER): 333 | continue 334 | 335 | if token_text == '\n' and last_token_text == ':': 336 | unmatched_indents.append(token_end_line) 337 | 338 | # The tokenizer appends dedents at EOF; don't consider 339 | # those as matching indentations. Instead just save them 340 | # up... 341 | if last_token_kind == tokenize.DEDENT: 342 | dedents += 1 343 | # And count them later, when we see something real. 344 | if token_kind != tokenize.DEDENT and dedents > 0: 345 | unmatched_indents = unmatched_indents[:-dedents] 346 | dedents = 0 347 | 348 | last_token_text, last_token_kind = token_text, token_kind 349 | 350 | except tokenize.TokenError: 351 | # Let the later compile() call report the error 352 | return [] 353 | 354 | if last_token_text == ':': 355 | unmatched_indents.append(len(source_lines)) 356 | 357 | return unmatched_indents 358 | 359 | 360 | def code_starts_with_dedent_keyword(source_lines): 361 | r"""Return True iff the incoming Python source_lines begin with "else", 362 | "elif", "except", or "finally". 363 | 364 | Initial comments and whitespace are ignored. 365 | 366 | >>> code_starts_with_dedent_keyword(split_lines('if x in y: pass')) 367 | False 368 | >>> code_starts_with_dedent_keyword(split_lines('except ifSomethingElse:')) 369 | True 370 | >>> code_starts_with_dedent_keyword( 371 | ... split_lines('\n# comment\nelse: # yes')) 372 | True 373 | """ 374 | token_text = None 375 | for token_kind, token_text, _, _, _ \ 376 | in tokenize.generate_tokens(lambda i=iter(source_lines): next(i)): 377 | 378 | if token_kind != tokenize.COMMENT and token_text.strip() != '': 379 | break 380 | 381 | return token_text in ('else', 'elif', 'except', 'finally') 382 | 383 | 384 | class ParseContext(object): 385 | 386 | """State carried through a parse of a template""" 387 | 388 | filename = '' 389 | template = '' 390 | line_starts = [] 391 | code_start_line = -1 392 | code_text = None 393 | tokens = None # The rest of the tokens 394 | close_lines = False 395 | 396 | def __init__(self, filename, template=None): 397 | self.filename = os.path.abspath(filename) 398 | if template is None: 399 | with open(filename) as f: 400 | self.template = f.read() 401 | else: 402 | self.template = template 403 | self.line_starts = get_line_starts(self.template) 404 | self.tokens = self.token_generator(tokenize_template(self.template)) 405 | self.next_token() 406 | 407 | def pos_to_line(self, pos): 408 | return bisect(self.line_starts, pos) - 1 409 | 410 | def token_generator(self, base_tokens): 411 | r"""Given an iterator over (kind, text, match) triples (see 412 | tokenize_template above), return a refined iterator over 413 | token_kinds. 414 | 415 | Among other adjustments to the elements found by base_tokens, 416 | this refined iterator tokenizes python code embedded in 417 | template text to help determine its true extent. The 418 | expression "base_tokens.send(pos)" is used to reset the index at 419 | which base_tokens resumes scanning the underlying text. 420 | 421 | >>> ctx = ParseContext('dummy', ''' 422 | ... %for x in y: 423 | ... % print x 424 | ... % end 425 | ... literally 426 | ... ''') 427 | >>> while ctx.token_kind: 428 | ... print((ctx.token_kind, ctx.code_text or ctx.token_text)) 429 | ... ignored = ctx.next_token() 430 | ('literal', '\n') 431 | ('gybLinesOpen', 'for x in y:\n') 432 | ('gybLines', ' print x\n') 433 | ('gybLinesClose', '% end') 434 | ('literal', 'literally\n') 435 | 436 | >>> ctx = ParseContext('dummy', 437 | ... '''Nothing 438 | ... % if x: 439 | ... % for i in range(3): 440 | ... ${i} 441 | ... % end 442 | ... % else: 443 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT 444 | ... ''') 445 | >>> while ctx.token_kind: 446 | ... print((ctx.token_kind, ctx.code_text or ctx.token_text)) 447 | ... ignored = ctx.next_token() 448 | ('literal', 'Nothing\n') 449 | ('gybLinesOpen', 'if x:\n') 450 | ('gybLinesOpen', ' for i in range(3):\n') 451 | ('substitutionOpen', 'i') 452 | ('literal', '\n') 453 | ('gybLinesClose', '% end') 454 | ('gybLinesOpen', 'else:\n') 455 | ('literal', 'THIS SHOULD NOT APPEAR IN THE OUTPUT\n') 456 | 457 | >>> ctx = ParseContext('dummy', 458 | ... '''% for x in [1, 2, 3]: 459 | ... % if x == 1: 460 | ... literal1 461 | ... % elif x > 1: # add output line here to fix bug 462 | ... % if x == 2: 463 | ... literal2 464 | ... % end 465 | ... % end 466 | ... % end 467 | ... ''') 468 | >>> while ctx.token_kind: 469 | ... print((ctx.token_kind, ctx.code_text or ctx.token_text)) 470 | ... ignored = ctx.next_token() 471 | ('gybLinesOpen', 'for x in [1, 2, 3]:\n') 472 | ('gybLinesOpen', ' if x == 1:\n') 473 | ('literal', 'literal1\n') 474 | ('gybLinesOpen', 'elif x > 1: # add output line here to fix bug\n') 475 | ('gybLinesOpen', ' if x == 2:\n') 476 | ('literal', 'literal2\n') 477 | ('gybLinesClose', '% end') 478 | ('gybLinesClose', '% end') 479 | ('gybLinesClose', '% end') 480 | """ 481 | for self.token_kind, self.token_text, self.token_match in base_tokens: 482 | kind = self.token_kind 483 | self.code_text = None 484 | 485 | # Do we need to close the current lines? 486 | self.close_lines = kind == 'gybLinesClose' 487 | 488 | # %{...}% and ${...} constructs 489 | if kind.endswith('Open'): 490 | 491 | # Tokenize text that follows as Python up to an unmatched '}' 492 | code_start = self.token_match.end(kind) 493 | self.code_start_line = self.pos_to_line(code_start) 494 | 495 | close_pos = tokenize_python_to_unmatched_close_curly( 496 | self.template, code_start, self.line_starts) 497 | self.code_text = self.template[code_start:close_pos] 498 | yield kind 499 | 500 | if (kind == 'gybBlockOpen'): 501 | # Absorb any '}% \n' 502 | m2 = gyb_block_close.match(self.template, close_pos) 503 | if not m2: 504 | raise ValueError("Invalid block closure") 505 | next_pos = m2.end(0) 506 | else: 507 | assert kind == 'substitutionOpen' 508 | # skip past the closing '}' 509 | next_pos = close_pos + 1 510 | 511 | # Resume tokenizing after the end of the code. 512 | base_tokens.send(next_pos) 513 | 514 | elif kind == 'gybLines': 515 | 516 | self.code_start_line = self.pos_to_line( 517 | self.token_match.start('gybLines')) 518 | indentation = self.token_match.group('_indent') 519 | 520 | # Strip off the leading indentation and %-sign 521 | source_lines = re.split( 522 | '^' + re.escape(indentation), 523 | self.token_match.group('gybLines') + '\n', 524 | flags=re.MULTILINE)[1:] 525 | 526 | if code_starts_with_dedent_keyword(source_lines): 527 | self.close_lines = True 528 | 529 | last_split = 0 530 | for line in split_gyb_lines(source_lines): 531 | self.token_kind = 'gybLinesOpen' 532 | self.code_text = ''.join(source_lines[last_split:line]) 533 | yield self.token_kind 534 | last_split = line 535 | self.code_start_line += line - last_split 536 | self.close_lines = False 537 | 538 | self.code_text = ''.join(source_lines[last_split:]) 539 | if self.code_text: 540 | self.token_kind = 'gybLines' 541 | yield self.token_kind 542 | else: 543 | yield self.token_kind 544 | 545 | def next_token(self): 546 | """Move to the next token""" 547 | for kind in self.tokens: 548 | return self.token_kind 549 | 550 | self.token_kind = None 551 | 552 | 553 | _default_line_directive = \ 554 | '// ###sourceLocation(file: "%(file)s", line: %(line)d)' 555 | 556 | 557 | class ExecutionContext(object): 558 | 559 | """State we pass around during execution of a template""" 560 | 561 | def __init__(self, line_directive=_default_line_directive, 562 | **local_bindings): 563 | self.local_bindings = local_bindings 564 | self.line_directive = line_directive 565 | self.local_bindings['__context__'] = self 566 | self.result_text = [] 567 | self.last_file_line = None 568 | 569 | def append_text(self, text, file, line): 570 | # see if we need to inject a line marker 571 | if self.line_directive: 572 | if (file, line) != self.last_file_line: 573 | # We can only insert the line directive at a line break 574 | if len(self.result_text) == 0 \ 575 | or self.result_text[-1].endswith('\n'): 576 | if sys.platform == 'win32': 577 | file = file.replace('\\', '/') 578 | substitutions = {'file': file, 'line': line + 1} 579 | format_str = self.line_directive + '\n' 580 | self.result_text.append(format_str % substitutions) 581 | # But if the new text contains any line breaks, we can create 582 | # one 583 | elif '\n' in text: 584 | i = text.find('\n') 585 | self.result_text.append(text[:i + 1]) 586 | # and try again 587 | self.append_text(text[i + 1:], file, line + 1) 588 | return 589 | 590 | self.result_text.append(text) 591 | self.last_file_line = (file, line + text.count('\n')) 592 | 593 | 594 | class ASTNode(object): 595 | 596 | """Abstract base class for template AST nodes""" 597 | 598 | def __init__(self): 599 | raise NotImplementedError("ASTNode.__init__ is not implemented.") 600 | 601 | def execute(self, context): 602 | raise NotImplementedError("ASTNode.execute is not implemented.") 603 | 604 | def __str__(self, indent=''): 605 | raise NotImplementedError("ASTNode.__str__ is not implemented.") 606 | 607 | def format_children(self, indent): 608 | if not self.children: 609 | return ' []' 610 | 611 | return '\n'.join( 612 | ['', indent + '['] + 613 | [x.__str__(indent + 4 * ' ') for x in self.children] + 614 | [indent + ']']) 615 | 616 | 617 | class Block(ASTNode): 618 | 619 | """A sequence of other AST nodes, to be executed in order""" 620 | 621 | children = [] 622 | 623 | def __init__(self, context): 624 | self.children = [] 625 | 626 | while context.token_kind and not context.close_lines: 627 | if context.token_kind == 'literal': 628 | node = Literal 629 | else: 630 | node = Code 631 | self.children.append(node(context)) 632 | 633 | def execute(self, context): 634 | for x in self.children: 635 | x.execute(context) 636 | 637 | def __str__(self, indent=''): 638 | return indent + 'Block:' + self.format_children(indent) 639 | 640 | 641 | class Literal(ASTNode): 642 | 643 | """An AST node that generates literal text""" 644 | 645 | def __init__(self, context): 646 | self.text = context.token_text 647 | start_position = context.token_match.start(context.token_kind) 648 | self.start_line_number = context.pos_to_line(start_position) 649 | self.filename = context.filename 650 | context.next_token() 651 | 652 | def execute(self, context): 653 | context.append_text(self.text, self.filename, self.start_line_number) 654 | 655 | def __str__(self, indent=''): 656 | return '\n'.join( 657 | [indent + x for x in ['Literal:'] + 658 | strip_trailing_nl(self.text).split('\n')]) 659 | 660 | 661 | class Code(ASTNode): 662 | 663 | """An AST node that is evaluated as Python""" 664 | 665 | code = None 666 | children = () 667 | kind = None 668 | 669 | def __init__(self, context): 670 | 671 | source = '' 672 | source_line_count = 0 673 | 674 | def accumulate_code(): 675 | s = source + (context.code_start_line - source_line_count) * '\n' \ 676 | + textwrap.dedent(context.code_text) 677 | line_count = context.code_start_line + \ 678 | context.code_text.count('\n') 679 | context.next_token() 680 | return s, line_count 681 | 682 | eval_exec = 'exec' 683 | if context.token_kind.startswith('substitution'): 684 | eval_exec = 'eval' 685 | source, source_line_count = accumulate_code() 686 | source = '(' + source.strip() + ')' 687 | 688 | else: 689 | while context.token_kind == 'gybLinesOpen': 690 | source, source_line_count = accumulate_code() 691 | source += ' __children__[%d].execute(__context__)\n' % len( 692 | self.children) 693 | source_line_count += 1 694 | 695 | self.children += (Block(context),) 696 | 697 | if context.token_kind == 'gybLinesClose': 698 | context.next_token() 699 | 700 | if context.token_kind == 'gybLines': 701 | source, source_line_count = accumulate_code() 702 | 703 | # Only handle a substitution as part of this code block if 704 | # we don't already have some %-lines. 705 | elif context.token_kind == 'gybBlockOpen': 706 | 707 | # Opening ${...} and %{...}% constructs 708 | source, source_line_count = accumulate_code() 709 | 710 | self.filename = context.filename 711 | self.start_line_number = context.code_start_line 712 | self.code = compile(source, context.filename, eval_exec) 713 | self.source = source 714 | 715 | def execute(self, context): 716 | # Save __children__ from the local bindings 717 | save_children = context.local_bindings.get('__children__') 718 | # Execute the code with our __children__ in scope 719 | context.local_bindings['__children__'] = self.children 720 | context.local_bindings['__file__'] = self.filename 721 | result = eval(self.code, context.local_bindings) 722 | 723 | if context.local_bindings['__children__'] is not self.children: 724 | raise ValueError("The code is not allowed to mutate __children__") 725 | # Restore the bindings 726 | context.local_bindings['__children__'] = save_children 727 | 728 | # If we got a result, the code was an expression, so append 729 | # its value 730 | if result is not None \ 731 | or (isinstance(result, basestring) and result != ''): 732 | from numbers import Number, Integral 733 | result_string = None 734 | if isinstance(result, Number) and not isinstance(result, Integral): 735 | result_string = repr(result) 736 | else: 737 | result_string = str(result) 738 | context.append_text( 739 | result_string, self.filename, self.start_line_number) 740 | 741 | def __str__(self, indent=''): 742 | source_lines = re.sub(r'^\n', '', strip_trailing_nl( 743 | self.source), flags=re.MULTILINE).split('\n') 744 | if len(source_lines) == 1: 745 | s = indent + 'Code: {' + source_lines[0] + '}' 746 | else: 747 | s = indent + 'Code:\n' + indent + '{\n' + '\n'.join( 748 | indent + 4 * ' ' + l for l in source_lines 749 | ) + '\n' + indent + '}' 750 | return s + self.format_children(indent) 751 | 752 | 753 | def expand(filename, line_directive=_default_line_directive, **local_bindings): 754 | r"""Return the contents of the givepn template file, executed with the given 755 | local bindings. 756 | 757 | >>> from tempfile import NamedTemporaryFile 758 | >>> # On Windows, the name of a NamedTemporaryFile cannot be used to open 759 | >>> # the file for a second time if delete=True. Therefore, we have to 760 | >>> # manually handle closing and deleting this file to allow us to open 761 | >>> # the file by its name across all platforms. 762 | >>> f = NamedTemporaryFile(delete=False) 763 | >>> f.write( 764 | ... r'''--- 765 | ... % for i in range(int(x)): 766 | ... a pox on ${i} for epoxy 767 | ... % end 768 | ... ${120 + 769 | ... 770 | ... 3} 771 | ... abc 772 | ... ${"w\nx\nX\ny"} 773 | ... z 774 | ... ''') 775 | >>> f.flush() 776 | >>> result = expand( 777 | ... f.name, 778 | ... line_directive='//#sourceLocation(file: "%(file)s", ' + \ 779 | ... 'line: %(line)d)', 780 | ... x=2 781 | ... ).replace( 782 | ... '"%s"' % f.name, '"dummy.file"') 783 | >>> print(result, end='') 784 | //#sourceLocation(file: "dummy.file", line: 1) 785 | --- 786 | //#sourceLocation(file: "dummy.file", line: 3) 787 | a pox on 0 for epoxy 788 | //#sourceLocation(file: "dummy.file", line: 3) 789 | a pox on 1 for epoxy 790 | //#sourceLocation(file: "dummy.file", line: 5) 791 | 123 792 | //#sourceLocation(file: "dummy.file", line: 8) 793 | abc 794 | w 795 | x 796 | X 797 | y 798 | //#sourceLocation(file: "dummy.file", line: 10) 799 | z 800 | >>> f.close() 801 | >>> os.remove(f.name) 802 | """ 803 | with open(filename) as f: 804 | t = parse_template(filename, f.read()) 805 | d = os.getcwd() 806 | os.chdir(os.path.dirname(os.path.abspath(filename))) 807 | try: 808 | return execute_template( 809 | t, line_directive=line_directive, **local_bindings) 810 | finally: 811 | os.chdir(d) 812 | 813 | 814 | def parse_template(filename, text=None): 815 | r"""Return an AST corresponding to the given template file. 816 | 817 | If text is supplied, it is assumed to be the contents of the file, 818 | as a string. 819 | 820 | >>> print(parse_template('dummy.file', text= 821 | ... '''% for x in [1, 2, 3]: 822 | ... % if x == 1: 823 | ... literal1 824 | ... % elif x > 1: # add output line after this line to fix bug 825 | ... % if x == 2: 826 | ... literal2 827 | ... % end 828 | ... % end 829 | ... % end 830 | ... ''')) 831 | Block: 832 | [ 833 | Code: 834 | { 835 | for x in [1, 2, 3]: 836 | __children__[0].execute(__context__) 837 | } 838 | [ 839 | Block: 840 | [ 841 | Code: 842 | { 843 | if x == 1: 844 | __children__[0].execute(__context__) 845 | elif x > 1: # add output line after this line to fix bug 846 | __children__[1].execute(__context__) 847 | } 848 | [ 849 | Block: 850 | [ 851 | Literal: 852 | literal1 853 | ] 854 | Block: 855 | [ 856 | Code: 857 | { 858 | if x == 2: 859 | __children__[0].execute(__context__) 860 | } 861 | [ 862 | Block: 863 | [ 864 | Literal: 865 | literal2 866 | ] 867 | ] 868 | ] 869 | ] 870 | ] 871 | ] 872 | ] 873 | 874 | >>> print(parse_template( 875 | ... 'dummy.file', 876 | ... text='%for x in range(10):\n% print(x)\n%end\njuicebox')) 877 | Block: 878 | [ 879 | Code: 880 | { 881 | for x in range(10): 882 | __children__[0].execute(__context__) 883 | } 884 | [ 885 | Block: 886 | [ 887 | Code: {print(x)} [] 888 | ] 889 | ] 890 | Literal: 891 | juicebox 892 | ] 893 | 894 | >>> print(parse_template('/dummy.file', text= 895 | ... '''Nothing 896 | ... % if x: 897 | ... % for i in range(3): 898 | ... ${i} 899 | ... % end 900 | ... % else: 901 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT 902 | ... ''')) 903 | Block: 904 | [ 905 | Literal: 906 | Nothing 907 | Code: 908 | { 909 | if x: 910 | __children__[0].execute(__context__) 911 | else: 912 | __children__[1].execute(__context__) 913 | } 914 | [ 915 | Block: 916 | [ 917 | Code: 918 | { 919 | for i in range(3): 920 | __children__[0].execute(__context__) 921 | } 922 | [ 923 | Block: 924 | [ 925 | Code: {(i)} [] 926 | Literal: 927 | 928 | ] 929 | ] 930 | ] 931 | Block: 932 | [ 933 | Literal: 934 | THIS SHOULD NOT APPEAR IN THE OUTPUT 935 | ] 936 | ] 937 | ] 938 | 939 | >>> print(parse_template('dummy.file', text='''% 940 | ... %for x in y: 941 | ... % print(y) 942 | ... ''')) 943 | Block: 944 | [ 945 | Code: 946 | { 947 | for x in y: 948 | __children__[0].execute(__context__) 949 | } 950 | [ 951 | Block: 952 | [ 953 | Code: {print(y)} [] 954 | ] 955 | ] 956 | ] 957 | 958 | >>> print(parse_template('dummy.file', text='''% 959 | ... %if x: 960 | ... % print(y) 961 | ... AAAA 962 | ... %else: 963 | ... BBBB 964 | ... ''')) 965 | Block: 966 | [ 967 | Code: 968 | { 969 | if x: 970 | __children__[0].execute(__context__) 971 | else: 972 | __children__[1].execute(__context__) 973 | } 974 | [ 975 | Block: 976 | [ 977 | Code: {print(y)} [] 978 | Literal: 979 | AAAA 980 | ] 981 | Block: 982 | [ 983 | Literal: 984 | BBBB 985 | ] 986 | ] 987 | ] 988 | 989 | >>> print(parse_template('dummy.file', text='''% 990 | ... %if x: 991 | ... % print(y) 992 | ... AAAA 993 | ... %# This is a comment 994 | ... %else: 995 | ... BBBB 996 | ... ''')) 997 | Block: 998 | [ 999 | Code: 1000 | { 1001 | if x: 1002 | __children__[0].execute(__context__) 1003 | # This is a comment 1004 | else: 1005 | __children__[1].execute(__context__) 1006 | } 1007 | [ 1008 | Block: 1009 | [ 1010 | Code: {print(y)} [] 1011 | Literal: 1012 | AAAA 1013 | ] 1014 | Block: 1015 | [ 1016 | Literal: 1017 | BBBB 1018 | ] 1019 | ] 1020 | ] 1021 | 1022 | >>> print(parse_template('dummy.file', text='''\ 1023 | ... %for x in y: 1024 | ... AAAA 1025 | ... %if x: 1026 | ... BBBB 1027 | ... %end 1028 | ... CCCC 1029 | ... ''')) 1030 | Block: 1031 | [ 1032 | Code: 1033 | { 1034 | for x in y: 1035 | __children__[0].execute(__context__) 1036 | } 1037 | [ 1038 | Block: 1039 | [ 1040 | Literal: 1041 | AAAA 1042 | Code: 1043 | { 1044 | if x: 1045 | __children__[0].execute(__context__) 1046 | } 1047 | [ 1048 | Block: 1049 | [ 1050 | Literal: 1051 | BBBB 1052 | ] 1053 | ] 1054 | Literal: 1055 | CCCC 1056 | ] 1057 | ] 1058 | ] 1059 | """ 1060 | return Block(ParseContext(filename, text)) 1061 | 1062 | 1063 | def execute_template( 1064 | ast, line_directive=_default_line_directive, **local_bindings): 1065 | r"""Return the text generated by executing the given template AST. 1066 | 1067 | Keyword arguments become local variable bindings in the execution context 1068 | 1069 | >>> root_directory = os.path.abspath('/') 1070 | >>> file_name = root_directory + 'dummy.file' 1071 | >>> ast = parse_template(file_name, text= 1072 | ... '''Nothing 1073 | ... % if x: 1074 | ... % for i in range(3): 1075 | ... ${i} 1076 | ... % end 1077 | ... % else: 1078 | ... THIS SHOULD NOT APPEAR IN THE OUTPUT 1079 | ... ''') 1080 | >>> out = execute_template(ast, 1081 | ... line_directive='//#sourceLocation(file: "%(file)s", line: %(line)d)', 1082 | ... x=1) 1083 | >>> out = out.replace(file_name, "DUMMY-FILE") 1084 | >>> print(out, end="") 1085 | //#sourceLocation(file: "DUMMY-FILE", line: 1) 1086 | Nothing 1087 | //#sourceLocation(file: "DUMMY-FILE", line: 4) 1088 | 0 1089 | //#sourceLocation(file: "DUMMY-FILE", line: 4) 1090 | 1 1091 | //#sourceLocation(file: "DUMMY-FILE", line: 4) 1092 | 2 1093 | 1094 | >>> ast = parse_template(file_name, text= 1095 | ... '''Nothing 1096 | ... % a = [] 1097 | ... % for x in range(3): 1098 | ... % a.append(x) 1099 | ... % end 1100 | ... ${a} 1101 | ... ''') 1102 | >>> out = execute_template(ast, 1103 | ... line_directive='//#sourceLocation(file: "%(file)s", line: %(line)d)', 1104 | ... x=1) 1105 | >>> out = out.replace(file_name, "DUMMY-FILE") 1106 | >>> print(out, end="") 1107 | //#sourceLocation(file: "DUMMY-FILE", line: 1) 1108 | Nothing 1109 | //#sourceLocation(file: "DUMMY-FILE", line: 6) 1110 | [0, 1, 2] 1111 | 1112 | >>> ast = parse_template(file_name, text= 1113 | ... '''Nothing 1114 | ... % a = [] 1115 | ... % for x in range(3): 1116 | ... % a.append(x) 1117 | ... % end 1118 | ... ${a} 1119 | ... ''') 1120 | >>> out = execute_template(ast, 1121 | ... line_directive='#line %(line)d "%(file)s"', x=1) 1122 | >>> out = out.replace(file_name, "DUMMY-FILE") 1123 | >>> print(out, end="") 1124 | #line 1 "DUMMY-FILE" 1125 | Nothing 1126 | #line 6 "DUMMY-FILE" 1127 | [0, 1, 2] 1128 | """ 1129 | execution_context = ExecutionContext( 1130 | line_directive=line_directive, **local_bindings) 1131 | ast.execute(execution_context) 1132 | return ''.join(execution_context.result_text) 1133 | 1134 | 1135 | def main(): 1136 | """ 1137 | Lint this file. 1138 | >>> import sys 1139 | >>> gyb_path = os.path.realpath(__file__).replace('.pyc', '.py') 1140 | >>> sys.path.append(os.path.dirname(gyb_path)) 1141 | >>> import python_lint 1142 | >>> python_lint.lint([gyb_path], verbose=False) 1143 | 0 1144 | """ 1145 | 1146 | import argparse 1147 | import sys 1148 | 1149 | parser = argparse.ArgumentParser( 1150 | formatter_class=argparse.RawDescriptionHelpFormatter, 1151 | description='Generate Your Boilerplate!', epilog=''' 1152 | A GYB template consists of the following elements: 1153 | 1154 | - Literal text which is inserted directly into the output 1155 | 1156 | - %% or $$ in literal text, which insert literal '%' and '$' 1157 | symbols respectively. 1158 | 1159 | - Substitutions of the form ${}. The Python 1160 | expression is converted to a string and the result is inserted 1161 | into the output. 1162 | 1163 | - Python code delimited by %{...}%. Typically used to inject 1164 | definitions (functions, classes, variable bindings) into the 1165 | evaluation context of the template. Common indentation is 1166 | stripped, so you can add as much indentation to the beginning 1167 | of this code as you like 1168 | 1169 | - Lines beginning with optional whitespace followed by a single 1170 | '%' and Python code. %-lines allow you to nest other 1171 | constructs inside them. To close a level of nesting, use the 1172 | "%end" construct. 1173 | 1174 | - Lines beginning with optional whitespace and followed by a 1175 | single '%' and the token "end", which close open constructs in 1176 | %-lines. 1177 | 1178 | Example template: 1179 | 1180 | - Hello - 1181 | %{ 1182 | x = 42 1183 | def succ(a): 1184 | return a+1 1185 | }% 1186 | 1187 | I can assure you that ${x} < ${succ(x)} 1188 | 1189 | % if int(y) > 7: 1190 | % for i in range(3): 1191 | y is greater than seven! 1192 | % end 1193 | % else: 1194 | y is less than or equal to seven 1195 | % end 1196 | 1197 | - The End. - 1198 | 1199 | When run with "gyb -Dy=9", the output is 1200 | 1201 | - Hello - 1202 | 1203 | I can assure you that 42 < 43 1204 | 1205 | y is greater than seven! 1206 | y is greater than seven! 1207 | y is greater than seven! 1208 | 1209 | - The End. - 1210 | ''' 1211 | ) 1212 | parser.add_argument( 1213 | '-D', action='append', dest='defines', metavar='NAME=VALUE', 1214 | default=[], 1215 | help='''Bindings to be set in the template's execution context''') 1216 | 1217 | parser.add_argument( 1218 | 'file', type=argparse.FileType(), 1219 | help='Path to GYB template file (defaults to stdin)', nargs='?', 1220 | default=sys.stdin) 1221 | parser.add_argument( 1222 | '-o', dest='target', type=argparse.FileType('w'), 1223 | help='Output file (defaults to stdout)', default=sys.stdout) 1224 | parser.add_argument( 1225 | '--test', action='store_true', 1226 | default=False, help='Run a self-test') 1227 | parser.add_argument( 1228 | '--verbose-test', action='store_true', 1229 | default=False, help='Run a verbose self-test') 1230 | parser.add_argument( 1231 | '--dump', action='store_true', 1232 | default=False, help='Dump the parsed template to stdout') 1233 | parser.add_argument( 1234 | '--line-directive', 1235 | default=_default_line_directive, 1236 | help=''' 1237 | Line directive format string, which will be 1238 | provided 2 substitutions, `%%(line)d` and `%%(file)s`. 1239 | 1240 | Example: `#sourceLocation(file: "%%(file)s", line: %%(line)d)` 1241 | 1242 | The default works automatically with the `line-directive` tool, 1243 | which see for more information. 1244 | ''') 1245 | 1246 | args = parser.parse_args(sys.argv[1:]) 1247 | 1248 | if args.test or args.verbose_test: 1249 | import doctest 1250 | selfmod = sys.modules[__name__] 1251 | if doctest.testmod(selfmod, verbose=args.verbose_test or None).failed: 1252 | sys.exit(1) 1253 | 1254 | bindings = dict(x.split('=', 1) for x in args.defines) 1255 | ast = parse_template(args.file.name, args.file.read()) 1256 | if args.dump: 1257 | print(ast) 1258 | # Allow the template to open files and import .py files relative to its own 1259 | # directory 1260 | os.chdir(os.path.dirname(os.path.abspath(args.file.name))) 1261 | sys.path = ['.'] + sys.path 1262 | 1263 | args.target.write(execute_template(ast, args.line_directive, **bindings)) 1264 | 1265 | 1266 | if __name__ == '__main__': 1267 | main() 1268 | -------------------------------------------------------------------------------- /gyb/gyb.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedMadRobot/golden-key/7fe1faa3a284bfee389be3f43ad12a6074dc1af4/gyb/gyb.pyc -------------------------------------------------------------------------------- /instruments_screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedMadRobot/golden-key/7fe1faa3a284bfee389be3f43ad12a6074dc1af4/instruments_screenshot.png -------------------------------------------------------------------------------- /logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RedMadRobot/golden-key/7fe1faa3a284bfee389be3f43ad12a6074dc1af4/logo.png --------------------------------------------------------------------------------