Repository navigation
Expand file tree
/
Copy pathSecretStore.swift
More file actions
114 lines (104 loc) · 4.24 KB
/
Copy pathSecretStore.swift
File metadata and controls
114 lines (104 loc) · 4.24 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import Foundation
import Security
enum SecretStore {
static func keychainPassword(service: String, account: String? = nil) -> String? {
if let viaSecurity = securityPassword(service: service, account: account), !viaSecurity.isEmpty {
return viaSecurity
}
return secItemPassword(service: service, account: account)
}
static func securityPassword(service: String, account: String? = nil) -> String? {
var args = ["find-generic-password", "-s", service, "-w"]
if let account, !account.isEmpty {
args.insert(contentsOf: ["-a", account], at: 3)
}
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/security")
process.arguments = args
let stdout = Pipe()
let stderr = Pipe()
process.standardOutput = stdout
process.standardError = stderr
do {
try process.run()
process.waitUntilExit()
} catch {
return nil
}
guard process.terminationStatus == 0 else { return nil }
let data = stdout.fileHandleForReading.readDataToEndOfFile()
let text = String(data: data, encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines)
return text?.isEmpty == false ? text : nil
}
private static func secItemPassword(service: String, account: String?) -> String? {
var query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecReturnData as String: true,
kSecMatchLimit as String: kSecMatchLimitOne,
]
if let account {
query[kSecAttrAccount as String] = account
}
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess, let data = item as? Data else { return nil }
return String(data: data, encoding: .utf8)
}
static func setAppSecret(account: String, secret: String) {
let service = "app.checkusage.secrets"
let data = Data(secret.utf8)
let query: [String: Any] = [
kSecClass as String: kSecClassGenericPassword,
kSecAttrService as String: service,
kSecAttrAccount as String: account,
]
SecItemDelete(query as CFDictionary)
guard !secret.isEmpty else { return }
var add = query
add[kSecValueData as String] = data
add[kSecAttrAccessible as String] = kSecAttrAccessibleAfterFirstUnlock
SecItemAdd(add as CFDictionary, nil)
}
static func appSecret(account: String) -> String? {
keychainPassword(service: "app.checkusage.secrets", account: account)
}
static func jsonObject(from text: String) -> JSONValue? {
guard let data = text.data(using: .utf8) else { return nil }
return try? JSONValue.parse(data)
}
static func readJSONFile(_ path: String) -> JSONValue? {
let url = URL(fileURLWithPath: (path as NSString).expandingTildeInPath)
guard let data = try? Data(contentsOf: url) else { return nil }
return try? JSONValue.parse(data)
}
static func sqliteValue(db path: String, sql: String) -> String? {
let expanded = (path as NSString).expandingTildeInPath
guard FileManager.default.fileExists(atPath: expanded) else { return nil }
let process = Process()
process.executableURL = URL(fileURLWithPath: "/usr/bin/sqlite3")
process.arguments = ["-readonly", expanded, sql]
let stdout = Pipe()
process.standardOutput = stdout
process.standardError = Pipe()
do {
try process.run()
process.waitUntilExit()
} catch {
return nil
}
guard process.terminationStatus == 0 else { return nil }
let text = String(data: stdout.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8)?
.trimmingCharacters(in: .whitespacesAndNewlines)
return text?.isEmpty == false ? text : nil
}
}
enum Home {
static var user: String {
NSUserName()
}
static func path(_ components: String...) -> String {
NSHomeDirectory() + "/" + components.joined(separator: "/")
}
}