├── .gitignore ├── LICENSE ├── Package.swift ├── README.md ├── README.zh_CN.md ├── Sources └── PerfectCrypto │ ├── Algorithms.swift │ ├── ByteIO.swift │ ├── Extensions.swift │ ├── JWK.swift │ ├── JWT.swift │ ├── Keys.swift │ ├── OpenSSLInternal.swift │ └── PerfectCrypto.swift └── Tests ├── LinuxMain.swift └── PerfectCryptoTests └── PerfectCryptoTests.swift /.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 | *.xcuserstate 23 | 24 | ## Obj-C/Swift specific 25 | *.hmap 26 | *.ipa 27 | *.dSYM.zip 28 | *.dSYM 29 | 30 | ## Playgrounds 31 | timeline.xctimeline 32 | playground.xcworkspace 33 | 34 | # Swift Package Manager 35 | # 36 | # Add this line if you want to avoid checking in source code from Swift Package Manager dependencies. 37 | # Packages/ 38 | .build/ 39 | *.resolved 40 | *.pins 41 | 42 | # CocoaPods 43 | # 44 | # We recommend against adding the Pods directory to your .gitignore. However 45 | # you should judge for yourself, the pros and cons are mentioned at: 46 | # https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control 47 | # 48 | # Pods/ 49 | 50 | # Carthage 51 | # 52 | # Add this line if you want to avoid checking in source code from Carthage dependencies. 53 | # Carthage/Checkouts 54 | 55 | Carthage/Build 56 | 57 | # fastlane 58 | # 59 | # It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the 60 | # screenshots whenever they are needed. 61 | # For more information about the recommended setup visit: 62 | # https://github.com/fastlane/fastlane/blob/master/fastlane/docs/Gitignore.md 63 | 64 | fastlane/report.xml 65 | fastlane/Preview.html 66 | fastlane/screenshots 67 | fastlane/test_output 68 | .DS_Store 69 | *.xcodeproj 70 | Packages/ 71 | Package.pins 72 | .build_lin 73 | PADockerfile_build 74 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /Package.swift: -------------------------------------------------------------------------------- 1 | // swift-tools-version:5.1 2 | // 3 | // Package.swift 4 | // PerfectCrypto 5 | // 6 | // Created by Kyle Jessup on 2017-02-07. 7 | // Copyright (C) 2017 PerfectlySoft, Inc. 8 | // 9 | //===----------------------------------------------------------------------===// 10 | // 11 | // This source file is part of the Perfect.org open source project 12 | // 13 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 14 | // Licensed under Apache License v2.0 15 | // 16 | // See http://perfect.org/licensing.html for license information 17 | // 18 | //===----------------------------------------------------------------------===// 19 | // 20 | 21 | import PackageDescription 22 | 23 | #if os(Linux) 24 | let cOpenSSLRepo = "https://github.com/PerfectlySoft/Perfect-COpenSSL-Linux.git" 25 | #else 26 | let cOpenSSLRepo = "https://github.com/PerfectlySoft/Perfect-COpenSSL.git" 27 | #endif 28 | 29 | let package = Package( 30 | name: "PerfectCrypto", 31 | platforms: [ 32 | .macOS(.v10_15) 33 | ], 34 | products: [ 35 | .library(name: "PerfectCrypto", targets: ["PerfectCrypto"]) 36 | ], 37 | dependencies: [ 38 | .package(url: "https://github.com/PerfectlySoft/PerfectLib.git", from: "4.0.0"), 39 | .package(url: "https://github.com/PerfectlySoft/Perfect-Thread.git", from: "3.0.0"), 40 | .package(url: cOpenSSLRepo, from: "4.0.0") 41 | ], 42 | targets: [ 43 | .target(name: "PerfectCrypto", dependencies: ["PerfectLib", "PerfectThread", "COpenSSL"]), 44 | .testTarget(name: "PerfectCryptoTests", dependencies: ["PerfectCrypto"]) 45 | ] 46 | ) 47 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Perfect-Crypto [简体中文](README.zh_CN.md) 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Swift 5.2 12 | 13 | 14 | Platforms OS X | Linux 15 | 16 | 17 | License Apache 18 | 19 |

20 | 21 | 22 | Digest, cipher and encoding support for Perfect. 23 | 24 | ## Building 25 | 26 | Add this project as a dependency in your Package.swift file. 27 | 28 | ``` 29 | .package(url: "https://github.com/PerfectlySoft/Perfect-Crypto.git", from: "4.0.0") 30 | ``` 31 | 32 | ## Linux Build Notes 33 | 34 | Ensure that you have installed libssl-dev. OpenSSL 1.0.2+ is required for this package. On Ubuntu 14 or some Debian distributions you will need to update your OpenSSL before this package will build. 35 | 36 | ``` 37 | sudo apt-get install openssl libssl-dev 38 | ``` 39 | 40 | ## Overview 41 | 42 | This package wraps up some of the functionality provided by OpenSSL and adds a Swift layer on top of it. The main features are: 43 | 44 | * Extensions for String, [UInt8] and UnsafeRawBufferPointer that provide simple encode, decode, digest and cipher operations. 45 | * JWT (JSON Web Token) generation and validation. 46 | * Generation of arbitrary amounts of random data. 47 | * Swift wrappers around OpenSSL BIOs, providing chainable, filterable byte IO sinks and sources. 48 | * Convenience functions for creating Strings given non-null terminated UTF8 containing UnsafeRawBufferPointer or [UInt8] objects. 49 | 50 | ## Usage Examples 51 | 52 | ### Encode/Decode Hex 53 | 54 | ```swift 55 | let testStr = "Hello, world!" 56 | guard let hexBytes = testStr.encode(.hex) else { 57 | return 58 | } 59 | 60 | String(validatingUTF8: hexBytes) == "48656c6c6f2c20776f726c6421" 61 | 62 | guard let unHex = hexBytes.decode(.hex) else { 63 | return 64 | } 65 | 66 | String(validatingUTF8: unHex) == testStr 67 | 68 | ``` 69 | 70 | ### Encode/Decode Base 64 71 | 72 | ```swift 73 | let testStr = "Hello, world!" 74 | guard let baseBytes = testStr.encode(.base64) else { 75 | return 76 | } 77 | 78 | String(validatingUTF8: baseBytes) == "SGVsbG8sIHdvcmxkIQ==" 79 | 80 | guard let unBase = baseBytes.decode(.base64) else { 81 | return 82 | } 83 | 84 | String(validatingUTF8: unBase) == testStr 85 | ``` 86 | 87 | ### Digest 88 | 89 | ```swift 90 | let testStr = "Hello, world!" 91 | let testAnswer = "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3" 92 | guard let enc = testStr.digest(.sha256)?.encode(.hex) else { 93 | return 94 | } 95 | 96 | String(validatingUTF8: enc) == testAnswer 97 | ``` 98 | 99 | ### HMAC Sign/Verify 100 | 101 | The following snippet will HMAC-SHA1 sign, encode as base64, then decode, and verify a data string. Replace usages of .sha1 or .base64 depending on your requirements. 102 | 103 | ```swift 104 | let password = "this is a good pw" 105 | let data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 106 | 107 | if let signed = data.sign(.sha1, key: HMACKey(password))?.encode(.base64), 108 | let base64Str = String(validatingUTF8: signed), 109 | 110 | let reRawData = base64Str.decode(.base64) { 111 | 112 | let verifyResult = data.verify(.sha1, signature: reRawData, key: HMACKey(password)) 113 | XCTAssert(verifyResult) 114 | } else { 115 | XCTAssert(false, "Failed signing") 116 | } 117 | ``` 118 | 119 | ### Public API 120 | 121 | ```swift 122 | public extension String { 123 | /// Construct a string from a UTF8 character pointer. 124 | /// Character data does not need to be null terminated. 125 | /// The buffer's count indicates how many characters are to be converted. 126 | /// Returns nil if the data is invalid. 127 | init?(validatingUTF8 ptr: UnsafeRawBufferPointer?) 128 | /// Construct a string from a UTF8 character array. 129 | /// The array's count indicates how many characters are to be converted. 130 | /// Returns nil if the data is invalid. 131 | init?(validatingUTF8 a: [UInt8]) 132 | } 133 | 134 | public extension String { 135 | /// Decode the String into an array of bytes using the indicated encoding. 136 | /// The string's UTF8 characters are decoded. 137 | func decode(_ encoding: Encoding) -> [UInt8]? 138 | /// Encode the String into an array of bytes using the indicated encoding. 139 | /// The string's UTF8 characters are decoded. 140 | func encode(_ encoding: Encoding) -> [UInt8]? 141 | /// Perform the digest algorithm on the String's UTF8 bytes 142 | func digest(_ digest: Digest) -> [UInt8]? 143 | /// Sign the String data into an array of bytes using the indicated algorithm and key. 144 | func sign(_ digest: Digest, key: Key) -> [UInt8]? 145 | /// Verify the signature against the String data. 146 | /// Returns true if the signature is verified. Returns false otherwise. 147 | func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool 148 | /// Encrypt this buffer using the indicated cipher, password, and salt. 149 | /// The string's UTF8 characters are encoded. 150 | /// Resulting data is in PEM encoded CMS format. 151 | func encrypt(_ cipher: Cipher, 152 | password: String, 153 | salt: String, 154 | keyIterations: Int = 2048, 155 | keyDigest: Digest = .md5) -> String? 156 | /// Decrypt this PEM encoded CMS buffer using the indicated password and salt. 157 | /// Resulting decrypted data must be valid UTF-8 characters or the operation will fail. 158 | func decrypt(_ cipher: Cipher, 159 | password: String, 160 | salt: String, 161 | keyIterations: Int = 2048, 162 | keyDigest: Digest = .md5) -> String? 163 | } 164 | 165 | public protocol Octal {} 166 | extension UInt8: Octal {} 167 | 168 | public extension Array where Element: Octal { 169 | /// Encode the Array into An array of bytes using the indicated encoding. 170 | func encode(_ encoding: Encoding) -> [UInt8]? 171 | /// Decode the Array into an array of bytes using the indicated encoding. 172 | func decode(_ encoding: Encoding) -> [UInt8]? 173 | /// Digest the Array data into an array of bytes using the indicated algorithm. 174 | func digest(_ digest: Digest) -> [UInt8]? 175 | /// Sign the Array data into an array of bytes using the indicated algorithm and key. 176 | func sign(_ digest: Digest, key: Key) -> [UInt8]? 177 | /// Verify the array against the signature. 178 | /// Returns true if the signature is verified. Returns false otherwise. 179 | func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool 180 | /// Decrypt this buffer using the indicated cipher, key an iv (initialization vector). 181 | func encrypt(_ cipher: Cipher, key: [UInt8], iv: [UInt8]) -> [UInt8]? 182 | /// Decrypt this buffer using the indicated cipher, key an iv (initialization vector). 183 | func decrypt(_ cipher: Cipher, key: [UInt8], iv: [UInt8]) -> [UInt8]? 184 | /// Encrypt this buffer using the indicated cipher, password, and salt. 185 | /// Resulting data is PEM encoded CMS format. 186 | func encrypt(_ cipher: Cipher, 187 | password: [UInt8], 188 | salt: [UInt8], 189 | keyIterations: Int = 2048, 190 | keyDigest: Digest = .md5) -> [UInt8]? 191 | /// Decrypt this PEM encoded CMS buffer using the indicated password and salt. 192 | func decrypt(_ cipher: Cipher, 193 | password: [UInt8], 194 | salt: [UInt8], 195 | keyIterations: Int = 2048, 196 | keyDigest: Digest = .md5) -> [UInt8]? 197 | } 198 | 199 | public extension UnsafeRawBufferPointer { 200 | /// Encode the buffer using the indicated encoding. 201 | /// The return value must be deallocated by the caller. 202 | func encode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? 203 | /// Decode the buffer using the indicated encoding. 204 | /// The return value must be deallocated by the caller. 205 | func decode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? 206 | /// Digest the buffer using the indicated algorithm. 207 | /// The return value must be deallocated by the caller. 208 | func digest(_ digest: Digest) -> UnsafeMutableRawBufferPointer? 209 | /// Sign the buffer using the indicated algorithm and key. 210 | /// The return value must be deallocated by the caller. 211 | func sign(_ digest: Digest, key: Key) -> UnsafeMutableRawBufferPointer? 212 | /// Verify the signature against the buffer. 213 | /// Returns true if the signature is verified. Returns false otherwise. 214 | func verify(_ digest: Digest, signature: UnsafeRawBufferPointer, key: Key) -> Bool 215 | /// Encrypt this buffer using the indicated cipher, key and iv (initialization vector). 216 | /// Returns a newly allocated buffer which must be freed by the caller. 217 | func encrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? 218 | /// Decrypt this buffer using the indicated cipher, key and iv (initialization vector). 219 | /// Returns a newly allocated buffer which must be freed by the caller. 220 | func decrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? 221 | /// Encrypt this buffer to PEM encoded CMS format using the indicated cipher, password, and salt. 222 | /// Returns a newly allocated buffer which must be freed by the caller. 223 | func encrypt(_ cipher: Cipher, 224 | password: UnsafeRawBufferPointer, 225 | salt: UnsafeRawBufferPointer, 226 | keyIterations: Int = 2048, 227 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? 228 | /// Decrypt this PEM encoded CMS buffer using the indicated password and salt. 229 | /// Returns a newly allocated buffer which must be freed by the caller. 230 | func decrypt(_ cipher: Cipher, 231 | password: UnsafeRawBufferPointer, 232 | salt: UnsafeRawBufferPointer, 233 | keyIterations: Int = 2048, 234 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? 235 | } 236 | 237 | public extension UnsafeRawBufferPointer { 238 | /// Allocate memory for `size` bytes with word alignment from the encryption library's 239 | /// random number generator. 240 | /// 241 | /// - Postcondition: The memory is allocated and initialized to random bits. 242 | static func allocateRandom(count size: Int) -> UnsafeRawBufferPointer? 243 | } 244 | 245 | public extension FixedWidthInteger { 246 | /// get a random integer, i.e., signed or unsigned int8/16/32/64 247 | public static var random: Self 248 | } 249 | public extension Float { 250 | /// get a random float 251 | public static var random: Float 252 | } 253 | public extension Double { 254 | /// get a random double 255 | public static var random: Double 256 | } 257 | ``` 258 | 259 | ### JSON Web Tokens (JWT) 260 | 261 | This crypto package provides an means for creating new JWT tokens and validating existing tokens. 262 | 263 | JSON Web Token (JWT) is an open standard (RFC 7519) that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. JWTs can be signed using a secret (with the HMAC algorithm) or a public/private key pair using RSA. Source: [JWT](https://jwt.io/introduction/). 264 | 265 | New JWT tokens are created through the `JWTCreator` object. 266 | 267 | ```swift 268 | /// Creates and signs new JWT tokens. 269 | public struct JWTCreator { 270 | /// Creates a new JWT token given a payload. 271 | /// The payload can then be signed to generate a JWT token string. 272 | public init?(payload: [String:Any]) 273 | /// Sign and return a new JWT token string using an HMAC key. 274 | /// Additional headers can be optionally provided. 275 | /// Throws a JWT.Error.signingError if there is a problem generating the token string. 276 | public func sign(alg: JWT.Alg, key: String, headers: [String:Any] = [:]) throws -> String 277 | /// Sign and return a new JWT token string using the given key. 278 | /// Additional headers can be optionally provided. 279 | /// The key type must be compatible with the indicated `algo`. 280 | /// Throws a JWT.Error.signingError if there is a problem generating the token string. 281 | public func sign(alg: JWT.Alg, key: Key, headers: [String:Any] = [:]) throws -> String 282 | } 283 | ``` 284 | 285 | Existing JWT tokens can be validated through the `JWTVerifier` object. 286 | 287 | ```swift 288 | /// Accepts a JWT token string and verifies its structural validity and signature. 289 | public struct JWTVerifier { 290 | /// The headers obtained from the token. 291 | public var header: [String:Any] 292 | /// The payload carried by the token. 293 | public var payload: [String:Any] 294 | /// Create a JWTVerifier given a source string in the "aaaa.bbbb.cccc" format. 295 | /// Returns nil if the given string is not a valid JWT. 296 | /// *Does not perform verification in this step.* Call `verify` with your key to validate. 297 | /// If verification succeeds then the `.headers` and `.payload` properties can be safely accessed. 298 | public init?(_ jwt: String) 299 | /// Verify the token based on the indicated algorithm and HMAC key. 300 | /// Throws a JWT.Error.verificationError if any aspect of the token is incongruent. 301 | /// Returns without any error if the token was able to be verified. 302 | /// The parameter `algo` must match the token's "alg" header. 303 | public func verify(algo: JWT.Alg, key: String) throws 304 | /// Verify the token based on the indicated algorithm and key. 305 | /// Throws a JWT.Error.verificationError if any aspect of the token is incongruent. 306 | /// Returns without any error if the token was able to be verified. 307 | /// The parameter `algo` must match the token's "alg" header. 308 | /// The key type must be compatible with the indicated `algo`. 309 | public func verify(algo: JWT.Alg, key: Key) throws 310 | } 311 | ``` 312 | 313 | The following example will create and then verify a token using the "HS256" alg scheme. 314 | 315 | ```swift 316 | let name = "John Doe" 317 | let tstPayload = ["sub": "1234567890", "name": name, "admin": true] as [String : Any] 318 | let secret = "secret" 319 | guard let jwt1 = JWTCreator(payload: tstPayload) else { 320 | return // fatal error 321 | } 322 | let token = try jwt1.sign(alg: .hs256, key: secret) 323 | guard let jwt = JWTVerifier(token) else { 324 | return // fatal error 325 | } 326 | try jwt.verify(algo: .hs256, key: HMACKey(secret)) 327 | let fndName = jwt.payload["name"] as? String 328 | // name == fndName! 329 | ``` 330 | 331 | It's important to note that the JWTVerifier will verify that the token is cryptographically sound, but it **does not** validate payload claims such as iss(uer) or exp(iration). You can obtain these from the payload dictionary and validate according to the needs of your application. 332 | 333 | ### Supported encodings, digests and ciphers 334 | 335 | ```swift 336 | /// Available encoding methods. 337 | public enum Encoding { 338 | case base64 339 | case hex 340 | } 341 | 342 | /// Available digest methods. 343 | public enum Digest { 344 | case md4 345 | case md5 346 | case sha 347 | case sha1 348 | case dss 349 | case dss1 350 | case ecdsa 351 | case sha224 352 | case sha256 353 | case sha384 354 | case sha512 355 | case ripemd160 356 | case whirlpool 357 | 358 | case custom(String) 359 | } 360 | 361 | /// Available ciphers. 362 | public enum Cipher { 363 | case des_ecb 364 | case des_ede 365 | case des_ede3 366 | case des_ede_ecb 367 | case des_ede3_ecb 368 | case des_cfb64 369 | case des_cfb1 370 | case des_cfb8 371 | case des_ede_cfb64 372 | case des_ede3_cfb1 373 | case des_ede3_cfb8 374 | case des_ofb 375 | case des_ede_ofb 376 | case des_ede3_ofb 377 | case des_cbc 378 | case des_ede_cbc 379 | case des_ede3_cbc 380 | case desx_cbc 381 | case des_ede3_wrap 382 | case rc4 383 | case rc4_40 384 | case rc4_hmac_md5 385 | case rc2_ecb 386 | case rc2_cbc 387 | case rc2_40_cbc 388 | case rc2_64_cbc 389 | case rc2_cfb64 390 | case rc2_ofb 391 | case bf_ecb 392 | case bf_cbc 393 | case bf_cfb64 394 | case bf_ofb 395 | case cast5_ecb 396 | case cast5_cbc 397 | case cast5_cfb64 398 | case cast5_ofb 399 | case aes_128_ecb 400 | case aes_128_cbc 401 | case aes_128_cfb1 402 | case aes_128_cfb8 403 | case aes_128_cfb128 404 | case aes_128_ofb 405 | case aes_128_ctr 406 | case aes_128_ccm 407 | case aes_128_gcm 408 | case aes_128_xts 409 | case aes_128_wrap 410 | case aes_192_ecb 411 | case aes_192_cbc 412 | case aes_192_cfb1 413 | case aes_192_cfb8 414 | case aes_192_cfb128 415 | case aes_192_ofb 416 | case aes_192_ctr 417 | case aes_192_ccm 418 | case aes_192_gcm 419 | case aes_192_wrap 420 | case aes_256_ecb 421 | case aes_256_cbc 422 | case aes_256_cfb1 423 | case aes_256_cfb8 424 | case aes_256_cfb128 425 | case aes_256_ofb 426 | case aes_256_ctr 427 | case aes_256_ccm 428 | case aes_256_gcm 429 | case aes_256_xts 430 | case aes_256_wrap 431 | case aes_128_cbc_hmac_sha1 432 | case aes_256_cbc_hmac_sha1 433 | case aes_128_cbc_hmac_sha256 434 | case aes_256_cbc_hmac_sha256 435 | case camellia_128_ecb 436 | case camellia_128_cbc 437 | case camellia_128_cfb1 438 | case camellia_128_cfb8 439 | case camellia_128_cfb128 440 | case camellia_128_ofb 441 | case camellia_192_ecb 442 | case camellia_192_cbc 443 | case camellia_192_cfb1 444 | case camellia_192_cfb8 445 | case camellia_192_cfb128 446 | case camellia_192_ofb 447 | case camellia_256_ecb 448 | case camellia_256_cbc 449 | case camellia_256_cfb1 450 | case camellia_256_cfb8 451 | case camellia_256_cfb128 452 | case camellia_256_ofb 453 | case seed_ecb 454 | case seed_cbc 455 | case seed_cfb128 456 | case seed_ofb 457 | 458 | case custom(String) 459 | } 460 | ``` 461 | 462 | ## Further Information 463 | 464 | For more documentation, please visit [perfect.org](http://www.perfect.org/docs/crypto.html). 465 | -------------------------------------------------------------------------------- /README.zh_CN.md: -------------------------------------------------------------------------------- 1 | # Perfect-Crypto 加密函数库 2 | 3 |

