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

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