|
|
|
@ -1,24 +1,31 @@ |
|
|
|
|
import Foundation |
|
|
|
|
import CryptoKit |
|
|
|
|
|
|
|
|
|
// For the moment, only supports ecdsa-sha2-nistp256 and ecdsa-sha2-nistp386 keys |
|
|
|
|
/// Generates OpenSSH representations of Secrets. |
|
|
|
|
public struct OpenSSHKeyWriter { |
|
|
|
|
|
|
|
|
|
/// Initializes the writer. |
|
|
|
|
public init() { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Generates an OpenSSH data payload identifying the secret. |
|
|
|
|
/// - Returns: OpenSSH data payload identifying the secret. |
|
|
|
|
public func data<SecretType: Secret>(secret: SecretType) -> Data { |
|
|
|
|
lengthAndData(of: curveType(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!) + |
|
|
|
|
lengthAndData(of: curveIdentifier(for: secret.algorithm, length: secret.keySize).data(using: .utf8)!) + |
|
|
|
|
lengthAndData(of: secret.publicKey) |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Generates an OpenSSH string representation of the secret. |
|
|
|
|
/// - Returns: OpenSSH string representation of the secret. |
|
|
|
|
public func openSSHString<SecretType: Secret>(secret: SecretType, comment: String? = nil) -> String { |
|
|
|
|
[curveType(for: secret.algorithm, length: secret.keySize), data(secret: secret).base64EncodedString(), comment] |
|
|
|
|
.compactMap { $0 } |
|
|
|
|
.joined(separator: " ") |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Generates an OpenSSH SHA256 fingerprint string. |
|
|
|
|
/// - Returns: OpenSSH SHA256 fingerprint string. |
|
|
|
|
public func openSSHSHA256Fingerprint<SecretType: Secret>(secret: SecretType) -> String { |
|
|
|
|
// OpenSSL format seems to strip the padding at the end. |
|
|
|
|
let base64 = Data(SHA256.hash(data: data(secret: secret))).base64EncodedString() |
|
|
|
@ -27,6 +34,8 @@ public struct OpenSSHKeyWriter { |
|
|
|
|
return "SHA256:\(cleaned)" |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/// Generates an OpenSSH MD5 fingerprint string. |
|
|
|
|
/// - Returns: OpenSSH MD5 fingerprint string. |
|
|
|
|
public func openSSHMD5Fingerprint<SecretType: Secret>(secret: SecretType) -> String { |
|
|
|
|
Insecure.MD5.hash(data: data(secret: secret)) |
|
|
|
|
.compactMap { ("0" + String($0, radix: 16, uppercase: false)).suffix(2) } |
|
|
|
@ -37,23 +46,37 @@ public struct OpenSSHKeyWriter { |
|
|
|
|
|
|
|
|
|
extension OpenSSHKeyWriter { |
|
|
|
|
|
|
|
|
|
/// Creates an OpenSSH protocol style data object, which has a length header, followed by the data payload. |
|
|
|
|
/// - Parameter data: The data payload. |
|
|
|
|
/// - Returns: OpenSSH data. |
|
|
|
|
public func lengthAndData(of data: Data) -> Data { |
|
|
|
|
let rawLength = UInt32(data.count) |
|
|
|
|
var endian = rawLength.bigEndian |
|
|
|
|
return Data(bytes: &endian, count: UInt32.bitWidth/8) + data |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func curveIdentifier(for algorithm: Algorithm, length: Int) -> String { |
|
|
|
|
/// The fully qualified OpenSSH identifier for the algorithm. |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - algorithm: The algorithm to identify. |
|
|
|
|
/// - length: The key length of the algorithm. |
|
|
|
|
/// - Returns: The OpenSSH identifier for the algorithm. |
|
|
|
|
public func curveType(for algorithm: Algorithm, length: Int) -> String { |
|
|
|
|
switch algorithm { |
|
|
|
|
case .ellipticCurve: |
|
|
|
|
return "nistp" + String(describing: length) |
|
|
|
|
return "ecdsa-sha2-nistp" + String(describing: length) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
public func curveType(for algorithm: Algorithm, length: Int) -> String { |
|
|
|
|
/// The OpenSSH identifier for an algorithm. |
|
|
|
|
/// - Parameters: |
|
|
|
|
/// - algorithm: The algorithm to identify. |
|
|
|
|
/// - length: The key length of the algorithm. |
|
|
|
|
/// - Returns: The OpenSSH identifier for the algorithm. |
|
|
|
|
private func curveIdentifier(for algorithm: Algorithm, length: Int) -> String { |
|
|
|
|
switch algorithm { |
|
|
|
|
case .ellipticCurve: |
|
|
|
|
return "ecdsa-sha2-nistp" + String(describing: length) |
|
|
|
|
return "nistp" + String(describing: length) |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
} |
|
|
|
|