mirror of
https://github.com/nalinbhardwaj/Vespass.git
synced 2026-01-14 00:07:56 -05:00
80 lines
3.1 KiB
Swift
80 lines
3.1 KiB
Swift
//
|
|
// GenericPasswordStore.swift
|
|
// Vespass
|
|
//
|
|
// Created by Nalin Bhardwaj on 22/12/22.
|
|
// Copyright © 2022 Vespass. All rights reserved.
|
|
//
|
|
// Methods for storing generic password convertible items in the keychain.
|
|
|
|
import Foundation
|
|
import CryptoKit
|
|
import Security
|
|
|
|
struct GenericPasswordStore {
|
|
/// Stores a CryptoKit key in the keychain as a generic password.
|
|
func storeKey<T: GenericPasswordConvertible>(_ key: T, account: String) throws {
|
|
// Treat the key data as a generic password.
|
|
let query = [kSecClass: kSecClassGenericPassword,
|
|
kSecAttrAccount: KEYCHAIN_STORE_PREFIX + account,
|
|
kSecAttrAccessible: kSecAttrAccessibleWhenUnlocked,
|
|
kSecUseDataProtectionKeychain: true,
|
|
kSecValueData: key.rawRepresentation] as [String: Any]
|
|
|
|
// Add the key data.
|
|
let status = SecItemAdd(query as CFDictionary, nil)
|
|
guard status == errSecSuccess else {
|
|
throw KeyStoreError("Unable to store item: \(status.message)")
|
|
}
|
|
}
|
|
|
|
/// Reads a CryptoKit key from the keychain as a generic password.
|
|
func readKey<T: GenericPasswordConvertible>(account: String) throws -> T? {
|
|
|
|
// Seek a generic password with the given account.
|
|
let query = [kSecClass: kSecClassGenericPassword,
|
|
kSecAttrAccount: KEYCHAIN_STORE_PREFIX + account,
|
|
kSecUseDataProtectionKeychain: true,
|
|
kSecReturnData: true] as [String: Any]
|
|
|
|
// Find and cast the result as data.
|
|
var item: CFTypeRef?
|
|
switch SecItemCopyMatching(query as CFDictionary, &item) {
|
|
case errSecSuccess:
|
|
guard let data = item as? Data else { return nil }
|
|
return try T(rawRepresentation: data) // Convert back to a key.
|
|
case errSecItemNotFound: return nil
|
|
case let status: throw KeyStoreError("Keychain read failed: \(status.message)")
|
|
}
|
|
}
|
|
|
|
/// Stores a key in the keychain and then reads it back.
|
|
func roundTrip<T: GenericPasswordConvertible>(_ key: T) throws -> T {
|
|
|
|
// An account name for the key in the keychain.
|
|
let account = "com.example.genericpassword.key"
|
|
|
|
// Start fresh.
|
|
try deleteKey(account: account)
|
|
|
|
// Store and read it back.
|
|
try storeKey(key, account: account)
|
|
guard let key: T = try readKey(account: account) else {
|
|
throw KeyStoreError("Failed to locate stored key.")
|
|
}
|
|
return key
|
|
}
|
|
|
|
/// Removes any existing key with the given account.
|
|
func deleteKey(account: String) throws {
|
|
let query = [kSecClass: kSecClassGenericPassword,
|
|
kSecUseDataProtectionKeychain: true,
|
|
kSecAttrAccount: KEYCHAIN_STORE_PREFIX + account] as [String: Any]
|
|
switch SecItemDelete(query as CFDictionary) {
|
|
case errSecItemNotFound, errSecSuccess: break // Okay to ignore
|
|
case let status:
|
|
throw KeyStoreError("Unexpected deletion error: \(status.message)")
|
|
}
|
|
}
|
|
}
|