diff --git a/Sources/CryptorECC/ECAlgorithm.swift b/Sources/CryptorECC/ECAlgorithm.swift deleted file mode 100644 index 2118b28..0000000 --- a/Sources/CryptorECC/ECAlgorithm.swift +++ /dev/null @@ -1,116 +0,0 @@ -// 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 - -// Information about the elliptic curve algorithm that will be used for signing/verifying. -@available(OSX 10.13, *) -struct ECAlgorithm { - #if os(Linux) - typealias CC_LONG = size_t - let signingAlgorithm: OpaquePointer? - let curve: Int32 - let hashEngine: (_ data: UnsafePointer, _ len: CC_LONG, _ md: UnsafeMutablePointer) -> UnsafeMutablePointer? = SHA256 - let hashLength: size_t = CC_LONG(SHA256_DIGEST_LENGTH) - #else - let signingAlgorithm: SecKeyAlgorithm - let curve: SecKeyAlgorithm = .eciesEncryptionStandardVariableIVX963SHA256AESGCM - let hashEngine: (_ data: UnsafeRawPointer?, _ len: CC_LONG, _ md: UnsafeMutablePointer?) -> UnsafeMutablePointer? - let hashLength: CC_LONG - #endif - let keySize: Int - enum Pid: String { - case prime256v1, secp384r1, secp521r1 - } - let id: Pid - - #if os(Linux) - /// Secure Hash Algorithm 2 256-bit - static let p256 = ECAlgorithm(signingAlgorithm: .init(EVP_sha256()), - curve: NID_X9_62_prime256v1, - keySize: 65, - id: .prime256v1) - #else - /// Secure Hash Algorithm 2 256-bit - static let p256 = ECAlgorithm(signingAlgorithm: .ecdsaSignatureDigestX962SHA256, - hashEngine: CC_SHA256, - hashLength: CC_LONG(CC_SHA256_DIGEST_LENGTH), - keySize: 65, - id: .prime256v1) - #endif - - #if os(Linux) - /// Secure Hash Algorithm 2 384-bit - static let p384 = ECAlgorithm(signingAlgorithm: .init(EVP_sha384()), - curve: NID_secp384r1, - keySize: 97, - id: .secp384r1) - #else - /// Secure Hash Algorithm 2 384-bit - static let p384 = ECAlgorithm(signingAlgorithm: .ecdsaSignatureDigestX962SHA384, - hashEngine: CC_SHA384, - hashLength: CC_LONG(CC_SHA384_DIGEST_LENGTH), - keySize: 97, - id: .secp384r1) - #endif - - #if os(Linux) - /// Secure Hash Algorithm 512-bit - static let p521 = ECAlgorithm(signingAlgorithm: .init(EVP_sha512()), - curve: NID_secp521r1, - keySize: 133, - id: .secp521r1) - #else - /// Secure Hash Algorithm 512-bit - static let p521 = ECAlgorithm(signingAlgorithm: .ecdsaSignatureDigestX962SHA512, - hashEngine: CC_SHA512, - hashLength: CC_LONG(CC_SHA512_DIGEST_LENGTH), - keySize: 133, - id: .secp521r1) - #endif - - // Select the ECAlgorithm based on the object identifier (OID) extracted from the EC key. - static func objectToHashAlg(ObjectIdentifier: Data) throws -> ECAlgorithm { - - if [UInt8](ObjectIdentifier) == [0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x03, 0x01, 0x07] { - // p-256 (e.g: prime256v1, secp256r1) private key - return p256 - } else if [UInt8](ObjectIdentifier) == [0x2B, 0x81, 0x04, 0x00, 0x22] { - // p-384 (e.g: secp384r1) private key - return p384 - } else if [UInt8](ObjectIdentifier) == [0x2B, 0x81, 0x04, 0x00, 0x23] { - // p-521 (e.g: secp521r1) private key - return p521 - } 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 { - _ = self.hashEngine($0, CC_LONG(data.count), &hash) - } - return Data(bytes: hash) - } -} diff --git a/Sources/CryptorECC/ECDecryptable.swift b/Sources/CryptorECC/ECDecryptable.swift index b7562a2..1c93a89 100644 --- a/Sources/CryptorECC/ECDecryptable.swift +++ b/Sources/CryptorECC/ECDecryptable.swift @@ -37,7 +37,7 @@ extension Data { EVP_CIPHER_CTX_init_wrapper(rsaDecryptCtx) let tagLength = 16 - let encKeyLength = key.algorithm.keySize + 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..? = nil guard let eData = SecKeyCreateDecryptedData(key.nativeKey, - key.algorithm.curve, + key.curve.encryptionAlgorithm, self as CFData, &error) else { diff --git a/Sources/CryptorECC/ECEncryptable.swift b/Sources/CryptorECC/ECEncryptable.swift index 19771d9..ec121bf 100644 --- a/Sources/CryptorECC/ECEncryptable.swift +++ b/Sources/CryptorECC/ECEncryptable.swift @@ -56,7 +56,7 @@ extension Data: ECEncryptable { public func encrypt(with key: ECPublicKey) throws -> Data { #if os(Linux) // Compute symmetric key - let ec_key = EC_KEY_new_by_curve_name(key.algorithm.curve) + let ec_key = EC_KEY_new_by_curve_name(key.curve.nativeCurve) defer { EC_KEY_free(ec_key) } @@ -72,7 +72,7 @@ extension Data: ECEncryptable { 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.allocate(capacity: key.algorithm.keySize) + let pubk = UnsafeMutablePointer.allocate(capacity: key.curve.keySize) BN_bn2bin(pub_bn, pubk) defer { BN_CTX_end(pub_bn_ctx) @@ -82,7 +82,7 @@ extension Data: ECEncryptable { pubk.deallocate() symKey.deallocate() #else - pubk.deallocate(capacity: key.algorithm.keySize) + pubk.deallocate(capacity: key.curve.keySize) symKey.deallocate(capacity: symKey_len) #endif } @@ -90,9 +90,9 @@ extension Data: ECEncryptable { // get aes key and iv using ANSI x9.63 Key Derivation Function let symKeyData = Data(bytes: symKey, count: symKey_len) let counterData = Data(bytes: [0x00, 0x00, 0x00, 0x01]) - let sharedInfo = Data(bytes: pubk, count: key.algorithm.keySize) + let sharedInfo = Data(bytes: pubk, count: key.curve.keySize) let preHashKey = symKeyData + counterData + sharedInfo - let hashedKey = key.algorithm.digest(data: preHashKey) + 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)) @@ -158,14 +158,14 @@ extension Data: ECEncryptable { } // Construct the envelope by combining the encrypted AES key, the encrypted date and the GCM tag. - let ekFinal = Data(bytes: pubk, count: key.algorithm.keySize) + 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? = nil guard let eData = SecKeyCreateEncryptedData(key.nativeKey, - key.algorithm.curve, + key.curve.encryptionAlgorithm, self as CFData, &error) else { diff --git a/Sources/CryptorECC/ECPrivateKey.swift b/Sources/CryptorECC/ECPrivateKey.swift index a2ad024..5acd31f 100644 --- a/Sources/CryptorECC/ECPrivateKey.swift +++ b/Sources/CryptorECC/ECPrivateKey.swift @@ -45,8 +45,12 @@ import OpenSSL */ @available(OSX 10.13, *) public class ECPrivateKey { - /// The Elliptic curve this key was generated from. + /// 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? deinit { EC_KEY_free(.make(optional: self.nativeKey)) } @@ -54,7 +58,6 @@ public class ECPrivateKey { typealias NativeKey = SecKey #endif let nativeKey: NativeKey - let algorithm: ECAlgorithm let pubKeyBytes: Data @@ -119,8 +122,7 @@ public class ECPrivateKey { else { throw ECError.failedASN1Decoding } - self.algorithm = try ECAlgorithm.objectToHashAlg(ObjectIdentifier: privateKeyID) - self.curveId = self.algorithm.id.rawValue + self.curve = try EllipticCurve.objectToCurve(ObjectIdentifier: privateKeyID) guard case let ASN1.ASN1Element.bytes(data: privateOctest) = es[2] else { throw ECError.failedASN1Decoding } @@ -147,8 +149,9 @@ public class ECPrivateKey { let trimmedPubBytes = publicKeyData.drop(while: { $0 == 0x00}) self.nativeKey = try ECPrivateKey.bytesToNativeKey(privateKeyData: privateKeyData, publicKeyData: trimmedPubBytes, - algorithm: algorithm) + curve: curve) self.pubKeyBytes = trimmedPubBytes + self.curveId = curve.description } /// Initialize an ECPrivateKey from a SEC1 `.der` file data. @@ -167,8 +170,7 @@ public class ECPrivateKey { else { throw ECError.failedASN1Decoding } - self.algorithm = try ECAlgorithm.objectToHashAlg(ObjectIdentifier: objectId) - self.curveId = self.algorithm.id.rawValue + 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 { @@ -177,8 +179,9 @@ public class ECPrivateKey { let trimmedPubBytes = publicKeyData.drop(while: { $0 == 0x00}) self.nativeKey = try ECPrivateKey.bytesToNativeKey(privateKeyData: privateKeyData, publicKeyData: trimmedPubBytes, - algorithm: algorithm) + curve: curve) self.pubKeyBytes = trimmedPubBytes + self.curveId = curve.description } /// Initialize the `ECPublicKey`for this private key by extracting the public key bytes. @@ -192,17 +195,17 @@ public class ECPrivateKey { // OBJECT IDENTIFIER // OBJECT IDENTIFIER // BIT STRING (This is the `pubKeyBytes` added afterwards) - if self.algorithm.id == .prime256v1 { + if self.curve == .prime256v1 { keyHeader = Data(bytes: [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.algorithm.id == .secp384r1 { + } else if self.curve == .secp384r1 { keyHeader = Data(bytes: [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.algorithm.id == .secp521r1 { + } else if self.curve == .secp521r1 { keyHeader = Data(bytes: [0x30, 0x81, 0x9B, 0x30, 0x10, 0x06, 0x07, 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01, @@ -219,7 +222,7 @@ public class ECPrivateKey { } - private static func bytesToNativeKey(privateKeyData: Data, publicKeyData: Data, algorithm: ECAlgorithm) throws -> NativeKey { + private static func bytesToNativeKey(privateKeyData: Data, publicKeyData: Data, curve: EllipticCurve) throws -> NativeKey { #if os(Linux) let bigNum = BN_new() defer { @@ -228,7 +231,7 @@ public class ECPrivateKey { privateKeyData.withUnsafeBytes({ (privateKeyBytes: UnsafePointer) -> Void in BN_bin2bn(privateKeyBytes, Int32(privateKeyData.count), bigNum) }) - let ecKey = EC_KEY_new_by_curve_name(algorithm.curve) + 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 diff --git a/Sources/CryptorECC/ECPublicKey.swift b/Sources/CryptorECC/ECPublicKey.swift index be97cb5..dd51dd7 100644 --- a/Sources/CryptorECC/ECPublicKey.swift +++ b/Sources/CryptorECC/ECPublicKey.swift @@ -47,8 +47,11 @@ import OpenSSL */ @available(OSX 10.13, *) public class ECPublicKey { - /// The Elliptic curve this key was generated from. + /// 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 @@ -57,7 +60,6 @@ public class ECPublicKey { typealias NativeKey = SecKey #endif let nativeKey: NativeKey - let algorithm: ECAlgorithm /// The public key represented as a PEM String. public let pemString: String @@ -112,8 +114,8 @@ public class ECPublicKey { else { throw ECError.failedASN1Decoding } - self.algorithm = try ECAlgorithm.objectToHashAlg(ObjectIdentifier: privateKeyID) - self.curveId = self.algorithm.id.rawValue + self.curve = try EllipticCurve.objectToCurve(ObjectIdentifier: privateKeyID) + self.curveId = curve.description let keyData = publicKeyData.drop(while: { $0 == 0x00}) #if os(Linux) self.pubKeyBytes = keyData @@ -124,7 +126,7 @@ public class ECPublicKey { publicKeyData.withUnsafeBytes({ (pubKeyBytes: UnsafePointer) -> Void in BN_bin2bn(pubKeyBytes, Int32(publicKeyData.count), bigNum) }) - let ecKey = EC_KEY_new_by_curve_name(algorithm.curve) + let ecKey = EC_KEY_new_by_curve_name(curve.nativeCurve) let ecGroup = EC_KEY_get0_group(ecKey) let ecPoint = EC_POINT_new(ecGroup) defer { diff --git a/Sources/CryptorECC/ECSignable.swift b/Sources/CryptorECC/ECSignable.swift index 8742fd0..cfbb404 100644 --- a/Sources/CryptorECC/ECSignable.swift +++ b/Sources/CryptorECC/ECSignable.swift @@ -59,7 +59,7 @@ extension Data: ECSignable { throw ECError.failedNativeKeyCreation } - guard EVP_DigestSignInit(md_ctx, nil, .make(optional: key.algorithm.signingAlgorithm), nil, evp_key) == 1 else { + guard EVP_DigestSignInit(md_ctx, nil, .make(optional: key.curve.signingAlgorithm), nil, evp_key) == 1 else { throw ECError.failedEvpInit } @@ -84,13 +84,13 @@ extension Data: ECSignable { } return try ECSignature(asn1: Data(bytes: sig, count: sig_len)) #else - let hash = key.algorithm.digest(data: self) + let hash = key.curve.digest(data: self) // Memory storage for error from SecKeyCreateSignature var error: Unmanaged? = nil // cfSignature is CFData that is ANS1 encoded as a sequence of two UInts (r and s) guard let cfSignature = SecKeyCreateSignature(key.nativeKey, - key.algorithm.signingAlgorithm, + key.curve.signingAlgorithm, hash as CFData, &error) else { diff --git a/Sources/CryptorECC/ECSignature.swift b/Sources/CryptorECC/ECSignature.swift index 9b75bcc..bd9cbba 100644 --- a/Sources/CryptorECC/ECSignature.swift +++ b/Sources/CryptorECC/ECSignature.swift @@ -94,7 +94,7 @@ public struct ECSignature { return false } - EVP_DigestVerifyInit(md_ctx, nil, .make(optional: ecPublicKey.algorithm.signingAlgorithm), nil, evp_key) + EVP_DigestVerifyInit(md_ctx, nil, .make(optional: ecPublicKey.curve.signingAlgorithm), nil, evp_key) guard plaintext.withUnsafeBytes({ (message: UnsafePointer) -> Int32 in return EVP_DigestUpdate(md_ctx, message, plaintext.count) }) == 1 else { @@ -107,12 +107,12 @@ public struct ECSignature { return rc == 1 #else - let hash = ecPublicKey.algorithm.digest(data: plaintext) + let hash = ecPublicKey.curve.digest(data: plaintext) // Memory storage for error from SecKeyVerifySignature var error: Unmanaged? = nil return SecKeyVerifySignature(ecPublicKey.nativeKey, - ecPublicKey.algorithm.signingAlgorithm, + ecPublicKey.curve.signingAlgorithm, hash as CFData, self.asn1 as CFData, &error) diff --git a/Sources/CryptorECC/EllipticCurve.swift b/Sources/CryptorECC/EllipticCurve.swift new file mode 100644 index 0000000..bb5563a --- /dev/null +++ b/Sources/CryptorECC/EllipticCurve.swift @@ -0,0 +1,135 @@ +// 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(OSX 10.13, *) +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?) -> UnsafeMutablePointer? + 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 { + _ = self.hashEngine($0, CC_LONG(data.count), &hash) + } + return Data(bytes: hash) + } +} diff --git a/Tests/CryptorECCTests/CryptorECCTests.swift b/Tests/CryptorECCTests/CryptorECCTests.swift index 73fd8d1..0fc17b5 100644 --- a/Tests/CryptorECCTests/CryptorECCTests.swift +++ b/Tests/CryptorECCTests/CryptorECCTests.swift @@ -23,6 +23,7 @@ final class CryptorECCTests: XCTestCase { ("test_LinuxEncrypted", test_LinuxEncrypted), ("test_EncryptionCycle384", test_EncryptionCycle384), ("test_EncryptionCycle512", test_EncryptionCycle512), + ("test_ExtractPublicKey", test_ExtractPublicKey), ] let ecPemPrivateKey = """