forked from Reflejo/pam-touchID
-
Notifications
You must be signed in to change notification settings - Fork 68
/
watchid-pam-extension.swift
102 lines (79 loc) · 3.3 KB
/
watchid-pam-extension.swift
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
import LocalAuthentication
// MARK: (Re)define PAM constants here so we don't need to import .h files.
private let PAM_SUCCESS = CInt(0)
private let PAM_AUTH_ERR = CInt(9)
private let PAM_IGNORE = CInt(25)
private let PAM_SILENT = CInt(bitPattern: 0x80000000)
private let DEFAULT_REASON = "perform an action that requires authentication"
public typealias vchar = UnsafePointer<UnsafeMutablePointer<CChar>>
public typealias pam_handle_t = UnsafeRawPointer?
// MARK: Biometric (touchID) authentication
@_cdecl("pam_sm_authenticate")
public func pam_sm_authenticate(pamh: pam_handle_t, flags: CInt, argc: CInt, argv: vchar) -> CInt {
let sudoArguments = ProcessInfo.processInfo.arguments
if sudoArguments.contains("-A") || sudoArguments.contains("--askpass") {
return PAM_IGNORE
}
let arguments = parseArguments(argc: Int(argc), argv: argv)
var reason = arguments["reason"] ?? DEFAULT_REASON
reason = reason.isEmpty ? DEFAULT_REASON : reason
let policy = LAPolicy.deviceOwnerAuthenticationIgnoringUserID
let context = LAContext()
if !context.canEvaluatePolicy(policy, error: nil) {
return PAM_IGNORE
}
let semaphore = DispatchSemaphore(value: 0)
var result = PAM_AUTH_ERR
context.evaluatePolicy(policy, localizedReason: reason) { success, error in
defer { semaphore.signal() }
if let error = error {
if flags & PAM_SILENT == 0 {
fputs("\(error.localizedDescription)\n", stderr)
}
result = PAM_IGNORE
return
}
result = success ? PAM_SUCCESS : PAM_AUTH_ERR
}
semaphore.wait()
return result
}
private func parseArguments(argc: Int, argv: vchar) -> [String: String] {
var parsed = [String: String]()
let arguments = UnsafeBufferPointer(start: argv, count: argc)
.compactMap { String(cString: $0) }
.joined(separator: " ")
let regex = try? NSRegularExpression(pattern: "[^\\s\"']+|\"([^\"]*)\"|'([^']*)'",
options: .dotMatchesLineSeparators)
let matches = regex?.matches(in: arguments, options: .withoutAnchoringBounds,
range: NSRange(location: 0, length: arguments.count))
let nsArguments = arguments as NSString
let groups = matches?
.map { nsArguments.substring(with: $0.range) }
.map { ($0 as String).trimmingCharacters(in: CharacterSet(charactersIn: "\"'")) }
for argument in groups ?? [] {
let pieces = argument.components(separatedBy: "=")
if pieces.count == 2, let key = pieces.first, let value = pieces.last {
parsed[key] = value
}
}
return parsed
}
private extension LAPolicy {
static var deviceOwnerAuthenticationIgnoringUserID: LAPolicy {
return .deviceOwnerAuthenticationWithBiometricsOrWatch
}
}
// MARK: - Ignored (unhandled) PAM events
@_cdecl("pam_sm_chauthtok")
public func pam_sm_chauthtok(pamh: pam_handle_t, flags: CInt, argc: CInt, argv: vchar) -> CInt {
return PAM_IGNORE
}
@_cdecl("pam_sm_setcred")
public func pam_sm_setcred(pamh: pam_handle_t, flags: CInt, argc: CInt, argv: vchar) -> CInt {
return PAM_IGNORE
}
@_cdecl("pam_sm_acct_mgmt")
public func pam_sm_acct_mgmt(pamh: pam_handle_t, flags: CInt, argc: CInt, argv: vchar) -> CInt {
return PAM_IGNORE
}