├── .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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
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 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
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 |
--------------------------------------------------------------------------------