feat(perception): integrate calibrated operator pipeline
Add calibrated K1 projection, recorded and near-live perception qualification, unified Rerun operator layers, bounded replay admission, audited viewer controls, worker experiments, and lab evidence.
This commit is contained in:
@@ -1,79 +1,649 @@
|
||||
import AppKit
|
||||
import CoreWLAN
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
private struct AssociationRequest: Decodable {
|
||||
let ssid: String
|
||||
let password: String
|
||||
private let keychainService = "NODEDC Mission Core Host Wi-Fi Profiles"
|
||||
private let credentialMaterialKeychainService =
|
||||
"NODEDC Mission Core Device Credential Materials"
|
||||
|
||||
private struct HostWifiRequest: Decodable {
|
||||
let action: String
|
||||
let profileID: String
|
||||
let ssid: String?
|
||||
let password: String?
|
||||
let scanTimeoutSeconds: Double?
|
||||
let credentialSourceID: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case action
|
||||
case profileID = "profile_id"
|
||||
case ssid
|
||||
case password
|
||||
case scanTimeoutSeconds = "scan_timeout_seconds"
|
||||
case credentialSourceID = "credential_source_id"
|
||||
}
|
||||
}
|
||||
|
||||
private struct AssociationResponse: Encodable {
|
||||
private struct StoredProfile: Codable {
|
||||
let schemaVersion: Int
|
||||
let ssid: String
|
||||
let password: String
|
||||
let credentialSource: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case schemaVersion = "schema_version"
|
||||
case ssid
|
||||
case password
|
||||
case credentialSource = "credential_source"
|
||||
}
|
||||
}
|
||||
|
||||
private struct StoredCredentialMaterial: Codable {
|
||||
let schemaVersion: Int
|
||||
let password: String
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case schemaVersion = "schema_version"
|
||||
case password
|
||||
}
|
||||
}
|
||||
|
||||
private struct HostWifiResponse: Encodable {
|
||||
let ok: Bool
|
||||
let adapter: String?
|
||||
let alreadyAssociated: Bool?
|
||||
let stored: Bool?
|
||||
let found: Bool?
|
||||
let profileAvailable: Bool?
|
||||
let profileEnrolled: Bool?
|
||||
let scanAttemptCount: Int?
|
||||
let scanElapsedMilliseconds: Int?
|
||||
let credentialSource: String?
|
||||
let reasonCode: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case ok
|
||||
case adapter
|
||||
case alreadyAssociated = "already_associated"
|
||||
case stored
|
||||
case found
|
||||
case profileAvailable = "profile_available"
|
||||
case profileEnrolled = "profile_enrolled"
|
||||
case scanAttemptCount = "scan_attempt_count"
|
||||
case scanElapsedMilliseconds = "scan_elapsed_ms"
|
||||
case credentialSource = "credential_source"
|
||||
case reasonCode = "reason_code"
|
||||
}
|
||||
}
|
||||
|
||||
private func emit(_ response: AssociationResponse, exitCode: Int32) -> Never {
|
||||
let encoder = JSONEncoder()
|
||||
if let data = try? encoder.encode(response) {
|
||||
private func emit(
|
||||
ok: Bool,
|
||||
adapter: String? = nil,
|
||||
alreadyAssociated: Bool? = nil,
|
||||
stored: Bool? = nil,
|
||||
found: Bool? = nil,
|
||||
profileAvailable: Bool? = nil,
|
||||
profileEnrolled: Bool? = nil,
|
||||
scanAttemptCount: Int? = nil,
|
||||
scanElapsedMilliseconds: Int? = nil,
|
||||
credentialSource: String? = nil,
|
||||
reasonCode: String? = nil,
|
||||
exitCode: Int32
|
||||
) -> Never {
|
||||
let response = HostWifiResponse(
|
||||
ok: ok,
|
||||
adapter: adapter,
|
||||
alreadyAssociated: alreadyAssociated,
|
||||
stored: stored,
|
||||
found: found,
|
||||
profileAvailable: profileAvailable,
|
||||
profileEnrolled: profileEnrolled,
|
||||
scanAttemptCount: scanAttemptCount,
|
||||
scanElapsedMilliseconds: scanElapsedMilliseconds,
|
||||
credentialSource: credentialSource,
|
||||
reasonCode: reasonCode
|
||||
)
|
||||
if let data = try? JSONEncoder().encode(response) {
|
||||
FileHandle.standardOutput.write(data)
|
||||
}
|
||||
exit(exitCode)
|
||||
}
|
||||
|
||||
private let input = FileHandle.standardInput.readDataToEndOfFile()
|
||||
guard input.count <= 1024 else {
|
||||
emit(
|
||||
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "request-too-large"),
|
||||
exitCode: 1
|
||||
private func profileIsValid(_ profile: StoredProfile) -> Bool {
|
||||
guard profile.schemaVersion == 1,
|
||||
let ssidData = profile.ssid.data(using: .utf8),
|
||||
(1 ... 32).contains(ssidData.count),
|
||||
let passwordData = profile.password.data(using: .utf8),
|
||||
(1 ... 64).contains(passwordData.count)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func credentialMaterialIsValid(_ material: StoredCredentialMaterial) -> Bool {
|
||||
guard material.schemaVersion == 1,
|
||||
let passwordData = material.password.data(using: .utf8),
|
||||
(8 ... 63).contains(passwordData.count)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
private func keychainQuery(service: String, account: String) -> [String: Any] {
|
||||
return [
|
||||
kSecClass as String: kSecClassGenericPassword,
|
||||
kSecAttrService as String: service,
|
||||
kSecAttrAccount as String: account,
|
||||
]
|
||||
}
|
||||
|
||||
private func profileKeychainQuery(profileID: String) -> [String: Any] {
|
||||
return keychainQuery(service: keychainService, account: profileID)
|
||||
}
|
||||
|
||||
private func materialKeychainQuery(sourceID: String) -> [String: Any] {
|
||||
return keychainQuery(service: credentialMaterialKeychainService, account: sourceID)
|
||||
}
|
||||
|
||||
private func loadProfile(profileID: String) throws -> StoredProfile {
|
||||
var query = profileKeychainQuery(profileID: profileID)
|
||||
query[kSecReturnData as String] = true
|
||||
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
guard status == errSecSuccess, let data = item as? Data else {
|
||||
throw NSError(domain: "HostWifiProfile", code: Int(status))
|
||||
}
|
||||
let profile = try JSONDecoder().decode(StoredProfile.self, from: data)
|
||||
guard profileIsValid(profile) else {
|
||||
throw NSError(domain: "HostWifiProfile", code: Int(errSecDecode))
|
||||
}
|
||||
return profile
|
||||
}
|
||||
|
||||
private func storeProfile(profileID: String, profile: StoredProfile) throws {
|
||||
guard profileIsValid(profile) else {
|
||||
throw NSError(domain: "HostWifiProfile", code: Int(errSecParam))
|
||||
}
|
||||
let data = try JSONEncoder().encode(profile)
|
||||
let query = profileKeychainQuery(profileID: profileID)
|
||||
let attributes = [
|
||||
kSecValueData as String: data,
|
||||
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
||||
] as [String: Any]
|
||||
|
||||
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
||||
if updateStatus == errSecSuccess {
|
||||
return
|
||||
}
|
||||
guard updateStatus == errSecItemNotFound else {
|
||||
throw NSError(domain: "HostWifiProfile", code: Int(updateStatus))
|
||||
}
|
||||
var newItem = query
|
||||
attributes.forEach { key, value in newItem[key] = value }
|
||||
let addStatus = SecItemAdd(newItem as CFDictionary, nil)
|
||||
guard addStatus == errSecSuccess else {
|
||||
throw NSError(domain: "HostWifiProfile", code: Int(addStatus))
|
||||
}
|
||||
}
|
||||
|
||||
private func loadCredentialMaterial(sourceID: String) throws -> StoredCredentialMaterial {
|
||||
var query = materialKeychainQuery(sourceID: sourceID)
|
||||
query[kSecReturnData as String] = true
|
||||
query[kSecMatchLimit as String] = kSecMatchLimitOne
|
||||
|
||||
var item: CFTypeRef?
|
||||
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
||||
guard status == errSecSuccess, let data = item as? Data else {
|
||||
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(status))
|
||||
}
|
||||
let material = try JSONDecoder().decode(StoredCredentialMaterial.self, from: data)
|
||||
guard credentialMaterialIsValid(material) else {
|
||||
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(errSecDecode))
|
||||
}
|
||||
return material
|
||||
}
|
||||
|
||||
private func storeCredentialMaterial(
|
||||
sourceID: String,
|
||||
material: StoredCredentialMaterial
|
||||
) throws {
|
||||
guard credentialMaterialIsValid(material) else {
|
||||
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(errSecParam))
|
||||
}
|
||||
let data = try JSONEncoder().encode(material)
|
||||
let query = materialKeychainQuery(sourceID: sourceID)
|
||||
let attributes = [
|
||||
kSecValueData as String: data,
|
||||
kSecAttrAccessible as String: kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly,
|
||||
] as [String: Any]
|
||||
|
||||
let updateStatus = SecItemUpdate(query as CFDictionary, attributes as CFDictionary)
|
||||
if updateStatus == errSecSuccess {
|
||||
return
|
||||
}
|
||||
guard updateStatus == errSecItemNotFound else {
|
||||
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(updateStatus))
|
||||
}
|
||||
var newItem = query
|
||||
attributes.forEach { key, value in newItem[key] = value }
|
||||
let addStatus = SecItemAdd(newItem as CFDictionary, nil)
|
||||
guard addStatus == errSecSuccess else {
|
||||
throw NSError(domain: "HostWifiCredentialMaterial", code: Int(addStatus))
|
||||
}
|
||||
}
|
||||
|
||||
private func loadSystemWiFiProfile(ssid: String, ssidData: Data) -> StoredProfile? {
|
||||
var password: NSString?
|
||||
let status = CWKeychainFindWiFiPassword(
|
||||
CWKeychainDomain.user,
|
||||
ssidData,
|
||||
&password
|
||||
)
|
||||
guard status == errSecSuccess, let password else {
|
||||
return nil
|
||||
}
|
||||
let profile = StoredProfile(
|
||||
schemaVersion: 1,
|
||||
ssid: ssid,
|
||||
password: password as String,
|
||||
credentialSource: "system-wifi-keychain"
|
||||
)
|
||||
return profileIsValid(profile) ? profile : nil
|
||||
}
|
||||
|
||||
private struct TargetedScanResult {
|
||||
let network: CWNetwork?
|
||||
let attemptCount: Int
|
||||
let elapsedMilliseconds: Int
|
||||
}
|
||||
|
||||
private func scanForExpectedNetwork(
|
||||
interface: CWInterface,
|
||||
ssid: String,
|
||||
ssidData: Data,
|
||||
timeoutSeconds: Double
|
||||
) throws -> TargetedScanResult {
|
||||
let started = ProcessInfo.processInfo.systemUptime
|
||||
var attemptCount = 0
|
||||
|
||||
while true {
|
||||
attemptCount += 1
|
||||
let networks = try interface.scanForNetworks(withSSID: ssidData)
|
||||
if let network = networks.first(where: { $0.ssid == ssid }) {
|
||||
return TargetedScanResult(
|
||||
network: network,
|
||||
attemptCount: attemptCount,
|
||||
elapsedMilliseconds: max(
|
||||
0,
|
||||
Int((ProcessInfo.processInfo.systemUptime - started) * 1_000)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
let elapsed = ProcessInfo.processInfo.systemUptime - started
|
||||
let remaining = timeoutSeconds - elapsed
|
||||
if remaining <= 0 {
|
||||
return TargetedScanResult(
|
||||
network: nil,
|
||||
attemptCount: attemptCount,
|
||||
elapsedMilliseconds: max(0, Int(elapsed * 1_000))
|
||||
)
|
||||
}
|
||||
Thread.sleep(forTimeInterval: min(0.75, remaining))
|
||||
}
|
||||
}
|
||||
|
||||
private func promptForDevicePassword(ssid: String) -> String? {
|
||||
let application = NSApplication.shared
|
||||
application.setActivationPolicy(.accessory)
|
||||
|
||||
let passwordField = NSSecureTextField(frame: NSRect(x: 0, y: 0, width: 360, height: 24))
|
||||
passwordField.placeholderString = "Пароль точки доступа K1"
|
||||
|
||||
let alert = NSAlert()
|
||||
alert.alertStyle = .informational
|
||||
alert.messageText = "Первое подключение к \(ssid)"
|
||||
alert.informativeText = "macOS не нашла локальный профиль этой точки доступа. Если credential вам неизвестен, нажмите «Отмена» и выполните авторизованный импорт device-профиля LixelGO. Введённое значение будет сохранено только в Keychain этого Mac и не попадёт в браузер, API, журнал или evidence Mission Core."
|
||||
alert.accessoryView = passwordField
|
||||
alert.addButton(withTitle: "Подключиться")
|
||||
alert.addButton(withTitle: "Отмена")
|
||||
|
||||
application.activate(ignoringOtherApps: true)
|
||||
guard alert.runModal() == .alertFirstButtonReturn else {
|
||||
return nil
|
||||
}
|
||||
return passwordField.stringValue
|
||||
}
|
||||
|
||||
private let input = FileHandle.standardInput.readDataToEndOfFile()
|
||||
guard input.count <= 2048 else {
|
||||
emit(ok: false, reasonCode: "request-too-large", exitCode: 1)
|
||||
}
|
||||
|
||||
do {
|
||||
let request = try JSONDecoder().decode(AssociationRequest.self, from: input)
|
||||
guard let ssidData = request.ssid.data(using: .utf8),
|
||||
(1 ... 32).contains(ssidData.count),
|
||||
let passwordData = request.password.data(using: .utf8),
|
||||
(1 ... 64).contains(passwordData.count)
|
||||
let request = try JSONDecoder().decode(HostWifiRequest.self, from: input)
|
||||
guard (1 ... 128).contains(request.profileID.count),
|
||||
request.profileID.allSatisfy({
|
||||
$0.isASCII && ($0.isLetter || $0.isNumber || ".-_".contains($0))
|
||||
})
|
||||
else {
|
||||
emit(ok: false, reasonCode: "profile-id-invalid", exitCode: 1)
|
||||
}
|
||||
|
||||
if request.action == "store-profile" {
|
||||
guard let ssid = request.ssid, let password = request.password else {
|
||||
emit(ok: false, reasonCode: "credential-missing", exitCode: 1)
|
||||
}
|
||||
try storeProfile(
|
||||
profileID: request.profileID,
|
||||
profile: StoredProfile(
|
||||
schemaVersion: 1,
|
||||
ssid: ssid,
|
||||
password: password,
|
||||
credentialSource: nil
|
||||
)
|
||||
)
|
||||
emit(ok: true, adapter: "macOS Keychain", stored: true, exitCode: 0)
|
||||
}
|
||||
|
||||
if request.action == "store-credential-material" {
|
||||
guard let password = request.password else {
|
||||
emit(ok: false, reasonCode: "credential-missing", exitCode: 1)
|
||||
}
|
||||
try storeCredentialMaterial(
|
||||
sourceID: request.profileID,
|
||||
material: StoredCredentialMaterial(schemaVersion: 1, password: password)
|
||||
)
|
||||
emit(ok: true, adapter: "macOS Keychain", stored: true, exitCode: 0)
|
||||
}
|
||||
|
||||
if request.action == "check-credential-material" {
|
||||
do {
|
||||
_ = try loadCredentialMaterial(sourceID: request.profileID)
|
||||
emit(
|
||||
ok: true,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: true,
|
||||
credentialSource: "exact-firmware-profile",
|
||||
exitCode: 0
|
||||
)
|
||||
} catch {
|
||||
emit(
|
||||
ok: true,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: false,
|
||||
exitCode: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if request.action == "ensure-profile" {
|
||||
guard let ssid = request.ssid,
|
||||
let ssidData = ssid.data(using: .utf8),
|
||||
(1 ... 32).contains(ssidData.count),
|
||||
let sourceID = request.credentialSourceID,
|
||||
(1 ... 128).contains(sourceID.count),
|
||||
sourceID.allSatisfy({
|
||||
$0.isASCII && ($0.isLetter || $0.isNumber || ".-_".contains($0))
|
||||
})
|
||||
else {
|
||||
emit(ok: false, reasonCode: "credential-source-invalid", exitCode: 1)
|
||||
}
|
||||
|
||||
let material: StoredCredentialMaterial
|
||||
do {
|
||||
material = try loadCredentialMaterial(sourceID: sourceID)
|
||||
} catch {
|
||||
emit(
|
||||
ok: true,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: false,
|
||||
profileEnrolled: false,
|
||||
reasonCode: "credential-source-unavailable",
|
||||
exitCode: 0
|
||||
)
|
||||
}
|
||||
|
||||
do {
|
||||
let existing = try loadProfile(profileID: request.profileID)
|
||||
guard existing.ssid == ssid else {
|
||||
emit(
|
||||
ok: false,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: false,
|
||||
profileEnrolled: false,
|
||||
reasonCode: "profile-ssid-mismatch",
|
||||
exitCode: 1
|
||||
)
|
||||
}
|
||||
if existing.password == material.password,
|
||||
existing.credentialSource != "exact-firmware-profile" {
|
||||
try storeProfile(
|
||||
profileID: request.profileID,
|
||||
profile: StoredProfile(
|
||||
schemaVersion: 1,
|
||||
ssid: ssid,
|
||||
password: existing.password,
|
||||
credentialSource: "exact-firmware-profile"
|
||||
)
|
||||
)
|
||||
}
|
||||
emit(
|
||||
ok: true,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: true,
|
||||
profileEnrolled: false,
|
||||
credentialSource: existing.password == material.password
|
||||
? "exact-firmware-profile"
|
||||
: (existing.credentialSource ?? "mission-core-keychain"),
|
||||
exitCode: 0
|
||||
)
|
||||
} catch {
|
||||
try storeProfile(
|
||||
profileID: request.profileID,
|
||||
profile: StoredProfile(
|
||||
schemaVersion: 1,
|
||||
ssid: ssid,
|
||||
password: material.password,
|
||||
credentialSource: "exact-firmware-profile"
|
||||
)
|
||||
)
|
||||
emit(
|
||||
ok: true,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: true,
|
||||
profileEnrolled: true,
|
||||
credentialSource: "exact-firmware-profile",
|
||||
exitCode: 0
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if request.action == "check-profile" {
|
||||
let profile: StoredProfile
|
||||
do {
|
||||
profile = try loadProfile(profileID: request.profileID)
|
||||
} catch {
|
||||
emit(
|
||||
ok: true,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: false,
|
||||
exitCode: 0
|
||||
)
|
||||
}
|
||||
if let expectedSSID = request.ssid, profile.ssid != expectedSSID {
|
||||
emit(
|
||||
ok: false,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: false,
|
||||
reasonCode: "profile-ssid-mismatch",
|
||||
exitCode: 1
|
||||
)
|
||||
}
|
||||
emit(
|
||||
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "credential-bounds"),
|
||||
exitCode: 1
|
||||
ok: true,
|
||||
adapter: "macOS Keychain",
|
||||
profileAvailable: true,
|
||||
exitCode: 0
|
||||
)
|
||||
}
|
||||
|
||||
guard request.action == "associate" || request.action == "scan-profile" else {
|
||||
emit(ok: false, reasonCode: "action-unsupported", exitCode: 1)
|
||||
}
|
||||
guard let interface = CWWiFiClient.shared().interface() else {
|
||||
emit(ok: false, reasonCode: "wifi-interface-unavailable", exitCode: 1)
|
||||
}
|
||||
let scanTimeoutSeconds = request.scanTimeoutSeconds ?? 0
|
||||
guard scanTimeoutSeconds >= 0, scanTimeoutSeconds <= 60 else {
|
||||
emit(ok: false, reasonCode: "scan-timeout-invalid", exitCode: 1)
|
||||
}
|
||||
|
||||
if request.action == "scan-profile" {
|
||||
let profile: StoredProfile
|
||||
do {
|
||||
profile = try loadProfile(profileID: request.profileID)
|
||||
} catch {
|
||||
emit(ok: false, reasonCode: "profile-unavailable", exitCode: 1)
|
||||
}
|
||||
let ssidData = profile.ssid.data(using: .utf8)!
|
||||
let scan = try scanForExpectedNetwork(
|
||||
interface: interface,
|
||||
ssid: profile.ssid,
|
||||
ssidData: ssidData,
|
||||
timeoutSeconds: scanTimeoutSeconds
|
||||
)
|
||||
emit(
|
||||
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "wifi-interface-unavailable"),
|
||||
ok: true,
|
||||
adapter: "CoreWLAN",
|
||||
found: scan.network != nil,
|
||||
scanAttemptCount: scan.attemptCount,
|
||||
scanElapsedMilliseconds: scan.elapsedMilliseconds,
|
||||
exitCode: 0
|
||||
)
|
||||
}
|
||||
|
||||
guard request.action == "associate" else {
|
||||
emit(ok: false, reasonCode: "action-unsupported", exitCode: 1)
|
||||
}
|
||||
guard let expectedSSID = request.ssid,
|
||||
let expectedSSIDData = expectedSSID.data(using: .utf8),
|
||||
(1 ... 32).contains(expectedSSIDData.count)
|
||||
else {
|
||||
emit(ok: false, reasonCode: "ssid-invalid", exitCode: 1)
|
||||
}
|
||||
|
||||
let scan = try scanForExpectedNetwork(
|
||||
interface: interface,
|
||||
ssid: expectedSSID,
|
||||
ssidData: expectedSSIDData,
|
||||
timeoutSeconds: scanTimeoutSeconds
|
||||
)
|
||||
guard let network = scan.network else {
|
||||
emit(
|
||||
ok: false,
|
||||
scanAttemptCount: scan.attemptCount,
|
||||
scanElapsedMilliseconds: scan.elapsedMilliseconds,
|
||||
reasonCode: "network-not-found",
|
||||
exitCode: 1
|
||||
)
|
||||
}
|
||||
|
||||
if interface.ssid() == request.ssid {
|
||||
emit(AssociationResponse(ok: true, alreadyAssociated: true, reasonCode: nil), exitCode: 0)
|
||||
var profileEnrolled = false
|
||||
var profileNeedsStore = false
|
||||
let credentialSource: String
|
||||
let profile: StoredProfile
|
||||
do {
|
||||
profile = try loadProfile(profileID: request.profileID)
|
||||
credentialSource = profile.credentialSource ?? "mission-core-keychain"
|
||||
} catch {
|
||||
if let systemProfile = loadSystemWiFiProfile(
|
||||
ssid: expectedSSID,
|
||||
ssidData: expectedSSIDData
|
||||
) {
|
||||
profile = systemProfile
|
||||
credentialSource = "system-wifi-keychain"
|
||||
profileNeedsStore = true
|
||||
} else {
|
||||
guard let password = promptForDevicePassword(ssid: expectedSSID) else {
|
||||
emit(
|
||||
ok: false,
|
||||
scanAttemptCount: scan.attemptCount,
|
||||
scanElapsedMilliseconds: scan.elapsedMilliseconds,
|
||||
reasonCode: "credential-entry-cancelled",
|
||||
exitCode: 1
|
||||
)
|
||||
}
|
||||
guard let passwordData = password.data(using: .utf8),
|
||||
(1 ... 64).contains(passwordData.count)
|
||||
else {
|
||||
emit(
|
||||
ok: false,
|
||||
scanAttemptCount: scan.attemptCount,
|
||||
scanElapsedMilliseconds: scan.elapsedMilliseconds,
|
||||
reasonCode: "credential-invalid",
|
||||
exitCode: 1
|
||||
)
|
||||
}
|
||||
profile = StoredProfile(
|
||||
schemaVersion: 1,
|
||||
ssid: expectedSSID,
|
||||
password: password,
|
||||
credentialSource: "native-secure-prompt"
|
||||
)
|
||||
credentialSource = "native-secure-prompt"
|
||||
profileNeedsStore = true
|
||||
}
|
||||
}
|
||||
|
||||
let networks = try interface.scanForNetworks(withSSID: ssidData)
|
||||
guard let network = networks.first(where: { $0.ssid == request.ssid }) else {
|
||||
guard profile.ssid == expectedSSID else {
|
||||
emit(
|
||||
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "network-not-found"),
|
||||
ok: false,
|
||||
scanAttemptCount: scan.attemptCount,
|
||||
scanElapsedMilliseconds: scan.elapsedMilliseconds,
|
||||
reasonCode: "profile-ssid-mismatch",
|
||||
exitCode: 1
|
||||
)
|
||||
}
|
||||
|
||||
try interface.associate(to: network, password: request.password)
|
||||
if interface.ssid() == profile.ssid {
|
||||
if profileNeedsStore {
|
||||
try storeProfile(profileID: request.profileID, profile: profile)
|
||||
profileEnrolled = true
|
||||
}
|
||||
emit(
|
||||
ok: true,
|
||||
adapter: "CoreWLAN",
|
||||
alreadyAssociated: true,
|
||||
profileEnrolled: profileEnrolled,
|
||||
scanAttemptCount: scan.attemptCount,
|
||||
scanElapsedMilliseconds: scan.elapsedMilliseconds,
|
||||
credentialSource: credentialSource,
|
||||
exitCode: 0
|
||||
)
|
||||
}
|
||||
try interface.associate(to: network, password: profile.password)
|
||||
if profileNeedsStore {
|
||||
try storeProfile(profileID: request.profileID, profile: profile)
|
||||
profileEnrolled = true
|
||||
}
|
||||
// CoreWLAN's synchronous association call throws on failure. Reading the
|
||||
// current SSID again would require Location authorization on recent macOS
|
||||
// versions and could turn a successful association into a false negative.
|
||||
emit(AssociationResponse(ok: true, alreadyAssociated: false, reasonCode: nil), exitCode: 0)
|
||||
} catch {
|
||||
emit(
|
||||
AssociationResponse(ok: false, alreadyAssociated: nil, reasonCode: "corewlan-error"),
|
||||
exitCode: 1
|
||||
ok: true,
|
||||
adapter: "CoreWLAN",
|
||||
alreadyAssociated: false,
|
||||
profileEnrolled: profileEnrolled,
|
||||
scanAttemptCount: scan.attemptCount,
|
||||
scanElapsedMilliseconds: scan.elapsedMilliseconds,
|
||||
credentialSource: credentialSource,
|
||||
exitCode: 0
|
||||
)
|
||||
} catch {
|
||||
emit(ok: false, reasonCode: "corewlan-error", exitCode: 1)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user