KeychainService.swift 2.2 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465
  1. import Foundation
  2. import Security
  3. /// Minimal Keychain wrapper for storing the Chad Music API key.
  4. enum KeychainService {
  5. private static let service = "com.mixboard.chad-music"
  6. private static let apiKeyAccount = "api-key"
  7. static func saveAPIKey(_ key: String) throws {
  8. guard let data = key.data(using: .utf8) else { return }
  9. let deleteQuery: [String: Any] = [
  10. kSecClass as String: kSecClassGenericPassword,
  11. kSecAttrService as String: service,
  12. kSecAttrAccount as String: apiKeyAccount,
  13. ]
  14. SecItemDelete(deleteQuery as CFDictionary)
  15. let addQuery: [String: Any] = [
  16. kSecClass as String: kSecClassGenericPassword,
  17. kSecAttrService as String: service,
  18. kSecAttrAccount as String: apiKeyAccount,
  19. kSecValueData as String: data,
  20. kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
  21. ]
  22. let status = SecItemAdd(addQuery as CFDictionary, nil)
  23. guard status == errSecSuccess else {
  24. throw KeychainError.saveFailed(status)
  25. }
  26. }
  27. static func loadAPIKey() -> String? {
  28. let query: [String: Any] = [
  29. kSecClass as String: kSecClassGenericPassword,
  30. kSecAttrService as String: service,
  31. kSecAttrAccount as String: apiKeyAccount,
  32. kSecReturnData as String: true,
  33. kSecMatchLimit as String: kSecMatchLimitOne,
  34. ]
  35. var result: AnyObject?
  36. let status = SecItemCopyMatching(query as CFDictionary, &result)
  37. guard status == errSecSuccess, let data = result as? Data else { return nil }
  38. return String(data: data, encoding: .utf8)
  39. }
  40. static func deleteAPIKey() {
  41. let query: [String: Any] = [
  42. kSecClass as String: kSecClassGenericPassword,
  43. kSecAttrService as String: service,
  44. kSecAttrAccount as String: apiKeyAccount,
  45. ]
  46. SecItemDelete(query as CFDictionary)
  47. }
  48. enum KeychainError: LocalizedError {
  49. case saveFailed(OSStatus)
  50. var errorDescription: String? {
  51. switch self {
  52. case .saveFailed(let status):
  53. "Keychain save failed with status \(status)"
  54. }
  55. }
  56. }
  57. }