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
Pods/BlueECC/LICENSE.txt generated Normal file
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

204
Pods/BlueECC/README.md generated Normal file
View File

@ -0,0 +1,204 @@
<p align="center">
<a href="http://kitura.io/">
<img src="https://raw.githubusercontent.com/IBM-Swift/Kitura/master/Sources/Kitura/resources/kitura-bird.svg?sanitize=true" height="100" alt="Kitura">
</a>
</p>
<p align="center">
<a href="https://ibm-swift.github.io/BlueECC/index.html">
<img src="https://img.shields.io/badge/apidoc-BlueECC-1FBCE4.svg?style=flat" alt="APIDoc">
</a>
<a href="https://travis-ci.org/IBM-Swift/BlueECC">
<img src="https://travis-ci.org/IBM-Swift/BlueECC.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-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>
# BlueECC
A cross platform Swift implementation of Elliptic Curve Digital Signature Algorithm (ECDSA) and Elliptic Curve Integrated Encryption Scheme (ECIES). This allows you to sign, verify, encrypt and decrypt using elliptic curve keys.
## Swift version
The latest version of BlueECC requires **Swift 4.1** or later. You can download this version of the Swift binaries by following this [link](https://swift.org/download/). Compatibility with other Swift versions is not guaranteed.
## Usage
#### Add dependencies
Add the `BlueECC` package to the dependencies within your applications `Package.swift` file. Substitute `"x.x.x"` with the latest `BlueECC` [release](https://github.com/IBM-Swift/BlueECC/releases).
```swift
.package(url: "https://github.com/IBM-Swift/BlueECC.git", from: "x.x.x")
```
Add `CryptorECC` to your target's dependencies:
```swift
.target(name: "example", dependencies: ["CryptorECC"]),
```
#### Import package
```swift
import CryptorECC
```
### Getting Started
#### Elliptic curve private key
you can generate an ECPrivate key using BlueECC.
```swift
let p256PrivateKey = try ECPrivateKey.make(for: .prime256v1)
```
You can then view the key in it's PEM format as follows:
```swift
let privateKeyPEM = p256PrivateKey.pemString
```
The following curves are supported:
- prime256v1
- secp384r1
- secp521r1
Alternatively, you may generate private key using a third party provider:
- You can generate a `p-256` private key as a `.p8` file for Apple services from [https://developer.apple.com/account/ios/authkey](https://developer.apple.com/account/ios/authkey/). This will produce a key that should be formatted as follows:
```swift
let privateKey =
"""
-----BEGIN PRIVATE KEY-----
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQglf7ztYnsaHX2yiHJ
meHFl5dg05y4a/hD7wwuB7hSRpmhRANCAASKRzmboLbG0NZ54B5PXxYSU7fvO8U7
PyniQCWG+Agc3bdcgKU0RKApWYuBJKrZqyqLB2tTlgdtwcWSB0AEzVI8
-----END PRIVATE KEY-----
"""
```
- You can use OpenSSL [Command Line Elliptic Curve Operations](https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations).
The following commands generate private keys for the three supported curves as `.pem` files:
```
// p-256
$ openssl ecparam -name prime256v1 -genkey -noout -out key.pem
// p-384
$ openssl ecparam -name secp384r1 -genkey -noout -out key.pem
// p-521
$ openssl ecparam -name secp521r1 -genkey -noout -out key.pem
```
These keys will be formatted as follows:
```swift
let privateKey =
"""
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJX+87WJ7Gh19sohyZnhxZeXYNOcuGv4Q+8MLge4UkaZoAoGCCqGSM49
AwEHoUQDQgAEikc5m6C2xtDWeeAeT18WElO37zvFOz8p4kAlhvgIHN23XIClNESg
KVmLgSSq2asqiwdrU5YHbcHFkgdABM1SPA==
-----END EC PRIVATE KEY-----
"""
```
The key string can then be used to initialize an `ECPrivateKey` instance:
```swift
let eccPrivateKey = try ECPrivateKey(key: privateKey)
```
#### Elliptic curve public key
You can use OpenSSL to generate an elliptic curve public key `.pem` file from any of the above elliptic curve private key files:
```
$ openssl ec -in key.pem -pubout -out public.pem
```
This will produce a public key formatted as follows:
```swift
let publicKey =
"""
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEikc5m6C2xtDWeeAeT18WElO37zvF
Oz8p4kAlhvgIHN23XIClNESgKVmLgSSq2asqiwdrU5YHbcHFkgdABM1SPA==
-----END PUBLIC KEY-----
"""
```
These keys can then be used to initialize an `ECPrivateKey` instance:
```swift
let eccPublicKey = try ECPublicKey(key: publicKey)
```
Alternatively, you can extract the public key from your `ECPrivateKey`:
```swift
let eccPublicKey = try eccPrivateKey.extractPublicKey()
print(eccPublicKey.pemString)
```
#### Signing String or Data
BlueECC extends `String` and `Data` so you can call sign directly on your plaintext using an EC private key. This creates an `ECSignature` containing the r and s signature values:
```swift
let message = "hello world"
let signature = try message.sign(with: eccPrivateKey)
```
#### Verifying the signature
Use the public key to verify the signature for the plaintext:
```swift
let verified = signature.verify(plaintext: message, using: eccPublicKey)
if verified {
print("Signature is valid for provided plaintext")
}
```
#### Encrypting String or Data
Use the public key to encrypt your plaintext String or Data to encrypted Data or an encrypted Base64Encoded String:
```swift
let encryptedData = try "Hello World".encrypt(with: eccPublicKey)
print(encryptedData.base64EncodedString())
```
#### Decrypting to plaintext
Use the private key to decrypt the encrypted Data or Base64Encoded String to plaintext Data or UTF8 String:
```swift
let decryptedData = try encryptedData.decrypt(with: eccPrivateKey)
print(String(data: decryptedData, encoding: .utf8))
```
#### Encryption interoperability
Cross platform encryption and decryption is currently only supported with `prime256v1` curves. The `secp384r1` and `secp521r1` curves do not support Linux encryption with Apple platform decryption and vice versa.
If you would like to interoperate with this repo,
The following describes the encryption process:
- Generate an ephemeral EC key pair
- Use ECDH of your EC pair to generate a symmetric key
- Use SHA256 ANSI x9.63 Key Derivation Function with the ephemeral public key to generate a 32 byte key
- Use the first 16 bytes as an AES-GCM key
- Use the second 16 bytes as the initialization vector (IV)
- Use aes_128_gcm to encrypt the plaintext and generate a 16 byte GCM tag
- Send the ephemeral public key, encrypted data and GCM tag
This is equivalent to: `kSecKeyAlgorithmECIESEncryptionStandardVariableIVX963SHA256AESGCM` when using apple security.
## API Documentation
For more information visit our [API reference](https://ibm-swift.github.io/BlueECC/index.html).
## 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/BlueECC/blob/master/LICENSE.txt).

View File

@ -0,0 +1,92 @@
/**
* Copyright IBM Corporation 2019
*
* 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
// Private and public keys are stores in ASN1 format.
// The following code is used to parse the data and retrieve the required elements.
struct ASN1 {
indirect enum ASN1Element {
case seq(elements: [ASN1Element])
case integer(int: Int)
case bytes(data: Data)
case constructed(tag: Int, elem: ASN1Element)
case unknown
}
static func toASN1Element(data: Data) -> (ASN1Element, Int) {
guard data.count >= 2 else {
// format error
return (.unknown, data.count)
}
switch data[0] {
case 0x30: // sequence
let (length, lengthOfLength) = readLength(data: data.advanced(by: 1))
var result: [ASN1Element] = []
var subdata = data.advanced(by: 1 + lengthOfLength)
var alreadyRead = 0
while alreadyRead < length {
let (e, l) = toASN1Element(data: subdata)
result.append(e)
subdata = subdata.count > l ? subdata.advanced(by: l) : Data()
alreadyRead += l
}
return (.seq(elements: result), 1 + lengthOfLength + length)
case 0x02: // integer
let (length, lengthOfLength) = readLength(data: data.advanced(by: 1))
if length < 8 {
var result: Int = 0
let subdata = data.advanced(by: 1 + lengthOfLength)
// ignore negative case
for i in 0..<length {
result = 256 * result + Int(subdata[i])
}
return (.integer(int: result), 1 + lengthOfLength + length)
}
// number is too large to fit in Int; return the bytes
return (.bytes(data: data.subdata(in: (1 + lengthOfLength) ..< (1 + lengthOfLength + length))), 1 + lengthOfLength + length)
case let s where (s & 0xe0) == 0xa0: // constructed
let tag = Int(s & 0x1f)
let (length, lengthOfLength) = readLength(data: data.advanced(by: 1))
let subdata = data.advanced(by: 1 + lengthOfLength)
let (e, _) = toASN1Element(data: subdata)
return (.constructed(tag: tag, elem: e), 1 + lengthOfLength + length)
default: // octet string
let (length, lengthOfLength) = readLength(data: data.advanced(by: 1))
return (.bytes(data: data.subdata(in: (1 + lengthOfLength) ..< (1 + lengthOfLength + length))), 1 + lengthOfLength + length)
}
}
static private func readLength(data: Data) -> (Int, Int) {
if data[0] & 0x80 == 0x00 { // short form
return (Int(data[0]), 1)
} else {
let lenghOfLength = Int(data[0] & 0x7F)
var result: Int = 0
for i in 1..<(1 + lenghOfLength) {
result = 256 * result + Int(data[i])
}
return (result, 1 + lenghOfLength)
}
}
}

View File

@ -0,0 +1,39 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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 !swift(>=5.0)
// Extension to allow Swift 5 `withUnsafeBytes` API for earlier versions
internal extension Data {
func withUnsafeBytes<T>(_ body: (UnsafeRawBufferPointer) throws -> T) rethrows -> T {
let c = count
return try withUnsafeBytes { (p: UnsafePointer<UInt8>) throws -> T in
try body(UnsafeRawBufferPointer(start: p, count: c))
}
}
mutating func withUnsafeMutableBytes<T>(_ body: (UnsafeMutableRawBufferPointer) throws -> T) rethrows -> T {
let c = count
return try withUnsafeMutableBytes { (p: UnsafeMutablePointer<UInt8>) throws -> T in
try body(UnsafeMutableRawBufferPointer(start: p, count: c))
}
}
init(_ bytes: [UInt8]) {
self.init(bytes: bytes)
}
}
#endif

View File

@ -0,0 +1,146 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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
/// Extensions for encrypting, decrypting or signing `Data` using the appropriate algorithm determined by the key's curve with the provided `ECPrivateKey` or `ECPublicKey`.
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
extension Data {
/// Decrypt the encrypted data using the provided `ECPrivateKey`.
/// The signing algorithm used is determined based on the private key's elliptic curve.
/// - Parameter ecPrivateKey: The elliptic curve private key.
/// - Returns: The plaintext Data.
/// - Throws: An ECError if the Encrypted data fails to be decrypted.
public func decrypt(with key: ECPrivateKey) throws -> Data {
#if os(Linux)
// Initialize the decryption context.
let rsaDecryptCtx = EVP_CIPHER_CTX_new()
EVP_CIPHER_CTX_init_wrapper(rsaDecryptCtx)
let tagLength = 16
let encKeyLength = key.curve.keySize
let encryptedDataLength = Int(self.count) - encKeyLength - tagLength
// Extract encryptedAESKey, encryptedData, GCM tag from data
let encryptedKey = self.subdata(in: 0..<encKeyLength)
let encryptedData = self.subdata(in: encKeyLength..<encKeyLength+encryptedDataLength)
var tagData = self.subdata(in: encKeyLength+encryptedDataLength..<self.count)
// Allocate memory for decryption
let ec_group = EC_KEY_get0_group(key.nativeKey)
let skey_len = Int((EC_GROUP_get_degree(ec_group) + 7) / 8)
let symKey = UnsafeMutablePointer<UInt8>.allocate(capacity: skey_len)
let decrypted = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(encryptedData.count + 16))
defer {
// On completion deallocate the memory
EVP_CIPHER_CTX_reset_wrapper(rsaDecryptCtx)
EVP_CIPHER_CTX_free_wrapper(rsaDecryptCtx)
#if swift(>=4.1)
symKey.deallocate()
decrypted.deallocate()
#else
symKey.deallocate(capacity: skey_len)
decrypted.deallocate(capacity: Int(encryptedData.count + 16))
#endif
}
// Get public key point from key
let pubk_point = EC_POINT_new(ec_group)
defer {
EC_POINT_free(pubk_point)
}
encryptedKey.withUnsafeBytes({ (pubk: UnsafeRawBufferPointer) in
let pubk_bn = BN_bin2bn(pubk.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(encryptedKey.count), nil)
let pubk_bn_ctx = BN_CTX_new()
BN_CTX_start(pubk_bn_ctx)
EC_POINT_bn2point(ec_group, pubk_bn, pubk_point, pubk_bn_ctx)
BN_CTX_end(pubk_bn_ctx)
BN_CTX_free(pubk_bn_ctx)
BN_clear_free(pubk_bn)
})
// calculate symmetric key
ECDH_compute_key(symKey, skey_len, pubk_point, key.nativeKey, nil)
// processedLen is the number of bytes that each EVP_DecryptUpdate/EVP_DecryptFinal decrypts.
// The sum of processedLen is the total size of the decrypted message (decMsgLen)
var processedLen: Int32 = 0
var decMsgLen: Int32 = 0
// get aes key and iv using ANSI x9.63 Key Derivation Function
let symKeyData = Data(bytes: symKey, count: skey_len)
let counterData = Data([0x00, 0x00, 0x00, 0x01])
let preHashKey = symKeyData + counterData + encryptedKey
let hashedKey = key.curve.digest(data: preHashKey)
let aesKey = [UInt8](hashedKey.subdata(in: 0 ..< 16))
let iv = [UInt8](hashedKey.subdata(in: 16 ..< 32))
// Set the IV length to be 16 bytes.
// Set the envelope decryption algorithm as 128 bit AES-GCM.
guard EVP_DecryptInit_ex(rsaDecryptCtx, EVP_aes_128_gcm(), nil, nil, nil) == 1 else {
throw ECError.failedEvpInit
}
guard EVP_CIPHER_CTX_ctrl(rsaDecryptCtx, EVP_CTRL_GCM_SET_IVLEN, 16, nil) == 1,
// Set the AES key to be 16 bytes.
EVP_CIPHER_CTX_set_key_length(rsaDecryptCtx, 16) == 1
else {
throw ECError.failedDecryptionAlgorithm
}
// Set the envelope decryption context AES key and IV.
guard EVP_DecryptInit_ex(rsaDecryptCtx, nil, nil, aesKey, iv) == 1 else {
throw ECError.failedDecryptionAlgorithm
}
// Decrypt the encrypted data using the symmetric key.
guard encryptedData.withUnsafeBytes({ (enc: UnsafeRawBufferPointer) -> Int32 in
return EVP_DecryptUpdate(rsaDecryptCtx, decrypted, &processedLen, enc.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(encryptedData.count))
}) != 0 else {
throw ECError.failedDecryptionAlgorithm
}
decMsgLen += processedLen
// Verify the provided GCM tag.
guard tagData.withUnsafeMutableBytes({ (tag: UnsafeMutableRawBufferPointer) -> Int32 in
return EVP_CIPHER_CTX_ctrl(rsaDecryptCtx, EVP_CTRL_GCM_SET_TAG, 16, tag.baseAddress)
}) == 1
else {
throw ECError.failedDecryptionAlgorithm
}
guard EVP_DecryptFinal_ex(rsaDecryptCtx, decrypted.advanced(by: Int(decMsgLen)), &processedLen) == 1 else {
throw ECError.failedDecryptionAlgorithm
}
decMsgLen += processedLen
// return the decrypted plaintext.
return Data(bytes: decrypted, count: Int(decMsgLen))
#else
var error: Unmanaged<CFError>? = nil
guard let eData = SecKeyCreateDecryptedData(key.nativeKey,
key.curve.encryptionAlgorithm,
self as CFData,
&error)
else {
guard let error = error?.takeRetainedValue() else {
throw ECError.failedEncryptionAlgorithm
}
throw error
}
return eData as Data
#endif
}
}

View File

@ -0,0 +1,177 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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
/// A protocol for encrypting an instance of some object to generate some encrypted data.
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
protocol ECEncryptable {
/// Encrypt the object using ECIES and produce some encrypted `Data`.
func encrypt(with: ECPublicKey) throws -> Data
}
/// Extensions for encrypting or signing a `String` by converting it to UTF8 Data, then using the appropriate algorithm determined by the key's curve with the provided `ECPrivateKey` or `ECPublicKey`.
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
extension String: ECEncryptable {
/// UTF8 encode the String to Data and encrypt it using the `ECPublicKey`.
/// This either uses the `SecKeyAlgorithm`: `eciesEncryptionStandardVariableIVX963SHA256AESGCM`,
/// or the equivalent OpenSSL implementation.
/// - Parameter ecPrivateKey: The elliptic curve private key.
/// - Returns: The encrypted Data.
/// - Throws: An ECError is the plaintext fails to be encrypted.
public func encrypt(with key: ECPublicKey) throws -> Data {
return try Data(self.utf8).encrypt(with: key)
}
}
/// Extension for signing `Data` with an `ECPrivateKey` and the algorithm determined by the key's curve.
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
extension Data: ECEncryptable {
/// Encrypt the data using the `ECPublicKey`.
/// This either uses the `SecKeyAlgorithm`: `eciesEncryptionStandardVariableIVX963SHA256AESGCM`,
/// or the equivalent OpenSSL implementation.
/// - Parameter ecPrivateKey: The elliptic curve private key.
/// - Returns: The encrypted Data.
/// - Throws: An ECError is the plaintext fails to be encrypted.
public func encrypt(with key: ECPublicKey) throws -> Data {
#if os(Linux)
// Compute symmetric key
let ec_key = EC_KEY_new_by_curve_name(key.curve.nativeCurve)
defer {
EC_KEY_free(ec_key)
}
EC_KEY_generate_key(ec_key)
let ec_group = EC_KEY_get0_group(ec_key)
let symKey_len = Int((EC_GROUP_get_degree(ec_group) + 7) / 8)
let symKey = UnsafeMutablePointer<UInt8>.allocate(capacity: symKey_len)
ECDH_compute_key(symKey, symKey_len, EC_KEY_get0_public_key(key.nativeKey), ec_key, nil)
// get temp public key data
let pub_bn_ctx = BN_CTX_new()
BN_CTX_start(pub_bn_ctx)
let pub = EC_KEY_get0_public_key(ec_key)
let pub_bn = BN_new()
EC_POINT_point2bn(ec_group, pub, POINT_CONVERSION_UNCOMPRESSED, pub_bn, pub_bn_ctx)
let pubk = UnsafeMutablePointer<UInt8>.allocate(capacity: key.curve.keySize)
BN_bn2bin(pub_bn, pubk)
defer {
BN_CTX_end(pub_bn_ctx)
BN_CTX_free(pub_bn_ctx)
BN_clear_free(pub_bn)
#if swift(>=4.1)
pubk.deallocate()
symKey.deallocate()
#else
pubk.deallocate(capacity: key.curve.keySize)
symKey.deallocate(capacity: symKey_len)
#endif
}
// get aes key and iv using ANSI x9.63 Key Derivation Function
let symKeyData = Data(bytes: symKey, count: symKey_len)
let counterData = Data([0x00, 0x00, 0x00, 0x01])
let sharedInfo = Data(bytes: pubk, count: key.curve.keySize)
let preHashKey = symKeyData + counterData + sharedInfo
let hashedKey = key.curve.digest(data: preHashKey)
let aesKey = [UInt8](hashedKey.subdata(in: 0 ..< (hashedKey.count - 16)))
let iv = [UInt8](hashedKey.subdata(in: (hashedKey.count - 16) ..< hashedKey.count))
// AES encrypt data
// Initialize encryption context
let rsaEncryptCtx = EVP_CIPHER_CTX_new_wrapper()
EVP_CIPHER_CTX_init_wrapper(rsaEncryptCtx)
// Allocate encryption memory
let tag = UnsafeMutablePointer<UInt8>.allocate(capacity: 16)
let encrypted = UnsafeMutablePointer<UInt8>.allocate(capacity: self.count + 16)
defer {
// On completion deallocate the memory
EVP_CIPHER_CTX_reset_wrapper(rsaEncryptCtx)
EVP_CIPHER_CTX_free_wrapper(rsaEncryptCtx)
#if swift(>=4.1)
tag.deallocate()
encrypted.deallocate()
#else
tag.deallocate(capacity: 16)
encrypted.deallocate(capacity: self.count + 16)
#endif
}
var processedLength: Int32 = 0
var encLength: Int32 = 0
guard EVP_EncryptInit_ex(rsaEncryptCtx, EVP_aes_128_gcm(), nil, nil, nil) == 1 else {
throw ECError.failedEvpInit
}
// Set the IV length to be 16 to match Apple.
guard EVP_CIPHER_CTX_ctrl(rsaEncryptCtx, EVP_CTRL_GCM_SET_IVLEN, 16, nil) == 1
// Add the aad to the encryption context.
// This is used in generating the GCM tag. We don't use this processedLength.
else {
throw ECError.failedEncryptionAlgorithm
}
guard EVP_EncryptInit_ex(rsaEncryptCtx, nil, nil, aesKey, iv) == 1 else {
throw ECError.failedDecryptionAlgorithm
}
// Encrypt the plaintext into encrypted using gcmAlgorithm with the random aes key and all 0 iv.
guard(self.withUnsafeBytes({ (plaintext: UnsafeRawBufferPointer) -> Int32 in
return EVP_EncryptUpdate(rsaEncryptCtx, encrypted, &processedLength, plaintext.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(self.count))
})) == 1 else {
throw ECError.failedEncryptionAlgorithm
}
encLength += processedLength
// Finalize the encryption so no more data will be added and generate the GCM tag.
guard EVP_EncryptFinal_ex(rsaEncryptCtx, encrypted.advanced(by: Int(encLength)), &processedLength) == 1 else {
throw ECError.failedEncryptionAlgorithm
}
encLength += processedLength
// Get the 16 byte GCM tag.
guard EVP_CIPHER_CTX_ctrl(rsaEncryptCtx, EVP_CTRL_GCM_GET_TAG, 16, tag) == 1 else {
throw ECError.failedEncryptionAlgorithm
}
// Construct the envelope by combining the encrypted AES key, the encrypted date and the GCM tag.
let ekFinal = Data(bytes: pubk, count: key.curve.keySize)
let cipher = Data(bytes: encrypted, count: Int(encLength))
let tagFinal = Data(bytes: tag, count: 16)
return ekFinal + cipher + tagFinal
#else
var error: Unmanaged<CFError>? = nil
guard let eData = SecKeyCreateEncryptedData(key.nativeKey,
key.curve.encryptionAlgorithm,
self as CFData,
&error)
else {
guard let error = error?.takeRetainedValue() else {
throw ECError.failedEncryptionAlgorithm
}
throw error
}
return eData as Data
#endif
}
}

View File

@ -0,0 +1,72 @@
/**
* Copyright IBM Corporation 2019
*
* 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 struct representing the different errors that can be thrown by BlueECC.
public struct ECError: Error, Equatable {
/// A human readable description of the error.
public let localizedDescription: String
private let internalError: InternalError
private enum InternalError {
case invalidPEMString, unknownPEMHeader, failedBase64Encoding, failedASN1Decoding, unsupportedCurve, failedNativeKeyCreation, failedEvpInit, failedSigningAlgorithm, invalidRSLength, failedEncryptionAlgorithm, failedUTF8Decoding, failedDecryptionAlgorithm
}
/// Error thrown when an invalid PEM String used to initialize a key.
public static let invalidPEMString = ECError(localizedDescription: "Input was not a valid PEM String", internalError: .invalidPEMString)
/// Error thrown when the PEM header is not recognized.
public static let unknownPEMHeader = ECError(localizedDescription: "Input PEM header was not recognized", internalError: .unknownPEMHeader)
/// Error thrown when a String fails to be Base64 encoded.
public static let failedBase64Encoding = ECError(localizedDescription: "Failed to base64 encode the String", internalError: .failedBase64Encoding)
/// Error thrown when the ASN1 data could not be decoded to the expected structure.
public static let failedASN1Decoding = ECError(localizedDescription: "ASN1 data could not be decoded to expected structure", internalError: .failedASN1Decoding)
/// Error thrown when the key's object identifier is for a curve that is not supported.
public static let unsupportedCurve = ECError(localizedDescription: "The key object identifier is for a non-supported curve", internalError: .unsupportedCurve)
/// Error thrown when the key could not be converted to a native key (`SecKey` for Apple, `EC_KEY` for linux).
public static let failedNativeKeyCreation = ECError(localizedDescription: "The key data could not be converted to a native key", internalError: .failedNativeKeyCreation)
/// Error thrown when the encryption envelope fails to initialize.
public static let failedEvpInit = ECError(localizedDescription: "Failed to initialize the signing envelope", internalError: .failedEvpInit)
/// Error thrown when the signing algorithm could not create the signature.
public static let failedSigningAlgorithm = ECError(localizedDescription: "Signing algorithm failed to create the signature", internalError: .failedSigningAlgorithm)
/// Error thrown when the provided R and S Data was not a valid length.
/// They must be the same length and either 32, 48 or 66 bytes (depending on the curve used).
public static let invalidRSLength = ECError(localizedDescription: "The provided R and S values were not a valid length", internalError: .invalidRSLength)
/// Error thrown when the encryption algorithm could not encrypt the plaintext.
public static let failedEncryptionAlgorithm = ECError(localizedDescription: "Encryption algorithm failed to encrypt the data", internalError: .failedEncryptionAlgorithm)
/// Error thrown when the decryption algorithm could not decrypt the encrypted Data.
public static let failedDecryptionAlgorithm = ECError(localizedDescription: "Decryption algorithm failed to decrypt the data", internalError: .failedDecryptionAlgorithm)
/// Error thrown when the Data could not be decoded into a UTF8 String.
public static let failedUTF8Decoding = ECError(localizedDescription: "Data could not be decoded as a UTF8 String", internalError: .failedUTF8Decoding)
/// Checks if ECSigningErrors are equal, required for Equatable protocol.
public static func == (lhs: ECError, rhs: ECError) -> Bool {
return lhs.internalError == rhs.internalError
}
}

View File

@ -0,0 +1,474 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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
/**
Represents an elliptic curve private key.
Supported curves are:
- prime256v1
- secp384r1
- NID_secp521r1
You can generate an elliptic curve Key using OpenSSL:
https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations#Generating_EC_Keys_and_Parameters
### Usage Example:
```swift
let pemKey = """
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJX+87WJ7Gh19sohyZnhxZeXYNOcuGv4Q+8MLge4UkaZoAoGCCqGSM49
AwEHoUQDQgAEikc5m6C2xtDWeeAeT18WElO37zvFOz8p4kAlhvgIHN23XIClNESg
KVmLgSSq2asqiwdrU5YHbcHFkgdABM1SPA==
-----END EC PRIVATE KEY-----
"""
let privateKey = try ECPrivateKey(key: pemKey)
let signature = "Hello world".sign(with: privateKey)
```
*/
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
public class ECPrivateKey {
/// A String description of the curve this key was generated from.
public let curveId: String
/// The `EllipticCurve` this key was generated from.
public let curve: EllipticCurve
/// The private key represented as a PEM String.
public let pemString: String
#if os(Linux)
typealias NativeKey = OpaquePointer?
deinit { EC_KEY_free(.make(optional: self.nativeKey)) }
#else
typealias NativeKey = SecKey
#endif
let nativeKey: NativeKey
let pubKeyBytes: Data
private var stripped: Bool = false
/**
Initialize an ECPrivateKey from a PEM String.
This can either be from a `.p8` file with the header "-----BEGIN PRIVATE KEY-----",
or from a `.pem` file with the header "-----BEGIN EC PRIVATE KEY-----".
### Usage Example: ###
```swift
let privateKeyString = """
-----BEGIN EC PRIVATE KEY-----
MHcCAQEEIJX+87WJ7Gh19sohyZnhxZeXYNOcuGv4Q+8MLge4UkaZoAoGCCqGSM49
AwEHoUQDQgAEikc5m6C2xtDWeeAeT18WElO37zvFOz8p4kAlhvgIHN23XIClNESg
KVmLgSSq2asqiwdrU5YHbcHFkgdABM1SPA==
-----END EC PRIVATE KEY-----
"""
let key = try ECPrivateKey(key: privateKeyString)
```
- Parameter key: The elliptic curve private key as a PEM string.
- Returns: An ECPrivateKey.
- Throws: An ECError if the PEM string can't be decoded or is not a valid key.
*/
public convenience init(key: String) throws {
// Strip whitespace characters
let strippedKey = String(key.filter { !" \n\t\r".contains($0) })
var pemComponents = strippedKey.components(separatedBy: "-----")
guard pemComponents.count >= 5 else {
throw ECError.invalidPEMString
}
// Remove any EC parameters since Curve is determined by OID
if pemComponents[1] == "BEGINECPARAMETERS" {
pemComponents.removeFirst(5)
guard pemComponents.count >= 5 else {
throw ECError.invalidPEMString
}
}
guard let der = Data(base64Encoded: pemComponents[2]) else {
throw ECError.failedBase64Encoding
}
if pemComponents[1] == "BEGINECPRIVATEKEY" {
try self.init(sec1DER: der)
} else if pemComponents[1] == "BEGINPRIVATEKEY" {
try self.init(pkcs8DER: der)
} else {
throw ECError.unknownPEMHeader
}
}
/// Initialize an ECPrivateKey from a PKCS8 `.der` file data.
/// This is equivalent to a PEM String that has had the "-----BEGIN PRIVATE KEY-----"
/// header and footer stripped and been base64 encoded to ASN1 Data.
/// - Parameter pkcs8DER: The elliptic curve private key Data.
/// - Returns: An ECPrivateKey.
/// - Throws: An ECError if the Data can't be decoded or is not a valid key.
public init(pkcs8DER: Data) throws {
let (result, _) = ASN1.toASN1Element(data: pkcs8DER)
guard case let ASN1.ASN1Element.seq(elements: es) = result,
es.count > 2,
case let ASN1.ASN1Element.seq(elements: ids) = es[1],
ids.count > 1,
case let ASN1.ASN1Element.bytes(data: privateKeyID) = ids[1]
else {
throw ECError.failedASN1Decoding
}
self.curve = try EllipticCurve.objectToCurve(ObjectIdentifier: privateKeyID)
guard case let ASN1.ASN1Element.bytes(data: privateOctest) = es[2] else {
throw ECError.failedASN1Decoding
}
let (octest, _) = ASN1.toASN1Element(data: privateOctest)
guard case let ASN1.ASN1Element.seq(elements: seq) = octest,
seq.count >= 3,
case let ASN1.ASN1Element.bytes(data: privateKeyData) = seq[1]
else {
throw ECError.failedASN1Decoding
}
let publicKeyData: Data
if case let ASN1.ASN1Element.constructed(tag: 1, elem: publicElement) = seq[2],
case let ASN1.ASN1Element.bytes(data: pubKeyData) = publicElement
{
publicKeyData = pubKeyData
} else if seq.count >= 4,
case let ASN1.ASN1Element.constructed(tag: 1, elem: publicElement) = seq[3],
case let ASN1.ASN1Element.bytes(data: pubKeyData) = publicElement
{
publicKeyData = pubKeyData
} else {
throw ECError.failedASN1Decoding
}
let trimmedPubBytes = publicKeyData.drop(while: { $0 == 0x00})
if trimmedPubBytes.count != publicKeyData.count {
stripped = true
}
self.nativeKey = try ECPrivateKey.bytesToNativeKey(privateKeyData: privateKeyData,
publicKeyData: trimmedPubBytes,
curve: curve)
let derData = ECPrivateKey.generateASN1(privateKey: privateKeyData,
publicKey: publicKeyData,
curve: curve)
self.pemString = ECPrivateKey.derToPrivatePEM(derData: derData)
self.pubKeyBytes = trimmedPubBytes
self.curveId = curve.description
}
/// Initialize an ECPrivateKey from a SEC1 `.der` file data.
/// This is equivalent to a PEM String that has had the "-----BEGIN EC PRIVATE KEY-----"
/// header and footer stripped and been base64 encoded to ASN1 Data.
/// - Parameter sec1DER: The elliptic curve private key Data.
/// - Returns: An ECPrivateKey.
/// - Throws: An ECError if the Data can't be decoded or is not a valid key.
public init(sec1DER: Data) throws {
self.pemString = ECPrivateKey.derToPrivatePEM(derData: sec1DER)
let (result, _) = ASN1.toASN1Element(data: sec1DER)
guard case let ASN1.ASN1Element.seq(elements: seq) = result,
seq.count > 3,
case let ASN1.ASN1Element.constructed(tag: _, elem: objectElement) = seq[2],
case let ASN1.ASN1Element.bytes(data: objectId) = objectElement,
case let ASN1.ASN1Element.bytes(data: privateKeyData) = seq[1]
else {
throw ECError.failedASN1Decoding
}
self.curve = try EllipticCurve.objectToCurve(ObjectIdentifier: objectId)
guard case let ASN1.ASN1Element.constructed(tag: _, elem: publicElement) = seq[3],
case let ASN1.ASN1Element.bytes(data: publicKeyData) = publicElement
else {
throw ECError.failedASN1Decoding
}
let trimmedPubBytes = publicKeyData.drop(while: { $0 == 0x00})
if trimmedPubBytes.count != publicKeyData.count {
stripped = true
}
self.nativeKey = try ECPrivateKey.bytesToNativeKey(privateKeyData: privateKeyData,
publicKeyData: trimmedPubBytes,
curve: curve)
self.pubKeyBytes = trimmedPubBytes
self.curveId = curve.description
}
/// Initialize the `ECPublicKey`for this private key by extracting the public key bytes.
/// - Returns: An ECPublicKey.
/// - Throws: An ECError if the public key fails to be initialized from this private key.
public func extractPublicKey() throws -> ECPublicKey {
let keyHeader: Data
// Add the ASN1 header for the public key. The bytes have the following structure:
// SEQUENCE (2 elem)
// SEQUENCE (2 elem)
// OBJECT IDENTIFIER
// OBJECT IDENTIFIER
// BIT STRING (This is the `pubKeyBytes` added afterwards)
if self.curve == .prime256v1 {
keyHeader = Data([0x30, 0x59,
0x30, 0x13,
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07, 0x03, 0x42])
} else if self.curve == .secp384r1 {
keyHeader = Data([0x30, 0x76,
0x30, 0x10,
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22, 0x03, 0x62])
} else if self.curve == .secp521r1 {
keyHeader = Data([0x30, 0x81, 0x9B,
0x30, 0x10,
0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01,
0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23, 0x03, 0x81, 0x86])
} else {
throw ECError.unsupportedCurve
}
// If we stripped the leading zero earlier, add it back here
var pubBytes = self.pubKeyBytes
if stripped {
pubBytes = Data(count: 1) + self.pubKeyBytes
}
return try ECPublicKey(der: keyHeader + pubBytes)
}
/**
Make an new ECPrivate key from a supported `EllipticCurve`.
- Parameter for curve: The elliptic curve that is used to generate the key.
- Returns: An ECPrivateKey.
- Throws: An ECError if the key fails to be created.
*/
public static func make(for curve: EllipticCurve) throws -> ECPrivateKey {
return try ECPrivateKey(for: curve)
}
/**
Initialise an new ECPrivate key from a supported `Curve`
- Parameter for curve: The elliptic curve that is used to generate the key.
- Returns: An ECPrivateKey.
- Throws: An ECError if the key fails to be created.
*/
private init(for curve: EllipticCurve) throws {
self.curve = curve
self.curveId = curve.description
self.stripped = true
#if os(Linux)
let ec_key = EC_KEY_new_by_curve_name(curve.nativeCurve)
EC_KEY_generate_key(ec_key)
self.nativeKey = ec_key
let pub_bn_ctx = BN_CTX_new()
BN_CTX_start(pub_bn_ctx)
let pub = EC_KEY_get0_public_key(ec_key)
let ec_group = EC_KEY_get0_group(ec_key)
let pub_bn = BN_new()
EC_POINT_point2bn(ec_group, pub, POINT_CONVERSION_UNCOMPRESSED, pub_bn, pub_bn_ctx)
let pubk = UnsafeMutablePointer<UInt8>.allocate(capacity: curve.keySize)
BN_bn2bin(pub_bn, pubk)
self.pubKeyBytes = Data(bytes: pubk, count: curve.keySize)
defer {
BN_CTX_end(pub_bn_ctx)
BN_CTX_free(pub_bn_ctx)
BN_clear_free(pub_bn)
#if swift(>=4.1)
pubk.deallocate()
#else
pubk.deallocate(capacity: curve.keySize)
#endif
}
#else
let kAsymmetricCryptoManagerKeyType = kSecAttrKeyTypeECSECPrimeRandom
let kAsymmetricCryptoManagerKeySize: Int
if curve == .prime256v1 {
kAsymmetricCryptoManagerKeySize = 256
} else if curve == .secp384r1 {
kAsymmetricCryptoManagerKeySize = 384
} else {
kAsymmetricCryptoManagerKeySize = 521
}
// parameters
let parameters: [String: AnyObject] = [
kSecAttrKeyType as String: kAsymmetricCryptoManagerKeyType,
kSecAttrKeySizeInBits as String: kAsymmetricCryptoManagerKeySize as AnyObject,
]
var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters as CFDictionary, &pubKey, &privKey)
guard status == 0, let newPubKey = pubKey, let newPrivKey = privKey else {
throw ECError.failedNativeKeyCreation
}
var error: Unmanaged<CFError>? = nil
guard let pubBytes = SecKeyCopyExternalRepresentation(newPubKey, &error) else {
guard let error = error?.takeRetainedValue() else {
throw ECError.failedNativeKeyCreation
}
throw error
}
self.pubKeyBytes = pubBytes as Data
self.nativeKey = newPrivKey
#endif
self.pemString = try ECPrivateKey.decodeToPEM(nativeKey: self.nativeKey, curve: self.curve)
}
/// Decode this ECPrivateKey to it's PEM format
private static func decodeToPEM(nativeKey: NativeKey, curve: EllipticCurve) throws -> String {
#if os(Linux)
let asn1Bio = BIO_new(BIO_s_mem())
defer { BIO_free_all(asn1Bio) }
// The return value of i2d_ECPrivateKey_bio is supposed to be the DER size.
// However it is just returning 1 for success.
// Since the size is fixed we have just used the known values here.
guard i2d_ECPrivateKey_bio(asn1Bio, nativeKey) >= 0 else {
throw ECError.failedNativeKeyCreation
}
let asn1Size: Int32
if curve == .prime256v1 {
asn1Size = 364
} else if curve == .secp384r1 {
asn1Size = 510
} else {
asn1Size = 673
}
let asn1 = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(asn1Size))
let readLength = BIO_read(asn1Bio, asn1, asn1Size)
guard readLength > 0 else {
throw ECError.failedASN1Decoding
}
let asn1Data = Data(bytes: asn1, count: Int(readLength))
// OpenSSL 1.1 already returns the shortened ANS1 so can return it straight away
if readLength < asn1Size - 1 {
return ECPrivateKey.derToPrivatePEM(derData: asn1Data)
}
// Otherwise need to decode ASN1 to get public and private key
#if swift(>=4.1)
asn1.deallocate()
#else
asn1.deallocate(capacity: Int(asn1Size))
#endif
let (result, _) = ASN1.toASN1Element(data: asn1Data)
guard case let ASN1.ASN1Element.seq(elements: seq) = result,
seq.count > 3,
case let ASN1.ASN1Element.bytes(data: privateKeyData) = seq[1]
else {
throw ECError.failedASN1Decoding
}
guard case let ASN1.ASN1Element.constructed(tag: _, elem: publicElement) = seq[3],
case let ASN1.ASN1Element.bytes(data: publicKeyData) = publicElement
else {
throw ECError.failedASN1Decoding
}
#else
var error: Unmanaged<CFError>? = nil
/*
From Apple docs:
For an elliptic curve private key, `SecKeyCopyExternalRepresentation` output is formatted as the public key concatenated with the big endian encoding of the secret scalar, or 04 || X || Y || K.
*/
guard let keyBytes = SecKeyCopyExternalRepresentation(nativeKey, &error) else {
guard let error = error?.takeRetainedValue() else {
throw ECError.failedNativeKeyCreation
}
throw error
}
let keyData = keyBytes as Data
let privateKeyData = keyData.dropFirst(curve.keySize)
let publicKeyData = Data(count: 1) + keyData.dropLast(keyData.count - curve.keySize)
#endif
let derData = ECPrivateKey.generateASN1(privateKey: privateKeyData, publicKey: publicKeyData, curve: curve)
return ECPrivateKey.derToPrivatePEM(derData: derData)
}
private static func generateASN1(privateKey: Data, publicKey: Data, curve: EllipticCurve) -> Data {
var keyHeader: Data
// Add the ASN1 header for the private key. The bytes have the following structure:
// SEQUENCE (4 elem)
// INTEGER 1
// OCTET STRING (32 byte) (This is the `privateKeyBytes`)
// [0] (1 elem)
// OBJECT IDENTIFIER
// [1] (1 elem)
// BIT STRING (This is the `pubKeyBytes`)
if curve == .prime256v1 {
keyHeader = Data([0x30, 0x77,
0x02, 0x01, 0x01,
0x04, 0x20])
keyHeader += privateKey
keyHeader += Data([0xA0,
0x0A, 0x06, 0x08, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07,
0xA1,
0x44, 0x03, 0x42])
keyHeader += publicKey
} else if curve == .secp384r1 {
keyHeader = Data([0x30, 0x81, 0xA4,
0x02, 0x01, 0x01,
0x04, 0x30])
keyHeader += privateKey
keyHeader += Data([0xA0,
0x07, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x22,
0xA1,
0x64, 0x03, 0x62])
keyHeader += publicKey
} else {
// 521 Private key can be 65 or 66 bytes long
if privateKey.count == 65 {
keyHeader = Data([0x30, 0x81, 0xDB,
0x02, 0x01, 0x01,
0x04, 0x41])
} else {
keyHeader = Data([0x30, 0x81, 0xDC,
0x02, 0x01, 0x01,
0x04, 0x42])
}
keyHeader += privateKey
keyHeader += Data([0xA0,
0x07, 0x06, 0x05, 0x2B, 0x81, 0x04, 0x00, 0x23,
0xA1,
0x81, 0x89, 0x03, 0x81, 0x86])
keyHeader += publicKey
}
return keyHeader
}
private static func bytesToNativeKey(privateKeyData: Data, publicKeyData: Data, curve: EllipticCurve) throws -> NativeKey {
#if os(Linux)
let bigNum = BN_new()
defer {
BN_free(bigNum)
}
privateKeyData.withUnsafeBytes({ (privateKeyBytes: UnsafeRawBufferPointer) -> Void in
BN_bin2bn(privateKeyBytes.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(privateKeyData.count), bigNum)
})
let ecKey = EC_KEY_new_by_curve_name(curve.nativeCurve)
guard EC_KEY_set_private_key(ecKey, bigNum) == 1 else {
EC_KEY_free(ecKey)
throw ECError.failedNativeKeyCreation
}
return ecKey
#else
let keyData = publicKeyData + privateKeyData
var error: Unmanaged<CFError>? = nil
guard let secKey = SecKeyCreateWithData(keyData as CFData,
[kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPrivate] as CFDictionary,
&error)
else {
if let secError = error?.takeRetainedValue() {
throw secError
} else {
throw ECError.failedNativeKeyCreation
}
}
return secKey
#endif
}
private static func derToPrivatePEM(derData: Data) -> String {
// First convert the DER data to a base64 string...
let base64String = derData.base64EncodedString()
// Split the string into strings of length 64.
let lines = base64String.split(to: 64)
// Join those lines with a new line...
let joinedLines = lines.joined(separator: "\n")
return "-----BEGIN EC PRIVATE KEY-----\n" + joinedLines + "\n-----END EC PRIVATE KEY-----"
}
}

View File

@ -0,0 +1,167 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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
/**
Represents an elliptic curve public key.
Supported curves are:
- prime256v1
- secp384r1
- NID_secp521r1
You can generate an elliptic curve Key using OpenSSL:
https://wiki.openssl.org/index.php/Command_Line_Elliptic_Curve_Operations#Generating_EC_Keys_and_Parameters
### Usage Example:
```swift
let pemKey = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEikc5m6C2xtDWeeAeT18WElO37zvF
Oz8p4kAlhvgIHN23XIClNESgKVmLgSSq2asqiwdrU5YHbcHFkgdABM1SPA==
-----END PUBLIC KEY-----
"""
let publicKey = try ECPublicKey(key: pemKey)
let base64Sig = "MEYCIQCvgBLn+tQoBDBR3D2G3485GloYGNxuk6PqR4qjr5GDqAIhAKNvsqvesVBD/MLub/KAyzLLNGtUZyQDxYZj/4vmHwWF"
let signature = try ECSignature(asn1: Data(base64Encoded: base64Sig))
let verified = signature.verify(plaintext: "Hello world", using: publicKey)
```
*/
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
public class ECPublicKey {
/// A String description of the curve this key was generated from.
public let curveId: String
/// The `EllipticCurve` this key was generated from.
public let curve: EllipticCurve
#if os(Linux)
typealias NativeKey = OpaquePointer?
let pubKeyBytes: Data
deinit { EC_KEY_free(.make(optional: self.nativeKey)) }
#else
typealias NativeKey = SecKey
#endif
let nativeKey: NativeKey
/// The public key represented as a PEM String.
public let pemString: String
/**
Initialize an ECPublicKey from a `.pem` file format.
### Usage Example: ###
```swift
let publicKeyString = """
-----BEGIN PUBLIC KEY-----
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEikc5m6C2xtDWeeAeT18WElO37zvF
Oz8p4kAlhvgIHN23XIClNESgKVmLgSSq2asqiwdrU5YHbcHFkgdABM1SPA==
-----END PUBLIC KEY-----
"""
let pemKey = try ECPublicKey(key: publicKeyString)
```
- Parameter key: The elliptic curve public key as a PEM string.
- Returns: An ECPublicKey.
- Throws: An ECError if the PEM string can't be decoded or is not a valid key.
*/
public convenience init(key: String) throws {
let strippedKey = String(key.filter { !" \n\t\r".contains($0) })
let pemComponents = strippedKey.components(separatedBy: "-----")
guard pemComponents.count == 5 else {
throw ECError.invalidPEMString
}
guard let der = Data(base64Encoded: pemComponents[2]) else {
throw ECError.invalidPEMString
}
if pemComponents[1] == "BEGINPUBLICKEY" {
try self.init(der: der)
} else {
throw ECError.unknownPEMHeader
}
}
/// Initialize an ECPublicKey from `.der` file data.
/// This is equivalent to a PEM String that has had the "-----BEGIN PUBLIC KEY-----"
/// header and footer stripped and been base64 encoded to ASN1 Data.
/// - Parameter der: The elliptic curve public key Data.
/// - Returns: An ECPublicKey.
/// - Throws: An ECError if the Data can't be decoded or is not a valid key.
public init(der: Data) throws {
pemString = ECPublicKey.derToPublicPEM(derData: der)
let (result, _) = ASN1.toASN1Element(data: der)
guard case let ASN1.ASN1Element.seq(elements: seq) = result,
seq.count > 1,
case let ASN1.ASN1Element.seq(elements: ids) = seq[0],
ids.count > 1,
case let ASN1.ASN1Element.bytes(data: privateKeyID) = ids[1],
case let ASN1.ASN1Element.bytes(data: publicKeyData) = seq[1]
else {
throw ECError.failedASN1Decoding
}
self.curve = try EllipticCurve.objectToCurve(ObjectIdentifier: privateKeyID)
self.curveId = curve.description
let keyData = publicKeyData.drop(while: { $0 == 0x00})
#if os(Linux)
self.pubKeyBytes = keyData
let bigNum = BN_new()
defer {
BN_free(bigNum)
}
publicKeyData.withUnsafeBytes({ (pubKeyBytes: UnsafeRawBufferPointer) -> Void in
BN_bin2bn(pubKeyBytes.baseAddress?.assumingMemoryBound(to: UInt8.self), Int32(publicKeyData.count), bigNum)
})
let ecKey = EC_KEY_new_by_curve_name(curve.nativeCurve)
let ecGroup = EC_KEY_get0_group(ecKey)
let ecPoint = EC_POINT_new(ecGroup)
defer {
EC_POINT_free(ecPoint)
}
EC_POINT_bn2point(ecGroup, bigNum, ecPoint, nil)
guard EC_KEY_set_public_key(ecKey, ecPoint) == 1 else {
EC_KEY_free(ecKey)
throw ECError.failedNativeKeyCreation
}
self.nativeKey = ecKey
#else
var error: Unmanaged<CFError>? = nil
guard let secKey = SecKeyCreateWithData(keyData as CFData,
[kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
kSecAttrKeyClass: kSecAttrKeyClassPublic] as CFDictionary,
&error)
else {
if let secError = error?.takeRetainedValue() {
throw secError
} else {
throw ECError.failedNativeKeyCreation
}
}
self.nativeKey = secKey
#endif
}
private static func derToPublicPEM(derData: Data) -> String {
// First convert the DER data to a base64 string...
let base64String = derData.base64EncodedString()
// Split the string into strings of length 64.
let lines = base64String.split(to: 64)
// Join those lines with a new line...
let joinedLines = lines.joined(separator: "\n")
return "-----BEGIN PUBLIC KEY-----\n" + joinedLines + "\n-----END PUBLIC KEY-----"
}
}

View File

@ -0,0 +1,105 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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
/// A protocol for signing an instance of some object to generate an `ECSignature`.
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
protocol ECSignable {
/// Sign the object using ECDSA and produce an `ECSignature`.
func sign(with: ECPrivateKey) throws -> ECSignature
}
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
extension String: ECSignable {
/// UTF8 encode the String to Data and sign it using the `ECPrivateKey`.
/// The Data is signed using ECDSA with either SHA256, SHA384 or SHA512, depending on the key's curve.
/// - Parameter with key: The elliptic curve private key.
/// - Returns: An ECSignature on failure.
/// - Throws: An ECError if a valid signature is unable to be created.
public func sign(with key: ECPrivateKey) throws -> ECSignature {
return try Data(self.utf8).sign(with: key)
}
}
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
extension Data: ECSignable {
/// Sign the plaintext data using the provided `ECPrivateKey`.
/// The Data is signed using ECDSA with either SHA256, SHA384 or SHA512, depending on the key's curve.
/// - Parameter with key: The elliptic curve private key.
/// - Returns: An ECSignature on failure.
/// - Throws: An ECError if a valid signature is unable to be created.
public func sign(with key: ECPrivateKey) throws -> ECSignature {
#if os(Linux)
let md_ctx = EVP_MD_CTX_new_wrapper()
let evp_key = EVP_PKEY_new()
defer {
EVP_PKEY_free(evp_key)
EVP_MD_CTX_free_wrapper(md_ctx)
}
guard EVP_PKEY_set1_EC_KEY(evp_key, .make(optional: key.nativeKey)) == 1 else {
throw ECError.failedNativeKeyCreation
}
guard EVP_DigestSignInit(md_ctx, nil, .make(optional: key.curve.signingAlgorithm), nil, evp_key) == 1 else {
throw ECError.failedEvpInit
}
guard self.withUnsafeBytes({ (message: UnsafeRawBufferPointer) -> Int32 in
return EVP_DigestUpdate(md_ctx, message.baseAddress?.assumingMemoryBound(to: UInt8.self), self.count)
}) == 1 else {
throw ECError.failedSigningAlgorithm
}
var sig_len: Int = 0
EVP_DigestSignFinal(md_ctx, nil, &sig_len)
let sig = UnsafeMutablePointer<UInt8>.allocate(capacity: sig_len)
defer {
#if swift(>=4.1)
sig.deallocate()
#else
sig.deallocate(capacity: sig_len)
#endif
}
guard EVP_DigestSignFinal(md_ctx, sig, &sig_len) == 1 else {
throw ECError.failedSigningAlgorithm
}
return try ECSignature(asn1: Data(bytes: sig, count: sig_len))
#else
let hash = key.curve.digest(data: self)
// Memory storage for error from SecKeyCreateSignature
var error: Unmanaged<CFError>? = nil
// cfSignature is CFData that is ANS1 encoded as a sequence of two UInts (r and s)
guard let cfSignature = SecKeyCreateSignature(key.nativeKey,
key.curve.signingAlgorithm,
hash as CFData,
&error)
else {
if let thrownError = error?.takeRetainedValue() {
throw thrownError
} else {
throw ECError.failedSigningAlgorithm
}
}
return try ECSignature(asn1: cfSignature as Data)
#endif
}
}

View File

@ -0,0 +1,202 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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
/// The signature produced by applying an Elliptic Curve Digital Signature Algorithm to some Plaintext data.
/// It consists of two binary unsigned integers, `r` and `s`.
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
public struct ECSignature {
// MARK: Signature Values
/// The r value of the signature.
/// The size of the signature data depends on the Secure Hash Algorithm used; it will be 32 bytes of data for SHA256, 48 bytes for SHA384, or 66 bytes for SHA512.
public let r: Data
/// The s value of the signature.
/// The size of the signature data depends on the Secure Hash Algorithm used; it will be 32 bytes of data for SHA256, 48 bytes for SHA384, or 66 bytes for SHA512.
public let s: Data
/// The r and s values of the signature encoded into an ASN1 sequence.
public let asn1: Data
// MARK: Initializers
/// Initialize an ECSignature by providing the r and s values.
/// These must be the same length and either 32, 48 or 66 bytes (Depending on the curve used).
/// - Parameter r: The r value of the signature as raw data.
/// - Parameter s: The s value of the signature as raw data.
/// - Returns: A new instance of `ECSignature`.
/// - Throws: An ECError if the r or s values are not a valid length.
public init(r: Data, s: Data) throws {
let asn1 = try ECSignature.rsSigToASN1(r: r, s: s)
self.r = r
self.s = s
self.asn1 = asn1
}
/// Initialize an ECSignature by providing an ASN1 encoded sequence containing the r and s values.
/// - Parameter asn1: The r and s values of the signature encoded as an ASN1 sequence.
/// - Returns: A new instance of `ECSignature`.
/// - Throws: An ECError if the ASN1 data can't be decoded.
public init(asn1: Data) throws {
self.asn1 = asn1
let (r,s) = try ECSignature.asn1ToRSSig(asn1: asn1)
self.r = r
self.s = s
}
// MARK: Verify Signature
/// Verify the signature for a given String using the provided public key.
/// The Data is verified using ECDSA with either SHA256, SHA384 or SHA512, depending on the key's curve.
/// - Parameter plaintext: The String that was originally signed to produce the signature.
/// - Parameter using ecPublicKey: The ECPublicKey that will be used to verify the plaintext.
/// - Returns: true if the plaintext is valid for the provided signature. Otherwise it returns false.
public func verify(plaintext: String, using ecPublicKey: ECPublicKey) -> Bool {
let plainTextData = Data(plaintext.utf8)
return verify(plaintext: plainTextData, using: ecPublicKey)
}
/// Verify the signature for the given Data using the provided public key.
/// The Data is verified using ECDSA with either SHA256, SHA384 or SHA512, depending on the key's curve.
/// - Parameter plaintext: The Data that was originally signed to produce the signature.
/// - Parameter using ecPublicKey: The ECPublicKey that will be used to verify the plaintext.
/// - Returns: true if the plaintext is valid for the provided signature. Otherwise it returns false.
public func verify(plaintext: Data, using ecPublicKey: ECPublicKey) -> Bool {
#if os(Linux)
let md_ctx = EVP_MD_CTX_new_wrapper()
let evp_key = EVP_PKEY_new()
defer {
EVP_PKEY_free(evp_key)
EVP_MD_CTX_free_wrapper(md_ctx)
}
guard EVP_PKEY_set1_EC_KEY(evp_key, .make(optional: ecPublicKey.nativeKey)) == 1 else {
return false
}
EVP_DigestVerifyInit(md_ctx, nil, .make(optional: ecPublicKey.curve.signingAlgorithm), nil, evp_key)
guard plaintext.withUnsafeBytes({ (message: UnsafeRawBufferPointer) -> Int32 in
return EVP_DigestUpdate(md_ctx, message.baseAddress?.assumingMemoryBound(to: UInt8.self), plaintext.count)
}) == 1 else {
return false
}
let rc = self.asn1.withUnsafeBytes({ (sig: UnsafeRawBufferPointer) -> Int32 in
return SSL_EVP_digestVerifyFinal_wrapper(md_ctx, sig.baseAddress?.assumingMemoryBound(to: UInt8.self), self.asn1.count)
})
return rc == 1
#else
let hash = ecPublicKey.curve.digest(data: plaintext)
// Memory storage for error from SecKeyVerifySignature
var error: Unmanaged<CFError>? = nil
return SecKeyVerifySignature(ecPublicKey.nativeKey,
ecPublicKey.curve.signingAlgorithm,
hash as CFData,
self.asn1 as CFData,
&error)
#endif
}
// ASN1 encode the r and s values.
static func rsSigToASN1(r: Data, s: Data) throws -> Data {
guard r.count == s.count, r.count == 32 || r.count == 48 || r.count == 66 else {
throw ECError.invalidRSLength
}
// Convert r,s signature to ASN1 for SecKeyVerifySignature
var asnSignature = Data()
// r value is first 32 bytes
var rSig = r
// If first bit is 1, add a 00 byte to mark it as positive for ASN1
if rSig[0] == 0 {
rSig = rSig.advanced(by: 1)
}
if rSig[0].leadingZeroBitCount == 0 {
rSig = Data(count: 1) + rSig
}
// r value is last 32 bytes
var sSig = s
// If first bit is 1, add a 00 byte to mark it as positive for ASN1
if sSig[0] == 0 {
sSig = sSig.advanced(by: 1)
}
if sSig[0].leadingZeroBitCount == 0 {
sSig = Data(count: 1) + sSig
}
// Count Byte lengths for ASN1 length bytes
let rLengthByte = UInt8(rSig.count)
let sLengthByte = UInt8(sSig.count)
// total bytes is r + s + rLengthByte + sLengthByte byte + Integer marking bytes
let tLengthByte = rLengthByte + sLengthByte + 4
// 0x30 means sequence, 0x02 means Integer
if tLengthByte > 127 {
asnSignature.append(contentsOf: [0x30, 0x81, tLengthByte])
} else {
asnSignature.append(contentsOf: [0x30, tLengthByte])
}
asnSignature.append(contentsOf: [0x02, rLengthByte])
asnSignature.append(rSig)
asnSignature.append(contentsOf: [0x02, sLengthByte])
asnSignature.append(sSig)
return asnSignature
}
static func asn1ToRSSig(asn1: Data) throws -> (Data, Data) {
let signatureLength: Int
if asn1.count < 96 {
signatureLength = 64
} else if asn1.count < 132 {
signatureLength = 96
} else {
signatureLength = 132
}
// Parse ASN into just r,s data as defined in:
// https://tools.ietf.org/html/rfc7518#section-3.4
let (asnSig, _) = ASN1.toASN1Element(data: asn1)
guard case let ASN1.ASN1Element.seq(elements: seq) = asnSig,
seq.count >= 2,
case let ASN1.ASN1Element.bytes(data: rData) = seq[0],
case let ASN1.ASN1Element.bytes(data: sData) = seq[1]
else {
throw ECError.failedASN1Decoding
}
// ASN adds 00 bytes in front of negative Int to mark it as positive.
// These must be removed to make r,a a valid EC signature
let trimmedRData: Data
let trimmedSData: Data
let rExtra = rData.count - signatureLength/2
if rExtra < 0 {
trimmedRData = Data(count: 1) + rData
} else {
trimmedRData = rData.dropFirst(rExtra)
}
let sExtra = sData.count - signatureLength/2
if sExtra < 0 {
trimmedSData = Data(count: 1) + sData
} else {
trimmedSData = sData.dropFirst(sExtra)
}
return (trimmedRData, trimmedSData)
}
}

View File

@ -0,0 +1,172 @@
// Copyright © 2019 IBM. All rights reserved.
//
// 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
/// An extensible list of elliptic curves supported by this repository.
@available(macOS 10.13, iOS 11, watchOS 4.0, tvOS 11.0, *)
public struct EllipticCurve: Equatable, CustomStringConvertible {
private let internalRepresentation: InternalRepresentation
// enum for faster comparisons
private enum InternalRepresentation: String {
case prime256v1, secp384r1, secp521r1
}
/// A prime256v1 curve.
public static let prime256v1 = EllipticCurve.p256
/// A secp384r1 curve.
public static let secp384r1 = EllipticCurve.p384
/// A secp521r1 curve.
public static let secp521r1 = EllipticCurve.p521
/// Checks if two Curves are equal, required for Equatable protocol.
public static func == (lhs: EllipticCurve, rhs: EllipticCurve) -> Bool {
return lhs.internalRepresentation == rhs.internalRepresentation
}
/// A String description of the Curve. Required for CustomStringConvertible protocol.
public var description: String {
return internalRepresentation.rawValue
}
#if os(Linux)
typealias CC_LONG = size_t
let signingAlgorithm: OpaquePointer?
let nativeCurve: Int32
let hashEngine = SHA256
let hashLength = CC_LONG(SHA256_DIGEST_LENGTH)
#else
let signingAlgorithm: SecKeyAlgorithm
let encryptionAlgorithm = SecKeyAlgorithm.eciesEncryptionStandardVariableIVX963SHA256AESGCM
let hashEngine: (_ data: UnsafeRawPointer?, _ len: CC_LONG, _ md: UnsafeMutablePointer<UInt8>?) -> UnsafeMutablePointer<UInt8>?
let hashLength: CC_LONG
#endif
let keySize: Int
#if os(Linux)
/// Secure Hash Algorithm 2 256-bit
static let p256 = EllipticCurve(internalRepresentation: .prime256v1,
signingAlgorithm: .init(EVP_sha256()),
nativeCurve: NID_X9_62_prime256v1,
keySize: 65)
/// Secure Hash Algorithm 2 384-bit
static let p384 = EllipticCurve(internalRepresentation: .secp384r1,
signingAlgorithm: .init(EVP_sha384()),
nativeCurve: NID_secp384r1,
keySize: 97)
/// Secure Hash Algorithm 512-bit
static let p521 = EllipticCurve(internalRepresentation: .secp521r1,
signingAlgorithm: .init(EVP_sha512()),
nativeCurve: NID_secp521r1,
keySize: 133)
#else
/// Secure Hash Algorithm 2 256-bit
static let p256 = EllipticCurve(internalRepresentation: .prime256v1,
signingAlgorithm: .ecdsaSignatureDigestX962SHA256,
hashEngine: CC_SHA256,
hashLength: CC_LONG(CC_SHA256_DIGEST_LENGTH),
keySize: 65)
/// Secure Hash Algorithm 2 384-bit
static let p384 = EllipticCurve(internalRepresentation: .secp384r1,
signingAlgorithm: .ecdsaSignatureDigestX962SHA384,
hashEngine: CC_SHA384,
hashLength: CC_LONG(CC_SHA384_DIGEST_LENGTH),
keySize: 97)
/// Secure Hash Algorithm 512-bit
static let p521 = EllipticCurve(internalRepresentation: .secp521r1,
signingAlgorithm: .ecdsaSignatureDigestX962SHA512,
hashEngine: CC_SHA512,
hashLength: CC_LONG(CC_SHA512_DIGEST_LENGTH),
keySize: 133)
#endif
// Select the ECAlgorithm based on the object identifier (OID) extracted from the EC key.
static func objectToCurve(ObjectIdentifier: Data) throws -> EllipticCurve {
if [UInt8](ObjectIdentifier) == [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07] {
// p-256 (e.g: prime256v1, secp256r1) private key
return .prime256v1
} else if [UInt8](ObjectIdentifier) == [0x2B, 0x81, 0x04, 0x00, 0x22] {
// p-384 (e.g: secp384r1) private key
return .secp384r1
} else if [UInt8](ObjectIdentifier) == [0x2B, 0x81, 0x04, 0x00, 0x23] {
// p-521 (e.g: secp521r1) private key
return .secp521r1
} else {
throw ECError.unsupportedCurve
}
}
/// Return a digest of the data based on the hashEngine.
func digest(data: Data) -> Data {
var hash = [UInt8](repeating: 0, count: Int(self.hashLength))
data.withUnsafeBytes { ptr in
guard let baseAddress = ptr.baseAddress else { return }
_ = self.hashEngine(baseAddress.assumingMemoryBound(to: UInt8.self), CC_LONG(data.count), &hash)
}
return Data(hash)
}
}
extension String {
///
/// Split a string to a specified length.
///
/// - Parameters:
/// - length: Length of each split string.
///
/// - Returns: `[String]` containing each string.
///
func split(to length: Int) -> [String] {
var result = [String]()
var collectedCharacters = [Character]()
collectedCharacters.reserveCapacity(length)
var count = 0
for character in self {
collectedCharacters.append(character)
count += 1
if count == length {
// Reached the desired length
count = 0
result.append(String(collectedCharacters))
collectedCharacters.removeAll(keepingCapacity: true)
}
}
// Append the remainder
if !collectedCharacters.isEmpty {
result.append(String(collectedCharacters))
}
return result
}
}

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)
}
}