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}