keychain.swift

  1import Foundation
  2import Security
  3
  4// Compilation: swiftc keychain.swift -o keychain
  5// Usage: ./keychain <get|set|delete> <service> <account> [password]
  6
  7enum KeychainError: Error {
  8    case unhandledError(status: OSStatus)
  9}
 10
 11func setPassword(service: String, account: String, password: Data) throws {
 12    let query: [String: Any] = [
 13        kSecClass as String: kSecClassGenericPassword,
 14        kSecAttrService as String: service,
 15        kSecAttrAccount as String: account,
 16        kSecValueData as String: password,
 17        kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlock
 18    ]
 19
 20    let status = SecItemAdd(query as CFDictionary, nil)
 21    
 22    if status == errSecDuplicateItem {
 23        let updateQuery: [String: Any] = [
 24            kSecClass as String: kSecClassGenericPassword,
 25            kSecAttrService as String: service,
 26            kSecAttrAccount as String: account
 27        ]
 28        let attributesToUpdate: [String: Any] = [
 29            kSecValueData as String: password
 30        ]
 31        let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributesToUpdate as CFDictionary)
 32        if updateStatus != errSecSuccess {
 33            throw KeychainError.unhandledError(status: updateStatus)
 34        }
 35    } else if status != errSecSuccess {
 36        throw KeychainError.unhandledError(status: status)
 37    }
 38}
 39
 40func getPassword(service: String, account: String) throws -> Data? {
 41    let query: [String: Any] = [
 42        kSecClass as String: kSecClassGenericPassword,
 43        kSecAttrService as String: service,
 44        kSecAttrAccount as String: account,
 45        kSecReturnData as String: kCFBooleanTrue!,
 46        kSecMatchLimit as String: kSecMatchLimitOne
 47    ]
 48
 49    var dataTypeRef: AnyObject?
 50    let status = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
 51
 52    if status == errSecItemNotFound {
 53        return nil
 54    } else if status != errSecSuccess {
 55        throw KeychainError.unhandledError(status: status)
 56    }
 57
 58    return dataTypeRef as? Data
 59}
 60
 61func deletePassword(service: String, account: String) throws {
 62    let query: [String: Any] = [
 63        kSecClass as String: kSecClassGenericPassword,
 64        kSecAttrService as String: service,
 65        kSecAttrAccount as String: account
 66    ]
 67
 68    let status = SecItemDelete(query as CFDictionary)
 69    if status != errSecSuccess && status != errSecItemNotFound {
 70        throw KeychainError.unhandledError(status: status)
 71    }
 72}
 73
 74let args = ProcessInfo.processInfo.arguments
 75guard args.count >= 4 else {
 76    print("Usage: ./keychain <get|set|delete> <service> <account> [password]")
 77    exit(1)
 78}
 79
 80let action = args[1]
 81let service = args[2]
 82let account = args[3]
 83
 84do {
 85    switch action {
 86    case "set":
 87        guard args.count > 4 else { exit(1) }
 88        let password = args[4].data(using: .utf8)!
 89        try setPassword(service: service, account: account, password: password)
 90        print("Success")
 91    case "get":
 92        if let data = try getPassword(service: service, account: account),
 93           let password = String(data: data, encoding: .utf8) {
 94            print(password)
 95        }
 96    case "delete":
 97        try deletePassword(service: service, account: account)
 98        print("Success")
 99    default:
100        exit(1)
101    }
102} catch {
103    print("Error: \(error)")
104    exit(1)
105}