Initial commit

This commit is contained in:
oscarz
2024-08-12 10:49:20 +08:00
parent 3002510aaf
commit 00fd0adf89
331 changed files with 53210 additions and 130 deletions
+176
View File
@@ -0,0 +1,176 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
+235
View File
@@ -0,0 +1,235 @@
<p align="center">
<a href="https://www.kitura.io/packages.html#all">
<img src="https://img.shields.io/badge/docs-kitura.io-1FBCE4.svg" alt="APIDoc">
</a>
<a href="https://travis-ci.org/IBM-Swift/BlueCryptor">
<img src="https://travis-ci.org/IBM-Swift/BlueCryptor.svg?branch=master" alt="Build Status - Master">
</a>
<img src="https://img.shields.io/badge/os-macOS-green.svg?style=flat" alt="macOS">
<img src="https://img.shields.io/badge/os-iOS-green.svg?style=flat" alt="iOS">
<img src="https://img.shields.io/badge/os-linux-green.svg?style=flat" alt="Linux">
<img src="https://img.shields.io/badge/license-Apache2-blue.svg?style=flat" alt="Apache 2">
<a href="http://swift-at-ibm-slack.mybluemix.net/">
<img src="http://swift-at-ibm-slack.mybluemix.net/badge.svg" alt="Slack Status">
</a>
<p>
# BlueCryptor
Swift cross-platform crypto library derived from [IDZSwiftCommonCrypto](https://github.com/iosdevzone/IDZSwiftCommonCrypto).
**IMPORTANT NOTE:** This release is **NOT** entirely source code compatible with previous releases. There are instances where *exceptions* are thrown now instead of the framework calling *fatalError()*. This means that there are more *recoverable* errors in the library than before. The only time that *fatalError()* is called is to indicate either a *programming error* or a *non-recoverable system error*.
**Note:** On macOS and iOS, _BlueCryptor_ uses the Apple provided *CommonCrypto* library. On Linux, it uses *libcrypto from the OpenSSL project*.
## Prerequisites
### Swift
* Swift Open Source `swift-4.0.0-RELEASE` toolchain (**Minimum REQUIRED for latest release**)
* Swift Open Source `swift-4.2-RELEASE` toolchain (**Recommended**)
* Swift toolchain included in *Xcode Version 10.0 (10A255) or higher*.
### macOS
* macOS 10.11.6 (*El Capitan*) or higher.
* Xcode Version 9.0 or higher using one of the above toolchains.
* Xcode Version 10.0 (10A255) or higher using the included toolchain (*Recommended*).
* CommonCrypto is provided by macOS.
### iOS
* iOS 10.0 or higher
* Xcode Version 9.0 or higher using one of the above toolchains.
* Xcode Version 10.0 (10A255) or higher using the included toolchain (*Recommended*).
* CommonCrypto is provided by iOS.
### Linux
* Ubuntu 16.04 (or 16.10 but only tested on 16.04) and 18.04.
* One of the Swift Open Source toolchain listed above.
* OpenSSL is provided by the distribution. **Note:** 1.0.x, 1.1.x and later releases of OpenSSL are supported.
* The appropriate **libssl-dev** package is required to be installed when building.
## Build
To build **Cryptor** from the command line:
```
% cd <path-to-clone>
% swift build
```
## Testing
To run the supplied unit tests for **Cryptor** from the command line:
```
% cd <path-to-clone>
% swift build
% swift test
```
## Getting started
### Including in your project
#### Swift Package Manager
To include BlueCryptor into a Swift Package Manager package, add it to the `dependencies` attribute defined in your `Package.swift` file. You can select the version using the `majorVersion` and `minor` parameters. For example:
```
dependencies: [
.Package(url: "https://github.com/IBM-Swift/BlueCryptor.git", majorVersion: <majorVersion>, minor: <minor>)
]
```
#### Carthage
To include BlueCryptor in a project using Carthage, add a line to your `Cartfile` with the GitHub organization and project names and version. For example:
```
github "IBM-Swift/BlueCryptor" ~> <majorVersion>.<minor>
```
#### CocoaPods
To include BlueCryptor in a project using CocoaPods, you just add `BlueCryptor` to your `Podfile`, for example:
```
platform :ios, '10.0'
target 'MyApp' do
use_frameworks!
pod 'BlueCryptor'
end
```
### Before starting
The first thing you need to do is import the Cryptor framework. This is done by the following:
```swift
import Cryptor
```
## API
### Cryptor
The following code demonstrates encryption and decryption using `AES` single block CBC mode using optional chaining.
```swift
let key = CryptoUtils.byteArray(fromHex: "2b7e151628aed2a6abf7158809cf4f3c")
let iv = CryptoUtils.byteArray(fromHex: "00000000000000000000000000000000")
let plainText = CryptoUtils.byteArray(fromHex: "6bc1bee22e409f96e93d7e117393172a")
var textToCipher = plainText
if plainText.count % Cryptor.Algorithm.aes.blockSize != 0 {
textToCipher = CryptoUtils.zeroPad(byteArray: plainText, blockSize: Cryptor.Algorithm.aes.blockSize)
}
do {
let cipherText = try Cryptor(operation: .encrypt, algorithm: .aes, options: .none, key: key, iv: iv).update(byteArray: textToCipher)?.final()
print(CryptoUtils.hexString(from: cipherText!))
let decryptedText = try Cryptor(operation: .decrypt, algorithm: .aes, options: .none, key: key, iv: iv).update(byteArray: cipherText!)?.final()
print(CryptoUtils.hexString(from: decryptedText!))
} catch let error {
guard let err = error as? CryptorError else {
// Handle non-Cryptor error...
return
}
// Handle Cryptor error... (See Status.swift for types of errors thrown)
}
```
### Digest
The following example illustrates generating an `MD5` digest from both a `String` and an instance of `NSData`.
```swift
let qbfBytes : [UInt8] = [0x54,0x68,0x65,0x20,0x71,0x75,0x69,0x63,0x6b,0x20,0x62,0x72,0x6f,0x77,0x6e,0x20,0x66,0x6f,0x78,0x20,0x6a,0x75,0x6d,0x70,0x73,0x20,0x6f,0x76,0x65,0x72,0x20,0x74,0x68,0x65,0x20,0x6c,0x61,0x7a,0x79,0x20,0x64,0x6f,0x67,0x2e]
let qbfString = "The quick brown fox jumps over the lazy dog."
// String...
let md5 = Digest(using: .md5)
md5.update(string: qfbString)
let digest = md5.final()
// NSData using optional chaining...
let qbfData = CryptoUtils.data(from: qbfBytes)
let digest = Digest(using: .md5).update(data: qbfData)?.final()
```
### HMAC
The following demonstrates generating an `SHA256` HMAC using byte arrays for keys and data.
```swift
let myKeyData = "0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b0b"
let myData = "4869205468657265"
let key = CryptoUtils.byteArray(fromHex: myKeyData)
let data : [UInt8] = CryptoUtils.byteArray(fromHex: myData)
let hmac = HMAC(using: HMAC.Algorithm.sha256, key: key).update(byteArray: data)?.final()
```
### Key Derivation
The following illustrates generating a key using a password, salt, number of rounds and a specified derived key length using the SHA1 algorithm. Then it shows how to generate a `String` from resultant key.
```swift
let password = "password"
let salt = salt
let rounds: UInt = 2
let derivedKeyLen = 20
do {
let key = PBKDF.deriveKey(fromPassword: password, salt: salt, prf: .sha1, rounds: rounds, derivedKeyLength: derivedKeyLen)
let keyString = CryptoUtils.hexString(from: key)
} catch let error {
guard let err = error as? CryptorError else {
// Handle non-Cryptor error...
return
}
// Handle Cryptor error... (See Status.swift for types of errors thrown)
}
```
### Random Byte Generation
The following demonstrates generating random bytes of a given length.
```swift
let numberOfBytes = 256*256
do {
let randomBytes = try Random.generate(byteCount: numberOfBytes)
} catch {
print("Error generating random bytes")
}
```
### Utilities
**Cryptor** also provides a set of data manipulation utility functions for conversion of data from various formats:
- To byteArray (`[UInt8]`)
- From hex string
- From UTF8 string
- To `Data`
- From hex string
- From byte array (`[UInt8]`)
- To `NSData`
- From hex string
- From byte array (`[UInt8]`)
- To `NSString`
- From byte array (`[UInt8]`)
- To hexList (`String`)
- From byte array (`[UInt8]`)
Also provided are an API to pad a byte array (`[UInt8]`) such that it is an integral number of `block size in bytes` long.
- ```func zeroPad(byteArray: [UInt8], blockSize: Int) -> [UInt8]```
- ```func zeroPad(string: String, blockSize: Int) -> [UInt8]```
## Restrictions
The following algorithm is not available on Linux since it is not supported by *OpenSSL*.
- Digest: MD2
In all cases, use of unsupported APIs or algorithms will result in a Swift `fatalError()`, terminating the program and should be treated as a programming error.
## Community
We love to talk server-side Swift and Kitura. Join our [Slack](http://swift-at-ibm-slack.mybluemix.net/) to meet the team!
## License
This library is licensed under Apache 2.0. Full license text is available in [LICENSE](https://github.com/IBM-Swift/BlueCryptor/blob/master/LICENSE).
+128
View File
@@ -0,0 +1,128 @@
//
// Crypto.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
///
/// Implements a simplified API for calculating digests over single buffers
///
public protocol CryptoDigest {
/// Calculates a message digest
func digest(using algorithm: Digest.Algorithm) -> Self
}
///
/// Extension to the CryptoDigest to return the digest appropriate to the selected algorithm.
///
extension CryptoDigest {
/// An MD2 digest of this object
public var md2: Self {
return self.digest(using: .md2)
}
/// An MD4 digest of this object
public var md4: Self {
return self.digest(using: .md4)
}
/// An MD5 digest of this object
public var md5: Self {
return self.digest(using: .md5)
}
/// An SHA1 digest of this object
public var sha1: Self {
return self.digest(using: .sha1)
}
/// An SHA224 digest of this object
public var sha224: Self {
return self.digest(using: .sha224)
}
/// An SHA256 digest of this object
public var sha256: Self {
return self.digest(using: .sha256)
}
/// An SHA384 digest of this object
public var sha384: Self {
return self.digest(using: .sha384)
}
/// An SHA512 digest of this object
public var sha512: Self {
return self.digest(using: .sha512)
}
}
///
/// Extension for Data to return an Data object containing the digest.
///
extension Data: CryptoDigest {
///
/// Calculates the Message Digest for this data.
///
/// - Parameter algorithm: The digest algorithm to use
///
/// - Returns: An `Data` object containing the message digest
///
public func digest(using algorithm: Digest.Algorithm) -> Data {
// This force unwrap may look scary but for CommonCrypto this cannot fail.
// The API allows for optionals to support the OpenSSL implementation which can.
#if swift(>=5.0)
return self.withUnsafeBytes() {
let result = (Digest(using: algorithm).update(from: $0.baseAddress!, byteCount: self.count)?.final())!
let data = type(of: self).init(bytes: result, count: result.count)
return data
}
#else
return self.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) -> Data in
let result = (Digest(using: algorithm).update(from: buffer, byteCount: self.count)?.final())!
let data = type(of: self).init(bytes: result, count: result.count)
return data
}
#endif
}
}
///
/// Extension for String to return a String containing the digest.
///
extension String: CryptoDigest {
///
/// Calculates the Message Digest for this string.
/// The string is converted to raw data using UTF8.
///
/// - Parameter algorithm: The digest algorithm to use
///
/// - Returns: A hex string of the calculated digest
///
public func digest(using algorithm: Digest.Algorithm) -> String {
// This force unwrap may look scary but for CommonCrypto this cannot fail.
// The API allows for optionals to support the OpenSSL implementation which can.
let result = (Digest(using: algorithm).update(string: self as String)?.final())!
return CryptoUtils.hexString(from: result)
}
}
+71
View File
@@ -0,0 +1,71 @@
//
// Cryptor.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
///
/// Encrypts or decrypts, accumulating result.
///
/// Useful for small in-memory buffers.
///
/// For large files or network streams use StreamCryptor.
///
public class Cryptor: StreamCryptor, Updatable {
/// Internal accumulator for gathering data from the update() and final() functions.
var accumulator: [UInt8] = []
///
/// Retrieves the encrypted or decrypted data.
///
///- Returns: the encrypted or decrypted data or nil if an error occured.
///
public func final() -> [UInt8]? {
let byteCount = Int(self.getOutputLength(inputByteCount: 0, isFinal: true))
var dataOut = Array<UInt8>(repeating: 0, count:byteCount)
var dataOutMoved = 0
(dataOutMoved, self.status) = final(byteArrayOut: &dataOut)
if self.status != .success {
return nil
}
accumulator += dataOut[0..<Int(dataOutMoved)]
return accumulator
}
///
/// Upates the accumulated encrypted/decrypted data with the contents
/// of a raw byte buffer.
///
/// It is not envisaged the users of the framework will need to call this directly.
///
/// - Returns: this Cryptor object or nil if an error occurs (for optional chaining)
///
public func update(from buffer: UnsafeRawPointer, byteCount: Int) -> Self? {
let outputLength = Int(self.getOutputLength(inputByteCount: byteCount, isFinal: false))
var dataOut = Array<UInt8>(repeating: 0, count:outputLength)
var dataOutMoved = 0
_ = update(bufferIn: buffer, byteCountIn: byteCount, bufferOut: &dataOut, byteCapacityOut: dataOut.count, byteCountOut: &dataOutMoved)
if self.status != .success {
return nil
}
accumulator += dataOut[0..<Int(dataOutMoved)]
return self
}
}
+279
View File
@@ -0,0 +1,279 @@
//
// Digest.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import CommonCrypto
#elseif os(Linux)
import OpenSSL
typealias CC_LONG = size_t
#endif
///
/// Public API for message digests.
///
/// Usage is straightforward
///
/// ```
/// let s = "The quick brown fox jumps over the lazy dog."
/// var md5: Digest = Digest(using:.md5)
/// md5.update(s)
/// let digest = md5.final()
///```
///
public class Digest: Updatable {
///
/// The status of the Digest.
/// For CommonCrypto this will always be `.Success`.
/// It is here to provide for engines which can fail.
///
public var status = Status.success
///
/// Enumerates available Digest algorithms
///
public enum Algorithm {
/// Message Digest 2 See: http://en.wikipedia.org/wiki/MD2_(cryptography)
case md2
/// Message Digest 4
case md4
/// Message Digest 5
case md5
/// Secure Hash Algorithm 1
case sha1
/// Secure Hash Algorithm 2 224-bit
case sha224
/// Secure Hash Algorithm 2 256-bit
case sha256
/// Secure Hash Algorithm 2 384-bit
case sha384
/// Secure Hash Algorithm 2 512-bit
case sha512
}
private var engine: DigestEngine
///
/// Create an algorithm-specific digest calculator
///
/// - Parameter alrgorithm: the desired message digest algorithm
///
public init(using algorithm: Algorithm) {
switch algorithm {
case .md2:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_MD2_CTX>(initializer:CC_MD2_Init, updater:CC_MD2_Update, finalizer:CC_MD2_Final, length:CC_MD2_DIGEST_LENGTH)
#elseif os(Linux)
fatalError("MD2 digest not supported by OpenSSL")
#endif
case .md4:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_MD4_CTX>(initializer:CC_MD4_Init, updater:CC_MD4_Update, finalizer:CC_MD4_Final, length:CC_MD4_DIGEST_LENGTH)
#elseif os(Linux)
engine = DigestEngineCC<MD4_CTX>(initializer:MD4_Init, updater:MD4_Update, finalizer:MD4_Final, length:MD4_DIGEST_LENGTH)
#endif
case .md5:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_MD5_CTX>(initializer:CC_MD5_Init, updater:CC_MD5_Update, finalizer:CC_MD5_Final, length:CC_MD5_DIGEST_LENGTH)
#elseif os(Linux)
engine = DigestEngineCC<MD5_CTX>(initializer:MD5_Init, updater:MD5_Update, finalizer:MD5_Final, length:MD5_DIGEST_LENGTH)
#endif
case .sha1:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_SHA1_CTX>(initializer:CC_SHA1_Init, updater:CC_SHA1_Update, finalizer:CC_SHA1_Final, length:CC_SHA1_DIGEST_LENGTH)
#elseif os(Linux)
engine = DigestEngineCC<SHA_CTX>(initializer:SHA1_Init, updater:SHA1_Update, finalizer:SHA1_Final, length:SHA_DIGEST_LENGTH)
#endif
case .sha224:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_SHA256_CTX>(initializer:CC_SHA224_Init, updater:CC_SHA224_Update, finalizer:CC_SHA224_Final, length:CC_SHA224_DIGEST_LENGTH)
#elseif os(Linux)
engine = DigestEngineCC<SHA256_CTX>(initializer:SHA224_Init, updater:SHA224_Update, finalizer:SHA224_Final, length:SHA224_DIGEST_LENGTH)
#endif
case .sha256:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_SHA256_CTX>(initializer:CC_SHA256_Init, updater:CC_SHA256_Update, finalizer:CC_SHA256_Final, length:CC_SHA256_DIGEST_LENGTH)
#elseif os(Linux)
engine = DigestEngineCC<SHA256_CTX>(initializer: SHA256_Init, updater:SHA256_Update, finalizer:SHA256_Final, length:SHA256_DIGEST_LENGTH)
#endif
case .sha384:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_SHA512_CTX>(initializer:CC_SHA384_Init, updater:CC_SHA384_Update, finalizer:CC_SHA384_Final, length:CC_SHA384_DIGEST_LENGTH)
#elseif os(Linux)
engine = DigestEngineCC<SHA512_CTX>(initializer:SHA384_Init, updater:SHA384_Update, finalizer:SHA384_Final, length:SHA384_DIGEST_LENGTH)
#endif
case .sha512:
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
engine = DigestEngineCC<CC_SHA512_CTX>(initializer:CC_SHA512_Init, updater:CC_SHA512_Update, finalizer:CC_SHA512_Final, length:CC_SHA512_DIGEST_LENGTH)
#elseif os(Linux)
engine = DigestEngineCC<SHA512_CTX>(initializer:SHA512_Init, updater:SHA512_Update, finalizer:SHA512_Final, length:SHA512_DIGEST_LENGTH)
#endif
}
}
///
/// Low-level update routine. Updates the message digest calculation with
/// the contents of a byte buffer.
///
/// - Parameters:
/// - buffer: The buffer
/// - byteCount: Number of bytes in buffer
///
/// - Returns: This Digest object (for optional chaining)
///
public func update(from buffer: UnsafeRawPointer, byteCount: size_t) -> Self? {
engine.update(buffer: buffer, byteCount: CC_LONG(byteCount))
return self
}
///
/// Completes the calculate of the messge digest
///
/// - Returns: The message digest
///
public func final() -> [UInt8] {
return engine.final()
}
}
// MARK: Internal Classes
///
/// Defines the interface between the Digest class and an
/// algorithm specific DigestEngine
///
private protocol DigestEngine {
///
/// Update method
///
/// - Parameters:
/// - buffer: The buffer to add.
/// - byteCount: The length of the buffer.
///
func update(buffer: UnsafeRawPointer, byteCount: CC_LONG)
///
/// Finalizer routine
///
/// - Returns: Byte array containing the digest.
///
func final() -> [UInt8]
}
///
/// Wraps the underlying algorithm specific structures and calls
/// in a generic interface.
///
/// - Parameter CTX: The context for the digest.
///
private class DigestEngineCC<CTX>: DigestEngine {
typealias Context = UnsafeMutablePointer<CTX>
typealias Buffer = UnsafeRawPointer
typealias Digest = UnsafeMutablePointer<UInt8>
typealias Initializer = (Context) -> (Int32)
typealias Updater = (Context, Buffer, CC_LONG) -> (Int32)
typealias Finalizer = (Digest, Context) -> (Int32)
let context = Context.allocate(capacity: 1)
var initializer: Initializer
var updater: Updater
var finalizer: Finalizer
var length: Int32
///
/// Default initializer
///
/// - Parameters:
/// - initializer: The digest initializer routine.
/// - updater: The digest updater routine.
/// - finalizer: The digest finalizer routine.
/// - length: The digest length.
///
init(initializer: @escaping Initializer, updater: @escaping Updater, finalizer: @escaping Finalizer, length: Int32) {
self.initializer = initializer
self.updater = updater
self.finalizer = finalizer
self.length = length
_ = initializer(context)
}
///
/// Cleanup
///
deinit {
#if swift(>=4.1)
context.deallocate()
#else
context.deallocate(capacity: 1)
#endif
}
///
/// Update method
///
/// - Parameters:
/// - buffer: The buffer to add.
/// - byteCount: The length of the buffer.
///
func update(buffer: Buffer, byteCount: CC_LONG) {
_ = updater(context, buffer, byteCount)
}
///
/// Finalizer routine
///
/// - Returns: Byte array containing the digest.
///
func final() -> [UInt8] {
let digestLength = Int(self.length)
var digest = Array<UInt8>(repeating: 0, count:digestLength)
_ = finalizer(&digest, context)
return digest
}
}
+335
View File
@@ -0,0 +1,335 @@
//
// HMAC.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import CommonCrypto
#elseif os(Linux)
import OpenSSL
#endif
///
/// Calculates a cryptographic Hash-Based Message Authentication Code (HMAC).
///
public class HMAC: Updatable {
///
/// Enumerates available algorithms.
///
public enum Algorithm {
/// Message Digest 5
case md5
/// Secure Hash Algorithm 1
case sha1
/// Secure Hash Algorithm 2 224-bit
case sha224
/// Secure Hash Algorithm 2 256-bit
case sha256
/// Secure Hash Algorithm 2 384-bit
case sha384
/// Secure Hash Algorithm 2 512-bit
case sha512
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
static let fromNative: [CCHmacAlgorithm: Algorithm] = [
CCHmacAlgorithm(kCCHmacAlgSHA1): .sha1,
CCHmacAlgorithm(kCCHmacAlgSHA1): .md5,
CCHmacAlgorithm(kCCHmacAlgSHA256): .sha256,
CCHmacAlgorithm(kCCHmacAlgSHA384): .sha384,
CCHmacAlgorithm(kCCHmacAlgSHA512): .sha512,
CCHmacAlgorithm(kCCHmacAlgSHA224): .sha224
]
static func fromNativeValue(nativeAlg: CCHmacAlgorithm) -> Algorithm? {
return fromNative[nativeAlg]
}
func nativeValue() -> CCHmacAlgorithm {
switch self {
case .sha1:
return CCHmacAlgorithm(kCCHmacAlgSHA1)
case .md5:
return CCHmacAlgorithm(kCCHmacAlgMD5)
case .sha224:
return CCHmacAlgorithm(kCCHmacAlgSHA224)
case .sha256:
return CCHmacAlgorithm(kCCHmacAlgSHA256)
case .sha384:
return CCHmacAlgorithm(kCCHmacAlgSHA384)
case .sha512:
return CCHmacAlgorithm(kCCHmacAlgSHA512)
}
}
#elseif os(Linux)
func nativeValue() -> OpaquePointer? {
switch self {
case .sha1:
return .init(EVP_sha1())
case .md5:
return .init(EVP_md5())
case .sha224:
return .init(EVP_sha224())
case .sha256:
return .init(EVP_sha256())
case .sha384:
return .init(EVP_sha384())
case .sha512:
return .init(EVP_sha512())
}
}
#endif
///
/// Obtains the digest length produced by this algorithm (in bytes).
///
public func digestLength() -> Int {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
switch self {
case .sha1:
return Int(CC_SHA1_DIGEST_LENGTH)
case .md5:
return Int(CC_MD5_DIGEST_LENGTH)
case .sha224:
return Int(CC_SHA224_DIGEST_LENGTH)
case .sha256:
return Int(CC_SHA256_DIGEST_LENGTH)
case .sha384:
return Int(CC_SHA384_DIGEST_LENGTH)
case .sha512:
return Int(CC_SHA512_DIGEST_LENGTH)
}
#elseif os(Linux)
switch self {
case .sha1:
return Int(SHA_DIGEST_LENGTH)
case .md5:
return Int(MD5_DIGEST_LENGTH)
case .sha224:
return Int(SHA224_DIGEST_LENGTH)
case .sha256:
return Int(SHA256_DIGEST_LENGTH)
case .sha384:
return Int(SHA384_DIGEST_LENGTH)
case .sha512:
return Int(SHA512_DIGEST_LENGTH)
}
#endif
}
}
/// Context
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
typealias Context = UnsafeMutablePointer<CCHmacContext>
#elseif os(Linux)
typealias Context = OpaquePointer?
#endif
/// Status of the calculation
public internal(set) var status: Status = .success
#if os(Linux)
private let context = HMAC_CTX_new_wrapper()
#else
private let context = Context.allocate(capacity: 1)
#endif
private var algorithm: Algorithm
// MARK: Lifecycle Methods
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - keyBuffer: Specifies pointer to the key
/// - keyByteCount: Number of bytes on keyBuffer
///
init(using algorithm: Algorithm, keyBuffer: UnsafeRawPointer, keyByteCount: Int) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), keyBuffer, size_t(keyByteCount))
#elseif os(Linux)
HMAC_Init_wrapper(context, keyBuffer, Int32(keyByteCount), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as Data
///
public init(using algorithm: Algorithm, key: Data) {
self.algorithm = algorithm
#if swift(>=5.0)
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
key.withUnsafeBytes() {
CCHmacInit(context, algorithm.nativeValue(), $0.baseAddress, size_t(key.count))
}
#elseif os(Linux)
_ = key.withUnsafeBytes() {
HMAC_Init_wrapper(context, $0.baseAddress, Int32(key.count), .make(optional: algorithm.nativeValue()))
}
#endif
#else
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
key.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
CCHmacInit(context, algorithm.nativeValue(), buffer, size_t(key.count))
}
#elseif os(Linux)
_ = key.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
HMAC_Init_wrapper(context, buffer, Int32(key.count), .make(optional: algorithm.nativeValue()))
}
#endif
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as NSData
///
public init(using algorithm: Algorithm, key: NSData) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), key.bytes, size_t(key.length))
#elseif os(Linux)
HMAC_Init_wrapper(context, key.bytes, Int32(key.length), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as byte array.
///
public init(using algorithm: Algorithm, key: [UInt8]) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), key, size_t(key.count))
#elseif os(Linux)
HMAC_Init_wrapper(context, key, Int32(key.count), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Creates a new HMAC instance with the specified algorithm and key string.
/// The key string is converted to bytes using UTF8 encoding.
///
/// - Parameters:
/// - algorithm: Selects the algorithm
/// - key: Specifies the key as String
///
public init(using algorithm: Algorithm, key: String) {
self.algorithm = algorithm
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacInit(context, algorithm.nativeValue(), key, size_t(key.lengthOfBytes(using: String.Encoding.utf8)))
#elseif os(Linux)
HMAC_Init_wrapper(context, key, Int32(key.utf8.count), .make(optional: algorithm.nativeValue()))
#endif
}
///
/// Cleanup
///
deinit {
#if os(Linux)
HMAC_CTX_free_wrapper(.make(optional: context))
#else
#if swift(>=4.1)
context.deallocate()
#else
context.deallocate(capacity: 1)
#endif
#endif
}
// MARK: Public Methods
///
/// Updates the calculation of the HMAC with the contents of a buffer.
///
/// - Parameter buffer: Update buffer
///
/// - Returns: The 'in-progress' calculated HMAC
///
public func update(from buffer: UnsafeRawPointer, byteCount: size_t) -> Self? {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacUpdate(context, buffer, byteCount)
#elseif os(Linux)
HMAC_Update(context, buffer.assumingMemoryBound(to: UInt8.self), byteCount)
#endif
return self
}
///
/// Finalizes the HMAC calculation
///
/// - Returns: The final calculated HMAC
///
public func final() -> [UInt8] {
var hmac = Array<UInt8>(repeating: 0, count:algorithm.digestLength())
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
CCHmacFinal(context, &hmac)
#elseif os(Linux)
var length: UInt32 = 0
HMAC_Final(context, &hmac, &length)
#endif
return hmac
}
}
+211
View File
@@ -0,0 +1,211 @@
//
// KeyDerivation.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import CommonCrypto
#elseif os(Linux)
import OpenSSL
#endif
///
/// Derives key material from a password or passphrase.
///
public class PBKDF {
/// Enumerates available pseudo random algorithms
public enum PseudoRandomAlgorithm {
/// Secure Hash Algorithm 1
case sha1
/// Secure Hash Algorithm 2 224-bit
case sha224
/// Secure Hash Algorithm 2 256-bit
case sha256
/// Secure Hash Algorithm 2 384-bit
case sha384
/// Secure Hash Algorithm 2 512-bit
case sha512
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
/// Return the OS native value
func nativeValue() -> CCPseudoRandomAlgorithm {
switch self {
case .sha1:
return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA1)
case .sha224:
return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA224)
case .sha256:
return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256)
case .sha384:
return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA384)
case .sha512:
return CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA512)
}
}
#elseif os(Linux)
/// Return the OS native value
func nativeValue() -> OpaquePointer? {
switch self {
case .sha1:
return .init(EVP_sha1())
case .sha224:
return .init(EVP_sha224())
case .sha256:
return .init(EVP_sha256())
case .sha384:
return .init(EVP_sha384())
case .sha512:
return .init(EVP_sha512())
}
}
#endif
}
///
/// Determines the (approximate) number of iterations of the key derivation algorithm that need
/// to be run to achieve a particular delay (or calculation time).
///
/// - Parameters:
/// - passwordLength: Password length in bytes
/// - saltLength: Salt length in bytes
/// - algorithm: The PseudoRandomAlgorithm to use
/// - derivedKeyLength: The desired key length
/// - msec: The desired calculation time
///
/// - Returns: The number of times the algorithm should be run
///
public class func calibrate(passwordLength: Int, saltLength: Int, algorithm: PseudoRandomAlgorithm, derivedKeyLength: Int, msec: UInt32) -> UInt {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
return UInt(CCCalibratePBKDF(CCPBKDFAlgorithm(kCCPBKDF2), passwordLength, saltLength, algorithm.nativeValue(), derivedKeyLength, msec))
#elseif os(Linux)
// Value as per RFC 2898.
return UInt(1000 * UInt(msec))
#endif
}
///
/// Derives key material from a password and salt.
///
/// - Parameters:
/// - password: The password string, will be converted using UTF8
/// - salt: The salt string will be converted using UTF8
/// - prf: The pseudo random function
/// - round: The number of rounds
/// - derivedKeyLength: The length of the desired derived key, in bytes.
///
/// - Returns: The derived key
///
public class func deriveKey(fromPassword password: String, salt: String, prf: PseudoRandomAlgorithm, rounds: uint, derivedKeyLength: UInt) throws -> [UInt8] {
var derivedKey = Array<UInt8>(repeating: 0, count:Int(derivedKeyLength))
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let status: Int32 = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), password, password.utf8.count, salt, salt.utf8.count, prf.nativeValue(), rounds, &derivedKey, derivedKey.count)
if status != Int32(kCCSuccess) {
throw CryptorError.fail(status, "ERROR: CCKeyDerivationPBDK failed with status \(status).")
}
#elseif os(Linux)
let status = PKCS5_PBKDF2_HMAC(password, Int32(password.utf8.count), salt, Int32(salt.utf8.count), Int32(rounds), .make(optional: prf.nativeValue()), Int32(derivedKey.count), &derivedKey)
if status != 1 {
let error = ERR_get_error()
throw CryptorError.fail(Int32(error), "ERROR: PKCS5_PBKDF2_HMAC failed, reason: \(errToString(ERR_error_string(error, nil)))")
}
#endif
return derivedKey
}
///
/// Derives key material from a password and salt.
///
/// - Parameters:
/// - password: The password string, will be converted using UTF8
/// - salt: The salt array of bytes
/// - prf: The pseudo random function
/// - round: The number of rounds
/// - derivedKeyLength: The length of the desired derived key, in bytes.
///
/// - Returns: The derived key
///
public class func deriveKey(fromPassword password: String, salt: [UInt8], prf: PseudoRandomAlgorithm, rounds: uint, derivedKeyLength: UInt) throws -> [UInt8] {
var derivedKey = Array<UInt8>(repeating: 0, count:Int(derivedKeyLength))
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let status: Int32 = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), password, password.utf8.count, salt, salt.count, prf.nativeValue(), rounds, &derivedKey, derivedKey.count)
if status != Int32(kCCSuccess) {
throw CryptorError.fail(status, "ERROR: CCKeyDerivationPBDK failed with status \(status).")
}
#elseif os(Linux)
let status = PKCS5_PBKDF2_HMAC(password, Int32(password.utf8.count), salt, Int32(salt.count), Int32(rounds), .make(optional: prf.nativeValue()), Int32(derivedKey.count), &derivedKey)
if status != 1 {
let error = ERR_get_error()
throw CryptorError.fail(Int32(error), "ERROR: PKCS5_PBKDF2_HMAC failed, reason: \(errToString(ERR_error_string(error, nil)))")
}
#endif
return derivedKey
}
///
/// Derives key material from a password buffer.
///
/// - Parameters:
/// - password: Pointer to the password buffer
/// - passwordLength: Password length in bytes
/// - salt: Pointer to the salt buffer
/// - saltLength: Salt length in bytes
/// - prf: The PseudoRandomAlgorithm to use
/// - rounds: The number of rounds of the algorithm to use
/// - derivedKey: Pointer to the derived key buffer.
/// - derivedKeyLength: The desired key length
///
/// - Returns: The number of times the algorithm should be run
///
public class func deriveKey(fromPassword password: UnsafePointer<Int8>, passwordLen: Int, salt: UnsafePointer<UInt8>, saltLen: Int, prf: PseudoRandomAlgorithm, rounds: uint, derivedKey: UnsafeMutablePointer<UInt8>, derivedKeyLen: Int) throws {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let status: Int32 = CCKeyDerivationPBKDF(CCPBKDFAlgorithm(kCCPBKDF2), password, passwordLen, salt, saltLen, prf.nativeValue(), rounds, derivedKey, derivedKeyLen)
if status != Int32(kCCSuccess) {
throw CryptorError.fail(status, "ERROR: CCKeyDerivationPBDK failed with status \(status).")
}
#elseif os(Linux)
let status = PKCS5_PBKDF2_HMAC(password, Int32(passwordLen), salt, Int32(saltLen), Int32(rounds), .make(optional: prf.nativeValue()), Int32(derivedKeyLen), derivedKey)
if status != 1 {
let error = ERR_get_error()
throw CryptorError.fail(Int32(error), "ERROR: PKCS5_PBKDF2_HMAC failed, reason: \(errToString(ERR_error_string(error, nil)))")
}
#endif
}
}
+106
View File
@@ -0,0 +1,106 @@
//
// Random.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import CommonCrypto
#elseif os(Linux)
import OpenSSL
#endif
public typealias RNGStatus = Status
///
/// Generates buffers of random bytes.
///
public class Random {
///
/// Wraps native call.
///
/// - Note: CCRNGStatus is typealiased to CCStatus but this routine can only return kCCSuccess or kCCRNGFailure
///
/// - Parameter bytes: A pointer to the buffer that will receive the bytes
///
/// - Returns: `.success` or `.rngFailure` as appropriate.
///
public class func generate(bytes: UnsafeMutablePointer<UInt8>, byteCount: Int) -> RNGStatus {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let statusCode = CCRandomGenerateBytes(bytes, byteCount)
guard let status = Status(rawValue: statusCode) else {
fatalError("CCRandomGenerateBytes returned unexpected status code: \(statusCode)")
}
return status
#elseif os(Linux)
let statusCode = RAND_bytes(bytes, Int32(byteCount))
if statusCode != 1 {
let errCode = ERR_get_error()
return Status.rngFailure(errCode)
}
return Status.success
#endif
}
///
/// Generates an array of random bytes.
///
/// - Parameter bytesCount: Number of random bytes to generate
///
/// - Returns: an array of random bytes
///
/// - Throws: `.success` or an `.rngFailure` on failure
///
public class func generate(byteCount: Int) throws -> [UInt8] {
guard byteCount > 0 else {
throw RNGStatus.paramError
}
var bytes = Array(repeating: UInt8(0), count:byteCount)
let status = generate(bytes: &bytes, byteCount: byteCount)
if status != .success {
throw status
}
return bytes
}
///
/// A version of generateBytes that always throws an error.
///
/// Use it to test that code handles this.
///
/// - Parameter bytesCount: Number of random bytes to generate
///
/// - Returns: An array of random bytes
///
public class func generateBytesThrow(byteCount: Int) throws -> [UInt8] {
if byteCount <= 0 {
fatalError("generate: byteCount must be positve and non-zero")
}
var bytes: [UInt8] = Array(repeating: UInt8(0), count:byteCount)
let status = generate(bytes: &bytes, byteCount: byteCount)
throw status
//return bytes
}
}
+100
View File
@@ -0,0 +1,100 @@
//===----------------------------------------------------------------------===//
//
// This source file is taken from SwiftNIO open source project
//
// Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors
// Licensed under Apache License v2.0
//
//===----------------------------------------------------------------------===//
// MARK:- Awful code begins here
// Hello dear reader. Let me explain what we're doing here.
//
// From OpenSSL 1.0 to OpenSSL 1.1 one of the major breaking changes was the so-called
// "great opaquifiying". Essentially, OpenSSL took all of its public structures and made
// them opaque, such that they cannot be introspected from client code. This is a great
// forward step, and brings them more in line with modern C library practices.
//
// However, it's an *enormous* inconvenience from Swift code. This is because the Swift
// translation of the C type `SSL_CTX *` changed from `UnsafeMutablePointer<SSL_CTX>` to
// `OpaquePointer`.
//
// This change exists for reasonable enough reasons in Swift land (see
// https://forums.swift.org/t/opaque-pointers-in-swift/6875 for a discussion), but
// nonetheless causes enormous problems in our codebase.
//
// Our cheap way out is to make everything an OpaquePointer, and then provide initializers
// between OpaquePointer and the typed pointers. This allows us to tolerate either pointer
// type in our Swift code by bridging them over to OpaquePointer and back, and lets the
// compiler worry about how exactly to make that work.
//
// Now, in fact, Swift already has initializers between the pointer types. What it does
// not have is self-initializers: the ability to create an `OpaquePointer` from an `OpaquePointer`,
// or an `UnsafePointer<T>` from an `UnsafePointer<T>`. We add those two initializers here.
// We also add a special "make" function that exists to handle the special case of optional pointer
// values, which we mostly encounter in the ALPN callbacks.
//
// The *downside* of this approach is that we totally break the pointer type system. It becomes
// trivially possible to alias a pointer of type T to type U through two calls to init. This
// is not a thing we want to widely promote. For this reason, these extensions are hidden in
// this file, where we can laugh and jeer at them and generally make them feel bad about
// themselves.
//
// Hopefully, in time, these extensions can be removed.
extension UnsafePointer {
init(_ ptr: UnsafePointer<Pointee>) {
self = ptr
}
static func make(optional ptr: UnsafePointer<Pointee>?) -> UnsafePointer<Pointee>? {
return ptr.map(UnsafePointer<Pointee>.init)
}
static func make(optional ptr: OpaquePointer?) -> UnsafePointer<Pointee>? {
return ptr.map(UnsafePointer<Pointee>.init)
}
}
extension UnsafeMutablePointer {
init(_ ptr: UnsafeMutableRawPointer) {
let x = UnsafeMutablePointer<Pointee>(bitPattern: UInt(bitPattern: ptr))!
self = x
}
static func make(optional ptr: UnsafeMutablePointer<Pointee>?) -> UnsafeMutablePointer<Pointee>? {
return ptr.map(UnsafeMutablePointer<Pointee>.init)
}
static func make(optional ptr: UnsafeMutableRawPointer?) -> UnsafeMutablePointer<Pointee>? {
return ptr.map(UnsafeMutablePointer<Pointee>.init)
}
static func make(optional ptr: OpaquePointer?) -> UnsafeMutablePointer<Pointee>? {
return ptr.map(UnsafeMutablePointer<Pointee>.init)
}
}
extension UnsafeMutableRawPointer {
static func make(optional ptr: OpaquePointer?) -> UnsafeMutableRawPointer? {
return ptr.map(UnsafeMutableRawPointer.init)
}
}
extension OpaquePointer {
init(_ ptr: OpaquePointer) {
self = ptr
}
static func make(optional ptr: OpaquePointer?) -> OpaquePointer? {
return ptr.map(OpaquePointer.init)
}
static func make(optional ptr: UnsafeMutableRawPointer?) -> OpaquePointer? {
return ptr.map(OpaquePointer.init)
}
static func make<Pointee>(optional ptr: UnsafeMutablePointer<Pointee>?) -> OpaquePointer? {
return ptr.map(OpaquePointer.init)
}
}
+288
View File
@@ -0,0 +1,288 @@
//
// Status.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import CommonCrypto
#elseif os(Linux)
import OpenSSL
#endif
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
///
/// Links the native CommonCryptoStatus enumeration to Swift versions.
///
public enum Status: CCCryptorStatus, Swift.Error, CustomStringConvertible {
/// Successful
case success
/// Parameter Error
case paramError
/// Buffer too Small
case bufferTooSmall
/// Memory Failure
case memoryFailure
/// Alignment Error
case alignmentError
/// Decode Error
case decodeError
/// Unimplemented
case unimplemented
/// Overflow
case overflow
/// Random Number Generator Err
case rngFailure
///
/// Converts this value to a native `CCCryptorStatus` value.
///
public func toRaw() -> CCCryptorStatus {
switch self {
case .success:
return CCCryptorStatus(kCCSuccess)
case .paramError:
return CCCryptorStatus(kCCParamError)
case .bufferTooSmall:
return CCCryptorStatus(kCCBufferTooSmall)
case .memoryFailure:
return CCCryptorStatus(kCCMemoryFailure)
case .alignmentError:
return CCCryptorStatus(kCCAlignmentError)
case .decodeError:
return CCCryptorStatus(kCCDecodeError)
case .unimplemented:
return CCCryptorStatus(kCCUnimplemented)
case .overflow:
return CCCryptorStatus(kCCOverflow)
case .rngFailure:
return CCCryptorStatus(kCCRNGFailure)
}
}
///
/// Human readable descriptions of the values. (Not needed in Swift 2.0?)
///
static let descriptions = [
success: "Success",
paramError: "ParamError",
bufferTooSmall: "BufferTooSmall",
memoryFailure: "MemoryFailure",
alignmentError: "AlignmentError",
decodeError: "DecodeError",
unimplemented: "Unimplemented",
overflow: "Overflow",
rngFailure: "RNGFailure"
]
///
/// Obtain human-readable string from enum value.
///
public var description: String {
return (Status.descriptions[self] != nil) ? Status.descriptions[self]! : ""
}
///
/// Create enum value from raw `CCCryptorStatus` value.
///
public static func fromRaw(status: CCCryptorStatus) -> Status? {
let from = [
kCCSuccess: success,
kCCParamError: paramError,
kCCBufferTooSmall: bufferTooSmall,
kCCMemoryFailure: memoryFailure,
kCCAlignmentError: alignmentError,
kCCDecodeError: decodeError,
kCCUnimplemented: unimplemented,
kCCOverflow: overflow,
kCCRNGFailure: rngFailure
]
return from[Int(status)]
}
}
#elseif os(Linux)
///
/// Error status
///
public enum Status: Swift.Error, CustomStringConvertible {
/// Success
case success
/// Unimplemented with reason
case unimplemented(String)
/// Not supported with reason
case notSupported(String)
/// Parameter Error
case paramError
/// Failure with error code
case fail(UInt)
/// Random Byte Generator Failure with error code
case rngFailure(UInt)
/// The error code itself
public var code: Int {
switch self {
case .success:
return 0
case .notSupported:
return -1
case .unimplemented:
return -2
case .paramError:
return -3
case .fail(let code):
return Int(code)
case .rngFailure(let code):
return Int(code)
}
}
///
/// Create enum value from raw `SSL error code` value.
///
public static func fromRaw(status: UInt) -> Status? {
return Status.fail(status)
}
///
/// Obtain human-readable string for the error code.
///
public var description: String {
switch self {
case .success:
return "No error"
case .notSupported(let reason):
return "Not supported: \(reason)"
case .unimplemented(let reason):
return "Not implemented: \(reason)"
case .paramError:
return "Invalid parameters passed"
case .fail(let errorCode):
return "ERROR: code: \(errorCode), reason: \(errToString(ERR_error_string(UInt(errorCode), nil)))"
case .rngFailure(let errorCode):
return "Random Byte Generator ERROR: code: \(errorCode), reason: \(errToString(ERR_error_string(UInt(errorCode), nil)))"
}
}
}
// MARK: Operators
func == (lhs: Status, rhs: Status) -> Bool {
return lhs.code == rhs.code
}
func != (lhs: Status, rhs: Status) -> Bool {
return lhs.code != rhs.code
}
#endif
///
/// CryptorError
/// Thrown in caaes where a _fatalError()_ is **NOT** appropriate.
///
public enum CryptorError: Swift.Error, CustomStringConvertible {
/// Success
case success
/// Invalid key size
case invalidKeySize
/// Invalid IV size
case invalidIVSizeOrLength
/// Fail with code and string
case fail(Int32, String)
/// The error code itself
public var errCode: Int32 {
switch self {
case .success:
return 0
case .invalidKeySize:
return -1
case .invalidIVSizeOrLength:
return -2
case .fail(let errCode, _):
return Int32(errCode)
}
}
/// Error Description
public var description: String {
switch self {
case .success:
return "Success"
case .invalidKeySize:
return "Invalid key size."
case .invalidIVSizeOrLength:
return "Invalid IV size or length."
case .fail(_, let reason):
return reason
}
}
}
+939
View File
@@ -0,0 +1,939 @@
//
// StreamCryptor.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import CommonCrypto
#elseif os(Linux)
import OpenSSL
#endif
///
/// Encrypts or decrypts return results as they become available.
///
/// - Note: The underlying cipher may be a block or a stream cipher.
///
/// Use for large files or network streams.
///
/// For small, in-memory buffers Cryptor may be easier to use.
///
public class StreamCryptor {
#if os(Linux)
//
// Key sizes
//
static let kCCKeySizeAES128 = 16
static let kCCKeySizeAES192 = 24
static let kCCKeySizeAES256 = 32
static let kCCKeySizeDES = 8
static let kCCKeySize3DES = 24
static let kCCKeySizeMinCAST = 5
static let kCCKeySizeMaxCAST = 16
static let kCCKeySizeMinRC2 = 1
static let kCCKeySizeMaxRC2 = 128
static let kCCKeySizeMinBlowfish = 8
static let kCCKeySizeMaxBlowfish = 56
//
// Block sizes
//
static let kCCBlockSizeAES128 = 16
static let kCCBlockSizeDES = 8
static let kCCBlockSize3DES = 8
static let kCCBlockSizeCAST = 8
static let kCCBlockSizeRC2 = 8
static let kCCBlockSizeBlowfish = 8
#endif
///
/// Enumerates Cryptor operations
///
public enum Operation {
/// Encrypting
case encrypt
/// Decrypting
case decrypt
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
/// Convert to native `CCOperation`
func nativeValue() -> CCOperation {
switch self {
case .encrypt:
return CCOperation(kCCEncrypt)
case .decrypt:
return CCOperation(kCCDecrypt)
}
}
#elseif os(Linux)
/// Convert to native value
func nativeValue() -> UInt32 {
switch self {
case .encrypt:
return 0
case .decrypt:
return 1
}
}
#endif
}
///
/// Enumerates valid key sizes.
///
public enum ValidKeySize {
case fixed(Int)
case discrete([Int])
case range(Int, Int)
///
/// Determines if a given `keySize` is valid for this algorithm.
///
/// - Parameter keySize: The size to test for validity.
///
/// - Returns: True if valid, false otherwise.
///
func isValidKeySize(keySize: Int) -> Bool {
switch self {
case .fixed(let fixed):
return (fixed == keySize)
case .range(let min, let max):
return ((keySize >= min) && (keySize <= max))
case .discrete(let values):
return values.contains(keySize)
}
}
///
/// Determines the next valid key size; that is, the first valid key size larger
/// than the given value.
///
/// - Parameter keySize: The size for which the `next` size is desired.
///
/// - Returns: Will return `nil` if the passed in `keySize` is greater than the max.
///
func paddedKeySize(keySize: Int) -> Int? {
switch self {
case .fixed(let fixed):
return (keySize <= fixed) ? fixed : nil
case .range(let min, let max):
return (keySize > max) ? nil : ((keySize < min) ? min : keySize)
case .discrete(let values):
return values.sorted().reduce(nil) { answer, current in
return answer ?? ((current >= keySize) ? current : nil)
}
}
}
}
///
/// Maps CommonCryptoOptions onto a Swift struct.
///
public struct Options: OptionSet {
public typealias RawValue = Int
public let rawValue: RawValue
/// Convert from a native value (i.e. `0`, `kCCOptionpkcs7Padding`, `kCCOptionECBMode`)
public init(rawValue: RawValue) {
self.rawValue = rawValue
}
/// Convert from a native value (i.e. `0`, `kCCOptionpkcs7Padding`, `kCCOptionECBMode`)
public init(_ rawValue: RawValue) {
self.init(rawValue: rawValue)
}
/// No options
public static let none = Options(rawValue: 0)
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
/// Use padding. Needed unless the input is a integral number of blocks long.
public static var pkcs7Padding = Options(rawValue:kCCOptionPKCS7Padding)
/// Electronic Code Book Mode. Don't use this.
public static var ecbMode = Options(rawValue:kCCOptionECBMode)
#elseif os(Linux)
/// Use padding. Needed unless the input is a integral number of blocks long.
public static var pkcs7Padding = Options(rawValue:0x0001)
/// Electronic Code Book Mode. Don't use this.
public static var ecbMode = Options(rawValue:0x0002)
#endif
}
///
/// Enumerates available algorithms
///
public enum Algorithm {
/// Advanced Encryption Standard
/// - Note: aes and aes128 are equivalent.
case aes, aes128, aes192, aes256
/// Data Encryption Standard
case des
/// Triple des
case tripleDes
/// cast
case cast
/// rc2
case rc2
/// blowfish
case blowfish
/// Blocksize, in bytes, of algorithm.
public var blockSize: Int {
switch self {
case .aes, .aes128, .aes192, .aes256:
return kCCBlockSizeAES128
case .des:
return kCCBlockSizeDES
case .tripleDes:
return kCCBlockSize3DES
case .cast:
return kCCBlockSizeCAST
case .rc2:
return kCCBlockSizeRC2
case .blowfish:
return kCCBlockSizeBlowfish
}
}
public var defaultKeySize: Int {
switch self {
case .aes, .aes128:
return kCCKeySizeAES128
case .aes192:
return kCCKeySizeAES192
case .aes256:
return kCCKeySizeAES256
case .des:
return kCCKeySizeDES
case .tripleDes:
return kCCKeySize3DES
case .cast:
return kCCKeySizeMinCAST
case .rc2:
return kCCKeySizeMinRC2
case .blowfish:
return kCCKeySizeMinBlowfish
}
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
/// Native, CommonCrypto constant for algorithm.
func nativeValue() -> CCAlgorithm {
switch self {
case .aes, .aes128, .aes192, .aes256:
return CCAlgorithm(kCCAlgorithmAES)
case .des:
return CCAlgorithm(kCCAlgorithmDES)
case .tripleDes:
return CCAlgorithm(kCCAlgorithm3DES)
case .cast:
return CCAlgorithm(kCCAlgorithmCAST)
case .rc2:
return CCAlgorithm(kCCAlgorithmRC2)
case .blowfish:
return CCAlgorithm(kCCAlgorithmBlowfish)
}
}
#elseif os(Linux)
/// Native, OpenSSL function for algorithm.
func nativeValue(options: Options) -> OpaquePointer? {
if options == .pkcs7Padding || options == .none {
switch self {
case .aes, .aes128:
return .init(EVP_aes_128_cbc())
case .aes192:
return .init(EVP_aes_192_cbc())
case .aes256:
return .init(EVP_aes_256_cbc())
case .des:
return .init(EVP_des_cbc())
case .tripleDes:
return .init(EVP_des_ede3_cbc())
case .cast:
return .init(EVP_cast5_cbc())
case .rc2:
return .init(EVP_rc2_cbc())
case .blowfish:
return .init(EVP_bf_cbc())
}
}
if options == .ecbMode {
switch self {
case .aes, .aes128:
return .init(EVP_aes_128_ecb())
case .aes192:
return .init(EVP_aes_192_ecb())
case .aes256:
return .init(EVP_aes_256_ecb())
case .des:
return .init(EVP_des_ecb())
case .tripleDes:
return .init(EVP_des_ede3_ecb())
case .cast:
return .init(EVP_cast5_ecb())
case .rc2:
return .init(EVP_rc2_ecb())
case .blowfish:
return .init(EVP_bf_ecb())
}
}
fatalError("Unsupported options and/or algorithm.")
}
#endif
///
/// Determines the valid key size for this algorithm
///
/// - Returns: Valid key size for this algorithm.
///
func validKeySize() -> ValidKeySize {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
switch self {
case .aes, .aes128, .aes192, .aes256:
return .discrete([kCCKeySizeAES128, kCCKeySizeAES192, kCCKeySizeAES256])
case .des:
return .fixed(kCCKeySizeDES)
case .tripleDes:
return .fixed(kCCKeySize3DES)
case .cast:
return .range(kCCKeySizeMinCAST, kCCKeySizeMaxCAST)
case .rc2:
return .range(kCCKeySizeMinRC2, kCCKeySizeMaxRC2)
case .blowfish:
return .range(kCCKeySizeMinBlowfish, kCCKeySizeMaxBlowfish)
}
#elseif os(Linux)
switch self {
case .aes, .aes128:
return .fixed(kCCKeySizeAES128)
case .aes192:
return .fixed(kCCKeySizeAES192)
case .aes256:
return .fixed(kCCKeySizeAES256)
case .des:
return .fixed(kCCKeySizeDES)
case .tripleDes:
return .fixed(kCCKeySize3DES)
case .cast:
return .range(kCCKeySizeMinCAST, kCCKeySizeMaxCAST)
case .rc2:
return .range(kCCKeySizeMinRC2, kCCKeySizeMaxRC2)
case .blowfish:
return .range(kCCKeySizeMinBlowfish, kCCKeySizeMaxBlowfish)
}
#endif
}
///
/// Tests if a given keySize is valid for this algorithm
///
/// - Parameter keySize: The key size to be validated.
///
/// - Returns: True if valid, false otherwise.
///
func isValidKeySize(keySize: Int) -> Bool {
return self.validKeySize().isValidKeySize(keySize: keySize)
}
///
/// Calculates the next, if any, valid keySize greater or equal to a given `keySize` for this algorithm
///
/// - Parameter keySize: Key size for which the next size is requested.
///
/// - Returns: Next key size or nil
///
func paddedKeySize(keySize: Int) -> Int? {
return self.validKeySize().paddedKeySize(keySize: keySize)
}
}
///
/// The status code resulting from the last method call to this Cryptor.
/// Used to get additional information when optional chaining collapes.
///
public internal(set) var status: Status = .success
///
/// Context obtained. True if we have it, false otherwise.
///
private var haveContext: Bool = false
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
/// CommonCrypto Context
private var context = UnsafeMutablePointer<CCCryptorRef?>.allocate(capacity: 1)
#elseif os(Linux)
/// OpenSSL Cipher Context
private let context: OpaquePointer? = .init(EVP_CIPHER_CTX_new())
/// Operation
private var operation: Operation = .encrypt
/// The algorithm
private var algorithm: Algorithm
#endif
// MARK: Lifecycle Methods
///
/// Default Initializer
///
/// - Parameters:
/// - operation: The operation to perform see Operation (Encrypt, Decrypt)
/// - algorithm: The algorithm to use see Algorithm (AES, des, tripleDes, cast, rc2, blowfish)
/// - keyBuffer: Pointer to key buffer
/// - keyByteCount: Number of bytes in the key
/// - ivBuffer: Initialization vector buffer
/// - ivLength: Length of the ivBuffer
///
/// - Returns: New StreamCryptor instance.
///
public init(operation: Operation, algorithm: Algorithm, options: Options, keyBuffer: [UInt8], keyByteCount: Int, ivBuffer: UnsafePointer<UInt8>, ivLength: Int = 0) throws {
guard algorithm.isValidKeySize(keySize: keyByteCount) else {
throw CryptorError.invalidKeySize
}
guard options.contains(.ecbMode) || ivLength == algorithm.blockSize else {
throw CryptorError.invalidIVSizeOrLength
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let rawStatus = CCCryptorCreate(operation.nativeValue(), algorithm.nativeValue(), CCOptions(options.rawValue), keyBuffer, keyByteCount, ivBuffer, self.context)
if let status = Status.fromRaw(status: rawStatus) {
self.status = status
} else {
throw CryptorError.fail(rawStatus, "Cryptor init returned unexpected status.")
}
self.haveContext = true
#elseif os(Linux)
self.algorithm = algorithm
self.operation = operation
var rawStatus: Int32
switch self.operation {
case .encrypt:
rawStatus = EVP_EncryptInit_ex(.make(optional: self.context), .make(optional: algorithm.nativeValue(options: options)), nil, keyBuffer, ivBuffer)
case .decrypt:
rawStatus = EVP_DecryptInit_ex(.make(optional: self.context), .make(optional: algorithm.nativeValue(options: options)), nil, keyBuffer, ivBuffer)
}
if rawStatus == 0 {
let errorCode = ERR_get_error()
if let status = Status.fromRaw(status: errorCode) {
self.status = status
} else {
throw CryptorError.fail(Int32(errorCode), "Cryptor init returned unexpected status.")
}
}
self.haveContext = true
// Default to no padding...
var needPadding: Int32 = 0
if options == .pkcs7Padding {
needPadding = 1
}
// Note: This call must be AFTER the init call above...
EVP_CIPHER_CTX_set_padding(.make(optional: self.context), needPadding)
self.status = Status.success
#endif
}
///
/// Creates a new StreamCryptor
///
/// - Parameters:
/// - operation: The operation to perform see Operation (Encrypt, Decrypt)
/// - algorithm: The algorithm to use see Algorithm (AES, des, tripleDes, cast, rc2, blowfish)
/// - key: A byte array containing key data
/// - iv: A byte array containing initialization vector
///
/// - Returns: New StreamCryptor instance.
///
public convenience init(operation: Operation, algorithm: Algorithm, options: Options, key: [UInt8], iv: [UInt8]) throws {
guard let paddedKeySize = algorithm.paddedKeySize(keySize: key.count) else {
throw CryptorError.invalidKeySize
}
try self.init(operation:operation,
algorithm:algorithm,
options:options,
keyBuffer:CryptoUtils.zeroPad(byteArray:key, blockSize: paddedKeySize),
keyByteCount:paddedKeySize,
ivBuffer:iv,
ivLength:iv.count)
}
///
/// Creates a new StreamCryptor
///
/// - Parameters:
/// - operation: The operation to perform see Operation (Encrypt, Decrypt)
/// - algorithm: The algorithm to use see Algorithm (AES, des, tripleDes, cast, rc2, blowfish)
/// - key: A string containing key data (will be interpreted as UTF8)
/// - iv: A string containing initialization vector data (will be interpreted as UTF8)
///
/// - Returns: New StreamCryptor instance.
///
public convenience init(operation: Operation, algorithm: Algorithm, options: Options, key: String, iv: String) throws {
let keySize = key.utf8.count
guard let paddedKeySize = algorithm.paddedKeySize(keySize: keySize) else {
throw CryptorError.invalidKeySize
}
try self.init(operation:operation,
algorithm:algorithm,
options:options,
keyBuffer:CryptoUtils.zeroPad(string: key, blockSize: paddedKeySize),
keyByteCount:paddedKeySize,
ivBuffer:iv,
ivLength:iv.utf8.count)
}
///
/// Cleanup
///
deinit {
// Ensure we've got a context before attempting to get rid of it...
if self.haveContext == false {
return
}
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
// Ensure we've got a context before attempting to get rid of it...
if self.context.pointee == nil {
return
}
let rawStatus = CCCryptorRelease(self.context.pointee)
if let status = Status.fromRaw(status: rawStatus) {
if status != .success {
NSLog("WARNING: CCCryptoRelease failed with status \(rawStatus).")
}
} else {
fatalError("CCCryptorUpdate returned unexpected status.")
}
#if swift(>=4.1)
context.deallocate()
#else
context.deallocate(capacity: 1)
#endif
self.haveContext = false
#elseif os(Linux)
EVP_CIPHER_CTX_free(.make(optional: self.context))
self.haveContext = false
#endif
}
// MARK: Public Methods
///
/// Add the contents of an Data buffer to the current encryption/decryption operation.
///
/// - Parameters:
/// - dataIn: The input data
/// - byteArrayOut: Output data
///
/// - Returns: A tuple containing the number of output bytes produced and the status (see Status)
///
public func update(dataIn: Data, byteArrayOut: inout [UInt8]) -> (Int, Status) {
let dataOutAvailable = byteArrayOut.count
var dataOutMoved = 0
#if swift(>=5.0)
dataIn.withUnsafeBytes() {
_ = update(bufferIn: $0.baseAddress!, byteCountIn: dataIn.count, bufferOut: &byteArrayOut, byteCapacityOut: dataOutAvailable, byteCountOut: &dataOutMoved)
}
#else
dataIn.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
_ = update(bufferIn: buffer, byteCountIn: dataIn.count, bufferOut: &byteArrayOut, byteCapacityOut: dataOutAvailable, byteCountOut: &dataOutMoved)
}
#endif
return (dataOutMoved, self.status)
}
///
/// Add the contents of an NSData buffer to the current encryption/decryption operation.
///
/// - Parameters:
/// - dataIn: The input data
/// - byteArrayOut: Output data
///
/// - Returns: A tuple containing the number of output bytes produced and the status (see Status)
///
public func update(dataIn: NSData, byteArrayOut: inout [UInt8]) -> (Int, Status) {
let dataOutAvailable = byteArrayOut.count
var dataOutMoved = 0
var ptr = dataIn.bytes.assumingMemoryBound(to: UInt8.self).pointee
_ = update(bufferIn: &ptr, byteCountIn: dataIn.length, bufferOut: &byteArrayOut, byteCapacityOut: dataOutAvailable, byteCountOut: &dataOutMoved)
return (dataOutMoved, self.status)
}
///
/// Add the contents of a byte array to the current encryption/decryption operation.
///
/// - Parameters:
/// - byteArrayIn: The input data
/// - byteArrayOut: Output data
///
/// - Returns: A tuple containing the number of output bytes produced and the status (see Status)
///
public func update(byteArrayIn: [UInt8], byteArrayOut: inout [UInt8]) -> (Int, Status) {
let dataOutAvailable = byteArrayOut.count
var dataOutMoved = 0
_ = update(bufferIn: byteArrayIn, byteCountIn: byteArrayIn.count, bufferOut: &byteArrayOut, byteCapacityOut: dataOutAvailable, byteCountOut: &dataOutMoved)
return (dataOutMoved, self.status)
}
///
/// Add the contents of a string (interpreted as UTF8) to the current encryption/decryption operation.
///
/// - Parameters:
/// - byteArrayIn: The input data
/// - byteArrayOut: Output data
///
/// - Returns: A tuple containing the number of output bytes produced and the status (see Status)
///
public func update(stringIn: String, byteArrayOut: inout [UInt8]) -> (Int, Status) {
let dataOutAvailable = byteArrayOut.count
var dataOutMoved = 0
_ = update(bufferIn: stringIn, byteCountIn: stringIn.utf8.count, bufferOut: &byteArrayOut, byteCapacityOut: dataOutAvailable, byteCountOut: &dataOutMoved)
return (dataOutMoved, self.status)
}
///
/// Retrieves all remaining encrypted or decrypted data from this cryptor.
///
/// - Note: If the underlying algorithm is an block cipher and the padding option has
/// not been specified and the cumulative input to the cryptor has not been an integral
/// multiple of the block length this will fail with an alignment error.
///
/// - Note: This method updates the status property
///
/// - Parameter byteArrayOut: The output bffer
///
/// - Returns: a tuple containing the number of output bytes produced and the status (see Status)
///
public func final(byteArrayOut: inout [UInt8]) -> (Int, Status) {
let dataOutAvailable = byteArrayOut.count
var dataOutMoved = 0
_ = final(bufferOut: &byteArrayOut, byteCapacityOut: dataOutAvailable, byteCountOut: &dataOutMoved)
return (dataOutMoved, self.status)
}
// MARK: - Low-level interface
///
/// Update the buffer
///
/// - Parameters:
/// - bufferIn: Pointer to input buffer
/// - inByteCount: Number of bytes contained in input buffer
/// - bufferOut: Pointer to output buffer
/// - outByteCapacity: Capacity of the output buffer in bytes
/// - outByteCount: On successful completion, the number of bytes written to the output buffer
///
/// - Returns: Status of the update
///
public func update(bufferIn: UnsafeRawPointer, byteCountIn: Int, bufferOut: UnsafeMutablePointer<UInt8>, byteCapacityOut: Int, byteCountOut: inout Int) -> Status {
if self.status == .success {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let rawStatus = CCCryptorUpdate(self.context.pointee, bufferIn, byteCountIn, bufferOut, byteCapacityOut, &byteCountOut)
if let status = Status.fromRaw(status: rawStatus) {
self.status = status
} else {
fatalError("CCCryptorUpdate returned unexpected status.")
}
#elseif os(Linux)
var rawStatus: Int32
var outLength: Int32 = 0
switch self.operation {
case .encrypt:
rawStatus = EVP_EncryptUpdate(.make(optional: self.context), bufferOut, &outLength, bufferIn.assumingMemoryBound(to: UInt8.self), Int32(byteCountIn))
case .decrypt:
rawStatus = EVP_DecryptUpdate(.make(optional: self.context), bufferOut, &outLength, bufferIn.assumingMemoryBound(to: UInt8.self), Int32(byteCountIn))
}
byteCountOut = Int(outLength)
if rawStatus == 0 {
let errorCode = ERR_get_error()
if let status = Status.fromRaw(status: errorCode) {
self.status = status
} else {
fatalError("Cryptor update returned unexpected status.")
}
} else {
self.status = Status.success
}
#endif
}
return self.status
}
///
/// Retrieves all remaining encrypted or decrypted data from this cryptor.
///
/// - Note: If the underlying algorithm is an block cipher and the padding option has
/// not been specified and the cumulative input to the cryptor has not been an integral
/// multiple of the block length this will fail with an alignment error.
///
/// - Note: This method updates the status property
///
/// - Parameters:
/// - bufferOut: Pointer to output buffer
/// - outByteCapacity: Capacity of the output buffer in bytes
/// - outByteCount: On successful completion, the number of bytes written to the output buffer
///
/// - Returns: Status of the update
///
public func final(bufferOut: UnsafeMutablePointer<UInt8>, byteCapacityOut: Int, byteCountOut: inout Int) -> Status {
if self.status == Status.success {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
let rawStatus = CCCryptorFinal(self.context.pointee, bufferOut, byteCapacityOut, &byteCountOut)
if let status = Status.fromRaw(status: rawStatus) {
self.status = status
} else {
fatalError("CCCryptorUpdate returned unexpected status.")
}
#elseif os(Linux)
var rawStatus: Int32
var outLength: Int32 = Int32(byteCapacityOut)
switch self.operation {
case .encrypt:
rawStatus = EVP_EncryptFinal_ex(.make(optional: self.context), bufferOut, &outLength)
case .decrypt:
rawStatus = EVP_DecryptFinal_ex(.make(optional: self.context), bufferOut, &outLength)
}
byteCountOut = Int(outLength)
if rawStatus == 0 {
let errorCode = ERR_get_error()
if let status = Status.fromRaw(status: errorCode) {
self.status = status
} else {
fatalError("Cryptor final returned unexpected status.")
}
} else {
self.status = Status.success
}
#endif
}
return self.status
}
///
/// Determines the number of bytes that will be output by this Cryptor if inputBytes of additional
/// data is input.
///
/// - Parameters:
/// - inputByteCount: Number of bytes that will be input.
/// - isFinal: True if buffer to be input will be the last input buffer, false otherwise.
///
/// - Returns: The final output length
///
public func getOutputLength(inputByteCount: Int, isFinal: Bool = false) -> Int {
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
return CCCryptorGetOutputLength(self.context.pointee, inputByteCount, isFinal)
#elseif os(Linux)
if inputByteCount == 0 {
return self.algorithm.blockSize
}
return (inputByteCount + self.algorithm.blockSize - (inputByteCount % self.algorithm.blockSize))
#endif
}
}
+107
View File
@@ -0,0 +1,107 @@
//
// Updateable.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
///
/// A protocol for calculations that can be updated with incremental data buffers.
///
public protocol Updatable {
/// Status of the calculation.
var status: Status { get }
///
/// Low-level update routine.
/// Updates the calculation with the contents of a data buffer.
///
/// - Parameters:
/// - buffer: Pointer to the data buffer
/// - byteCount: Length of the buffer in bytes
///
/// - Returns: `Self` if no error for optional chaining, nil otherwise
///
func update(from buffer: UnsafeRawPointer, byteCount: size_t) -> Self?
}
///
/// Factors out common update code from Digest, HMAC and Cryptor.
///
extension Updatable {
///
/// Updates the current calculation with data contained in an `NSData` object.
///
/// - Parameter data: The `NSData` object
///
/// - Returns: Optional `Self` or nil
///
public func update(data: NSData) -> Self? {
_ = update(from: data.bytes, byteCount: size_t(data.length))
return self.status == .success ? self : nil
}
///
/// Updates the current calculation with data contained in an `Data` object.
///
/// - Parameters data: The `Data` object
///
/// - Returns: Optional `Self` or nil
///
public func update(data: Data) -> Self? {
#if swift(>=5.0)
_ = data.withUnsafeBytes() {
_ = update(from: $0.baseAddress!, byteCount: size_t(data.count))
}
#else
_ = data.withUnsafeBytes() { (buffer: UnsafePointer<UInt8>) in
_ = update(from: buffer, byteCount: size_t(data.count))
}
#endif
return self.status == .success ? self : nil
}
///
/// Updates the current calculation with data contained in a byte array.
///
/// - Parameter byteArray: The byte array
///
/// - Returns: Optional `Self` or nil
///
public func update(byteArray: [UInt8]) -> Self? {
_ = update(from: byteArray, byteCount: size_t(byteArray.count))
return self.status == .success ? self : nil
}
///
/// Updates the current calculation with data contained in a String.
/// The corresponding data will be generated using UTF8 encoding.
///
/// - Parameter string: The string of data
///
/// - Returns: Optional `Self` or nil
///
public func update(string: String) -> Self? {
_ = update(from: string, byteCount: size_t(string.utf8.count))
return self.status == .success ? self : nil
}
}
+262
View File
@@ -0,0 +1,262 @@
//
// Utilities.swift
// Cryptor
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
import Foundation
//
// Replaces Swift's native `fatalError` function to allow redirection
// For more details about how this all works see:
// https://marcosantadev.com/test-swift-fatalerror/
//
func fatalError(_ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) -> Never {
FatalErrorUtil.fatalErrorClosure(message(), file, line)
}
// Convert an UnsafeMutablePointer<Int8>? to a String, providing a
// default value of empty string if the pointer is nil.
//
// - Parameter ptr: Pointer to string to be converted.
//
// - Returns: Converted string.
//
func errToString(_ ptr: UnsafeMutablePointer<Int8>?) -> String {
if let ptr = ptr {
return String(cString: ptr)
} else {
return ""
}
}
///
/// Allows redirection of `fatalError` for Unit Testing or for
/// library users that want to handle such errors in another way.
///
struct FatalErrorUtil {
static var fatalErrorClosure: (String, StaticString, UInt) -> Never = defaultFatalErrorClosure
private static let defaultFatalErrorClosure = { Swift.fatalError($0, file: $1, line: $2) }
static func replaceFatalError(closure: @escaping (String, StaticString, UInt) -> Never) {
fatalErrorClosure = closure
}
static func restoreFatalError() {
fatalErrorClosure = defaultFatalErrorClosure
}
}
///
/// Various utility functions for conversions
///
public struct CryptoUtils {
///
/// Converts a single hexadecimal digit encoded as a Unicode Scalar to it's corresponding value.
///
/// - Parameter digit: A Unicode scalar in the set 0..9a..fA..F
///
/// - Returns: The hexadecimal value of the digit
///
static func convert(hexDigit digit: UnicodeScalar) -> UInt8? {
switch digit {
case UnicodeScalar(unicodeScalarLiteral:"0")...UnicodeScalar(unicodeScalarLiteral:"9"):
return UInt8(digit.value - UnicodeScalar(unicodeScalarLiteral:"0").value)
case UnicodeScalar(unicodeScalarLiteral:"a")...UnicodeScalar(unicodeScalarLiteral:"f"):
return UInt8(digit.value - UnicodeScalar(unicodeScalarLiteral:"a").value + 0xa)
case UnicodeScalar(unicodeScalarLiteral:"A")...UnicodeScalar(unicodeScalarLiteral:"F"):
return UInt8(digit.value - UnicodeScalar(unicodeScalarLiteral:"A").value + 0xa)
default:
return nil
}
}
///
/// Converts a string of hexadecimal digits to a byte array.
///
/// - Parameter string: The hex string (must contain an even number of digits)
///
/// - Returns: A byte array or [] if the input is not valid hexadecimal
///
public static func byteArray(fromHex string: String) -> [UInt8] {
var iterator = string.unicodeScalars.makeIterator()
var byteArray: [UInt8] = []
while let msn = iterator.next() {
if let lsn = iterator.next(),
let hexMSN = convert(hexDigit: msn) ,
let hexLSN = convert(hexDigit: lsn) {
byteArray += [ (hexMSN << 4 | hexLSN) ]
} else {
// @TODO: In the next major release this function should throw instead of returning an empty array.
return []
}
}
return byteArray
}
///
/// Converts a UTF-8 String to a byte array.
///
/// - Parameter string: the string
///
/// - Returns: A byte array
///
public static func byteArray(from string: String) -> [UInt8] {
let array = [UInt8](string.utf8)
return array
}
///
/// Converts a string of hexadecimal digits to an `NSData` object.
///
/// - Parameter string: The hex string (must contain an even number of digits)
///
/// - Returns: An `NSData` object
///
public static func data(fromHex string: String) -> NSData {
let a = byteArray(fromHex: string)
return NSData(bytes:a, length:a.count)
}
///
/// Converts a string of hexadecimal digits to an `Data` object.
///
/// - Parameter string: The hex string (must contain an even number of digits)
///
/// - Returns: An `Data` object
///
public static func data(fromHex string: String) -> Data {
let a = byteArray(fromHex: string)
return Data(bytes: a, count: a.count)
}
///
/// Converts a byte array to an `NSData` object.
///
/// - Parameter byteArray: The byte array
///
/// - Returns: An `NSData` object
///
public static func data(from byteArray: [UInt8]) -> NSData {
return NSData(bytes:byteArray, length:byteArray.count)
}
///
/// Converts a byte array to an `Data` object.
///
/// - Parameter byteArray: The byte array
///
/// - Returns: An `Data` object
///
public static func data(from byteArray: [UInt8]) -> Data {
return Data(bytes: byteArray, count: byteArray.count)
}
///
/// Converts a byte array to a string of hexadecimal digits.
///
/// - Parameters:
/// - byteArray: The Swift array
/// - uppercase: True to use uppercase for letter digits, lowercase otherwise
///
/// - Returns: A String
///
public static func hexString(from byteArray: [UInt8], uppercase: Bool = false) -> String {
return byteArray.map() { String(format: (uppercase) ? "%02X" : "%02x", $0) }.reduce("", +)
}
///
/// Converts a Swift array to an `NSString` object.
///
/// - Parameters:
/// - byteArray: The Swift array
/// - uppercase: True to use uppercase for letter digits, lowercase otherwise
///
/// - Returns: An `NSString` object
///
public static func hexNSString(from byteArray: [UInt8], uppercase: Bool = false) -> NSString {
let formatString = (uppercase) ? "%02X" : "%02x"
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
return byteArray.map() { String(format: formatString, $0) }.reduce("", +) as NSString
#else
let aString = byteArray.map() { String(format: formatString, $0) }.reduce("", +)
return NSString(string: aString)
#endif
}
///
/// Converts a byte array to a String containing a comma separated list of bytes.
/// This is used to generate test data programmatically.
///
/// - Parameter byteArray: The byte array
///
/// - Returns: A String
///
public static func hexList(from byteArray: [UInt8]) -> String {
return byteArray.map() { String(format:"0x%02x, ", $0) }.reduce("", +)
}
///
/// Zero pads a byte array such that it is an integral number of `blockSizeinBytes` long.
///
/// - Parameters:
/// - byteArray: The byte array
/// - blockSizeInBytes: The block size in bytes.
///
/// - Returns: A Swift string
///
public static func zeroPad(byteArray: [UInt8], blockSize: Int) -> [UInt8] {
let pad = blockSize - (byteArray.count % blockSize)
guard pad != 0 else {
return byteArray
}
return byteArray + Array<UInt8>(repeating: 0, count: pad)
}
///
/// Zero pads a String (after UTF8 conversion) such that it is an integral number of `blockSizeinBytes` long.
///
/// - Parameters:
/// - string: The String
/// - blockSizeInBytes: The block size in bytes
///
/// - Returns: A byte array
///
public static func zeroPad(string: String, blockSize: Int) -> [UInt8] {
return zeroPad(byteArray: Array<UInt8>(string.utf8), blockSize: blockSize)
}
}