4 | 5 | Get Involed with Perfect! 6 | 7 |

8 | 9 |

10 | 11 | Swift 5.2 12 | 13 | 14 | Platforms OS X | Linux 15 | 16 | 17 | License Apache 18 | 19 |

20 | 21 | Perfect 摘要、加密和编解码函数库 22 | 23 | ## 编译 24 | 25 | 请在您的Package.swift文件中增加下列依存关系: 26 | 27 | ``` 28 | .package(url: "https://github.com/PerfectlySoft/Perfect-Crypto.git", from: "4.0.0") 29 | ``` 30 | 31 | ## Linux 编译说明 32 | 33 | 请确保您的系统上已经安装了 libssl-dev 函数库 34 | 35 | ``` 36 | sudo apt-get install openssl libssl-dev 37 | ``` 38 | 39 | ## 概述 40 | 41 | 本函数库将OpenSSL的部分功能进行了封装并在Swift基本类型上进行了扩展,主要内容包括: 42 | 43 | * 对于字符串、[UInt8] 和 UnsafeRawBufferPointer 指针增加了基本的编解码、摘要码和加密操作。 44 | * 针对于非零结尾指针创建UTF-8字符串的方法 45 | * 对OpenSSL BIO函数类的封装,提供可过滤的链式操作。 46 | 47 | ## 使用范例 48 | 49 | ### 16进制编解码 50 | 51 | ```swift 52 | let testStr = "Hello, world!" 53 | guard let hexBytes = testStr.encode(.hex) else { 54 | return 55 | } 56 | 57 | String(validatingUTF8: hexBytes) == "48656c6c6f2c20776f726c6421" 58 | 59 | guard let unHex = hexBytes.decode(.hex) else { 60 | return 61 | } 62 | 63 | String(validatingUTF8: unHex) == testStr 64 | 65 | ``` 66 | 67 | ### Base 64 编解码 68 | 69 | ```swift 70 | let testStr = "Hello, world!" 71 | guard let baseBytes = testStr.encode(.base64) else { 72 | return 73 | } 74 | 75 | String(validatingUTF8: baseBytes) == "SGVsbG8sIHdvcmxkIQ==" 76 | 77 | guard let unBase = baseBytes.decode(.base64) else { 78 | return 79 | } 80 | 81 | String(validatingUTF8: unBase) == testStr 82 | ``` 83 | 84 | ### 摘要码 85 | 86 | ```swift 87 | let testStr = "Hello, world!" 88 | let testAnswer = "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3" 89 | guard let enc = testStr.digest(.sha256)?.encode(.hex) else { 90 | return 91 | } 92 | 93 | String(validatingUTF8: enc) == testAnswer 94 | ``` 95 | 96 | ### HMAC 签名和校验 97 | 98 | 下列代码用于 HMAC-SHA1 内容签名和 base64 编码,然后解码并校验。请根据需要自行调整.sha1 或者 .base64 算法: 99 | 100 | ```swift 101 | let password = "用于测试的密码" 102 | let data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 103 | 104 | if let signed = data.sign(.sha1, key: HMACKey(password))?.encode(.base64), 105 | let base64Str = String(validatingUTF8: signed), 106 | 107 | let reRawData = base64Str.decode(.base64) { 108 | 109 | let verifyResult = data.verify(.sha1, signature: reRawData, key: HMACKey(password)) 110 | XCTAssert(verifyResult) 111 | } else { 112 | XCTAssert(false, "签名失败") 113 | } 114 | ``` 115 | 116 | ### API参考 117 | 118 | ``` swift 119 | public extension String { 120 | /// 从UTF8数组创建字符串,数组长度决定了转换内容长度;如果数据无效则字符串为空 121 | init?(validatingUTF8 a: [UInt8]) 122 | /// 从指针构造字符串。指针可以不是零值结尾,而是由缓冲区长度决定转换内容长度 123 | /// 输入内容无效则返回为空 124 | init?(validatingUTF8 ptr: UnsafeRawBufferPointer?) 125 | } 126 | 127 | public extension String { 128 | /// 将字符串转换为指定编码类型的线性表。 129 | func encode(_ encoding: Encoding) -> [UInt8]? 130 | /// 将字符串解码为制定编码类型的线性表。 131 | func decode(_ encoding: Encoding) -> [UInt8]? 132 | /// 摘要计算 133 | func digest(_ digest: Digest) -> [UInt8]? 134 | /// 根据算法和钥匙签署字符串并生成字节数组 135 | func sign(_ digest: Digest, key: Key) -> [UInt8]? 136 | /// 根据字符串验证签名 137 | /// 验证成功返回真,否则返回伪 138 | func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool 139 | /// 根据缓冲区密文、密码和盐对数据进行加密 140 | /// 字符串的 UTF8 字符将被编码 141 | /// 返回数据为CMS格式的PEM编码。 142 | func encrypt(_ cipher: Cipher, 143 | password: String, 144 | salt: String, 145 | keyIterations: Int = 2048, 146 | keyDigest: Digest = .md5) -> String? 147 | /// 根据密码和盐进行CMS格式PEM编码数据解码 148 | /// 解码结果必须为UTF8编码否则操作失败 149 | func decrypt(_ cipher: Cipher, 150 | password: String, 151 | salt: String, 152 | keyIterations: Int = 2048, 153 | keyDigest: Digest = .md5) -> String? 154 | } 155 | 156 | public protocol Octal {} 157 | extension UInt8: Octal {} 158 | 159 | public extension Array where Element: Octal { 160 | /// 将数组转换为指定编码类型的线性表。 161 | func encode(_ encoding: Encoding) -> [UInt8]? 162 | /// 将数组解码为制定编码类型的线性表。 163 | func decode(_ encoding: Encoding) -> [UInt8]? 164 | /// 摘要计算 165 | func digest(_ digest: Digest) -> [UInt8]? 166 | /// 根据算法和钥匙签署字符串并生成字节数组 167 | func sign(_ digest: Digest, key: Key) -> [UInt8]? 168 | /// 根据字符串验证签名 169 | /// 验证成功返回真,否则返回伪 170 | func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool 171 | /// 根据缓冲区密文、密码和盐对数据进行加密 172 | /// 字符串的 UTF8 字符将被编码 173 | /// 返回数据为CMS格式的PEM编码。 174 | func encrypt(_ cipher: Cipher, 175 | password: String, 176 | salt: String, 177 | keyIterations: Int = 2048, 178 | keyDigest: Digest = .md5) -> String? 179 | /// 根据密码和盐进行CMS格式PEM编码数据解码 180 | /// 解码结果必须为UTF8编码否则操作失败 181 | func decrypt(_ cipher: Cipher, 182 | password: String, 183 | salt: String, 184 | keyIterations: Int = 2048, 185 | keyDigest: Digest = .md5) -> String? 186 | } 187 | 188 | public extension UnsafeRawBufferPointer { 189 | /// 使用缓冲区生成编码内容,返回结果使用完后必须自行释放 190 | func encode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? 191 | /// 使用缓冲区生成解码内容,返回结果使用完后必须自行释放 192 | func decode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? 193 | /// 生成摘要内容,生成结果必须手工释放 194 | func digest(_ digest: Digest) -> UnsafeMutableRawBufferPointer? 195 | /// 根据算法和钥匙签署字符串并生成字节数组 196 | /// 返回结果必须由用户自行释放内存 197 | func sign(_ digest: Digest, key: Key) -> UnsafeMutableRawBufferPointer? 198 | /// 根据数据验证签名 199 | /// 验证成功返回真,否则返回伪 200 | func verify(_ digest: Digest, signature: UnsafeRawBufferPointer, key: Key) -> Bool 201 | /// 根据密文、密码和iv(初始化向量)对数据进行加密 202 | /// 生成结果必须手工释放 203 | func encrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? 204 | /// 根据密文、密码和iv(初始化向量)对数据进行解密 205 | /// 生成结果必须手工释放 206 | func decrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? 207 | /// 根据密文、密码和盐对数据进行进行CMS格式PEM加密 208 | /// 生成结果必须手工释放 209 | func encrypt(_ cipher: Cipher, 210 | password: UnsafeRawBufferPointer, 211 | salt: UnsafeRawBufferPointer, 212 | keyIterations: Int = 2048, 213 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? 214 | /// 根据密码和盐对数据进行进行CMS格式PEM解密 215 | /// 生成结果必须手工释放 216 | func decrypt(_ cipher: Cipher, 217 | password: UnsafeRawBufferPointer, 218 | salt: UnsafeRawBufferPointer, 219 | keyIterations: Int = 2048, 220 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? 221 | } 222 | 223 | public extension UnsafeRawBufferPointer { 224 | /// 根据长度要求填充随机数 225 | /// 226 | /// - 结果:内存被分配并被自动初始化为随机数 227 | static func allocateRandom(count size: Int) -> UnsafeRawBufferPointer? 228 | } 229 | 230 | public extension FixedWidthInteger { 231 | /// 生成一个随机数,有符号或者无符号的8位/16位/32位/64位整数 232 | public static var random: Self 233 | } 234 | public extension Float { 235 | /// 生成一个随机浮点数 236 | public static var random: Float 237 | } 238 | public extension Double { 239 | /// 生成一个随机双精度浮点数 240 | public static var random: Double 241 | } 242 | ``` 243 | 244 | ### JSON 网络通行证 (JWT) 245 | 246 | 本组件为JWT创建和验证函数库。 247 | 248 | JSON Web Token (以下简称网络通行证JWT) 为开放互联网标准协议 (RFC 7519) 用于定义在通信双方会话过程中以JSON为载体安全传输加密信息的方法。该信息可以用于互信和校验因为采用数字签名。JWTs 可用于HMAC算法加密签名,或者采用RSA公开/私有钥匙对签名方法,详见 [JWT](https://jwt.io/introduction/). 249 | 250 | 首先,新的JWT令牌可以可用 `JWTCreator` 对象创建。 251 | 252 | ```swift 253 | /// 创建并签署新的 JWT 令牌。 254 | public struct JWTCreator { 255 | /// 根据荷载内容创建新的通行券。 256 | /// 荷载内容可以用于创建JWT字符串 257 | public init?(payload: [String:Any]) 258 | /// 使用HMAC钥匙创建并返回一个新的JWT令牌。 259 | /// 可根据需要自行追加其他头数据 260 | /// 如果生成令牌中出现问题,会抛出签名错误 JWT.Error.signingError 261 | public func sign(alg: JWT.Alg, key: String, headers: [String:Any] = [:]) throws -> String 262 | /// 根据指定钥匙签署并生成新的 JWT 令牌。 263 | /// 可根据需要自行追加其他头数据 264 | /// 钥匙类型必须与算法 `algo` 兼容 265 | /// 如果生成令牌中出现问题,会抛出签名错误 JWT.Error.signingError 266 | public func sign(alg: JWT.Alg, key: Key, headers: [String:Any] = [:]) throws -> String 267 | } 268 | ``` 269 | 270 | 现有 JWT 通行证可以通过 `JWTVerifier` 对象进行验证 271 | 272 | ```swift 273 | /// 接受一个 JWT 通行证并验证签名 274 | public struct JWTVerifier { 275 | /// 从通行证内取出的头数据 276 | public var header: [String:Any] 277 | /// 通行证内的荷载数据 278 | public var payload: [String:Any] 279 | /// 从通行证字符串中创建 JWTVerifier 签名对象。通行证格式应该为 "aaaa.bbbb.cccc" 280 | /// 如果通行证无效则返回为 nil 281 | /// *注意这一步不做验证* 必须手工调用 `verify` 验证钥匙 282 | /// 如果验证成功,则头数据`.headers`和荷载数据 `.payload`才能安全生效 283 | public init?(_ jwt: String) 284 | /// 使用指定算法和HMAC钥匙验证通行证 285 | /// 如果生成令牌中出现问题,会抛出验证错误 JWT.Error.verificationError 286 | /// 如果验证无误则正常执行 287 | /// 参数 `algo` 必须与通行证中的 "alg" 头数据字段吻合 288 | public func verify(algo: JWT.Alg, key: String) throws 289 | /// 使用指定算法和HMAC钥匙验证通行证 290 | /// 如果生成令牌中出现问题,会抛出验证错误 JWT.Error.verificationError 291 | /// 如果验证无误则正常执行 292 | /// 参数 `algo` 必须与通行证中的 "alg" 头数据字段吻合 293 | public func verify(algo: JWT.Alg, key: Key) throws 294 | } 295 | ``` 296 | 297 | 以下示范说明了如何创建并使用“HS256”算法验证一个网络通行证。 298 | 299 | ```swift 300 | let name = "John Doe" 301 | let tstPayload = ["sub": "1234567890", "name": name, "admin": true] as [String : Any] 302 | let secret = "secret" 303 | guard let jwt1 = JWTCreator(payload: tstPayload) else { 304 | return // fatal error 305 | } 306 | let token = try jwt1.sign(alg: .hs256, key: secret) 307 | guard let jwt = JWTVerifier(token) else { 308 | return // fatal error 309 | } 310 | try jwt.verify(algo: .hs256, key: HMACKey(secret)) 311 | let fndName = jwt.payload["name"] as? String 312 | // name == fndName! 313 | ``` 314 | 315 | ⚠️注意⚠️ JWTVerifier 能够验证通行证加密,但是 ⚠️**不会**⚠️ 验证数据内容,比如签发者(iss)和有效期(exp)。用户需要自行从荷载数据字典中提取上述信息并进行进一步用户身份验证。 316 | 317 | 318 | 319 | ### 算法清单 320 | 321 | ```swift 322 | /// 编码方法 323 | public enum Encoding { 324 | case base64 325 | case hex 326 | } 327 | 328 | /// 摘要码算法 329 | public enum Digest { 330 | case md4 331 | case md5 332 | case sha 333 | case sha1 334 | case dss 335 | case dss1 336 | case ecdsa 337 | case sha224 338 | case sha256 339 | case sha384 340 | case sha512 341 | case ripemd160 342 | case whirlpool 343 | 344 | case custom(String) 345 | } 346 | 347 | /// 可用密文 348 | public enum Cipher { 349 | case des_ecb 350 | case des_ede 351 | case des_ede3 352 | case des_ede_ecb 353 | case des_ede3_ecb 354 | case des_cfb64 355 | case des_cfb1 356 | case des_cfb8 357 | case des_ede_cfb64 358 | case des_ede3_cfb1 359 | case des_ede3_cfb8 360 | case des_ofb 361 | case des_ede_ofb 362 | case des_ede3_ofb 363 | case des_cbc 364 | case des_ede_cbc 365 | case des_ede3_cbc 366 | case desx_cbc 367 | case des_ede3_wrap 368 | case rc4 369 | case rc4_40 370 | case rc4_hmac_md5 371 | case rc2_ecb 372 | case rc2_cbc 373 | case rc2_40_cbc 374 | case rc2_64_cbc 375 | case rc2_cfb64 376 | case rc2_ofb 377 | case bf_ecb 378 | case bf_cbc 379 | case bf_cfb64 380 | case bf_ofb 381 | case cast5_ecb 382 | case cast5_cbc 383 | case cast5_cfb64 384 | case cast5_ofb 385 | case aes_128_ecb 386 | case aes_128_cbc 387 | case aes_128_cfb1 388 | case aes_128_cfb8 389 | case aes_128_cfb128 390 | case aes_128_ofb 391 | case aes_128_ctr 392 | case aes_128_ccm 393 | case aes_128_gcm 394 | case aes_128_xts 395 | case aes_128_wrap 396 | case aes_192_ecb 397 | case aes_192_cbc 398 | case aes_192_cfb1 399 | case aes_192_cfb8 400 | case aes_192_cfb128 401 | case aes_192_ofb 402 | case aes_192_ctr 403 | case aes_192_ccm 404 | case aes_192_gcm 405 | case aes_192_wrap 406 | case aes_256_ecb 407 | case aes_256_cbc 408 | case aes_256_cfb1 409 | case aes_256_cfb8 410 | case aes_256_cfb128 411 | case aes_256_ofb 412 | case aes_256_ctr 413 | case aes_256_ccm 414 | case aes_256_gcm 415 | case aes_256_xts 416 | case aes_256_wrap 417 | case aes_128_cbc_hmac_sha1 418 | case aes_256_cbc_hmac_sha1 419 | case aes_128_cbc_hmac_sha256 420 | case aes_256_cbc_hmac_sha256 421 | case camellia_128_ecb 422 | case camellia_128_cbc 423 | case camellia_128_cfb1 424 | case camellia_128_cfb8 425 | case camellia_128_cfb128 426 | case camellia_128_ofb 427 | case camellia_192_ecb 428 | case camellia_192_cbc 429 | case camellia_192_cfb1 430 | case camellia_192_cfb8 431 | case camellia_192_cfb128 432 | case camellia_192_ofb 433 | case camellia_256_ecb 434 | case camellia_256_cbc 435 | case camellia_256_cfb1 436 | case camellia_256_cfb8 437 | case camellia_256_cfb128 438 | case camellia_256_ofb 439 | case seed_ecb 440 | case seed_cbc 441 | case seed_cfb128 442 | case seed_ofb 443 | 444 | case custom(String) 445 | } 446 | ``` 447 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/Algorithms.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Algorithms.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2017-02-07. 6 | // Copyright (C) 2017 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | /// Available encoding methods. 21 | public enum Encoding { 22 | case base64 23 | case base64url 24 | case hex 25 | } 26 | 27 | /// Available digest methods. 28 | public enum Digest { 29 | case md4 30 | case md5 31 | case sha1 32 | case sha224 33 | case sha256 34 | case sha384 35 | case sha512 36 | case ripemd160 37 | case whirlpool 38 | 39 | case custom(String) 40 | } 41 | 42 | /// Available ciphers. 43 | public enum Cipher { 44 | case des_ecb 45 | case des_ede 46 | case des_ede3 47 | case des_ede_ecb 48 | case des_ede3_ecb 49 | case des_cfb64 50 | case des_cfb1 51 | case des_cfb8 52 | case des_ede_cfb64 53 | case des_ede3_cfb1 54 | case des_ede3_cfb8 55 | case des_ofb 56 | case des_ede_ofb 57 | case des_ede3_ofb 58 | case des_cbc 59 | case des_ede_cbc 60 | case des_ede3_cbc 61 | case desx_cbc 62 | case des_ede3_wrap 63 | case rc4 64 | case rc4_40 65 | case rc4_hmac_md5 66 | case rc2_ecb 67 | case rc2_cbc 68 | case rc2_40_cbc 69 | case rc2_64_cbc 70 | case rc2_cfb64 71 | case rc2_ofb 72 | case bf_ecb 73 | case bf_cbc 74 | case bf_cfb64 75 | case bf_ofb 76 | case cast5_ecb 77 | case cast5_cbc 78 | case cast5_cfb64 79 | case cast5_ofb 80 | case aes_128_ecb 81 | case aes_128_cbc 82 | case aes_128_cfb1 83 | case aes_128_cfb8 84 | case aes_128_cfb128 85 | case aes_128_ofb 86 | case aes_128_ctr 87 | case aes_128_ccm 88 | case aes_128_gcm 89 | case aes_128_xts 90 | case aes_128_wrap 91 | case aes_192_ecb 92 | case aes_192_cbc 93 | case aes_192_cfb1 94 | case aes_192_cfb8 95 | case aes_192_cfb128 96 | case aes_192_ofb 97 | case aes_192_ctr 98 | case aes_192_ccm 99 | case aes_192_gcm 100 | case aes_192_wrap 101 | case aes_256_ecb 102 | case aes_256_cbc 103 | case aes_256_cfb1 104 | case aes_256_cfb8 105 | case aes_256_cfb128 106 | case aes_256_ofb 107 | case aes_256_ctr 108 | case aes_256_ccm 109 | case aes_256_gcm 110 | case aes_256_xts 111 | case aes_256_wrap 112 | case aes_128_cbc_hmac_sha1 113 | case aes_256_cbc_hmac_sha1 114 | case aes_128_cbc_hmac_sha256 115 | case aes_256_cbc_hmac_sha256 116 | case camellia_128_ecb 117 | case camellia_128_cbc 118 | case camellia_128_cfb1 119 | case camellia_128_cfb8 120 | case camellia_128_cfb128 121 | case camellia_128_ofb 122 | case camellia_192_ecb 123 | case camellia_192_cbc 124 | case camellia_192_cfb1 125 | case camellia_192_cfb8 126 | case camellia_192_cfb128 127 | case camellia_192_ofb 128 | case camellia_256_ecb 129 | case camellia_256_cbc 130 | case camellia_256_cfb1 131 | case camellia_256_cfb8 132 | case camellia_256_cfb128 133 | case camellia_256_ofb 134 | case seed_ecb 135 | case seed_cbc 136 | case seed_cfb128 137 | case seed_ofb 138 | 139 | case custom(String) 140 | } 141 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/ByteIO.swift: -------------------------------------------------------------------------------- 1 | // 2 | // ByteIO.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2017-02-07. 6 | // Copyright (C) 2017 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | // 20 | 21 | import COpenSSL 22 | #if os(Linux) 23 | import SwiftGlibc 24 | #else 25 | import Darwin 26 | #endif 27 | 28 | // TODO: SSLFilter 29 | // it needs many options to configure it 30 | 31 | /// An object which is used in byte IO operations. 32 | public protocol ByteIO { 33 | 34 | } 35 | 36 | /// An object which supports `put` and `write` operations. 37 | public protocol ByteSink: ByteIO { 38 | /// Perform a `puts` operation on the stream. 39 | /// Parameter should be a null terminated character buffer. 40 | func put(string: UnsafePointer) throws 41 | /// Writes the buffer to the stream. 42 | /// Returns the bumber of bytes which were successfully written. 43 | func write(bytes: UnsafeRawBufferPointer) throws -> Int 44 | } 45 | 46 | /// An object which supports `get` and `read` operations. 47 | public protocol ByteSource: ByteIO { 48 | /// Reads data placing it into the indicated buffer. 49 | /// A maximum of `bytes.count` bytes will be read. 50 | /// The number of bytes which were read is returned. 51 | func read(_ bytes: UnsafeMutableRawBufferPointer) throws -> Int 52 | /// Perform a `gets` on the stream. 53 | /// A maximum of `bytes.count` bytes will be read. 54 | /// The number of bytes which were read is returned. 55 | /// Data is not null termibated. 56 | func get(_ bytes: UnsafeMutableRawBufferPointer) throws -> Int 57 | } 58 | 59 | /// An object which is used in byte IO filter operations. 60 | public protocol ByteFilter: ByteIO { 61 | 62 | } 63 | 64 | typealias BIOPointer = UnsafeMutablePointer? 65 | 66 | /// Base class for byte IO objects. 67 | public class ByteIOBase: CustomStringConvertible { 68 | var bio: BIOPointer 69 | var head: BIOPointer 70 | var prev: ByteIOBase? 71 | 72 | fileprivate init(bio: BIOPointer) { 73 | self.bio = bio 74 | self.head = bio 75 | self.prev = nil 76 | } 77 | 78 | fileprivate init(method: UnsafeMutablePointer?) { 79 | let bio = BIO_new(method) 80 | self.bio = bio 81 | self.head = bio 82 | self.prev = nil 83 | } 84 | 85 | deinit { 86 | if let bio = bio { 87 | BIO_free(bio) 88 | self.bio = nil 89 | self.head = nil 90 | self.prev = nil 91 | } 92 | } 93 | 94 | public var description: String { 95 | var ret = "" 96 | var ptr = head 97 | while let p = ptr { 98 | if !ret.isEmpty { 99 | ret.append("<->") 100 | } 101 | if p == bio { 102 | ret.append("(\(String(validatingUTF8: BIO_method_name(p)) ?? "?"))") 103 | } else { 104 | ret.append("\(String(validatingUTF8: BIO_method_name(p)) ?? "?")") 105 | } 106 | ptr = BIO_next(p) 107 | } 108 | return ret 109 | } 110 | 111 | fileprivate var streamName: String? { 112 | return String(validatingUTF8: BIO_method_name(bio)) 113 | } 114 | 115 | private func clear() { 116 | self.bio = nil 117 | self.prev?.clear() 118 | self.prev = nil 119 | } 120 | 121 | /// Deallocate and clears all underlying objects. 122 | /// This will destroy the entire IO chain. 123 | public func close() { 124 | BIO_free_all(head) 125 | clear() 126 | } 127 | 128 | /// Resets the objetc to its initial state. 129 | /// Exact results depend on the underlying IO object type. 130 | @discardableResult 131 | public func reset() -> Self { 132 | BIO_ctrl(bio, BIO_CTRL_RESET, 0, nil) 133 | return self 134 | } 135 | 136 | /// Write out all pending data and/or signal EOF for the stream. 137 | @discardableResult 138 | public func flush() throws -> Self { 139 | try checkedResult(BIO_ctrl(head, BIO_CTRL_FLUSH, 0, nil)) 140 | return self 141 | } 142 | 143 | /// Returns true if the stream as at end-of-file. 144 | public var eof: Bool { 145 | return 1 == BIO_ctrl(head, BIO_CTRL_EOF, 0, nil) 146 | } 147 | 148 | /// Returns the nuymber of bytes pending for read. 149 | public var readPending: Int { 150 | return BIO_ctrl_pending(head) 151 | } 152 | 153 | /// Returns the number of bytes pending for write. 154 | public var writePending: Int { 155 | return BIO_ctrl_wpending(head) 156 | } 157 | 158 | /// Sets the IO to non-blocking. 159 | public func setNonBlocking() { 160 | BIO_ctrl(bio, BIO_C_SET_NBIO, 1, nil) 161 | } 162 | 163 | /// Chain another object to this IO stream. 164 | /// IO filter generally go at the front of the chain and sinks/sources go at the end. 165 | @discardableResult 166 | public func chain(_ next: T) -> T { 167 | next.prev = self 168 | next.head = self.head 169 | BIO_push(self.bio, next.bio) 170 | return next 171 | } 172 | 173 | /// Pair this IO chain with the other. 174 | /// Any data written to one end can be read on the other and vice versa. 175 | public func pair(with: ByteIOBase, thisWriteBuffer: Int = 0, thatWriteBuffer: Int = 0) throws { 176 | try checkedResult(BIO_ctrl(bio, BIO_C_SET_WRITE_BUF_SIZE, thisWriteBuffer, nil)) 177 | try checkedResult(BIO_ctrl(with.bio, BIO_C_SET_WRITE_BUF_SIZE, thatWriteBuffer, nil)) 178 | try checkedResult(BIO_ctrl(bio, BIO_C_MAKE_BIO_PAIR, 0, with.bio)) 179 | } 180 | 181 | /// Detach this object from the chain. Objects before and after this object are bound together. 182 | @discardableResult 183 | public func detach() -> Self { 184 | BIO_pop(bio) 185 | head = bio 186 | prev = nil 187 | return self 188 | } 189 | 190 | @discardableResult 191 | func checkedResult(_ result: Int) throws -> Int { 192 | guard result > -1 else { 193 | try CryptoError.throwOpenSSLError() 194 | } 195 | return result 196 | } 197 | 198 | @discardableResult 199 | func checkedResult(_ result: Int32) throws -> Int { 200 | return try checkedResult(Int(result)) 201 | } 202 | } 203 | 204 | extension ByteSink where Self: ByteIOBase { 205 | public func put(string: UnsafePointer) throws { 206 | try checkedResult(Int(BIO_puts(head, string))) 207 | } 208 | 209 | public func write(bytes: UnsafeRawBufferPointer) throws -> Int { 210 | return try checkedResult(Int(BIO_write(head, bytes.baseAddress, Int32(bytes.count)))) 211 | } 212 | } 213 | 214 | extension ByteSource where Self: ByteIOBase { 215 | public func read(_ bytes: UnsafeMutableRawBufferPointer) throws -> Int { 216 | let result = try checkedResult(BIO_read(head, bytes.baseAddress, Int32(bytes.count))) 217 | return result 218 | } 219 | 220 | public func get(_ bytes: UnsafeMutableRawBufferPointer) throws -> Int { 221 | let result = try checkedResult(BIO_gets(head, bytes.baseAddress?.assumingMemoryBound(to: Int8.self), Int32(bytes.count))) 222 | return result 223 | } 224 | } 225 | 226 | /// A non-descript byte IO object. 227 | /// Generally returned as a result of using IOPair. 228 | public class GenericIO: ByteIOBase, ByteSink, ByteSource { 229 | public init() { 230 | super.init(method: UnsafeMutablePointer(mutating: UnsafePointer(BIO_s_bio()))) 231 | } 232 | override init(bio: BIOPointer) { 233 | super.init(bio: bio) 234 | } 235 | } 236 | 237 | /// Creates two byte IO objects which are connected to each other such that 238 | /// data written on one end can be read from the other and vice versa. 239 | public struct IOPair { 240 | /// The "first" end of the pair. 241 | public let first: GenericIO 242 | /// The "second" end of the pair. 243 | public let second: GenericIO 244 | /// Create a new IO pair. The buffers for each end can be indicated. 245 | /// Data will be pushed only after the buffer size is reached or the chain is flushed. 246 | /// Default buffer size is approx 4k. 247 | public init(firstWriteBuffer: Int = 0, secondWriteBuffer: Int = 0) { 248 | var fPtr: BIOPointer = nil 249 | var sPtr: BIOPointer = nil 250 | BIO_new_bio_pair(&fPtr, firstWriteBuffer, &sPtr, secondWriteBuffer) 251 | self.first = GenericIO(bio: fPtr) 252 | self.second = GenericIO(bio: sPtr) 253 | } 254 | } 255 | 256 | /// A sink/source object which reads from or writes to a memory buffer. 257 | /// Buffer is automatically resized when writing to it. 258 | public class MemoryIO: ByteIOBase, ByteSink, ByteSource { 259 | /// The current buffer data held by this object. 260 | var memory: UnsafeRawBufferPointer? { 261 | var m: UnsafePointer? = nil 262 | let count = BIO_ctrl(bio, BIO_CTRL_INFO, 0, &m) 263 | guard let mm = m else { 264 | return nil 265 | } 266 | return UnsafeRawBufferPointer(start: mm, count: count) 267 | } 268 | /// Create a new object with no initial data. 269 | public init() { 270 | super.init(method: UnsafeMutablePointer(mutating: UnsafePointer(BIO_s_mem()))) 271 | } 272 | /// Create a new buffer and allocate the indicated number of bytes. 273 | public convenience init(allocate count: Int) { 274 | self.init() 275 | let mem = BUF_MEM_new() 276 | BUF_MEM_grow(mem, count) 277 | BIO_ctrl(bio, BIO_C_SET_BUF_MEM, Int(BIO_CLOSE), UnsafeMutableRawPointer(mutating: mem)) 278 | } 279 | /// Create a new object from an existing data buffer. 280 | /// Pointer must remain valid while using it as a buffer. 281 | public init(_ pointer: UnsafeRawBufferPointer) { 282 | super.init(bio: BIO_new_mem_buf(pointer.baseAddress, Int32(pointer.count))) 283 | } 284 | /// Create a new buffer from the indicated data. 285 | /// The buffer's data is copied to a new buffer and so does not need to remain valid. 286 | public convenience init(copying: UnsafeRawBufferPointer) { 287 | self.init() 288 | let mem = BUF_MEM_new() 289 | BUF_MEM_grow(mem, copying.count) 290 | if let data = mem?.pointee.data, let baseAddress = copying.baseAddress { 291 | memcpy(data, baseAddress, copying.count) 292 | } 293 | BIO_ctrl(bio, BIO_C_SET_BUF_MEM, Int(BIO_CLOSE), UnsafeMutableRawPointer(mutating: mem)) 294 | } 295 | /// Create a new buffer given the string data. 296 | /// String data is converted to UTF8 and the data is copied to a new buffer. 297 | public convenience init(_ string: String) { 298 | var chars = [UInt8](string.utf8) 299 | let count = chars.count 300 | self.init() 301 | 302 | let mem = BUF_MEM_new() 303 | BUF_MEM_grow(mem, count) 304 | if let data = mem?.pointee.data { 305 | memcpy(data, &chars, count) 306 | } 307 | BIO_ctrl(bio, BIO_C_SET_BUF_MEM, Int(BIO_CLOSE), UnsafeMutableRawPointer(mutating: mem)) 308 | } 309 | } 310 | 311 | /// Byte IO object which reads from or write to a file. 312 | public class FileIO: ByteIOBase, ByteSink, ByteSource { 313 | /// Create a ne wobject with the given file name. 314 | /// Mode can be any of the standard "FILE" open modes: 315 | /// r or rb - Open file for reading. 316 | /// w or wb - Truncate to zero length or create file for writing. 317 | /// a or ab - Append; open or create file for writing at end-of-file. 318 | /// r+ or rb+ or r+b - Open file for update (reading and writing). 319 | /// w+ or wb+ or w+b - Truncate to zero length or create file for update. 320 | /// a+ or ab+ or a+b - Append; open or create file for update, writing at end-of-file. 321 | public init(name: String, mode: String) { 322 | super.init(bio: BIO_new_file(name, mode)) 323 | } 324 | /// Create a new object with an existing file descriptor. 325 | /// If `close` is true then the file will be closed when the IO object is destroyed. 326 | public init(file: Int, close: Bool) { 327 | super.init(bio: BIO_new_fd(Int32(file), close ? BIO_CLOSE : BIO_NOCLOSE)) 328 | } 329 | /// Create a new object with an existing socket file descriptor. 330 | /// If `close` is true then the file will be closed when the IO object is destroyed. 331 | public init(socket: Int, close: Bool) { 332 | super.init(bio: BIO_new_socket(Int32(socket), close ? BIO_CLOSE : BIO_NOCLOSE)) 333 | } 334 | } 335 | 336 | /// Create a new object capable of reading from STDIN. 337 | public class FileIOStdin: ByteIOBase, ByteSource { 338 | public init() { 339 | super.init(bio: BIO_new_fp(stdin, BIO_NOCLOSE)) 340 | } 341 | } 342 | 343 | /// Create a new object capable of writing to STDOUT. 344 | public class FileIOStdout: ByteIOBase, ByteSink { 345 | public init() { 346 | super.init(bio: BIO_new_fp(stdout, BIO_NOCLOSE)) 347 | } 348 | } 349 | 350 | /// Create a new object capable of writing to STDERR. 351 | public class FileIOStderr: ByteIOBase, ByteSink { 352 | public init() { 353 | super.init(bio: BIO_new_fp(stderr, BIO_NOCLOSE)) 354 | } 355 | } 356 | 357 | /// A sink/source which neither reads nor writes and data. 358 | /// Useful for combining with a filter such as DigestFilter which does not actually 359 | /// need to store data written through it. 360 | public class NullIO: ByteIOBase, ByteSink, ByteSource { 361 | public init() { 362 | super.init(method: UnsafeMutablePointer(mutating: UnsafePointer(BIO_s_null()))) 363 | } 364 | } 365 | 366 | /// A sink/source which will accept network connections. 367 | public class AcceptIO: ByteIOBase, ByteSource, ByteSink { 368 | /// Name is "host:port" 369 | public init(name: String) { 370 | super.init(bio: BIO_new_accept(name)) 371 | BIO_ctrl(bio, BIO_C_SET_BIND_MODE, Int(BIO_BIND_REUSEADDR), nil) 372 | } 373 | /// Attempt to listen on the indicated address. 374 | public func listen() throws { 375 | let result = BIO_ctrl(bio, BIO_C_DO_STATE_MACHINE, 0, nil) 376 | guard result == 1 else { 377 | try checkedResult(result) 378 | return 379 | } 380 | } 381 | /// Wait for a new connectioon to be made. 382 | public func accept() throws { 383 | let result = BIO_ctrl(bio, BIO_C_DO_STATE_MACHINE, 0, nil) 384 | guard result == 1 else { 385 | try checkedResult(result) 386 | return 387 | } 388 | } 389 | /// Switch the accept to non-blocking mode. 390 | public func setNonBlockingAccept() { 391 | var p: UnsafePointer? = nil 392 | // does not matter what p is, just needs to be non-nil 393 | BIO_ctrl(bio, BIO_C_SET_ACCEPT, 1, &p) 394 | } 395 | } 396 | 397 | /// A sink/source which will perform a network connection. 398 | public class ConnectIO: ByteIOBase, ByteSource, ByteSink { 399 | /// Name is "host:port" 400 | public init(name: String) { 401 | super.init(bio: BIO_new_connect(name)) 402 | } 403 | /// Attempt to open the connection. 404 | public func connect() throws { 405 | let result = BIO_ctrl(bio, BIO_C_DO_STATE_MACHINE, 0, nil) 406 | guard result == 1 else { 407 | try checkedResult(result) 408 | return 409 | } 410 | } 411 | } 412 | 413 | /// An IO filter which base 64 *encodes* data *written* to it and 414 | /// base 64 *decodes* any data *read* from it. 415 | public class Base64Filter: ByteIOBase { 416 | /// Create a new base 64 filter object. 417 | /// If `requireNewLines` is true then standard base 64 line wrapping will be expected in 418 | /// data read and performed on outgoing data. 419 | public init(requireNewLines: Bool = false) { 420 | super.init(bio: BIO_new(BIO_f_base64())) 421 | if !requireNewLines { 422 | BIO_set_flags(bio, BIO_FLAGS_BASE64_NO_NL) 423 | } 424 | } 425 | } 426 | 427 | /// An IO ibject which performs buffering on any reads or writes. 428 | public class BufferFilter: ByteIOBase { 429 | public static let minimumBufferSize = 4096 430 | /// Initialize with buffer size. Minimum buffer size is 4k. 431 | public init(bufferSize: Int = 0) { 432 | super.init(bio: BIO_new(BIO_f_buffer())) 433 | if bufferSize > BufferFilter.minimumBufferSize { 434 | BIO_ctrl(bio, BIO_C_SET_BUFF_SIZE, bufferSize, nil) 435 | } 436 | } 437 | } 438 | 439 | /// An IO filter which runs the indicated digest algorithm on and data 440 | /// read from or written to the stream. 441 | /// The resulting digest can be finalized and retreived by calling `gets` on the digest filter itself. 442 | /// The resulting required digest size can be determined through `Digest.length`. 443 | public class DigestFilter: ByteIOBase, ByteSource { 444 | public init(_ digest: Digest) { 445 | super.init(method: UnsafeMutablePointer(mutating: UnsafePointer(BIO_f_md()))) 446 | let p = digest.evp 447 | BIO_ctrl(bio, BIO_C_SET_MD, 1, UnsafeMutableRawPointer(mutating: p)) 448 | } 449 | } 450 | 451 | /// An IO object which encrypts all data written through the stream and 452 | /// decrypts data read from it. 453 | public class CipherFilter: ByteIOBase { 454 | /// Initialize with the indicated cipher, key, iv. 455 | /// The final parameter, `encrypting`, must be set to control the operation. 456 | public init(_ cipher: Cipher, key: UnsafePointer, iv: UnsafePointer, encrypting: Bool) { 457 | super.init(bio: BIO_new(BIO_f_cipher())) 458 | BIO_set_cipher(bio, cipher.evp, key, iv, encrypting ? 1 : 0) 459 | } 460 | 461 | /// Checks the status of the *decryption* and throws an error if it failed. 462 | public func ensureDecryptSuccess() throws { 463 | try checkedResult(BIO_ctrl(bio, BIO_C_GET_CIPHER_STATUS, 0, nil)) 464 | } 465 | } 466 | 467 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/Extensions.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Extensions.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2017-02-07. 6 | // Copyright (C) 2017 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | // 20 | 21 | import Foundation 22 | import PerfectLib 23 | 24 | public extension FixedWidthInteger { 25 | /// get a random number by the type 26 | static var random: Self { 27 | var x = [Self.max] 28 | x.withUnsafeMutableBytes { $0.initializeRandom() } 29 | return x[0] 30 | } 31 | } 32 | public extension Float { 33 | /// get a random number by the type 34 | static var random: Float { 35 | var x: [Float] = [0] 36 | x.withUnsafeMutableBytes { $0.initializeRandom() } 37 | return x[0] 38 | } 39 | } 40 | public extension Double { 41 | /// get a random number by the type 42 | static var random: Double { 43 | var x: [Double] = [0] 44 | x.withUnsafeMutableBytes { $0.initializeRandom() } 45 | return x[0] 46 | } 47 | } 48 | 49 | public extension File { 50 | /// Digest a file into a hex based signature 51 | /// - parameter digest: the algorithm of digest 52 | /// - parameter bufferSize: the file digesting buffer, which is subject to the OS. Default is 16k, can be larger or smaller. 53 | /// - returns: digest bytes 54 | /// - throws: CryptoError 55 | func digest(_ digest: Digest, bufferSize: Int = 16384) throws -> [UInt8] { 56 | let filter = DigestFilter(digest) 57 | let chain = filter.chain(NullIO()) 58 | try self.open() 59 | while let buf = try? self.readSomeBytes(count: bufferSize) { 60 | let rd = try buf.withUnsafeBytes { pointer in 61 | return try chain.write(bytes: pointer) 62 | } 63 | if rd < 1 { break } 64 | } 65 | self.close() 66 | try chain.flush() 67 | let validLength = digest.length 68 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: validLength, alignment: 0) 69 | guard try filter.get(ret) == validLength else { 70 | ret.deallocate() 71 | return [] 72 | } 73 | return ret.map { $0 } 74 | } 75 | } 76 | 77 | public extension String { 78 | /// Construct a string from a UTF8 character array. 79 | /// The array's count indicates how many characters are to be converted. 80 | /// Returns nil if the data is invalid. 81 | init?(validatingUTF8 a: [UInt8]) { 82 | self = UTF8Encoding.encode(generator: a.makeIterator()) 83 | } 84 | /// Construct a string from a UTF8 character pointer. 85 | /// Character data does not need to be null terminated. 86 | /// The buffer's count indicates how many characters are to be converted. 87 | /// Returns nil if the data is invalid. 88 | init?(validatingUTF8 ptr: UnsafeRawBufferPointer?) { 89 | guard let ptr = ptr else { 90 | return nil 91 | } 92 | self = UTF8Encoding.encode(generator: ptr.makeIterator()) 93 | } 94 | /// Obtain a buffer pointer for the String's UTF8 characters. 95 | func withBufferPointer(_ body: (UnsafeRawBufferPointer) throws -> Result) rethrows -> Result { 96 | return try [UInt8](self.utf8).withUnsafeBytes(body) 97 | } 98 | } 99 | 100 | public typealias EncodingS = Encoding 101 | 102 | public extension String { 103 | /// Decode the String into an array of bytes using the indicated encoding. 104 | /// The string's UTF8 characters are decoded. 105 | func decode(_ encoding: EncodingS) -> [UInt8]? { 106 | return Array(utf8).decode(encoding) 107 | } 108 | /// Encode the String into an array of bytes using the indicated encoding. 109 | /// The string's UTF8 characters are encoded. 110 | func encode(_ encoding: EncodingS) -> [UInt8]? { 111 | return Array(utf8).encode(encoding) 112 | } 113 | /// Perform the digest algorithm on the String's UTF8 bytes. 114 | func digest(_ digest: Digest) -> [UInt8]? { 115 | return Array(utf8).digest(digest) 116 | } 117 | /// Sign the String data into an array of bytes using the indicated algorithm and key. 118 | func sign(_ digest: Digest, key: Key) -> [UInt8]? { 119 | return Array(utf8).sign(digest, key: key) 120 | } 121 | /// Verify the signature against the String data. 122 | /// Returns true if the signature is verified. Returns false otherwise. 123 | func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool { 124 | return Array(utf8).verify(digest, signature: signature, key: key) 125 | } 126 | /// Encrypt this buffer using the indicated cipher, password, and salt. 127 | /// The string's UTF8 characters are encoded. 128 | /// Resulting data is in PEM encoded CMS format. 129 | func encrypt(_ cipher: Cipher, 130 | password: String, 131 | salt: String, 132 | keyIterations: Int = 2048, 133 | keyDigest: Digest = .md5) -> String? { 134 | guard let v = Array(utf8).encrypt(cipher, password: Array(password.utf8), salt: Array(salt.utf8), keyIterations: keyIterations, keyDigest: keyDigest) else { 135 | return nil 136 | } 137 | return String(validatingUTF8: v) 138 | } 139 | /// Decrypt this PEM encoded CMS buffer using the indicated password and salt. 140 | /// Resulting decrypted data must be valid UTF-8 characters or the operation will fail. 141 | func decrypt(_ cipher: Cipher, 142 | password: String, 143 | salt: String, 144 | keyIterations: Int = 2048, 145 | keyDigest: Digest = .md5) -> String? { 146 | guard let v = Array(utf8).decrypt(cipher, password: Array(password.utf8), salt: Array(salt.utf8), keyIterations: keyIterations, keyDigest: keyDigest) else { 147 | return nil 148 | } 149 | return String(validatingUTF8: v) 150 | } 151 | } 152 | 153 | public protocol Octal {} 154 | extension UInt8: Octal {} 155 | 156 | public extension Array where Element: Octal { 157 | /// Creates a new array containing the specified number of a single random values. 158 | init(randomCount count: Int) { 159 | self.init(repeating: UInt8(0) as! Element, count: count) 160 | withUnsafeMutableBytes { 161 | $0.initializeRandom() 162 | } 163 | } 164 | } 165 | 166 | public extension Array where Element: Octal { 167 | /// Encode the Array into An array of bytes using the indicated encoding. 168 | func encode(_ encoding: Encoding) -> [UInt8]? { 169 | guard let newPtr = withUnsafeBytes({$0.encode(encoding)}) else { 170 | return nil 171 | } 172 | defer { newPtr.deallocate() } 173 | return newPtr.map { $0 } 174 | } 175 | /// Decode the Array into an array of bytes using the indicated encoding. 176 | func decode(_ encoding: Encoding) -> [UInt8]? { 177 | guard let newPtr = withUnsafeBytes({$0.decode(encoding)}) else { 178 | return nil 179 | } 180 | defer { newPtr.deallocate() } 181 | return newPtr.map { $0 } 182 | } 183 | /// Digest the Array data into an array of bytes using the indicated algorithm. 184 | func digest(_ digest: Digest) -> [UInt8]? { 185 | guard let newPtr = withUnsafeBytes({$0.digest(digest)}) else { 186 | return nil 187 | } 188 | defer { newPtr.deallocate() } 189 | return newPtr.map { $0 } 190 | } 191 | /// Sign the Array data into an array of bytes using the indicated algorithm and key. 192 | func sign(_ digest: Digest, key: Key) -> [UInt8]? { 193 | guard let newPtr = withUnsafeBytes({$0.sign(digest, key: key)}) else { 194 | return nil 195 | } 196 | defer { newPtr.deallocate() } 197 | return newPtr.map { $0 } 198 | } 199 | /// Verify the array against the signature. 200 | /// Returns true if the signature is verified. Returns false otherwise. 201 | func verify(_ digest: Digest, signature: [UInt8], key: Key) -> Bool { 202 | return withUnsafeBytes { 203 | ptr in 204 | signature.withUnsafeBytes { 205 | ptr.verify(digest, signature: $0, key: key) 206 | } 207 | } 208 | } 209 | /// Decrypt this buffer using the indicated cipher, key an iv (initialization vector). 210 | func encrypt(_ cipher: Cipher, key: [UInt8], iv: [UInt8]) -> [UInt8]? { 211 | let vv = withUnsafeBytes { 212 | sv in key.withUnsafeBytes { 213 | key in iv.withUnsafeBytes { iv in 214 | cipher.encrypt(sv, key: key, iv: iv)}}} 215 | guard let v = vv else { 216 | return nil 217 | } 218 | defer { 219 | v.deallocate() 220 | } 221 | return v.map { UInt8($0) } 222 | } 223 | /// Decrypt this buffer using the indicated cipher, key an iv (initialization vector). 224 | func decrypt(_ cipher: Cipher, key: [UInt8], iv: [UInt8]) -> [UInt8]? { 225 | let vv = withUnsafeBytes { sv in 226 | key.withUnsafeBytes { key in 227 | iv.withUnsafeBytes { iv in 228 | cipher.decrypt(sv, key: key, iv: iv)}}} 229 | guard let v = vv else { 230 | return nil 231 | } 232 | defer { 233 | v.deallocate() 234 | } 235 | return v.map { UInt8($0) } 236 | } 237 | /// Encrypt this buffer using the indicated cipher, password, and salt. 238 | /// Resulting data is PEM encoded CMS format. 239 | func encrypt(_ cipher: Cipher, 240 | password: [UInt8], 241 | salt: [UInt8], 242 | keyIterations: Int = 2048, 243 | keyDigest: Digest = .md5) -> [UInt8]? { 244 | let vv = withUnsafeBytes { sv in 245 | password.withUnsafeBytes { password in 246 | salt.withUnsafeBytes { salt in 247 | sv.encrypt(cipher, 248 | password: password, 249 | salt: salt, 250 | keyIterations: keyIterations, 251 | keyDigest: keyDigest)}}} 252 | guard let v = vv else { 253 | return nil 254 | } 255 | defer { 256 | v.deallocate() 257 | } 258 | return v.map { UInt8($0) } 259 | } 260 | /// Decrypt this PEM encoded CMS buffer using the indicated password and salt. 261 | func decrypt(_ cipher: Cipher, 262 | password: [UInt8], 263 | salt: [UInt8], 264 | keyIterations: Int = 2048, 265 | keyDigest: Digest = .md5) -> [UInt8]? { 266 | let vv = withUnsafeBytes { sv in 267 | password.withUnsafeBytes { password in 268 | salt.withUnsafeBytes { salt in 269 | sv.decrypt(cipher, 270 | password: password, 271 | salt: salt, 272 | keyIterations: keyIterations, 273 | keyDigest: keyDigest)}}} 274 | guard let v = vv else { 275 | return nil 276 | } 277 | defer { 278 | v.deallocate() 279 | } 280 | return v.map { UInt8($0) } 281 | } 282 | } 283 | 284 | public extension UnsafeMutableRawBufferPointer { 285 | /// Allocate memory for `size` bytes with word alignment from the encryption library's 286 | /// random number generator. 287 | /// 288 | /// - Postcondition: The memory is allocated and initialized to random bits. 289 | static func allocateRandom(count size: Int) -> UnsafeMutableRawBufferPointer? { 290 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: size, alignment: 0) 291 | guard 1 == internal_RAND_bytes(into: ret) else { 292 | ret.deallocate() 293 | return nil 294 | } 295 | return ret 296 | } 297 | 298 | /// Initialize the buffer with random bytes. 299 | func initializeRandom() { 300 | _ = internal_RAND_bytes(into: self) 301 | } 302 | } 303 | 304 | extension UnsafeMutableRawBufferPointer { 305 | // Added for Swift 4.0/4.1 compat 306 | #if swift(>=4.1) 307 | #else 308 | static func allocate(byteCount: Int, alignment: Int) -> UnsafeMutableRawBufferPointer { 309 | return allocate(count: byteCount) 310 | } 311 | #endif 312 | } 313 | 314 | extension UnsafeMutablePointer { 315 | // Added for Swift 4.0/4.1 compat 316 | #if swift(>=4.1) 317 | #else 318 | func deallocate() { 319 | deallocate(capacity: 0) 320 | } 321 | #endif 322 | } 323 | 324 | public extension UnsafeRawBufferPointer { 325 | /// Allocate memory for `size` bytes with word alignment from the encryption library's 326 | /// random number generator. 327 | /// 328 | /// - Postcondition: The memory is allocated and initialized to random bits. 329 | static func allocateRandom(count size: Int) -> UnsafeRawBufferPointer? { 330 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: size, alignment: 0) 331 | guard 1 == internal_RAND_bytes(into: ret) else { 332 | ret.deallocate() 333 | return nil 334 | } 335 | return UnsafeRawBufferPointer(ret) 336 | } 337 | /// Encode the buffer using the indicated encoding. 338 | /// The return value must be deallocated by the caller. 339 | func encode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? { 340 | return encoding.encodeBytes(self) 341 | } 342 | /// Decode the buffer using the indicated encoding. 343 | /// The return value must be deallocated by the caller. 344 | func decode(_ encoding: Encoding) -> UnsafeMutableRawBufferPointer? { 345 | return encoding.decodeBytes(self) 346 | } 347 | /// Digest the buffer using the indicated algorithm. 348 | /// The return value must be deallocated by the caller. 349 | func digest(_ digest: Digest) -> UnsafeMutableRawBufferPointer? { 350 | let filter = DigestFilter(digest) 351 | let chain = filter.chain(NullIO()) 352 | do { 353 | _ = try chain.write(bytes: self) 354 | try chain.flush() 355 | let validLength = digest.length 356 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: validLength, alignment: 0) 357 | guard try filter.get(ret) == validLength else { 358 | ret.deallocate() 359 | return nil 360 | } 361 | return ret 362 | } catch { 363 | return nil 364 | } 365 | } 366 | /// Sign the buffer using the indicated algorithm and key. 367 | /// The return value must be deallocated by the caller. 368 | func sign(_ digest: Digest, key: Key) -> UnsafeMutableRawBufferPointer? { 369 | return digest.sign(self, privateKey: key) 370 | } 371 | /// Verify the signature against the buffer. 372 | /// Returns true if the signature is verified. Returns false otherwise. 373 | func verify(_ digest: Digest, signature: UnsafeRawBufferPointer, key: Key) -> Bool { 374 | return digest.verify(self, signature: signature, publicKey: key) 375 | } 376 | /// Encrypt this buffer using the indicated cipher, key and iv (initialization vector). 377 | /// Returns a newly allocated buffer which must be freed by the caller. 378 | func encrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 379 | return cipher.encrypt(self, key: key, iv: iv) 380 | } 381 | /// Decrypt this buffer using the indicated cipher, key and iv (initialization vector). 382 | /// Returns a newly allocated buffer which must be freed by the caller. 383 | func decrypt(_ cipher: Cipher, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 384 | return cipher.decrypt(self, key: key, iv: iv) 385 | } 386 | /// Encrypt this buffer to PEM encoded CMS format using the indicated cipher, password, and salt. 387 | /// Returns a newly allocated buffer which must be freed by the caller. 388 | func encrypt(_ cipher: Cipher, 389 | password: UnsafeRawBufferPointer, 390 | salt: UnsafeRawBufferPointer, 391 | keyIterations: Int = 2048, 392 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? { 393 | return cipher.encrypt(self, password: password, salt: salt, keyIterations: keyIterations, keyDigest: keyDigest) 394 | } 395 | /// Decrypt this PEM encoded CMS buffer using the indicated password and salt. 396 | /// Returns a newly allocated buffer which must be freed by the caller. 397 | func decrypt(_ cipher: Cipher, 398 | password: UnsafeRawBufferPointer, 399 | salt: UnsafeRawBufferPointer, 400 | keyIterations: Int = 2048, 401 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? { 402 | return cipher.decrypt(self, password: password, salt: salt, keyIterations: keyIterations, keyDigest: keyDigest) 403 | } 404 | } 405 | 406 | extension UInt8 { 407 | init?(hexOne c1v: UInt8, hexTwo c2v: UInt8) { 408 | let capA: UInt8 = 65 409 | let capF: UInt8 = 70 410 | let lowA: UInt8 = 97 411 | let lowF: UInt8 = 102 412 | let zero: UInt8 = 48 413 | let nine: UInt8 = 57 414 | 415 | var newChar = UInt8(0) 416 | 417 | if c1v >= capA && c1v <= capF { 418 | newChar = c1v - capA + 10 419 | } else if c1v >= lowA && c1v <= lowF { 420 | newChar = c1v - lowA + 10 421 | } else if c1v >= zero && c1v <= nine { 422 | newChar = c1v - zero 423 | } else { 424 | return nil 425 | } 426 | 427 | newChar *= 16 428 | 429 | if c2v >= capA && c2v <= capF { 430 | newChar += c2v - capA + 10 431 | } else if c2v >= lowA && c2v <= lowF { 432 | newChar += c2v - lowA + 10 433 | } else if c2v >= zero && c2v <= nine { 434 | newChar += c2v - zero 435 | } else { 436 | return nil 437 | } 438 | self = newChar 439 | } 440 | } 441 | 442 | // A generalized wrapper around the Unicode codec operations. 443 | struct UEncoding { 444 | // Return a String given a character generator. 445 | static func encode(codec inCodec: D, generator: G) -> String where G.Element == D.CodeUnit { 446 | var encodedString = "" 447 | var finished: Bool = false 448 | var mutableDecoder = inCodec 449 | var mutableGenerator = generator 450 | repeat { 451 | let decodingResult = mutableDecoder.decode(&mutableGenerator) 452 | switch decodingResult { 453 | case .scalarValue(let char): 454 | encodedString.append(String(char)) 455 | case .emptyInput: 456 | finished = true 457 | case .error: 458 | finished = true 459 | } 460 | } while !finished 461 | return encodedString 462 | } 463 | } 464 | 465 | // Utility wrapper permitting a UTF-8 character generator to encode a String. Also permits a String to be converted into a UTF-8 byte array. 466 | struct UTF8Encoding { 467 | // Use a character generator to create a String. 468 | static func encode(generator gen: G) -> String where G.Element == UTF8.CodeUnit { 469 | return UEncoding.encode(codec: UTF8(), generator: gen) 470 | } 471 | // Use a character sequence to create a String. 472 | static func encode(bytes byts: S) -> String where S.Iterator.Element == UTF8.CodeUnit { 473 | return encode(generator: byts.makeIterator()) 474 | } 475 | // Use a character sequence to create a String. 476 | static func encode(bytes byts: [UTF8.CodeUnit]) -> String { 477 | return byts.withUnsafeBytes { encode(generator: $0.makeIterator()) } 478 | } 479 | // Decode a String into an array of UInt8. 480 | static func decode(string str: String) -> Array { 481 | return [UInt8](str.utf8) 482 | } 483 | } 484 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/JWK.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWK.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2020-03-08. 6 | // 7 | 8 | import COpenSSL 9 | import Foundation 10 | 11 | func BN_num_bytes(_ bn: UnsafeMutablePointer) -> Int32 { 12 | return ((BN_num_bits(bn)+7)/8) 13 | } 14 | 15 | public struct JWK: Codable { 16 | public struct Key: Codable { 17 | public let kty: String 18 | public let kid: String 19 | public var e: String? = nil 20 | public var n: String? = nil 21 | public var d: String? = nil 22 | init(key: PEMKey) throws { 23 | kid = UUID().uuidString 24 | let pkey = key.pkey 25 | switch key.type { 26 | case .rsa: 27 | kty = "RSA" 28 | var ke: UnsafePointer? 29 | var kn: UnsafePointer? 30 | var kd: UnsafePointer? 31 | if let rsa = EVP_PKEY_get1_RSA(pkey) { 32 | RSA_get0_key(rsa, &kn, &ke, &kd) 33 | n = encnum(kn) 34 | e = encnum(ke) 35 | d = encnum(kd) 36 | } 37 | case .dsa: 38 | kty = "DSA" 39 | let _ = EVP_PKEY_get1_DSA(pkey) 40 | throw CryptoError(code: -1, msg: "Not implemented.") 41 | case .ec: 42 | kty = "EC" 43 | let _ = EVP_PKEY_get1_EC_KEY(pkey) 44 | throw CryptoError(code: -1, msg: "Not implemented.") 45 | } 46 | } 47 | private func encnum(_ kn: UnsafePointer?) -> String? { 48 | if let kn = kn { 49 | let len = BN_num_bytes(UnsafeMutablePointer(mutating: kn)) 50 | let ptr = UnsafeMutableBufferPointer.allocate(capacity: Int(len)) 51 | defer { 52 | ptr.deallocate() 53 | } 54 | BN_bn2bin(kn, ptr.baseAddress) 55 | if let encoded = UnsafeRawBufferPointer(ptr).encode(.base64url) { 56 | return String(validatingUTF8: UnsafeRawBufferPointer(encoded)) 57 | } 58 | } 59 | return nil 60 | } 61 | } 62 | public let keys: [Key] 63 | public init(key: PEMKey) throws { 64 | try self.init(keys: [key]) 65 | } 66 | public init(keys: PEMKey, pems: PEMKey...) throws { 67 | try self.init(keys: [keys] + pems) 68 | } 69 | public init(keys pems: [PEMKey]) throws { 70 | keys = try pems.map{try Key(key: $0)} 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/JWT.swift: -------------------------------------------------------------------------------- 1 | // 2 | // JWT.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2017-03-13. 6 | // Copyright (C) 2017 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import COpenSSL 21 | import PerfectLib 22 | import Foundation 23 | 24 | private let dot = UInt8(46) 25 | private let jwtEncoding = Encoding.base64url 26 | 27 | /// Types used by both JWTCreator and JWTVerifier 28 | public struct JWT { 29 | /// Supported JWT alg types 30 | public enum Alg { 31 | case hs256, hs384, hs512 32 | case rs256, rs384, rs512 33 | case es256, es384, es512 34 | case none 35 | } 36 | /// A signing or validation error 37 | public enum Error: Swift.Error { 38 | case verificationError(String) 39 | case signingError(String) 40 | } 41 | } 42 | 43 | /// Accepts a JWT token string and verifies its structural validity and signature. 44 | public struct JWTVerifier { 45 | let headerBytes: [UInt8] 46 | let payloadBytes: [UInt8] 47 | let signatureBytes: [UInt8] 48 | /// The headers obtained from the token. 49 | public var header: [String:Any] { 50 | return (try? String(validatingUTF8: headerBytes)?.jsonDecode()) as? [String:Any] ?? [:] 51 | } 52 | /// The payload carried by the token. 53 | public var payload: [String:Any] { 54 | return (try? payloadString?.jsonDecode()) as? [String:Any] ?? [:] 55 | } 56 | // Payload data as UTF-8 57 | public var payloadString: String? { 58 | return String(validatingUTF8: payloadBytes) 59 | } 60 | 61 | /// Create a JWTVerifier given a source string in the "aaaa.bbbb.cccc" format. 62 | /// Returns nil if the given string is not a valid JWT. 63 | /// *Does not perform verification in this step.* Call `verify` with your key to validate. 64 | /// If verification succeeds then the `.headers` and `.payload` properties can be safely accessed. 65 | public init?(_ jwt: String) { 66 | let split = jwt.utf8.split(separator: dot, omittingEmptySubsequences: false) 67 | #if swift(>=4.1) 68 | let decoded = split.compactMap { $0.map { $0 }.decode(jwtEncoding) } 69 | #else 70 | let decoded = split.flatMap { $0.map { $0 }.decode(jwtEncoding) } 71 | #endif 72 | guard decoded.count == 3 else { 73 | return nil 74 | } 75 | headerBytes = decoded[0] 76 | payloadBytes = decoded[1] 77 | signatureBytes = decoded[2] 78 | } 79 | 80 | /// Verify the token based on the indicated algorithm and HMAC key. 81 | /// Throws a JWT.Error.verificationError if any aspect of the token is incongruent. 82 | /// Returns without any error if the token was able to be verified. 83 | /// The parameter `algo` must match the token's "alg" header. 84 | public func verify(algo: JWT.Alg, key: String) throws { 85 | return try verify(algo: algo, key: HMACKey(key)) 86 | } 87 | 88 | /// Verify the token based on the indicated algorithm and key. 89 | /// Throws a JWT.Error.verificationError if any aspect of the token is incongruent. 90 | /// Returns without any error if the token was able to be verified. 91 | /// The parameter `algo` must match the token's "alg" header. 92 | /// The key type must be compatible with the indicated `algo`. 93 | public func verify(algo: JWT.Alg, key: Key) throws { 94 | let header = self.header 95 | guard header["alg"] as? String == algo.string else { 96 | throw JWT.Error.verificationError("alg mismatch. expected \(algo.string) got \(String(describing: header["alg"]))") 97 | } 98 | if case .none = algo { 99 | return 100 | } 101 | guard let header64 = headerBytes.encode(jwtEncoding), 102 | let payload64 = payloadBytes.encode(jwtEncoding) else { 103 | throw JWT.Error.verificationError("Internal error. Unable to base64url encode header and payload.") 104 | } 105 | let part1 = header64 + [dot] + payload64 106 | guard try verify(part1, signature: signatureBytes, algo: algo, key: key) else { 107 | throw JWT.Error.verificationError("Signatures did not match.") 108 | } 109 | } 110 | 111 | func verify(_ data: [UInt8], signature: [UInt8], algo: JWT.Alg, key: Key) throws -> Bool { 112 | if case .none = algo { 113 | return true 114 | } 115 | guard let digest = algo.digest else { 116 | throw JWT.Error.signingError("Digest \(algo.string) not supported") 117 | } 118 | return data.verify(digest, signature: signature, key: key) 119 | } 120 | } 121 | 122 | /// Creates and signs new JWT tokens. 123 | public struct JWTCreator { 124 | let payloadBytes: [UInt8] 125 | /// Creates a new JWT given a payload. 126 | /// The payload can then be signed to generate a JWT token string. 127 | public init?(payload: [String:Any]) { 128 | guard let json = try? payload.jsonEncodedString() else { 129 | return nil 130 | } 131 | payloadBytes = Array(json.utf8) 132 | } 133 | /// Sign and return a new JWT token string using an HMAC key. 134 | /// Additional headers can be optionally provided. 135 | /// Throws a JWT.Error.signingError if there is a problem generating the token string. 136 | public func sign(alg: JWT.Alg, key: String, headers: [String:Any] = [:]) throws -> String { 137 | return try sign(alg: alg, key: HMACKey(key), headers: headers) 138 | } 139 | /// Sign and return a new JWT token string using the given key. 140 | /// Additional headers can be optionally provided. 141 | /// The key type must be compatible with the indicated `algo`. 142 | /// Throws a JWT.Error.signingError if there is a problem generating the token string. 143 | public func sign(alg: JWT.Alg, key: Key, headers: [String:Any] = [:]) throws -> String { 144 | var useHeaders: [String:Any] = ["alg":alg.string, "typ":"JWT"] 145 | headers.forEach { 146 | key, value in 147 | useHeaders[key] = value 148 | } 149 | let headerBytes = Array(try useHeaders.jsonEncodedString().utf8) 150 | guard let h64 = headerBytes.encode(jwtEncoding), 151 | let p64 = payloadBytes.encode(jwtEncoding) else { 152 | throw JWT.Error.signingError("Internal error. Unable to base64url encode header and payload.") 153 | } 154 | let part1 = h64 + [dot] + p64 155 | let sig = try sign(part1, algo: alg, key: key) 156 | guard let s64 = sig.encode(jwtEncoding), 157 | let ret = String(validatingUTF8: part1 + [dot] + s64) else { 158 | throw JWT.Error.signingError("Invalid resulting JWT") 159 | } 160 | return ret 161 | } 162 | 163 | func sign(_ data: [UInt8], algo: JWT.Alg, key: Key) throws -> [UInt8] { 164 | if case .none = algo { 165 | return [] 166 | } 167 | guard let digest = algo.digest else { 168 | throw JWT.Error.signingError("Digest \(algo.string) not supported") 169 | } 170 | guard let bytes = data.sign(digest, key: key) else { 171 | throw JWT.Error.signingError("Fatal error during signing") 172 | } 173 | return bytes 174 | } 175 | } 176 | 177 | public extension JWTCreator { 178 | /// Create a new JWT given a Codable object. 179 | /// The payload can then be signed to generate a JWT token string. 180 | init(payload: T) throws { 181 | let json = try JSONEncoder().encode(payload) 182 | payloadBytes = Array(json) 183 | } 184 | } 185 | 186 | public extension JWTVerifier { 187 | func verify(algo: JWT.Alg, key: Key, as: T.Type) throws -> T { 188 | try verify(algo: algo, key: key) 189 | return try JSONDecoder().decode(`as`, from: Data(payloadBytes)) 190 | } 191 | func verify(algo: JWT.Alg, key: String, as: T.Type) throws -> T { 192 | try verify(algo: algo, key: key) 193 | return try JSONDecoder().decode(`as`, from: Data(payloadBytes)) 194 | } 195 | func decode(as: T.Type) throws -> T { 196 | return try JSONDecoder().decode(`as`, from: Data(payloadBytes)) 197 | } 198 | } 199 | 200 | extension JWT.Alg { 201 | init?(_ string: String) { 202 | switch string { 203 | case "HS256": self = .hs256 204 | case "HS384": self = .hs384 205 | case "HS512": self = .hs512 206 | case "RS256": self = .rs256 207 | case "RS384": self = .rs384 208 | case "RS512": self = .rs512 209 | case "ES256": self = .es256 210 | case "ES384": self = .es384 211 | case "ES512": self = .es512 212 | case "none": self = .none 213 | default: return nil 214 | } 215 | } 216 | var digest: Digest? { 217 | switch self { 218 | case .hs256, .rs256, .es256: return .sha256 219 | case .hs384, .rs384, .es384: return .sha384 220 | case .hs512, .rs512, .es512: return .sha512 221 | case .none: 222 | return nil 223 | } 224 | } 225 | var string: String { 226 | switch self { 227 | case .hs256: return "HS256" 228 | case .hs384: return "HS384" 229 | case .hs512: return "HS512" 230 | case .rs256: return "RS256" 231 | case .rs384: return "RS384" 232 | case .rs512: return "RS512" 233 | case .es256: return "ES256" 234 | case .es384: return "ES384" 235 | case .es512: return "ES512" 236 | case .none: return "none" 237 | } 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/Keys.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Keys.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2017-02-13. 6 | // Copyright (C) 2017 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import COpenSSL 21 | import PerfectLib 22 | 23 | public struct KeyError: Error { 24 | public let msg: String 25 | init(_ msg: String) { 26 | self.msg = msg 27 | } 28 | } 29 | 30 | public class Key { 31 | let pkey: UnsafeMutablePointer? 32 | deinit { 33 | EVP_PKEY_free(pkey) 34 | } 35 | init(_ key: UnsafeMutablePointer?) { 36 | self.pkey = key 37 | } 38 | } 39 | 40 | public class HMACKey: Key { 41 | public convenience init(_ key: String) { 42 | self.init(key.utf8.map{UInt8($0)}) 43 | } 44 | public init(_ key: [UInt8]) { 45 | let p = key.withUnsafeBytes { 46 | (p: UnsafeRawBufferPointer) -> UnsafeMutablePointer in 47 | return EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, nil, 48 | p.bindMemory(to: UInt8.self).baseAddress, 49 | Int32(p.count)) 50 | } 51 | super.init(p) 52 | } 53 | } 54 | 55 | public enum PEMKeyType { 56 | case rsa, dsa, ec 57 | } 58 | 59 | public class PEMKey: Key { 60 | public var type: PEMKeyType { 61 | let typeId = EVP_PKEY_base_id(pkey) 62 | switch typeId { 63 | case EVP_PKEY_RSA: return .rsa 64 | case EVP_PKEY_DSA: return .dsa 65 | case EVP_PKEY_EC: return .ec 66 | default: 67 | return .rsa 68 | } 69 | } 70 | init(kp: UnsafeMutablePointer?) { 71 | super.init(kp) 72 | } 73 | public convenience init(pemPath: String) throws { 74 | try self.init(source: try File(pemPath).readString()) 75 | } 76 | 77 | public init(source original: String) throws { 78 | let source = PEMKey.cleanSource(original) 79 | var kp: UnsafeMutablePointer? = nil 80 | func tryOne(_ call: (MemoryIO) throws -> ()) throws -> Bool { 81 | let f = MemoryIO(source) 82 | try call(f) 83 | return nil != kp 84 | } 85 | do { // rsa 86 | if try tryOne({ 87 | f in 88 | if let rsa = PEM_read_bio_RSAPrivateKey(f.bio, nil, nil, nil) { 89 | kp = EVP_PKEY_new() 90 | guard 1 == EVP_PKEY_assign(kp, EVP_PKEY_RSA, rsa) else { 91 | RSA_free(rsa) 92 | EVP_PKEY_free(kp) 93 | throw KeyError("No public or private key could be read. Could not fetch RSA private key.") 94 | } 95 | } 96 | }) { 97 | super.init(kp) 98 | return 99 | } 100 | if try tryOne({ 101 | f in 102 | if let rsa = PEM_read_bio_RSAPublicKey(f.bio, nil, nil, nil) { 103 | kp = EVP_PKEY_new() 104 | guard 1 == EVP_PKEY_assign(kp, EVP_PKEY_RSA, rsa) else { 105 | RSA_free(rsa) 106 | EVP_PKEY_free(kp) 107 | throw KeyError("No public or private key could be read. Could not fetch RSA public key.") 108 | } 109 | } 110 | }) { 111 | super.init(kp) 112 | return 113 | } 114 | } 115 | do { // dsa 116 | if try tryOne({ 117 | f in 118 | if let dsa = PEM_read_bio_DSAPrivateKey(f.bio, nil, nil, nil) { 119 | kp = EVP_PKEY_new() 120 | guard 1 == EVP_PKEY_assign(kp, EVP_PKEY_DSA, dsa) else { 121 | DSA_free(dsa) 122 | EVP_PKEY_free(kp) 123 | throw KeyError("No public or private key could be read. Could not fetch DSA private key.") 124 | } 125 | } 126 | }) { 127 | super.init(kp) 128 | return 129 | } 130 | if try tryOne({ 131 | f in 132 | if let dsa = PEM_read_bio_DSA_PUBKEY(f.bio, nil, nil, nil) { 133 | kp = EVP_PKEY_new() 134 | guard 1 == EVP_PKEY_assign(kp, EVP_PKEY_DSA, dsa) else { 135 | DSA_free(dsa) 136 | EVP_PKEY_free(kp) 137 | throw KeyError("No public or private key could be read. Could not fetch DSA public key.") 138 | } 139 | } 140 | }) { 141 | super.init(kp) 142 | return 143 | } 144 | } 145 | do { // ec 146 | if try tryOne({ 147 | f in 148 | if let ec = PEM_read_bio_ECPrivateKey(f.bio, nil, nil, nil) { 149 | kp = EVP_PKEY_new() 150 | guard 1 == EVP_PKEY_assign(kp, EVP_PKEY_EC, UnsafeMutableRawPointer(ec)) else { 151 | EC_KEY_free(ec) 152 | EVP_PKEY_free(kp) 153 | throw KeyError("No public or private key could be read. Could not fetch EC private key.") 154 | } 155 | } 156 | }) { 157 | super.init(kp) 158 | return 159 | } 160 | if try tryOne({ 161 | f in 162 | if let ec = PEM_read_bio_EC_PUBKEY(f.bio, nil, nil, nil) { 163 | kp = EVP_PKEY_new() 164 | guard 1 == EVP_PKEY_assign(kp, EVP_PKEY_EC, UnsafeMutableRawPointer(ec)) else { 165 | EC_KEY_free(ec) 166 | EVP_PKEY_free(kp) 167 | throw KeyError("No public or private key could be read. Could not fetch EC public key.") 168 | } 169 | } 170 | }) { 171 | super.init(kp) 172 | return 173 | } 174 | } 175 | if try tryOne({ 176 | f in 177 | if let x509 = PEM_read_bio_X509(f.bio, nil, nil, nil) { 178 | kp = X509_get_pubkey(x509) 179 | X509_free(x509) 180 | } 181 | }) { 182 | super.init(kp) 183 | return 184 | } 185 | if try tryOne({ PEM_read_bio_PrivateKey($0.bio, &kp, nil, nil) }) { 186 | super.init(kp) 187 | return 188 | } 189 | if try tryOne({ PEM_read_bio_PUBKEY($0.bio, &kp, nil, nil) }) { 190 | super.init(kp) 191 | return 192 | } 193 | throw KeyError("No public or private key could be read.") 194 | } 195 | 196 | public init(type: PEMKeyType, bits: Int, exp: Int = RSA_F4) throws { 197 | var kp: UnsafeMutablePointer? 198 | switch type { 199 | case .rsa: 200 | let ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, nil) 201 | guard 1 == EVP_PKEY_keygen_init(ctx), 202 | 1 == EVP_PKEY_CTX_ctrl(ctx, EVP_PKEY_RSA, EVP_PKEY_OP_KEYGEN, 203 | EVP_PKEY_CTRL_RSA_KEYGEN_BITS, Int32(bits), nil), 204 | 1 == EVP_PKEY_keygen(ctx, &kp) else { 205 | try CryptoError.throwOpenSSLError() 206 | } 207 | case .dsa: 208 | let dsa = DSA_new() 209 | DSA_generate_parameters_ex(dsa, Int32(bits), nil, 0, nil, nil, nil) 210 | guard 1 == DSA_generate_key(dsa) else { 211 | try CryptoError.throwOpenSSLError() 212 | } 213 | kp = EVP_PKEY_new() 214 | EVP_PKEY_assign(kp, EVP_PKEY_DSA, dsa) 215 | case .ec: 216 | let curve = "secp521r1" 217 | let eccgrp = OBJ_txt2nid(curve) 218 | let ecc = EC_KEY_new_by_curve_name(eccgrp) 219 | EC_KEY_set_asn1_flag(ecc, OPENSSL_EC_NAMED_CURVE) 220 | guard 1 == EC_KEY_generate_key(ecc) else { 221 | try CryptoError.throwOpenSSLError() 222 | } 223 | kp = EVP_PKEY_new() 224 | EVP_PKEY_assign(kp, EVP_PKEY_EC, UnsafeMutableRawPointer(ecc)) 225 | } 226 | super.init(kp) 227 | } 228 | 229 | static func cleanSource(_ source: String) -> String { 230 | var inHeader = true 231 | let charMax = 64 232 | var charCount = 0 233 | var accum = "" 234 | source.forEach { 235 | c in 236 | switch c { 237 | case "\r", "\n", "\r\n": 238 | if inHeader { 239 | inHeader = false 240 | accum += "\n" 241 | charCount = 0 242 | } 243 | case "-": 244 | if !inHeader { 245 | accum += "\n" 246 | charCount = 0 247 | } 248 | inHeader = true 249 | accum += "-" 250 | charCount += 1 251 | default: 252 | if charCount == charMax { 253 | accum += "\n" 254 | charCount = 0 255 | } else { 256 | charCount += 1 257 | } 258 | accum += String(c) 259 | } 260 | } 261 | if inHeader { 262 | accum += "\n" 263 | } 264 | return accum 265 | } 266 | } 267 | 268 | extension PEMKey: CustomStringConvertible { 269 | public var description: String { 270 | let mem = MemoryIO() 271 | PEM_write_bio_PrivateKey(mem.bio, pkey, nil, nil, 0, nil, nil) 272 | PEM_write_bio_PUBKEY(mem.bio, pkey) 273 | return String(validatingUTF8: mem.memory) ?? "" 274 | } 275 | public var privateKeyString: String? { 276 | let mem = MemoryIO() 277 | let result: Int32 278 | switch type { 279 | case .rsa: 280 | let rsa = EVP_PKEY_get1_RSA(pkey) 281 | result = PEM_write_bio_RSAPrivateKey(mem.bio, rsa, nil, nil, 0, nil, nil) 282 | case .dsa: 283 | let dsa = EVP_PKEY_get1_DSA(pkey) 284 | result = PEM_write_bio_DSAPrivateKey(mem.bio, dsa, nil, nil, 0, nil, nil) 285 | case .ec: 286 | let ec = EVP_PKEY_get1_EC_KEY(pkey) 287 | result = PEM_write_bio_ECPrivateKey(mem.bio, ec, nil, nil, 0, nil, nil) 288 | } 289 | guard 1 == result else { //1 == PEM_write_bio_PUBKEY(mem.bio, pkey) else { 290 | return nil 291 | } 292 | return String(validatingUTF8: mem.memory) 293 | } 294 | public var publicKeyString: String? { 295 | let mem = MemoryIO() 296 | let result: Int32 297 | switch type { 298 | case .rsa: 299 | let rsa = EVP_PKEY_get1_RSA(pkey) 300 | result = PEM_write_bio_RSAPublicKey(mem.bio, rsa) 301 | case .dsa: 302 | let dsa = EVP_PKEY_get1_DSA(pkey) 303 | result = PEM_write_bio_DSA_PUBKEY(mem.bio, dsa) 304 | case .ec: 305 | let ec = EVP_PKEY_get1_EC_KEY(pkey) 306 | result = PEM_write_bio_EC_PUBKEY(mem.bio, ec) 307 | } 308 | guard 1 == result else { //1 == PEM_write_bio_PUBKEY(mem.bio, pkey) else { 309 | return nil 310 | } 311 | return String(validatingUTF8: mem.memory) 312 | } 313 | 314 | public var privateKey: PEMKey? { 315 | guard let str = privateKeyString else { 316 | return nil 317 | } 318 | return try? PEMKey(source: str) 319 | } 320 | public var publicKey: PEMKey? { 321 | guard let str = publicKeyString else { 322 | return nil 323 | } 324 | return try? PEMKey(source: str) 325 | } 326 | } 327 | 328 | 329 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/OpenSSLInternal.swift: -------------------------------------------------------------------------------- 1 | // 2 | // OpenSSLInternal.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2017-02-07. 6 | // Copyright (C) 2017 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | import COpenSSL 21 | import PerfectThread 22 | #if os(Linux) 23 | import SwiftGlibc 24 | #else 25 | import Darwin 26 | #endif 27 | 28 | private var openSSLLocks: [Threading.Lock] = [] 29 | 30 | struct OpenSSLInternal { 31 | static var isInitialized: Bool = { 32 | copenssl_SSL_library_init() 33 | 34 | for i in 0..?, Int32) -> () = { 39 | (mode:Int32, n:Int32, file:UnsafePointer?, line:Int32) in 40 | 41 | if (mode & CRYPTO_LOCK) != 0 { 42 | openSSLLocks[Int(n)].lock() 43 | } else { 44 | openSSLLocks[Int(n)].unlock() 45 | } 46 | } 47 | CRYPTO_set_locking_callback(lockingCallback) 48 | 49 | let threadIdCallback: @convention(c) () -> UInt = { 50 | #if os(Linux) 51 | return pthread_self() 52 | #else 53 | return UInt(bitPattern: pthread_self()) 54 | #endif 55 | } 56 | 57 | CRYPTO_set_id_callback(threadIdCallback) 58 | return true 59 | }() 60 | } 61 | 62 | extension CryptoError { 63 | init() { 64 | let errorCode = ERR_get_error() 65 | let maxLen = 1024 66 | let buf = UnsafeMutablePointer.allocate(capacity: maxLen) 67 | defer { 68 | buf.deallocate() 69 | } 70 | ERR_error_string_n(errorCode, buf, maxLen) 71 | let msg = String(validatingUTF8: buf) ?? "" 72 | self.init(code: Int(errorCode), msg: msg) 73 | } 74 | static func throwOpenSSLError() throws -> Never { 75 | throw CryptoError() 76 | } 77 | } 78 | 79 | private let plus = UInt8(43) 80 | private let dash = UInt8(45) 81 | private let fslash = UInt8(47) 82 | private let uscore = UInt8(95) 83 | private let equal = UInt8(61) 84 | 85 | extension Encoding { 86 | func encodeBytes(_ source: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 87 | switch self { 88 | case .base64: 89 | return toBytesBase64(source) 90 | case .base64url: 91 | return toBytesBase64URL(source) 92 | case .hex: 93 | return toBytesHex(source) 94 | } 95 | } 96 | 97 | func decodeBytes(_ source: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 98 | switch self { 99 | case .base64: 100 | return fromBytesBase64(source) 101 | case .base64url: 102 | return fromBytesBase64URL(source) 103 | case .hex: 104 | return fromBytesHex(source) 105 | } 106 | } 107 | 108 | private func toBytesBase64(_ source: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 109 | let chain = Base64Filter().chain(MemoryIO()) 110 | do { 111 | _ = try chain.write(bytes: source) 112 | try chain.flush() 113 | let length = chain.readPending 114 | guard let memory = chain.memory else { 115 | return nil 116 | } 117 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: length, alignment: 0) 118 | #if swift(>=4.1) 119 | ret.copyMemory(from: memory) 120 | #else 121 | ret.copyBytes(from: memory) 122 | #endif 123 | return ret 124 | } catch { 125 | return nil 126 | } 127 | } 128 | 129 | private func fromBytesBase64(_ source: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 130 | let chain = Base64Filter().chain(MemoryIO(source)) 131 | do { 132 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: source.count, alignment: 0) 133 | let count = try chain.read(ret) 134 | return UnsafeMutableRawBufferPointer(start: ret.baseAddress, count: count) 135 | } catch { 136 | return nil 137 | } 138 | } 139 | 140 | private func toBytesBase64URL(_ source: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 141 | let chain = Base64Filter().chain(MemoryIO()) 142 | do { 143 | _ = try chain.write(bytes: source) 144 | try chain.flush() 145 | var length = chain.readPending 146 | guard let memory = chain.memory else { 147 | return nil 148 | } 149 | while length > 0 { 150 | if memory[length-1] == equal { 151 | length -= 1 152 | } else { 153 | break 154 | } 155 | } 156 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: length, alignment: 0) 157 | for i in 0.. UnsafeMutableRawBufferPointer? { 174 | var deurled: [UInt8] = source.map { 175 | switch $0 { 176 | case dash: 177 | return plus 178 | case uscore: 179 | return fslash 180 | default: 181 | return $0 182 | } 183 | } 184 | for _ in 0..<(deurled.count % 4) { 185 | deurled.append(equal) 186 | } 187 | let chain = deurled.withUnsafeBytes { Base64Filter().chain(MemoryIO($0)) } 188 | do { 189 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: deurled.count, alignment: 0) 190 | let count = try chain.read(ret) 191 | return UnsafeMutableRawBufferPointer(start: ret.baseAddress, count: count) 192 | } catch { 193 | return nil 194 | } 195 | } 196 | 197 | private func toBytesHex(_ source: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 198 | let sourceCount = source.count 199 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: sourceCount * 2, alignment: 0) 200 | var ri = 0 201 | for i in 0..> 4 204 | let b2 = byte & 0x0F 205 | let nb1 = b1 > 9 ? b1 - 10 + 97 : b1 + 48 206 | let nb2 = b2 > 9 ? b2 - 10 + 97 : b2 + 48 207 | ret[ri] = nb1 208 | ret[ri+1] = nb2 209 | ri += 2 210 | } 211 | return ret 212 | } 213 | 214 | private func fromBytesHex(_ source: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 215 | let sourceCount = source.count 216 | guard sourceCount % 2 == 0 else { 217 | return nil 218 | } 219 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: sourceCount / 2, alignment: 0) 220 | var ri = 0 221 | for index in stride(from: source.startIndex, to: source.endIndex, by: 2) { 222 | guard let c = UInt8(hexOne: source[index], hexTwo: source[index+1]) else { 223 | return nil 224 | } 225 | ret[ri] = c 226 | ri += 1 227 | } 228 | return ret 229 | } 230 | 231 | private func byteFromHexDigits(one c1v: UInt8, two c2v: UInt8) -> UInt8? { 232 | 233 | let capA: UInt8 = 65 234 | let capF: UInt8 = 70 235 | let lowA: UInt8 = 97 236 | let lowF: UInt8 = 102 237 | let zero: UInt8 = 48 238 | let nine: UInt8 = 57 239 | 240 | var newChar = UInt8(0) 241 | 242 | if c1v >= capA && c1v <= capF { 243 | newChar = c1v - capA + 10 244 | } else if c1v >= lowA && c1v <= lowF { 245 | newChar = c1v - lowA + 10 246 | } else if c1v >= zero && c1v <= nine { 247 | newChar = c1v - zero 248 | } else { 249 | return nil 250 | } 251 | 252 | newChar *= 16 253 | 254 | if c2v >= capA && c2v <= capF { 255 | newChar += c2v - capA + 10 256 | } else if c2v >= lowA && c2v <= lowF { 257 | newChar += c2v - lowA + 10 258 | } else if c2v >= zero && c2v <= nine { 259 | newChar += c2v - zero 260 | } else { 261 | return nil 262 | } 263 | return newChar 264 | } 265 | } 266 | 267 | func internal_RAND_bytes(into: UnsafeMutableRawBufferPointer) -> Int { 268 | guard let p = into.baseAddress?.assumingMemoryBound(to: UInt8.self) else { 269 | return 0 270 | } 271 | return Int(RAND_bytes(p, Int32(into.count))) 272 | } 273 | 274 | extension Digest { 275 | func bio() -> UnsafePointer? { 276 | let md = self.evp 277 | let bio = BIO_new(BIO_f_md()) 278 | BIO_ctrl(bio, BIO_C_SET_MD, 1, UnsafeMutableRawPointer(mutating: md)) 279 | return UnsafePointer(bio) 280 | } 281 | var evp: UnsafePointer { 282 | switch self { 283 | case .md4: return EVP_md4() 284 | case .md5: return EVP_md5() 285 | case .sha1: return EVP_sha1() 286 | case .sha224: return EVP_sha224() 287 | case .sha256: return EVP_sha256() 288 | case .sha384: return EVP_sha384() 289 | case .sha512: return EVP_sha512() 290 | case .ripemd160: return EVP_ripemd160() 291 | case .whirlpool: return EVP_whirlpool() 292 | case .custom(let name): return EVP_get_digestbyname(name) 293 | } 294 | } 295 | var length: Int { 296 | return Int(copenssl_EVP_MD_size(evp)) 297 | } 298 | 299 | func sign(_ data: UnsafeRawBufferPointer, privateKey key: Key) -> UnsafeMutableRawBufferPointer? { 300 | guard let ctx = copenssl_EVP_MD_CTX_create() else { 301 | return nil 302 | } 303 | defer { 304 | copenssl_EVP_MD_CTX_destroy(ctx) 305 | } 306 | guard 1 == EVP_DigestSignInit(ctx, nil, self.evp, nil, key.pkey) else { 307 | return nil 308 | } 309 | guard 1 == EVP_DigestUpdate(ctx, data.baseAddress, data.count) else { 310 | return nil 311 | } 312 | var mdLen = 0 313 | guard 1 == EVP_DigestSignFinal(ctx, nil, &mdLen) else { 314 | return nil 315 | } 316 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: mdLen, alignment: 0) 317 | var finalLen = mdLen 318 | guard 1 == EVP_DigestSignFinal(ctx, ret.baseAddress?.assumingMemoryBound(to: UInt8.self), &finalLen) else { 319 | ret.deallocate() 320 | return nil 321 | } 322 | if finalLen < mdLen { 323 | return UnsafeMutableRawBufferPointer(start: ret.baseAddress, count: finalLen) 324 | } 325 | return ret 326 | } 327 | 328 | 329 | func verify(_ data: UnsafeRawBufferPointer, signature: UnsafeRawBufferPointer, publicKey key: Key) -> Bool { 330 | 331 | if key is HMACKey { 332 | guard let signed = data.sign(self, key: key) else { 333 | return false 334 | } 335 | defer { 336 | signed.deallocate() 337 | } 338 | guard signed.count == signature.count else { 339 | return false 340 | } 341 | return 0 == CRYPTO_memcmp(signed.baseAddress, signature.baseAddress, signed.count) 342 | } 343 | 344 | guard let ctx = copenssl_EVP_MD_CTX_create() else { 345 | return false 346 | } 347 | defer { 348 | copenssl_EVP_MD_CTX_destroy(ctx) 349 | } 350 | guard 1 == EVP_DigestVerifyInit(ctx, nil, evp, nil, key.pkey) else { 351 | return false 352 | } 353 | guard 1 == EVP_DigestUpdate(ctx, data.baseAddress, data.count) else { 354 | return false 355 | } 356 | let mdLen = signature.count 357 | guard 1 == EVP_DigestVerifyFinal(ctx, signature.baseAddress?.assumingMemoryBound(to: UInt8.self), mdLen) else { 358 | return false 359 | } 360 | return true 361 | } 362 | 363 | /// Derive a suitable encryption key based on a password and salt. 364 | /// The "PKCS5 PBKDF2 HMAC" algorithm will be used to generate the key. 365 | /// The `iterations` parameter should generally be a number greater than 1000. 366 | /// The `keyLength` parameter should indicate the desired key length and will generally match the `keyLength` of a cipher. 367 | public func deriveKey(password: String, salt: String, iterations: Int, keyLength: Int) -> [UInt8]? { 368 | return deriveKey(password: Array(password.utf8), 369 | salt: Array(salt.utf8), 370 | iterations: iterations, keyLength: keyLength) 371 | } 372 | 373 | /// Derive a suitable encryption key based on a password and salt. 374 | /// The "PKCS5 PBKDF2 HMAC" algorithm will be used to generate the key. 375 | /// The `iterations` parameter should generally be a number greater than 1000. 376 | /// The `keyLength` parameter should indicate the desired key length and will generally match the `keyLength` of a cipher. 377 | public func deriveKey(password: [UInt8], salt: [UInt8], iterations: Int, keyLength: Int) -> [UInt8]? { 378 | return password.withUnsafeBytes { 379 | password in 380 | salt.withUnsafeBytes { 381 | salt in 382 | deriveKey(password: password, 383 | salt: salt, 384 | iterations: iterations, keyLength: keyLength) 385 | } 386 | } 387 | } 388 | 389 | /// Derive a suitable encryption key based on a password and salt. 390 | /// The "PKCS5 PBKDF2 HMAC" algorithm will be used to generate the key. 391 | /// The `iterations` parameter should generally be a number greater than 1000. 392 | /// The `keyLength` parameter should indicate the desired key length and will generally match the `keyLength` of a cipher. 393 | public func deriveKey(password: UnsafeRawBufferPointer, salt: UnsafeRawBufferPointer, iterations: Int, keyLength: Int) -> [UInt8]? { 394 | guard let pw = password.baseAddress?.assumingMemoryBound(to: Int8.self), 395 | let sw = salt.baseAddress?.assumingMemoryBound(to: UInt8.self) else { 396 | return nil 397 | } 398 | var a = [UInt8](repeating: 0, count: keyLength) 399 | guard 0 != PKCS5_PBKDF2_HMAC(pw, Int32(password.count), 400 | sw, Int32(salt.count), 401 | Int32(iterations), evp, Int32(keyLength), 402 | &a) else { 403 | return nil 404 | } 405 | return a 406 | } 407 | } 408 | 409 | extension Cipher { 410 | var evp: UnsafePointer { 411 | switch self { 412 | case .des_ecb: return EVP_des_ecb() 413 | case .des_ede: return EVP_des_ede() 414 | case .des_ede3: return EVP_des_ede3() 415 | case .des_ede_ecb: return EVP_des_ede_ecb() 416 | case .des_ede3_ecb: return EVP_des_ede3_ecb() 417 | case .des_cfb64: return EVP_des_cfb64() 418 | case .des_cfb1: return EVP_des_cfb1() 419 | case .des_cfb8: return EVP_des_cfb8() 420 | case .des_ede_cfb64: return EVP_des_ede_cfb64() 421 | case .des_ede3_cfb1: return EVP_des_ede3_cfb1() 422 | case .des_ede3_cfb8: return EVP_des_ede3_cfb8() 423 | case .des_ofb: return EVP_des_ofb() 424 | case .des_ede_ofb: return EVP_des_ede_ofb() 425 | case .des_ede3_ofb: return EVP_des_ede3_ofb() 426 | case .des_cbc: return EVP_des_cbc() 427 | case .des_ede_cbc: return EVP_des_ede_cbc() 428 | case .des_ede3_cbc: return EVP_des_ede3_cbc() 429 | case .desx_cbc: return EVP_desx_cbc() 430 | case .des_ede3_wrap: return EVP_des_ede3_wrap() 431 | case .rc4: return EVP_rc4() 432 | case .rc4_40: return EVP_rc4_40() 433 | case .rc4_hmac_md5: return EVP_rc4_hmac_md5() 434 | case .rc2_ecb: return EVP_rc2_ecb() 435 | case .rc2_cbc: return EVP_rc2_cbc() 436 | case .rc2_40_cbc: return EVP_rc2_40_cbc() 437 | case .rc2_64_cbc: return EVP_rc2_64_cbc() 438 | case .rc2_cfb64: return EVP_rc2_cfb64() 439 | case .rc2_ofb: return EVP_rc2_ofb() 440 | case .bf_ecb: return EVP_bf_ecb() 441 | case .bf_cbc: return EVP_bf_cbc() 442 | case .bf_cfb64: return EVP_bf_cfb64() 443 | case .bf_ofb: return EVP_bf_ofb() 444 | case .cast5_ecb: return EVP_cast5_ecb() 445 | case .cast5_cbc: return EVP_cast5_cbc() 446 | case .cast5_cfb64: return EVP_cast5_cfb64() 447 | case .cast5_ofb: return EVP_cast5_ofb() 448 | case .aes_128_ecb: return EVP_aes_128_ecb() 449 | case .aes_128_cbc: return EVP_aes_128_cbc() 450 | case .aes_128_cfb1: return EVP_aes_128_cfb1() 451 | case .aes_128_cfb8: return EVP_aes_128_cfb8() 452 | case .aes_128_cfb128: return EVP_aes_128_cfb128() 453 | case .aes_128_ofb: return EVP_aes_128_ofb() 454 | case .aes_128_ctr: return EVP_aes_128_ctr() 455 | case .aes_128_ccm: return EVP_aes_128_ccm() 456 | case .aes_128_gcm: return EVP_aes_128_gcm() 457 | case .aes_128_xts: return EVP_aes_128_xts() 458 | case .aes_128_wrap: return EVP_aes_128_wrap() 459 | case .aes_192_ecb: return EVP_aes_192_ecb() 460 | case .aes_192_cbc: return EVP_aes_192_cbc() 461 | case .aes_192_cfb1: return EVP_aes_192_cfb1() 462 | case .aes_192_cfb8: return EVP_aes_192_cfb8() 463 | case .aes_192_cfb128: return EVP_aes_192_cfb128() 464 | case .aes_192_ofb: return EVP_aes_192_ofb() 465 | case .aes_192_ctr: return EVP_aes_192_ctr() 466 | case .aes_192_ccm: return EVP_aes_192_ccm() 467 | case .aes_192_gcm: return EVP_aes_192_gcm() 468 | case .aes_192_wrap: return EVP_aes_192_wrap() 469 | case .aes_256_ecb: return EVP_aes_256_ecb() 470 | case .aes_256_cbc: return EVP_aes_256_cbc() 471 | case .aes_256_cfb1: return EVP_aes_256_cfb1() 472 | case .aes_256_cfb8: return EVP_aes_256_cfb8() 473 | case .aes_256_cfb128: return EVP_aes_256_cfb128() 474 | case .aes_256_ofb: return EVP_aes_256_ofb() 475 | case .aes_256_ctr: return EVP_aes_256_ctr() 476 | case .aes_256_ccm: return EVP_aes_256_ccm() 477 | case .aes_256_gcm: return EVP_aes_256_gcm() 478 | case .aes_256_xts: return EVP_aes_256_xts() 479 | case .aes_256_wrap: return EVP_aes_256_wrap() 480 | case .aes_128_cbc_hmac_sha1: return EVP_aes_128_cbc_hmac_sha1() 481 | case .aes_256_cbc_hmac_sha1: return EVP_aes_256_cbc_hmac_sha1() 482 | case .aes_128_cbc_hmac_sha256: return EVP_aes_128_cbc_hmac_sha256() 483 | case .aes_256_cbc_hmac_sha256: return EVP_aes_256_cbc_hmac_sha256() 484 | case .camellia_128_ecb: return EVP_camellia_128_ecb() 485 | case .camellia_128_cbc: return EVP_camellia_128_cbc() 486 | case .camellia_128_cfb1: return EVP_camellia_128_cfb1() 487 | case .camellia_128_cfb8: return EVP_camellia_128_cfb8() 488 | case .camellia_128_cfb128: return EVP_camellia_128_cfb128() 489 | case .camellia_128_ofb: return EVP_camellia_128_ofb() 490 | case .camellia_192_ecb: return EVP_camellia_192_ecb() 491 | case .camellia_192_cbc: return EVP_camellia_192_cbc() 492 | case .camellia_192_cfb1: return EVP_camellia_192_cfb1() 493 | case .camellia_192_cfb8: return EVP_camellia_192_cfb8() 494 | case .camellia_192_cfb128: return EVP_camellia_192_cfb128() 495 | case .camellia_192_ofb: return EVP_camellia_192_ofb() 496 | case .camellia_256_ecb: return EVP_camellia_256_ecb() 497 | case .camellia_256_cbc: return EVP_camellia_256_cbc() 498 | case .camellia_256_cfb1: return EVP_camellia_256_cfb1() 499 | case .camellia_256_cfb8: return EVP_camellia_256_cfb8() 500 | case .camellia_256_cfb128: return EVP_camellia_256_cfb128() 501 | case .camellia_256_ofb: return EVP_camellia_256_ofb() 502 | case .seed_ecb: return EVP_seed_ecb() 503 | case .seed_cbc: return EVP_seed_cbc() 504 | case .seed_cfb128: return EVP_seed_cfb128() 505 | case .seed_ofb: return EVP_seed_ofb() 506 | case .custom(let name): return EVP_get_cipherbyname(name) 507 | } 508 | } 509 | 510 | public var blockSize: Int { 511 | return Int(copenssl_EVP_CIPHER_block_size(evp)) 512 | } 513 | 514 | public var keyLength: Int { 515 | return Int(copenssl_EVP_CIPHER_key_length(evp)) 516 | } 517 | 518 | public var ivLength: Int { 519 | return Int(copenssl_EVP_CIPHER_iv_length(evp)) 520 | } 521 | 522 | func encryptLength(sourceCount l: Int) -> Int { 523 | return l + (self.blockSize - l % self.blockSize) 524 | } 525 | 526 | func decryptLength(sourceCount l: Int) -> Int { 527 | return l 528 | } 529 | 530 | func encrypt(_ data: UnsafeRawBufferPointer, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 531 | guard let ctx = EVP_CIPHER_CTX_new(), 532 | let keyBase = key.baseAddress, 533 | let ivBase = iv.baseAddress else { 534 | return nil 535 | } 536 | defer { 537 | EVP_CIPHER_CTX_free(ctx) 538 | } 539 | guard 1 == EVP_EncryptInit_ex(ctx, self.evp, nil, 540 | keyBase.assumingMemoryBound(to: UInt8.self), 541 | ivBase.assumingMemoryBound(to: UInt8.self)) else { 542 | return nil 543 | } 544 | let allocLength = encryptLength(sourceCount: data.count) 545 | let dstPtr = UnsafeMutableRawBufferPointer.allocate(byteCount: allocLength, alignment: 0) 546 | var wroteLength = Int32(0) 547 | guard 1 == EVP_EncryptUpdate(ctx, 548 | dstPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), 549 | &wroteLength, 550 | data.baseAddress?.assumingMemoryBound(to: UInt8.self), 551 | Int32(data.count)) else { 552 | dstPtr.deallocate() 553 | return nil 554 | } 555 | let owroteLength = Int(wroteLength) 556 | guard 1 == EVP_EncryptFinal(ctx, 557 | dstPtr.baseAddress?.assumingMemoryBound(to: UInt8.self).advanced(by: Int(wroteLength)), 558 | &wroteLength) else { 559 | dstPtr.deallocate() 560 | return nil 561 | } 562 | let iwroteLength = Int(wroteLength) + owroteLength 563 | if iwroteLength < allocLength { 564 | let newDstPtr = UnsafeMutableRawBufferPointer.allocate(byteCount: iwroteLength, alignment: 0) 565 | memcpy(newDstPtr.baseAddress!, dstPtr.baseAddress!, iwroteLength) 566 | dstPtr.deallocate() 567 | return newDstPtr 568 | } 569 | return dstPtr 570 | } 571 | 572 | func decrypt(_ data: UnsafeRawBufferPointer, key: UnsafeRawBufferPointer, iv: UnsafeRawBufferPointer) -> UnsafeMutableRawBufferPointer? { 573 | guard let ctx = EVP_CIPHER_CTX_new(), 574 | let keyBase = key.baseAddress, 575 | let ivBase = iv.baseAddress else { 576 | return nil 577 | } 578 | defer { 579 | EVP_CIPHER_CTX_free(ctx) 580 | } 581 | guard 1 == EVP_DecryptInit_ex(ctx, self.evp, nil, 582 | keyBase.assumingMemoryBound(to: UInt8.self), 583 | ivBase.assumingMemoryBound(to: UInt8.self)) else { 584 | return nil 585 | } 586 | let allocLength = decryptLength(sourceCount: data.count) 587 | let dstPtr = UnsafeMutableRawBufferPointer.allocate(byteCount: allocLength, alignment: 0) 588 | var wroteLength = Int32(0) 589 | guard 1 == EVP_DecryptUpdate(ctx, 590 | dstPtr.baseAddress?.assumingMemoryBound(to: UInt8.self), 591 | &wroteLength, 592 | data.baseAddress?.assumingMemoryBound(to: UInt8.self), 593 | Int32(data.count)) else { 594 | dstPtr.deallocate() 595 | return nil 596 | } 597 | let owroteLength = Int(wroteLength) 598 | guard 1 == EVP_DecryptFinal(ctx, 599 | dstPtr.baseAddress?.assumingMemoryBound(to: UInt8.self).advanced(by: Int(wroteLength)), 600 | &wroteLength) else { 601 | dstPtr.deallocate() 602 | return nil 603 | } 604 | let iwroteLength = Int(wroteLength) + owroteLength 605 | if iwroteLength < allocLength { 606 | let newDstPtr = UnsafeMutableRawBufferPointer.allocate(byteCount: iwroteLength, alignment: 0) 607 | memcpy(newDstPtr.baseAddress!, dstPtr.baseAddress!, iwroteLength) 608 | dstPtr.deallocate() 609 | return newDstPtr 610 | } 611 | return dstPtr 612 | } 613 | 614 | func encrypt(_ data: UnsafeRawBufferPointer, 615 | password: UnsafeRawBufferPointer, 616 | salt: UnsafeRawBufferPointer, 617 | keyIterations: Int = 2048, 618 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? { 619 | guard let derived = keyDigest.deriveKey(password: password, 620 | salt: salt, 621 | iterations: keyIterations, 622 | keyLength: keyLength) else { 623 | return nil 624 | } 625 | let memBio = MemoryIO(data) 626 | guard let cms = derived.withUnsafeBytes({ CMS_EncryptedData_encrypt(memBio.bio, evp, 627 | $0.baseAddress?.assumingMemoryBound(to: UInt8.self), 628 | $0.count, 629 | UInt32(CMS_STREAM|CMS_BINARY))}) else { 630 | return nil 631 | } 632 | defer { 633 | CMS_ContentInfo_free(cms) 634 | } 635 | let outBio = MemoryIO() 636 | guard 0 != PEM_write_bio_CMS_stream(outBio.bio, cms, memBio.bio, Int32(CMS_STREAM|CMS_BINARY)), 637 | let mem = outBio.memory else { 638 | return nil 639 | } 640 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: mem.count, alignment: 0) 641 | guard let r = ret.baseAddress, let m = mem.baseAddress else { 642 | ret.deallocate() 643 | return nil 644 | } 645 | memcpy(r, m, mem.count) 646 | return ret 647 | } 648 | 649 | func decrypt(_ data: UnsafeRawBufferPointer, 650 | password: UnsafeRawBufferPointer, 651 | salt: UnsafeRawBufferPointer, 652 | keyIterations: Int = 2048, 653 | keyDigest: Digest = .md5) -> UnsafeMutableRawBufferPointer? { 654 | let memBio = MemoryIO(data) 655 | guard let cms = PEM_read_bio_CMS(memBio.bio, nil, nil, nil) else { 656 | return nil 657 | } 658 | defer { 659 | CMS_ContentInfo_free(cms) 660 | } 661 | let outBio = MemoryIO() 662 | do { 663 | guard let derived = keyDigest.deriveKey(password: password, 664 | salt: salt, 665 | iterations: keyIterations, 666 | keyLength: keyLength) else { 667 | return nil 668 | } 669 | let cres = derived.withUnsafeBytes { 670 | derivedPassword -> Int32 in 671 | return CMS_EncryptedData_decrypt(cms, 672 | derivedPassword.baseAddress?.assumingMemoryBound(to: UInt8.self), 673 | derivedPassword.count, 674 | nil, outBio.bio, UInt32(CMS_STREAM|CMS_BINARY)) 675 | } 676 | guard 0 != cres else { 677 | return nil 678 | } 679 | } 680 | guard let mem = outBio.memory, !mem.isEmpty else { 681 | return nil 682 | } 683 | let ret = UnsafeMutableRawBufferPointer.allocate(byteCount: mem.count, alignment: 0) 684 | guard let r = ret.baseAddress, let m = mem.baseAddress else { 685 | ret.deallocate() 686 | return nil 687 | } 688 | memcpy(r, m, mem.count) 689 | return ret 690 | } 691 | } 692 | 693 | 694 | 695 | -------------------------------------------------------------------------------- /Sources/PerfectCrypto/PerfectCrypto.swift: -------------------------------------------------------------------------------- 1 | // 2 | // PerfectCrypto.swift 3 | // PerfectCrypto 4 | // 5 | // Created by Kyle Jessup on 2017-02-07. 6 | // Copyright (C) 2017 PerfectlySoft, Inc. 7 | // 8 | //===----------------------------------------------------------------------===// 9 | // 10 | // This source file is part of the Perfect.org open source project 11 | // 12 | // Copyright (c) 2015 - 2017 PerfectlySoft Inc. and the Perfect project authors 13 | // Licensed under Apache License v2.0 14 | // 15 | // See http://perfect.org/licensing.html for license information 16 | // 17 | //===----------------------------------------------------------------------===// 18 | // 19 | 20 | public enum PerfectCrypto { 21 | public static var isInitialized: Bool = { 22 | return OpenSSLInternal.isInitialized 23 | }() 24 | } 25 | 26 | public struct CryptoError: Error { 27 | public let code: Int 28 | public let msg: String 29 | } 30 | 31 | 32 | -------------------------------------------------------------------------------- /Tests/LinuxMain.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectCryptoTests 3 | 4 | XCTMain([ 5 | testCase(PerfectCryptoTests.allTests), 6 | ]) 7 | -------------------------------------------------------------------------------- /Tests/PerfectCryptoTests/PerfectCryptoTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import PerfectCrypto 3 | import Foundation 4 | import PerfectLib 5 | 6 | extension File { 7 | /// write a random binary file 8 | /// - parameter totalBytes: the expected size to generate 9 | /// - parameter bufferSize: the buffer size to apply in file writing 10 | /// - throws: CryptoError in case of exceptions. 11 | public func random(totalBytes: Int, bufferSize: Int = 16384) throws { 12 | let szbuf = bufferSize > 0 ? bufferSize : 16384 13 | guard totalBytes > 0 else { 14 | throw CryptoError(code: -1, msg: "invalid parameter") 15 | } 16 | self.delete() 17 | try self.open(.write) 18 | var size = 0 19 | var remain = totalBytes 20 | repeat { 21 | size = min(remain, szbuf) 22 | remain -= size 23 | let buf = Array(randomCount: size) 24 | try self.write(bytes: buf) 25 | } while remain > 0 26 | self.close() 27 | guard self.size == totalBytes else { 28 | throw CryptoError(code: -2, msg: "unexpected size \(totalBytes) != \(self.size)") 29 | } 30 | } 31 | } 32 | 33 | class PerfectCryptoTests: XCTestCase { 34 | 35 | override func setUp() { 36 | _ = PerfectCrypto.isInitialized 37 | } 38 | 39 | func testInitialized() { 40 | XCTAssert(PerfectCrypto.isInitialized) 41 | } 42 | 43 | func testHexEncDec1() { 44 | let testStr = "Hello, world!" 45 | guard let hexBytes = testStr.encode(.hex) else { 46 | return XCTAssert(false) 47 | } 48 | XCTAssert(String(validatingUTF8: hexBytes) == "48656c6c6f2c20776f726c6421") 49 | guard let unHex = hexBytes.decode(.hex) else { 50 | return XCTAssert(false) 51 | } 52 | XCTAssert(String(validatingUTF8: unHex) == testStr) 53 | } 54 | 55 | func test64EncDec1() { 56 | let testStr = "Hello, world!" 57 | guard let baseBytes = Array(testStr.utf8).encode(.base64) else { 58 | return XCTAssert(false) 59 | } 60 | XCTAssert(String(validatingUTF8: baseBytes) == "SGVsbG8sIHdvcmxkIQ==") 61 | guard let unHex = baseBytes.decode(.base64) else { 62 | return XCTAssert(false) 63 | } 64 | XCTAssert(String(validatingUTF8: unHex) == testStr) 65 | } 66 | 67 | func test64EncDec2() { 68 | let testStr = "Räksmörgåsen" 69 | guard let baseBytes = Array(testStr.utf8).encode(.base64) else { 70 | return XCTAssert(false) 71 | } 72 | guard let s = String(validatingUTF8: baseBytes) else { 73 | return XCTAssert(false) 74 | } 75 | XCTAssert(s == "UsOka3Ntw7ZyZ8Olc2Vu") 76 | guard let unHex = s.decode(.base64) else { 77 | return XCTAssert(false) 78 | } 79 | XCTAssert(String(validatingUTF8: unHex) == testStr) 80 | } 81 | 82 | func test64EncDec3() { 83 | let testStr = "🤡 Räksmörgåsen" 84 | guard let baseBytes = Array(testStr.utf8).encode(.base64url) else { 85 | return XCTAssert(false) 86 | } 87 | let baseStr = String(validatingUTF8: baseBytes) 88 | XCTAssert(baseStr == "8J-koSBSw6Rrc23DtnJnw6VzZW4", "\(String(describing: baseStr))") 89 | guard let unHex = baseBytes.decode(.base64url) else { 90 | return XCTAssert(false) 91 | } 92 | let unhexed = String(validatingUTF8: unHex) 93 | XCTAssert(unhexed == testStr, "\(String(describing: unhexed))") 94 | } 95 | 96 | func testHexEncDec2() { 97 | let testStr = "Hello, world!" 98 | guard let hexBytes = Array(testStr.utf8).encode(.hex) else { 99 | return XCTAssert(false) 100 | } 101 | guard let s = String(validatingUTF8: hexBytes) else { 102 | return XCTAssert(false) 103 | } 104 | XCTAssert(s == "48656c6c6f2c20776f726c6421") 105 | guard let unHex = s.decode(.hex) else { 106 | return XCTAssert(false) 107 | } 108 | XCTAssert(String(validatingUTF8: unHex) == testStr) 109 | } 110 | 111 | func testIOPair() { 112 | let testStr = "Hello, world!" 113 | let chars = [UInt8](testStr.utf8) 114 | let count = chars.count 115 | let pair = IOPair() 116 | let write = pair.first 117 | let read = pair.second 118 | do { 119 | try write.pair(with: read) 120 | _ = try chars.withUnsafeBytes { try write.write(bytes: $0) } 121 | try write.flush() 122 | let dest = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 0) 123 | defer { 124 | dest.deallocate() 125 | } 126 | let result = try read.read(dest) 127 | XCTAssert(result == count) 128 | } catch { 129 | XCTAssert(false, "\(error)") 130 | } 131 | } 132 | 133 | func testBase64Filter1() { 134 | let testStr = "Hello, world!" 135 | let chars = [UInt8](testStr.utf8) 136 | let count = chars.count 137 | let chain = Base64Filter().chain(MemoryIO()) 138 | do { 139 | try chars.withUnsafeBytes { 140 | XCTAssert(try chain.write(bytes: $0) == count) 141 | } 142 | let dest = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 0) 143 | defer { 144 | dest.deallocate() 145 | } 146 | let result = try chain.flush().read(dest) 147 | 148 | XCTAssert(String(validatingUTF8: UnsafeRawBufferPointer(start: dest.baseAddress, count: result)) == testStr) 149 | } catch { 150 | XCTAssert(false, "\(error)") 151 | } 152 | } 153 | 154 | func testBase64Filter2() { 155 | let testStr = "Hello, world!" 156 | let testAnswer = "SGVsbG8sIHdvcmxkIQ==" 157 | let chars = [UInt8](testStr.utf8) 158 | let count = chars.count 159 | let chain = Base64Filter().chain(MemoryIO()) 160 | do { 161 | XCTAssert("\(chain)" == "base64 encoding<->(memory buffer)") 162 | let wrote = try chars.withUnsafeBytes { 163 | try chain.write(bytes: $0) 164 | } 165 | XCTAssert(wrote == count) 166 | let result = try chain.flush().memory 167 | XCTAssert(result?.count == testAnswer.utf8.count) 168 | let resultString = String(validatingUTF8: result) 169 | XCTAssert(testAnswer == resultString) 170 | } catch { 171 | XCTAssert(false, "\(error)") 172 | } 173 | } 174 | 175 | func testDigest1() { 176 | let testStr = "Hello, world!" 177 | let testAnswer = "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3" 178 | 179 | let dest = UnsafeMutableRawBufferPointer.allocate(byteCount: 1024, alignment: 0) 180 | defer { 181 | dest.deallocate() 182 | } 183 | 184 | do { 185 | let digest = DigestFilter(.sha256) 186 | _ = try testStr.utf8.map{$0}.withUnsafeBytes { 187 | try digest.chain(NullIO()).write(bytes: $0) 188 | } 189 | 190 | let resultLen = try digest.get(dest) 191 | let digestBytes = UnsafeRawBufferPointer(start: dest.baseAddress, count: resultLen) 192 | guard let hexString = digestBytes.encode(.hex) else { 193 | return XCTAssert(false) 194 | } 195 | defer { 196 | hexString.deallocate() 197 | } 198 | XCTAssert(testAnswer == String(validatingUTF8: UnsafeRawBufferPointer(hexString)), "\(hexString)") 199 | } catch { 200 | XCTAssert(false, "\(error)") 201 | } 202 | } 203 | 204 | func testDigest2() { 205 | let testStr = "Hello, world!" 206 | let testAnswer = "315f5bdb76d078c43b8ac0064e4a0164612b1fce77c869345bfc94c75894edd3" 207 | guard let enc = testStr.digest(.sha256)?.encode(.hex) else { 208 | return XCTAssert(false) 209 | } 210 | XCTAssert(String(validatingUTF8: enc) == testAnswer) 211 | } 212 | 213 | func testCipherSizes() { 214 | let algo = Cipher.des_ede3_cbc 215 | let bs = algo.blockSize 216 | let kl = algo.keyLength 217 | let il = algo.ivLength 218 | 219 | XCTAssertEqual(bs, 8) 220 | XCTAssertEqual(kl, 24) 221 | XCTAssertEqual(il, 8) 222 | } 223 | 224 | func testRandomBuffer1() { 225 | guard let buff = UnsafeMutableRawBufferPointer.allocateRandom(count: 2048) else { 226 | return XCTAssert(false) 227 | } 228 | defer { 229 | buff.deallocate() 230 | } 231 | XCTAssert(buff.count == 2048) 232 | 233 | guard let enc = UnsafeRawBufferPointer(buff).encode(.hex) else { 234 | return XCTAssert(false) 235 | } 236 | enc.deallocate() 237 | } 238 | 239 | func testRandomBuffer2() { 240 | let buff = [UInt8](randomCount: 2048) 241 | let buff2 = [UInt8](randomCount: 2048) 242 | 243 | XCTAssert(buff != buff2) 244 | } 245 | 246 | func testCipher1() { 247 | let cipher = Cipher.aes_256_cbc 248 | guard let random = UnsafeRawBufferPointer.allocateRandom(count: 2048), 249 | let key = UnsafeRawBufferPointer.allocateRandom(count: cipher.keyLength), 250 | let iv = UnsafeRawBufferPointer.allocateRandom(count: cipher.ivLength) else { 251 | return XCTAssert(false) 252 | } 253 | defer { 254 | random.deallocate() 255 | key.deallocate() 256 | iv.deallocate() 257 | } 258 | 259 | guard let encrypted = random.encrypt(cipher, key: key, iv: iv) else { 260 | return XCTAssert(false) 261 | } 262 | defer { 263 | encrypted.deallocate() 264 | } 265 | 266 | let encryptedRaw = UnsafeRawBufferPointer(encrypted) 267 | guard let decrypted = encryptedRaw.decrypt(cipher, key: key, iv: iv) else { 268 | return XCTAssert(false) 269 | } 270 | defer { 271 | decrypted.deallocate() 272 | } 273 | 274 | XCTAssert(decrypted.count == random.count) 275 | for (a, b) in zip(decrypted, random) { 276 | XCTAssert(a == b) 277 | } 278 | } 279 | 280 | func testCipher2() { 281 | let cipher = Cipher.aes_256_cbc 282 | let random = [UInt8](randomCount: 2048) 283 | let key = [UInt8](randomCount: cipher.keyLength) 284 | let iv = [UInt8](randomCount: cipher.ivLength) 285 | guard let encrypted = random.encrypt(cipher, key: key, iv: iv) else { 286 | return XCTAssert(false) 287 | } 288 | guard let decrypted = encrypted.decrypt(cipher, key: key, iv: iv) else { 289 | return XCTAssert(false) 290 | } 291 | XCTAssert(decrypted.count == random.count) 292 | for (a, b) in zip(decrypted, random) { 293 | XCTAssert(a == b) 294 | } 295 | } 296 | 297 | func testJWTVerify() { 298 | let tstJwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ" 299 | let secret = "secret" 300 | let name = "John Doe" 301 | guard let jwt = JWTVerifier(tstJwt) else { 302 | return XCTAssert(false) 303 | } 304 | do { 305 | try jwt.verify(algo: .hs256, key: secret) 306 | 307 | let fndName = jwt.payload["name"] as? String 308 | XCTAssert(name == fndName!) 309 | } catch { 310 | XCTAssert(false, "\(error)") 311 | } 312 | } 313 | 314 | func testJWTCreate1() { 315 | struct JWTObj: Codable { 316 | let sub: String 317 | let name: String 318 | let admin: Bool 319 | } 320 | let tstPayload = JWTObj(sub: "1234567890", name: "John Doe", admin: true) 321 | let secret = "secret" 322 | let name = "John Doe" 323 | for _ in 0..<30 { 324 | do { 325 | let jwt1 = try JWTCreator(payload: tstPayload) 326 | let token = try jwt1.sign(alg: .hs256, key: secret) 327 | 328 | guard let jwt = JWTVerifier(token) else { 329 | return XCTAssert(false) 330 | } 331 | let obj = try jwt.verify(algo: .hs256, key: HMACKey(secret), as: JWTObj.self) 332 | let fndName = obj.name 333 | XCTAssertEqual(name, fndName) 334 | } catch { 335 | XCTFail("\(error)") 336 | } 337 | } 338 | } 339 | 340 | func testJWTCreate2() { 341 | let tstPayload = ["sub": "1234567890", "name": "John Doe", "admin": true] as [String : Any] 342 | let name = "John Doe" 343 | let pubKey = "-----BEGIN PUBLIC KEY-----\nMIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQAB\n-----END PUBLIC KEY-----\n" 344 | let privKey = "-----BEGIN RSA PRIVATE KEY-----\nMIICWwIBAAKBgQDdlatRjRjogo3WojgGHFHYLugdUWAY9iR3fy4arWNA1KoS8kVw33cJibXr8bvwUAUparCwlvdbH6dvEOfou0/gCFQsHUfQrSDv+MuSUMAe8jzKE4qW+jK+xQU9a03GUnKHkkle+Q0pX/g6jXZ7r1/xAK5Do2kQ+X5xK9cipRgEKwIDAQABAoGAD+onAtVye4ic7VR7V50DF9bOnwRwNXrARcDhq9LWNRrRGElESYYTQ6EbatXS3MCyjjX2eMhu/aF5YhXBwkppwxg+EOmXeh+MzL7Zh284OuPbkglAaGhV9bb6/5CpuGb1esyPbYW+Ty2PC0GSZfIXkXs76jXAu9TOBvD0ybc2YlkCQQDywg2R/7t3Q2OE2+yo382CLJdrlSLVROWKwb4tb2PjhY4XAwV8d1vy0RenxTB+K5Mu57uVSTHtrMK0GAtFr833AkEA6avx20OHo61Yela/4k5kQDtjEf1N0LfI+BcWZtxsS3jDM3i1Hp0KSu5rsCPb8acJo5RO26gGVrfAsDcIXKC+bQJAZZ2XIpsitLyPpuiMOvBbzPavd4gY6Z8KWrfYzJoI/Q9FuBo6rKwl4BFoToD7WIUS+hpkagwWiz+6zLoX1dbOZwJACmH5fSSjAkLRi54PKJ8TFUeOP15h9sQzydI8zJU+upvDEKZsZc/UhT/SySDOxQ4G/523Y0sz/OZtSWcol/UMgQJALesy++GdvoIDLfJX5GBQpuFgFenRiRDabxrE9MNUZ2aPFaFp+DyAe+b4nDwuJaW2LURbr8AEZga7oQj0uYxcYw==\n-----END RSA PRIVATE KEY-----\n" 345 | for _ in 0..<30 { 346 | guard let jwt1 = JWTCreator(payload: tstPayload) else { 347 | return XCTAssert(false) 348 | } 349 | do { 350 | let key = try PEMKey(source: privKey) 351 | let token = try jwt1.sign(alg: .rs256, key: key) 352 | guard let jwt = JWTVerifier(token) else { 353 | return XCTAssert(false) 354 | } 355 | let key2 = try PEMKey(source: pubKey) 356 | try jwt.verify(algo: .rs256, key: key2) 357 | let fndName = jwt.payload["name"] as? String 358 | XCTAssert(name == fndName!) 359 | } catch { 360 | XCTAssert(false, "\(error)") 361 | } 362 | } 363 | } 364 | 365 | func testJWTCreate3() { 366 | let tstPayload = ["sub": "1234567890", "name": "John Doe", "admin": true] as [String : Any] 367 | let name = "John Doe" 368 | let pubKey = "-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAENyTiyHJTNSQU3UqvzGxKe9ztD08SeBKWRfdvFi5Dp3hGXTgQE3Hb6v0jHZV62R0T1Uu4b+R3IZV6DeozO7JpSQ==\n-----END PUBLIC KEY-----" 369 | let privKey = "-----BEGIN PRIVATE KEY-----\nMIGTAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBHkwdwIBAQQgW3Yv7y/niwo3xaG/Hzq8s+Jnil0jnsMCguCeKKTxG3OgCgYIKoZIzj0DAQehRANCAAQ3JOLIclM1JBTdSq/MbEp73O0PTxJ4EpZF928WLkOneEZdOBATcdvq/SMdlXrZHRPVS7hv5HchlXoN6jM7smlJ\n-----END PRIVATE KEY-----" 370 | for _ in 0..<30 { 371 | guard let jwt1 = JWTCreator(payload: tstPayload) else { 372 | return XCTAssert(false) 373 | } 374 | do { 375 | let key = try PEMKey(source: privKey) 376 | let token = try jwt1.sign(alg: .es256, key: key) 377 | guard let jwt = JWTVerifier(token) else { 378 | return XCTAssert(false) 379 | } 380 | let key2 = try PEMKey(source: pubKey) 381 | try jwt.verify(algo: .es256, key: key2) 382 | let fndName = jwt.payload["name"] as? String 383 | XCTAssert(name == fndName!) 384 | } catch { 385 | XCTAssert(false, "\(error)") 386 | } 387 | } 388 | } 389 | 390 | func testJWTCreateCert() { 391 | let tstPayload = ["sub": "1234567890", "name": "John Doe", "admin": true] as [String : Any] 392 | let name = "John Doe" 393 | let pubKey = "-----BEGIN CERTIFICATE-----\nMIIDNDCCAhwCCQDH2QBnQs6n6DANBgkqhkiG9w0BAQUFADBcMQswCQYDVQQGEwJB\nVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0\ncyBQdHkgTHRkMRUwEwYDVQQDEwxiYWR0aGluZy5vcmcwHhcNMTcwOTIyMTY0MDI0\nWhcNMTcxMDIyMTY0MDI0WjBcMQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1T\ndGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMRUwEwYDVQQD\nEwxiYWR0aGluZy5vcmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCk\n9+U38uJNgz80opuSLPB9RAMMYzLA10E7Ix0Ge2FI5VVRWR5GItDH3h7fxH8kLyZ+\nX1Qovq4NSXLUIQv6kR+OXhyDa1Q8MYwr9s8UNN24QFBoPGvj06aKfu+u3Kt1ezFD\nea2/DRB5WMFZmKO37LNYUJQZs7/NFFltpt7m0Q3tewYdnzMfChRgzcfKT3I21KMU\nrPACysMInijoWNA93e1cIGpIUT9oNNrTHKQ18VWJjf2DGTlRDw+Lc1AoMtUCjyGQ\nFZ3zyzkt1DvUuu+g+lhTol2ffBx/vMlC9K9Nh+y1O7zddHQhcpM/alcL5o+R5Jnp\nXd3AfO+OYQF2ZN3gBZhdAgMBAAEwDQYJKoZIhvcNAQEFBQADggEBAHixAUQ22cpv\n9MKNyaTeiReNeipL1UKDCE/PDIg15WdNjzjcEbZAYqEdga4VnLcxDeV/OsvJDz/r\nioQiZgNTog0f15Q9USi5g1KtZrwParTitfRS/Uh9gjj+cbDj/M/WcIEiCHwMl2Mv\neOMYtyL/asdUQiVJBMvUggU4PDRtVjA+uVKvvv9brcJb+yBy9kSazem4olPGJCz4\nPxqAOUQ6KhQyuhKfLc7qIAej8NGXw5K7fG1e2Gx9etNM8lUZRM2Klo/0rZ5iqiq7\nuI5korDYLIAOXOPRvfP3B3mIakZtg++SnDCgVpU2LdEx9V5eov4qij8VAORS8g9o\nuaXv2Q++efc=\n-----END CERTIFICATE-----\n" 394 | let privKey = "-----BEGIN RSA PRIVATE KEY-----\nMIIEowIBAAKCAQEApPflN/LiTYM/NKKbkizwfUQDDGMywNdBOyMdBnthSOVVUVke\nRiLQx94e38R/JC8mfl9UKL6uDUly1CEL+pEfjl4cg2tUPDGMK/bPFDTduEBQaDxr\n49Omin7vrtyrdXsxQ3mtvw0QeVjBWZijt+yzWFCUGbO/zRRZbabe5tEN7XsGHZ8z\nHwoUYM3Hyk9yNtSjFKzwAsrDCJ4o6FjQPd3tXCBqSFE/aDTa0xykNfFViY39gxk5\nUQ8Pi3NQKDLVAo8hkBWd88s5LdQ71LrvoPpYU6Jdn3wcf7zJQvSvTYfstTu83XR0\nIXKTP2pXC+aPkeSZ6V3dwHzvjmEBdmTd4AWYXQIDAQABAoIBAEM9Jxhezw546FIz\n1OUHnB3ykquB4zXmhpfr//CcaVKk5tl5UXWUyzQrvLnIBWpiLXZktJDG53pS7ZK4\nxYEjlZEZmtWV8Yd3SoLA3jaGNbjbveo+dlst8TuR8W98UgZYaAPwnHi6gnRzUJuM\nM27L822TqkmvkgWsvaaL1V6O5vZb/sdB1+2vV0uE6kKX0gXoCmkwSc/an7a5tY/O\njLl/AZ/P0yJOnCaEZvkpvauP4lK7tNjl078pn3D3scBumL3/mpAtCDB7uPuC8LZn\nWh6pxgNSE9cCpP96EBQbUskgNqG9k5TCtVO8kaCmuV4aPDLWVELuLuryCDsjAWmD\n/PqMV6ECgYEA1SuPcoLoH/DOA+QL2sXmMzeOY2tKZ39UXoqoGM0Y7WaxIvmF/Uv2\nY7BrEpjwsSATFEDzx7Hfds1iBLdO6yb/3z/ajHpEdXu74Efx5HhHSxwi3JaxqLqx\n1nzHfR6qMeSNuGQpVdQpQKnwVDUipgNIcEDkseIj2MVfiXNtBf7D6psCgYEAxh0R\nN4dwVxV9EJLnd5F/CGyyHAoUfMxIrRBKjJTr/qqq+dnbJMX8PzCkAmuS8r5Lr8nn\nER+iAExf7oQhi27qVlOICoWGrHjcqwsi5Tn9TLokbbQCOrUHHn5N8dCIoPVw3Fpp\ndaS/ko2ThdI1DgDS1jq8UPdBrJ/02fO8XK+P3GcCgYA2Vs5QQHJvgfDiKQWklQHj\nWGwhh74Ft/2HxAyplc6e5aiN49F2CiEatGP276mbXTO/2/bIlt0B6cTsstWZN+3N\nuPc7DAfbctkniO9ucAKscNWqKXfMLRscM96eVGzKHxrJQC8RQ+3oH+m1bX4Rl5Cl\nnMUvWxgML/P0k8nc116VtQKBgClPsmFj6rceEgA8weua+WRmVhWmvHLxnk4IUaNT\nAosOR6zmEt5uMpVyrSCcEf5wVBQKBBb8A6oQQwjXoK8Up+TscjfPdC/O3CUGo3Yt\nS3aOcj42BSj8ysk/CT3dgEAgLjKk38zaV+BViWekV8/duBlYEiDIDnfSuxofyy2A\npn0NAoGBALIlCu5KjZn4pEmWo4AAO66CLseGFNhtcbW6Uy/L0kPdmZMr57rl56iQ\nbezjOSECKsqRTT2xJzJ7NVl4VdnqrQ71+LkYHtIB1znq7WzcCZ4fBnW/rrO8JZ54\nkKwm2gxxEoTlawTqhjp5O6wSu31+hjYPv/xRelMOpGOrqVLqd8nU\n-----END RSA PRIVATE KEY-----\n" 395 | for _ in 0..<30 { 396 | guard let jwt1 = JWTCreator(payload: tstPayload) else { 397 | return XCTAssert(false) 398 | } 399 | do { 400 | let key = try PEMKey(source: privKey) 401 | let token = try jwt1.sign(alg: .es256, key: key) 402 | guard let jwt = JWTVerifier(token) else { 403 | return XCTAssert(false) 404 | } 405 | let key2 = try PEMKey(source: pubKey) 406 | try jwt.verify(algo: .es256, key: key2) 407 | let fndName = jwt.payload["name"] as? String 408 | XCTAssert(name == fndName!) 409 | } catch { 410 | XCTAssert(false, "\(error)") 411 | } 412 | } 413 | } 414 | 415 | func testCipherCMS1() { 416 | let cipher = Cipher.aes_256_cbc 417 | let password = Array("this is a good pw".utf8) 418 | let salt = Array("this is a salty salt".utf8) 419 | let randomArray = [UInt8](randomCount: 1029) 420 | guard let result = randomArray.encrypt(cipher, password: password, salt: salt) else { 421 | return XCTAssert(false, "\(CryptoError())") 422 | } 423 | guard let decryptedAry = result.decrypt(cipher, password: password, salt: salt) else { 424 | return XCTAssert(false, "\(CryptoError())") 425 | } 426 | XCTAssertEqual(decryptedAry, randomArray) 427 | } 428 | 429 | func testCipherCMS2() { 430 | let cipher = Cipher.aes_256_cbc 431 | let password = Array("this is a good pw".utf8) 432 | let passwordBad = Array("this is a bad pw".utf8) 433 | let salt = Array("this is a salty salt".utf8) 434 | let randomArray = [UInt8](randomCount: 1029) 435 | guard let result = randomArray.encrypt(cipher, password: password, salt: salt) else { 436 | return XCTAssert(false, "\(CryptoError())") 437 | } 438 | let decryptedAry = result.decrypt(cipher, password: passwordBad, salt: salt) 439 | XCTAssertNil(decryptedAry) 440 | } 441 | 442 | func testCipherCMS3() { 443 | let cipher = Cipher.aes_256_cbc 444 | let password = "this is a good pw" 445 | let salt = "this is a salty salt" 446 | let data = (1...1000).map { "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\($0)" }.joined(separator: "\n") 447 | guard let result = data.encrypt(cipher, password: password, salt: salt) else { 448 | return XCTAssert(false, "\(CryptoError())") 449 | } 450 | guard let decryptedData = result.decrypt(cipher, password: password, salt: salt) else { 451 | return XCTAssert(false, "\(CryptoError())") 452 | } 453 | XCTAssertEqual(decryptedData, data) 454 | } 455 | 456 | func testHMACKey() { 457 | let password = "this is a good pw" 458 | let data = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" 459 | 460 | if let signed = data.sign(.sha1, key: HMACKey(password))?.encode(.base64), 461 | let base64Str = String(validatingUTF8: signed), 462 | 463 | let reRawData = base64Str.decode(.base64) { 464 | 465 | let verifyResult = data.verify(.sha1, signature: reRawData, key: HMACKey(password)) 466 | XCTAssert(verifyResult) 467 | } else { 468 | XCTAssert(false, "Failed signing") 469 | } 470 | } 471 | 472 | func testKeyGen() { 473 | do { 474 | let bits = 1024 475 | struct JSONPayload: Codable { 476 | let id: Foundation.UUID 477 | let expires: Int 478 | } 479 | let payload = JSONPayload(id: UUID(), expires: time(nil) + 3600) 480 | let jwt = try JWTCreator(payload: payload) 481 | do { 482 | let keyPair = try PEMKey(type: .rsa, bits: bits) 483 | guard let pubKey = keyPair.publicKey, 484 | let privKey = keyPair.privateKey else { 485 | return XCTFail("Unable to get pub/priv keys") 486 | } 487 | XCTAssert(keyPair.publicKeyString!.hasPrefix("-----BEGIN RSA PUBLIC KEY-----")) 488 | XCTAssert(keyPair.privateKeyString!.hasPrefix("-----BEGIN RSA PRIVATE KEY-----")) 489 | let signed = try jwt.sign(alg: .rs256, key: privKey) 490 | guard let jwtVer = JWTVerifier(signed) else { 491 | return XCTFail("JWT verify failed") 492 | } 493 | let obj = try jwtVer.verify(algo: .rs256, key: pubKey, as: JSONPayload.self) 494 | XCTAssertEqual(obj.id, payload.id) 495 | } 496 | do { 497 | let keyPair = try PEMKey(type: .dsa, bits: bits) 498 | let pubKey = keyPair.publicKey 499 | let privKey = keyPair.privateKey 500 | XCTAssertNotNil(pubKey) 501 | XCTAssertNotNil(privKey) 502 | XCTAssert(keyPair.publicKeyString!.hasPrefix("-----BEGIN PUBLIC KEY-----")) 503 | XCTAssert(keyPair.privateKeyString!.hasPrefix("-----BEGIN DSA PRIVATE KEY-----")) 504 | } 505 | do { 506 | let keyPair = try PEMKey(type: .ec, bits: bits) 507 | guard let pubKey = keyPair.publicKey, 508 | let privKey = keyPair.privateKey else { 509 | return XCTFail("Unable to get pub/priv keys") 510 | } 511 | XCTAssert(keyPair.publicKeyString!.hasPrefix("-----BEGIN PUBLIC KEY-----")) 512 | XCTAssert(keyPair.privateKeyString!.hasPrefix("-----BEGIN EC PRIVATE KEY-----")) 513 | let signed = try jwt.sign(alg: .es256, key: privKey) 514 | guard let jwtVer = JWTVerifier(signed) else { 515 | return XCTFail("JWT verify failed") 516 | } 517 | let obj = try jwtVer.verify(algo: .es256, key: pubKey, as: JSONPayload.self) 518 | XCTAssertEqual(obj.id, payload.id) 519 | } 520 | 521 | 522 | 523 | } catch { 524 | XCTFail("\(error)") 525 | } 526 | } 527 | 528 | func runProc(cmd: String, args: [String]) throws -> String? { 529 | #if os(Linux) 530 | let command:[String] = [cmd] + args 531 | let fd = popen(command.joined(separator: " "), "r") 532 | var rd = 0 533 | let _bufferSize = 16384 534 | let buffer = UnsafeMutablePointer.allocate(capacity: _bufferSize) 535 | defer { 536 | buffer.deallocate() 537 | } 538 | var buf: [UInt8] = [] 539 | repeat { 540 | buffer.initialize(to: 0) 541 | rd = fread(buffer, 1, _bufferSize, fd) 542 | if rd > 0 { 543 | let array = UnsafeBufferPointer(start: buffer, count: rd) 544 | buf.append(contentsOf: Array(array)) 545 | } 546 | } while rd > 0 547 | pclose(fd) 548 | #else 549 | let task = Process() 550 | task.launchPath = cmd 551 | task.arguments = args 552 | task.environment = ["PATH": "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"] 553 | let oup = Pipe() 554 | task.standardOutput = oup 555 | task.launch() 556 | task.waitUntilExit() 557 | let buf = oup.fileHandleForReading.readDataToEndOfFile() 558 | #endif 559 | return String(bytes: buf, encoding: .utf8) 560 | } 561 | 562 | func openssl(command: String, file: String) throws -> String { 563 | guard let output = try runProc(cmd: "/usr/bin/openssl", args: [command, file]), 564 | let eq = output.firstIndex(of: "=") else { 565 | throw CryptoError(code: -11, msg: "openssl failed") 566 | } 567 | let chopped = output[eq.. () throws -> Void)] { 616 | return [ 617 | ("testInitialized", testInitialized), 618 | ("testIOPair", testIOPair), 619 | ("testHexEncDec1", testHexEncDec1), 620 | ("test64EncDec1", test64EncDec1), 621 | ("test64EncDec3", test64EncDec3), 622 | ("testHexEncDec2", testHexEncDec2), 623 | ("test64EncDec2", test64EncDec2), 624 | ("testIOPair", testIOPair), 625 | ("testBase64Filter1", testBase64Filter1), 626 | ("testBase64Filter2", testBase64Filter2), 627 | ("testDigest1", testDigest1), 628 | ("testDigest2", testDigest2), 629 | ("testCipherSizes", testCipherSizes), 630 | ("testRandomBuffer1", testRandomBuffer1), 631 | ("testRandomBuffer2", testRandomBuffer2), 632 | ("testCipher1", testCipher1), 633 | ("testCipher2", testCipher2), 634 | ("testJWTCreate1", testJWTCreate1), 635 | ("testJWTCreate2", testJWTCreate2), 636 | ("testJWTCreate3", testJWTCreate3), 637 | ("testJWTCreateCert", testJWTCreateCert), 638 | ("testCipherCMS1", testCipherCMS1), 639 | ("testCipherCMS2", testCipherCMS2), 640 | ("testCipherCMS3", testCipherCMS3), 641 | ("testHMACKey", testHMACKey), 642 | ("testFiles", testFiles), 643 | ("testKeyGen", testKeyGen), 644 | ] 645 | } 646 | } 647 | --------------------------------------------------------------------------------