import Foundation import SecretKit import CryptoKit struct Stub {} extension Stub { public final class Store: SecretStore { public let isAvailable = true public let id = UUID() public let name = "Stub" public var secrets: [Secret] = [] public var shouldThrow = false public init() { // try! create(size: 256) // try! create(size: 384) } public func create(size: Int) throws { let flags: SecAccessControlCreateFlags = [] let access = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flags, nil) as Any let attributes = KeychainDictionary([ kSecAttrLabel: name, kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits: size, kSecPrivateKeyAttrs: [ kSecAttrIsPermanent: true, kSecAttrAccessControl: access ] ]) let privateKey = SecKeyCreateRandomKey(attributes, nil)! let publicKey = SecKeyCopyPublicKey(privateKey)! let publicAttributes = SecKeyCopyAttributes(publicKey) as! [CFString: Any] let privateAttributes = SecKeyCopyAttributes(privateKey) as! [CFString: Any] let publicData = (publicAttributes[kSecValueData] as! Data) let privateData = (privateAttributes[kSecValueData] as! Data) let secret = Secret(keySize: size, publicKey: publicData, privateKey: privateData) print(secret) print("Public Key OpenSSH: \(OpenSSHKeyWriter().openSSHString(secret: secret))") } public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) throws -> Data { guard !shouldThrow else { throw NSError(domain: "test", code: 0, userInfo: nil) } let privateKey = SecKeyCreateWithData(secret.privateKey as CFData, KeychainDictionary([ kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom, kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeyClass: kSecAttrKeyClassPrivate ]) , nil)! return SecKeyCreateSignature(privateKey, signatureAlgorithm(for: secret), data as CFData, nil)! as Data } public func verify(signature: Data, for data: Data, with secret: Stub.Secret) throws -> Bool { let attributes = KeychainDictionary([ kSecAttrKeyType: secret.algorithm.secAttrKeyType, kSecAttrKeySizeInBits: secret.keySize, kSecAttrKeyClass: kSecAttrKeyClassPublic ]) var verifyError: Unmanaged? let untyped: CFTypeRef? = SecKeyCreateWithData(secret.publicKey as CFData, attributes, &verifyError) guard let untypedSafe = untyped else { throw NSError(domain: "test", code: 0, userInfo: nil) } let key = untypedSafe as! SecKey let verified = SecKeyVerifySignature(key, signatureAlgorithm(for: secret), data as CFData, signature as CFData, &verifyError) if let verifyError { if verifyError.takeUnretainedValue() ~= .verifyError { return false } else { throw NSError(domain: "test", code: 0, userInfo: nil) } } return verified } public func existingPersistedAuthenticationContext(secret: Stub.Secret) -> PersistedAuthenticationContext? { nil } public func persistAuthentication(secret: Stub.Secret, forDuration duration: TimeInterval) throws { } public func reloadSecrets() { } } } extension Stub { struct Secret: SecretKit.Secret, CustomDebugStringConvertible { let id = UUID().uuidString.data(using: .utf8)! let name = UUID().uuidString let algorithm = Algorithm.ellipticCurve let keySize: Int let publicKey: Data let requiresAuthentication = false let privateKey: Data init(keySize: Int, publicKey: Data, privateKey: Data) { self.keySize = keySize self.publicKey = publicKey self.privateKey = privateKey } var debugDescription: String { """ Key Size \(keySize) Private: \(privateKey.base64EncodedString()) Public: \(publicKey.base64EncodedString()) """ } } } extension Stub.Store { struct StubError: Error { } }