2024-08-07 09:43:48 -07:00
|
|
|
//
|
|
|
|
|
// Security.swift
|
|
|
|
|
// Meshtastic
|
|
|
|
|
//
|
|
|
|
|
// Copyright(c) Garth Vander Houwen 8/7/24.
|
|
|
|
|
//
|
|
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
|
import SwiftUI
|
|
|
|
|
import CoreData
|
|
|
|
|
import MeshtasticProtobufs
|
|
|
|
|
import OSLog
|
2025-06-16 17:24:54 -04:00
|
|
|
import CryptoKit
|
2024-08-07 09:43:48 -07:00
|
|
|
|
|
|
|
|
struct SecurityConfig: View {
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2024-08-11 20:50:48 -07:00
|
|
|
private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom }
|
2024-08-07 09:43:48 -07:00
|
|
|
@Environment(\.managedObjectContext) var context
|
Transports Interface to Support TCP for all Platforms and Serial on Mac (#1341)
* Initial implementation of transports
* Initial LogRadio implementation
* Fixes for Settings view (caused by debug commenting)
* Refinement of the object and actor model
* Connect view text and tab updates
* Fix mac catalyst and tests
* Warning and logging clean-up
* In progress commit
* Serial Transport and Reconnect draft work
* Serial transport and reconnection draft work
* Quick fix for BLE - still more work to do
* interim commit
* More in progress changes
* Minor improvements
* Pretty good initial implementation
* Bump version beyond the app store
* Fix for disconnection swipeAction
* Tweaks to TCPConnection implementation
* Retry for NONCE_ONLY_DB
* Revert json string change
* Simplified some of the API + "Anti-discovery"
* Tweaks for devices leaving the discovery process
* Bump version
* iOS26 Tweaks
* Tweaks and bug fixes
* Add link with slash sf symbol
* update symbol image on connect view
* BLE disconnect handling
* Log privacy attributes
* Onboarding and minor fixes.
* change database to nodes, add emoji to tcp logs
* Error handling improvements
* More logging emojis
* Suppressed unnecessary errors on disconnect
* Heartbeat emoji
* Add bluetooth symbol
* add privacy attributes to [TCP] logs, add custom bluetooth logo
* Improve routing logs
* Emoji for connect logs
* Heartbeat emoji
* Add CBCentralManagerScanOptionAllowDuplicatesKey options to central for bluetooth
* fix nav errors by switching from observableobject to state
* Update connection indicator icon
* fix for BLE disconnects
* Connection process fixes
* More fixes/tweaks to connection process
* Strict concurrency
* Fix some warnings, remove wifi warning
* delete stale keys
* interim commit
* Update privacy for log, fix wrong space
* fix a couple of linting items
* Switch to targeted
* interim commit
* BLE Signal strenth on connect view
* Remove BLE RSSI from long press menu
* Modem lights
* minor spacing tweak
* Additional BLE logging and a scanning fix.
* Discovery and BLE RSSI improvements
* Background suspension
* Update isConnected to enable UI during db load
* update protobufs
* Replace config if statements with switches, Fix unknown module config logging, make dark mode modem circle stroke color white so they are visible
* Additional logging cleanup
* hast
* Set unmessagable to true if the longname has the unmessagable emoji
* Connect error handling improvements
* Admin popup list icon and activity lights updates
* Revert use of .toolbar back to .navigationBarItems
* More public logging
* Better BLE error handling
* Node DB progress meter
* minor tweak to activity light interaction timing
* Fix comment linting, remove stale keys
* Remove stale keys
* Easy linting fixes
* Two more simple linting fixes
* clean up meshtasticapp
* More public logging
* Replay config
* Logging
* Fix for unselected node on Settings
* Tweak to progress meter based on device idiom
* Update protos
* Session replay redaction of messages
* Serial fix for old devices, and a let statement
* Mask text too
* Fix typo
* BLE poweredOff is now an auto-reconnectable error
* Update logging
* Fix for peerRemovedPairingInformation
* Logging for BLE peripheral:didUpdateValueFor errors.
* Fix for inconsistent swipe disconnect behavior
* periperal:didUpdateValueFor error handling
* Fix for BLEConnection continuation guarding
* BLEConnection actor deadlock on disconnect
* Heartbeat nonce
* Fix for swipe disconnect and task cancellation
* Fix for swipe actions not honoring .disabled()
* Tell BLETransport when BLEConnection is cancelled
* Update navigation logging
* Logging updates
* Bump version to 2.7.0
* Organize into folders and heartbeat stuff
* Minor improvements to manual TCP connection
* Auto-connect toggle
* Possible BLE bug, still waiting to see in logs
* Concurrency tweaks
* Concurrency improvements
* requestDeviceMetadata fix. fixes remote admin
* Minor typo fixes
* "All" button for log filters: category and level
* More robust continuation handling for BLE
* @FetchRequest based ChannelMessageList
* Update info.plist and device hardware file
* Move auto connect toggle to app settings and debug mode, tint properly with the accent color
* Add label to auto connect toggle
* Update log for node info received from ourselves over the mesh
* Remove unused scrollViewProxy
* Update Meshtastic/Views/Onboarding/DeviceOnboarding.swift
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update target for connect view
* Properly Set datadog environment
* Comment out ble manager
* Adjust cyclomatic complexity thresholds in .swiftlint.yml
* Linting fixes, delete ble manager
* Make session replay debug only
---------
Co-authored-by: jake-b <jake-b@users.noreply.github.com>
Co-authored-by: jake <jake@jakes-Mac-mini.local>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-27 08:09:02 -07:00
|
|
|
@EnvironmentObject var accessoryManager: AccessoryManager
|
2024-08-07 09:43:48 -07:00
|
|
|
@Environment(\.dismiss) private var goBack
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2024-08-07 09:43:48 -07:00
|
|
|
var node: NodeInfoEntity?
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2024-08-07 09:43:48 -07:00
|
|
|
@State var hasChanges = false
|
|
|
|
|
@State var publicKey = ""
|
|
|
|
|
@State var privateKey = ""
|
2024-08-19 21:50:28 -07:00
|
|
|
@State var hasValidPrivateKey: Bool = false
|
2025-03-14 07:28:58 -07:00
|
|
|
@State var adminKey: String = ""
|
|
|
|
|
@State var adminKey2: String = ""
|
|
|
|
|
@State var adminKey3: String = ""
|
2024-08-19 21:50:28 -07:00
|
|
|
@State var hasValidAdminKey: Bool = true
|
2024-10-10 06:13:14 -07:00
|
|
|
@State var hasValidAdminKey2: Bool = true
|
|
|
|
|
@State var hasValidAdminKey3: Bool = true
|
2024-08-08 07:33:31 -07:00
|
|
|
@State var isManaged = false
|
|
|
|
|
@State var serialEnabled = false
|
2024-08-08 10:39:45 -07:00
|
|
|
@State var debugLogApiEnabled = false
|
2025-06-16 17:24:54 -04:00
|
|
|
@State var privateKeyIsSecure = true
|
2025-06-18 08:44:13 -07:00
|
|
|
@State var backupStatus: KeyBackupStatus?
|
|
|
|
|
@State var backupStatusError: OSStatus?
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2025-06-16 17:24:54 -04:00
|
|
|
private var isValidKeyPair: Bool {
|
2025-06-16 15:51:27 -07:00
|
|
|
guard let privateKeyBytes = Data(base64Encoded: privateKey),
|
2025-06-17 09:11:51 -07:00
|
|
|
let calculatedPublicKey = generatePublicKeyDisplay(from: privateKeyBytes),
|
2025-06-16 15:51:27 -07:00
|
|
|
let decodedPublicKey = Data(base64Encoded: publicKey) else {
|
|
|
|
|
return false
|
2025-06-16 17:24:54 -04:00
|
|
|
}
|
2025-06-16 15:51:27 -07:00
|
|
|
return calculatedPublicKey == decodedPublicKey
|
2025-06-16 17:24:54 -04:00
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2024-08-07 09:43:48 -07:00
|
|
|
var body: some View {
|
2025-10-05 17:51:18 -07:00
|
|
|
Form {
|
|
|
|
|
ConfigHeader(title: "Security", config: \.securityConfig, node: node, onAppear: setSecurityValues)
|
|
|
|
|
Text("Security Config Settings require a firmware version 2.5+")
|
|
|
|
|
.font(.title3)
|
|
|
|
|
Section(header: Text("Direct Message Key")) {
|
|
|
|
|
VStack(alignment: .leading) {
|
|
|
|
|
Label("Public Key", systemImage: "key")
|
|
|
|
|
Text(publicKey)
|
|
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
|
|
|
|
.allowsTightening(true)
|
|
|
|
|
.monospaced()
|
|
|
|
|
.keyboardType(.alphabet)
|
|
|
|
|
.foregroundStyle(.tertiary)
|
|
|
|
|
.disableAutocorrection(true)
|
|
|
|
|
.textSelection(.enabled)
|
|
|
|
|
.background(
|
|
|
|
|
RoundedRectangle(cornerRadius: 10.0)
|
|
|
|
|
.stroke(isValidKeyPair ? Color.clear : Color.red, lineWidth: 2.0)
|
|
|
|
|
)
|
|
|
|
|
Text("Your public key is generated from your private key and sent to other nodes on the mesh so they can compute a shared secret key with you.")
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
|
|
|
|
Divider()
|
|
|
|
|
Label("Private Key", systemImage: "key.fill")
|
|
|
|
|
SecureInput("Private Key", text: $privateKey, isValid: $hasValidPrivateKey, isSecure: $privateKeyIsSecure)
|
|
|
|
|
.background(
|
|
|
|
|
RoundedRectangle(cornerRadius: 10.0)
|
|
|
|
|
.stroke(hasValidPrivateKey ? Color.clear : Color.red, lineWidth: 2.0)
|
|
|
|
|
)
|
|
|
|
|
Text("Used to create a shared key with a remote device.")
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
|
|
|
|
if let currentNode = node {
|
2024-10-10 06:13:14 -07:00
|
|
|
Divider()
|
2025-10-05 17:51:18 -07:00
|
|
|
Label("Key Backup", systemImage: "icloud")
|
|
|
|
|
HStack(alignment: .firstTextBaseline) {
|
|
|
|
|
let keychainKey = "PrivateKeyNode\(currentNode.num)"
|
|
|
|
|
Button {
|
|
|
|
|
let status = KeychainHelper.standard.save(key: keychainKey, value: privateKey)
|
|
|
|
|
if status == errSecSuccess {
|
|
|
|
|
backupStatus = KeyBackupStatus.saved
|
|
|
|
|
} else {
|
|
|
|
|
backupStatus = KeyBackupStatus.saveFailed
|
|
|
|
|
backupStatusError = status
|
2025-06-18 01:00:27 -07:00
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
label: {
|
|
|
|
|
Image(systemName: "icloud.and.arrow.up")
|
|
|
|
|
Text("Backup")
|
2025-06-18 08:44:13 -07:00
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
.buttonStyle(.bordered)
|
|
|
|
|
.buttonBorderShape(.capsule)
|
|
|
|
|
.controlSize(.small)
|
2025-06-14 14:25:46 -07:00
|
|
|
Spacer()
|
|
|
|
|
Button {
|
2025-10-05 17:51:18 -07:00
|
|
|
if let value = KeychainHelper.standard.read(key: keychainKey) {
|
|
|
|
|
self.privateKey = value
|
2025-06-16 17:24:54 -04:00
|
|
|
self.privateKeyIsSecure = false
|
2025-10-05 17:51:18 -07:00
|
|
|
backupStatus = KeyBackupStatus.restored
|
|
|
|
|
} else {
|
|
|
|
|
backupStatus = KeyBackupStatus.restoreFailed
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
label: {
|
|
|
|
|
Image(systemName: "key.icloud")
|
|
|
|
|
Text("Restore")
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.bordered)
|
|
|
|
|
.buttonBorderShape(.capsule)
|
|
|
|
|
.controlSize(.small)
|
|
|
|
|
Spacer()
|
|
|
|
|
Button {
|
|
|
|
|
let status = KeychainHelper.standard.delete(key: keychainKey)
|
|
|
|
|
if status == errSecSuccess {
|
|
|
|
|
backupStatus = KeyBackupStatus.deleted
|
|
|
|
|
} else {
|
|
|
|
|
backupStatus = KeyBackupStatus.deleteFailed
|
2025-06-14 14:25:46 -07:00
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
}
|
|
|
|
|
label: {
|
|
|
|
|
Image(systemName: "trash")
|
2025-06-14 14:25:46 -07:00
|
|
|
}
|
|
|
|
|
.buttonStyle(.bordered)
|
|
|
|
|
.buttonBorderShape(.capsule)
|
|
|
|
|
.controlSize(.small)
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
if let status = backupStatus {
|
|
|
|
|
let state = status.success
|
|
|
|
|
Text("\(status.description)")
|
|
|
|
|
.font(.caption)
|
|
|
|
|
.foregroundColor(state ? .green : .red)
|
|
|
|
|
}
|
|
|
|
|
Text("Backup your private key to your iCloud keychain.")
|
2025-06-20 06:52:57 -07:00
|
|
|
.foregroundStyle(.secondary)
|
2025-06-20 06:48:58 -07:00
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
2024-08-08 07:33:31 -07:00
|
|
|
}
|
2025-06-18 01:00:27 -07:00
|
|
|
Divider()
|
2025-10-05 17:51:18 -07:00
|
|
|
HStack(alignment: .firstTextBaseline) {
|
|
|
|
|
Label("Regenerate Private Key", systemImage: "arrow.clockwise.circle")
|
|
|
|
|
Spacer()
|
|
|
|
|
Button {
|
|
|
|
|
if let keyBytes = generatePrivateKey(count: 32) {
|
|
|
|
|
privateKey = keyBytes.base64EncodedString()
|
|
|
|
|
self.privateKeyIsSecure = false
|
|
|
|
|
}
|
|
|
|
|
} label: {
|
|
|
|
|
Image(systemName: "lock.rotation")
|
|
|
|
|
.font(.title)
|
|
|
|
|
}
|
|
|
|
|
.buttonStyle(.bordered)
|
|
|
|
|
.buttonBorderShape(.capsule)
|
|
|
|
|
.controlSize(.small)
|
|
|
|
|
}
|
|
|
|
|
Text("Generate a new private key to replace the one currently in use. The public key will automatically be regenerated from your private key.")
|
2025-06-18 01:00:27 -07:00
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
}
|
|
|
|
|
Section(header: Text("Admin Keys")) {
|
|
|
|
|
Label("Primary Admin Key", systemImage: "key.viewfinder")
|
|
|
|
|
SecureInput("Primary Admin Key", text: $adminKey, isValid: $hasValidAdminKey)
|
|
|
|
|
.background(
|
|
|
|
|
RoundedRectangle(cornerRadius: 10.0)
|
|
|
|
|
.stroke(hasValidAdminKey ? Color.clear : Color.red, lineWidth: 2.0)
|
|
|
|
|
)
|
|
|
|
|
Text("The primary public key authorized to send admin messages to this node.")
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
|
|
|
|
Label("Secondary Admin Key", systemImage: "key.viewfinder")
|
|
|
|
|
SecureInput("Secondary Admin Key", text: $adminKey2, isValid: $hasValidAdminKey2)
|
|
|
|
|
.background(
|
|
|
|
|
RoundedRectangle(cornerRadius: 10.0)
|
|
|
|
|
.stroke(hasValidAdminKey2 ? Color.clear : Color.red, lineWidth: 2.0)
|
|
|
|
|
)
|
|
|
|
|
Text("The secondary public key authorized to send admin messages to this node.")
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
|
|
|
|
Label("Tertiary Admin Key", systemImage: "key.viewfinder")
|
|
|
|
|
SecureInput("Tertiary Admin Key", text: $adminKey3, isValid: $hasValidAdminKey3)
|
|
|
|
|
.background(
|
|
|
|
|
RoundedRectangle(cornerRadius: 10.0)
|
|
|
|
|
.stroke(hasValidAdminKey3 ? Color.clear : Color.red, lineWidth: 2.0)
|
|
|
|
|
)
|
|
|
|
|
Text("The tertiary public key authorized to send admin messages to this node.")
|
|
|
|
|
.foregroundStyle(.secondary)
|
|
|
|
|
.font(idiom == .phone ? .caption : .callout)
|
|
|
|
|
}
|
|
|
|
|
Section(header: Text("Logs")) {
|
|
|
|
|
Toggle(isOn: $serialEnabled) {
|
|
|
|
|
Label("Serial Console", systemImage: "terminal")
|
|
|
|
|
Text("Serial Console over the Stream API.")
|
|
|
|
|
}
|
|
|
|
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
|
|
|
|
Toggle(isOn: $debugLogApiEnabled) {
|
|
|
|
|
Label("Debug Logs", systemImage: "ant.fill")
|
|
|
|
|
Text("Output live debug logging over serial, view and export position-redacted device logs over Bluetooth.")
|
|
|
|
|
}
|
|
|
|
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
|
|
|
|
}
|
|
|
|
|
if adminKey.length > 0 || UserDefaults.enableAdministration {
|
|
|
|
|
Section(header: Text("Administration")) {
|
|
|
|
|
Toggle(isOn: $isManaged) {
|
|
|
|
|
Label("Managed Device", systemImage: "gearshape.arrow.triangle.2.circlepath")
|
|
|
|
|
Text("Device is managed by a mesh administrator, the user is unable to access any of the device settings.")
|
2024-08-08 07:33:31 -07:00
|
|
|
}
|
|
|
|
|
.toggleStyle(SwitchToggleStyle(tint: .accentColor))
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
.disabled(!accessoryManager.isConnected || node?.securityConfig == nil)
|
|
|
|
|
.safeAreaInset(edge: .bottom, alignment: .center) {
|
|
|
|
|
HStack(spacing: 0) {
|
|
|
|
|
SaveConfigButton(node: node, hasChanges: $hasChanges) {
|
|
|
|
|
|
|
|
|
|
if !hasValidPrivateKey || !hasValidAdminKey || !hasValidAdminKey2 || !hasValidAdminKey3 {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
guard let deviceNum = accessoryManager.activeDeviceNum,
|
|
|
|
|
let connectedNode = getNodeInfo(id: deviceNum, context: context),
|
|
|
|
|
let fromUser = connectedNode.user,
|
|
|
|
|
let toUser = node?.user else {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
var config = Config.SecurityConfig()
|
|
|
|
|
config.privateKey = Data(base64Encoded: privateKey) ?? Data()
|
|
|
|
|
config.adminKey = [Data(base64Encoded: adminKey) ?? Data(), Data(base64Encoded: adminKey2) ?? Data(), Data(base64Encoded: adminKey3) ?? Data()]
|
|
|
|
|
config.isManaged = isManaged
|
|
|
|
|
config.serialEnabled = serialEnabled
|
|
|
|
|
config.debugLogApiEnabled = debugLogApiEnabled
|
|
|
|
|
|
|
|
|
|
let keyUpdated = node?.securityConfig?.privateKey?.base64EncodedString() ?? "" != privateKey
|
|
|
|
|
Task {
|
|
|
|
|
_ = try await accessoryManager.saveSecurityConfig(
|
|
|
|
|
config: config,
|
|
|
|
|
fromUser: fromUser,
|
|
|
|
|
toUser: toUser
|
|
|
|
|
)
|
|
|
|
|
Task { @MainActor in
|
|
|
|
|
// Should show a saved successfully alert once I know that to be true
|
|
|
|
|
// for now just disable the button after a successful save
|
|
|
|
|
if keyUpdated {
|
|
|
|
|
node?.user?.publicKey = Data(base64Encoded: publicKey) ?? Data()
|
|
|
|
|
do {
|
|
|
|
|
try context.save()
|
|
|
|
|
Logger.data.info("💾 Saved UserEntity Public Key to Core Data for \(node?.num ?? 0, privacy: .public)")
|
|
|
|
|
} catch {
|
|
|
|
|
context.rollback()
|
|
|
|
|
let nsError = error as NSError
|
|
|
|
|
Logger.data.error("Error Updating Core Data UserEntity: \(nsError, privacy: .public)")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
hasChanges = false
|
|
|
|
|
if keyUpdated {
|
|
|
|
|
Task {
|
|
|
|
|
do {
|
|
|
|
|
try await accessoryManager.sendReboot(
|
|
|
|
|
fromUser: fromUser,
|
|
|
|
|
toUser: toUser
|
|
|
|
|
)
|
|
|
|
|
} catch {
|
|
|
|
|
Logger.mesh.warning("Reboot Failed")
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-08 07:33:31 -07:00
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
goBack()
|
2024-08-08 07:33:31 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-07 09:43:48 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-11 20:50:48 -07:00
|
|
|
.scrollDismissesKeyboard(.immediately)
|
2024-08-08 07:33:31 -07:00
|
|
|
.navigationTitle("Security Config")
|
2024-08-11 09:59:52 -07:00
|
|
|
.navigationBarItems(trailing: ZStack {
|
Transports Interface to Support TCP for all Platforms and Serial on Mac (#1341)
* Initial implementation of transports
* Initial LogRadio implementation
* Fixes for Settings view (caused by debug commenting)
* Refinement of the object and actor model
* Connect view text and tab updates
* Fix mac catalyst and tests
* Warning and logging clean-up
* In progress commit
* Serial Transport and Reconnect draft work
* Serial transport and reconnection draft work
* Quick fix for BLE - still more work to do
* interim commit
* More in progress changes
* Minor improvements
* Pretty good initial implementation
* Bump version beyond the app store
* Fix for disconnection swipeAction
* Tweaks to TCPConnection implementation
* Retry for NONCE_ONLY_DB
* Revert json string change
* Simplified some of the API + "Anti-discovery"
* Tweaks for devices leaving the discovery process
* Bump version
* iOS26 Tweaks
* Tweaks and bug fixes
* Add link with slash sf symbol
* update symbol image on connect view
* BLE disconnect handling
* Log privacy attributes
* Onboarding and minor fixes.
* change database to nodes, add emoji to tcp logs
* Error handling improvements
* More logging emojis
* Suppressed unnecessary errors on disconnect
* Heartbeat emoji
* Add bluetooth symbol
* add privacy attributes to [TCP] logs, add custom bluetooth logo
* Improve routing logs
* Emoji for connect logs
* Heartbeat emoji
* Add CBCentralManagerScanOptionAllowDuplicatesKey options to central for bluetooth
* fix nav errors by switching from observableobject to state
* Update connection indicator icon
* fix for BLE disconnects
* Connection process fixes
* More fixes/tweaks to connection process
* Strict concurrency
* Fix some warnings, remove wifi warning
* delete stale keys
* interim commit
* Update privacy for log, fix wrong space
* fix a couple of linting items
* Switch to targeted
* interim commit
* BLE Signal strenth on connect view
* Remove BLE RSSI from long press menu
* Modem lights
* minor spacing tweak
* Additional BLE logging and a scanning fix.
* Discovery and BLE RSSI improvements
* Background suspension
* Update isConnected to enable UI during db load
* update protobufs
* Replace config if statements with switches, Fix unknown module config logging, make dark mode modem circle stroke color white so they are visible
* Additional logging cleanup
* hast
* Set unmessagable to true if the longname has the unmessagable emoji
* Connect error handling improvements
* Admin popup list icon and activity lights updates
* Revert use of .toolbar back to .navigationBarItems
* More public logging
* Better BLE error handling
* Node DB progress meter
* minor tweak to activity light interaction timing
* Fix comment linting, remove stale keys
* Remove stale keys
* Easy linting fixes
* Two more simple linting fixes
* clean up meshtasticapp
* More public logging
* Replay config
* Logging
* Fix for unselected node on Settings
* Tweak to progress meter based on device idiom
* Update protos
* Session replay redaction of messages
* Serial fix for old devices, and a let statement
* Mask text too
* Fix typo
* BLE poweredOff is now an auto-reconnectable error
* Update logging
* Fix for peerRemovedPairingInformation
* Logging for BLE peripheral:didUpdateValueFor errors.
* Fix for inconsistent swipe disconnect behavior
* periperal:didUpdateValueFor error handling
* Fix for BLEConnection continuation guarding
* BLEConnection actor deadlock on disconnect
* Heartbeat nonce
* Fix for swipe disconnect and task cancellation
* Fix for swipe actions not honoring .disabled()
* Tell BLETransport when BLEConnection is cancelled
* Update navigation logging
* Logging updates
* Bump version to 2.7.0
* Organize into folders and heartbeat stuff
* Minor improvements to manual TCP connection
* Auto-connect toggle
* Possible BLE bug, still waiting to see in logs
* Concurrency tweaks
* Concurrency improvements
* requestDeviceMetadata fix. fixes remote admin
* Minor typo fixes
* "All" button for log filters: category and level
* More robust continuation handling for BLE
* @FetchRequest based ChannelMessageList
* Update info.plist and device hardware file
* Move auto connect toggle to app settings and debug mode, tint properly with the accent color
* Add label to auto connect toggle
* Update log for node info received from ourselves over the mesh
* Remove unused scrollViewProxy
* Update Meshtastic/Views/Onboarding/DeviceOnboarding.swift
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update target for connect view
* Properly Set datadog environment
* Comment out ble manager
* Adjust cyclomatic complexity thresholds in .swiftlint.yml
* Linting fixes, delete ble manager
* Make session replay debug only
---------
Co-authored-by: jake-b <jake-b@users.noreply.github.com>
Co-authored-by: jake <jake@jakes-Mac-mini.local>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-27 08:09:02 -07:00
|
|
|
ConnectedDevice(deviceConnected: accessoryManager.isConnected, name: accessoryManager.activeConnection?.device.shortName ?? "?")
|
2024-08-08 07:33:31 -07:00
|
|
|
})
|
2025-07-23 20:26:50 +00:00
|
|
|
.onChange(of: node) { _, _ in
|
2025-06-28 10:11:31 -07:00
|
|
|
setSecurityValues()
|
|
|
|
|
}
|
2024-10-06 08:50:12 -07:00
|
|
|
.onChange(of: isManaged) { _, newIsManaged in
|
|
|
|
|
if newIsManaged != node?.securityConfig?.isManaged { hasChanges = true }
|
2024-08-11 09:59:52 -07:00
|
|
|
}
|
2024-10-06 08:50:12 -07:00
|
|
|
.onChange(of: serialEnabled) { _, newSerialEnabled in
|
|
|
|
|
if newSerialEnabled != node?.securityConfig?.serialEnabled { hasChanges = true }
|
2024-08-11 09:59:52 -07:00
|
|
|
}
|
2024-10-06 08:50:12 -07:00
|
|
|
.onChange(of: debugLogApiEnabled) { _, newDebugLogApiEnabled in
|
|
|
|
|
if newDebugLogApiEnabled != node?.securityConfig?.debugLogApiEnabled { hasChanges = true }
|
2024-08-10 07:05:09 -07:00
|
|
|
}
|
2025-06-14 15:01:26 -07:00
|
|
|
.onChange(of: privateKey) { _, key in
|
2024-08-19 21:50:28 -07:00
|
|
|
let tempKey = Data(base64Encoded: privateKey) ?? Data()
|
|
|
|
|
if tempKey.count == 32 {
|
|
|
|
|
hasValidPrivateKey = true
|
2025-06-16 17:24:54 -04:00
|
|
|
if let privateKeyBytes = Data(base64Encoded: privateKey), privateKeyBytes.count == 32 {
|
|
|
|
|
// Valid private key -- generate the public key
|
2025-06-17 09:11:51 -07:00
|
|
|
publicKey = generatePublicKeyDisplay(from: privateKeyBytes)?.base64EncodedString() ?? ""
|
2025-06-16 17:24:54 -04:00
|
|
|
}
|
2024-08-19 21:50:28 -07:00
|
|
|
} else {
|
|
|
|
|
hasValidPrivateKey = false
|
|
|
|
|
}
|
2025-06-14 15:01:26 -07:00
|
|
|
if key != node?.securityConfig?.privateKey?.base64EncodedString() ?? "" && hasValidPrivateKey { hasChanges = true }
|
2024-08-18 11:43:42 -07:00
|
|
|
}
|
2024-10-05 15:50:57 -07:00
|
|
|
.onChange(of: adminKey) { _, key in
|
2024-08-20 07:33:29 -07:00
|
|
|
let tempKey = Data(base64Encoded: key) ?? Data()
|
|
|
|
|
if key.isEmpty {
|
|
|
|
|
hasValidAdminKey = true
|
|
|
|
|
} else if tempKey.count == 32 {
|
2024-08-19 21:50:28 -07:00
|
|
|
hasValidAdminKey = true
|
|
|
|
|
} else {
|
|
|
|
|
hasValidAdminKey = false
|
|
|
|
|
}
|
2025-06-14 15:01:26 -07:00
|
|
|
if key != node?.securityConfig?.adminKey?.base64EncodedString() ?? "" && hasValidAdminKey { hasChanges = true }
|
2024-08-18 10:00:15 -07:00
|
|
|
}
|
2024-10-10 06:13:14 -07:00
|
|
|
.onChange(of: adminKey2) { _, key in
|
|
|
|
|
let tempKey = Data(base64Encoded: key) ?? Data()
|
|
|
|
|
if key.isEmpty {
|
|
|
|
|
hasValidAdminKey2 = true
|
|
|
|
|
} else if tempKey.count == 32 {
|
|
|
|
|
hasValidAdminKey2 = true
|
|
|
|
|
} else {
|
|
|
|
|
hasValidAdminKey2 = false
|
|
|
|
|
}
|
2025-06-14 15:01:26 -07:00
|
|
|
if key != node?.securityConfig?.adminKey2?.base64EncodedString() ?? "" && hasValidAdminKey2 { hasChanges = true }
|
2024-10-10 06:13:14 -07:00
|
|
|
}
|
|
|
|
|
.onChange(of: adminKey3) { _, key in
|
|
|
|
|
let tempKey = Data(base64Encoded: key) ?? Data()
|
|
|
|
|
if key.isEmpty {
|
|
|
|
|
hasValidAdminKey3 = true
|
|
|
|
|
} else if tempKey.count == 32 {
|
|
|
|
|
hasValidAdminKey3 = true
|
|
|
|
|
} else {
|
|
|
|
|
hasValidAdminKey3 = false
|
|
|
|
|
}
|
2025-06-15 12:40:30 -07:00
|
|
|
if key != node?.securityConfig?.adminKey3?.base64EncodedString() ?? "" && hasValidAdminKey3 { hasChanges = true }
|
2024-10-10 06:13:14 -07:00
|
|
|
}
|
2024-08-19 00:27:26 -07:00
|
|
|
.onFirstAppear {
|
2025-06-14 15:01:26 -07:00
|
|
|
// Need to request a SecurityConfig from the remote node before allowing changes
|
Transports Interface to Support TCP for all Platforms and Serial on Mac (#1341)
* Initial implementation of transports
* Initial LogRadio implementation
* Fixes for Settings view (caused by debug commenting)
* Refinement of the object and actor model
* Connect view text and tab updates
* Fix mac catalyst and tests
* Warning and logging clean-up
* In progress commit
* Serial Transport and Reconnect draft work
* Serial transport and reconnection draft work
* Quick fix for BLE - still more work to do
* interim commit
* More in progress changes
* Minor improvements
* Pretty good initial implementation
* Bump version beyond the app store
* Fix for disconnection swipeAction
* Tweaks to TCPConnection implementation
* Retry for NONCE_ONLY_DB
* Revert json string change
* Simplified some of the API + "Anti-discovery"
* Tweaks for devices leaving the discovery process
* Bump version
* iOS26 Tweaks
* Tweaks and bug fixes
* Add link with slash sf symbol
* update symbol image on connect view
* BLE disconnect handling
* Log privacy attributes
* Onboarding and minor fixes.
* change database to nodes, add emoji to tcp logs
* Error handling improvements
* More logging emojis
* Suppressed unnecessary errors on disconnect
* Heartbeat emoji
* Add bluetooth symbol
* add privacy attributes to [TCP] logs, add custom bluetooth logo
* Improve routing logs
* Emoji for connect logs
* Heartbeat emoji
* Add CBCentralManagerScanOptionAllowDuplicatesKey options to central for bluetooth
* fix nav errors by switching from observableobject to state
* Update connection indicator icon
* fix for BLE disconnects
* Connection process fixes
* More fixes/tweaks to connection process
* Strict concurrency
* Fix some warnings, remove wifi warning
* delete stale keys
* interim commit
* Update privacy for log, fix wrong space
* fix a couple of linting items
* Switch to targeted
* interim commit
* BLE Signal strenth on connect view
* Remove BLE RSSI from long press menu
* Modem lights
* minor spacing tweak
* Additional BLE logging and a scanning fix.
* Discovery and BLE RSSI improvements
* Background suspension
* Update isConnected to enable UI during db load
* update protobufs
* Replace config if statements with switches, Fix unknown module config logging, make dark mode modem circle stroke color white so they are visible
* Additional logging cleanup
* hast
* Set unmessagable to true if the longname has the unmessagable emoji
* Connect error handling improvements
* Admin popup list icon and activity lights updates
* Revert use of .toolbar back to .navigationBarItems
* More public logging
* Better BLE error handling
* Node DB progress meter
* minor tweak to activity light interaction timing
* Fix comment linting, remove stale keys
* Remove stale keys
* Easy linting fixes
* Two more simple linting fixes
* clean up meshtasticapp
* More public logging
* Replay config
* Logging
* Fix for unselected node on Settings
* Tweak to progress meter based on device idiom
* Update protos
* Session replay redaction of messages
* Serial fix for old devices, and a let statement
* Mask text too
* Fix typo
* BLE poweredOff is now an auto-reconnectable error
* Update logging
* Fix for peerRemovedPairingInformation
* Logging for BLE peripheral:didUpdateValueFor errors.
* Fix for inconsistent swipe disconnect behavior
* periperal:didUpdateValueFor error handling
* Fix for BLEConnection continuation guarding
* BLEConnection actor deadlock on disconnect
* Heartbeat nonce
* Fix for swipe disconnect and task cancellation
* Fix for swipe actions not honoring .disabled()
* Tell BLETransport when BLEConnection is cancelled
* Update navigation logging
* Logging updates
* Bump version to 2.7.0
* Organize into folders and heartbeat stuff
* Minor improvements to manual TCP connection
* Auto-connect toggle
* Possible BLE bug, still waiting to see in logs
* Concurrency tweaks
* Concurrency improvements
* requestDeviceMetadata fix. fixes remote admin
* Minor typo fixes
* "All" button for log filters: category and level
* More robust continuation handling for BLE
* @FetchRequest based ChannelMessageList
* Update info.plist and device hardware file
* Move auto connect toggle to app settings and debug mode, tint properly with the accent color
* Add label to auto connect toggle
* Update log for node info received from ourselves over the mesh
* Remove unused scrollViewProxy
* Update Meshtastic/Views/Onboarding/DeviceOnboarding.swift
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update target for connect view
* Properly Set datadog environment
* Comment out ble manager
* Adjust cyclomatic complexity thresholds in .swiftlint.yml
* Linting fixes, delete ble manager
* Make session replay debug only
---------
Co-authored-by: jake-b <jake-b@users.noreply.github.com>
Co-authored-by: jake <jake@jakes-Mac-mini.local>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-27 08:09:02 -07:00
|
|
|
if let deviceNum = accessoryManager.activeDeviceNum, let node {
|
|
|
|
|
if let connectedNode = getNodeInfo(id: deviceNum, context: context) {
|
|
|
|
|
if node.num != deviceNum {
|
2024-09-05 19:31:29 -07:00
|
|
|
if UserDefaults.enableAdministration {
|
|
|
|
|
/// 2.5 Administration with session passkey
|
|
|
|
|
let expiration = node.sessionExpiration ?? Date()
|
|
|
|
|
if expiration < Date() || node.securityConfig == nil {
|
Transports Interface to Support TCP for all Platforms and Serial on Mac (#1341)
* Initial implementation of transports
* Initial LogRadio implementation
* Fixes for Settings view (caused by debug commenting)
* Refinement of the object and actor model
* Connect view text and tab updates
* Fix mac catalyst and tests
* Warning and logging clean-up
* In progress commit
* Serial Transport and Reconnect draft work
* Serial transport and reconnection draft work
* Quick fix for BLE - still more work to do
* interim commit
* More in progress changes
* Minor improvements
* Pretty good initial implementation
* Bump version beyond the app store
* Fix for disconnection swipeAction
* Tweaks to TCPConnection implementation
* Retry for NONCE_ONLY_DB
* Revert json string change
* Simplified some of the API + "Anti-discovery"
* Tweaks for devices leaving the discovery process
* Bump version
* iOS26 Tweaks
* Tweaks and bug fixes
* Add link with slash sf symbol
* update symbol image on connect view
* BLE disconnect handling
* Log privacy attributes
* Onboarding and minor fixes.
* change database to nodes, add emoji to tcp logs
* Error handling improvements
* More logging emojis
* Suppressed unnecessary errors on disconnect
* Heartbeat emoji
* Add bluetooth symbol
* add privacy attributes to [TCP] logs, add custom bluetooth logo
* Improve routing logs
* Emoji for connect logs
* Heartbeat emoji
* Add CBCentralManagerScanOptionAllowDuplicatesKey options to central for bluetooth
* fix nav errors by switching from observableobject to state
* Update connection indicator icon
* fix for BLE disconnects
* Connection process fixes
* More fixes/tweaks to connection process
* Strict concurrency
* Fix some warnings, remove wifi warning
* delete stale keys
* interim commit
* Update privacy for log, fix wrong space
* fix a couple of linting items
* Switch to targeted
* interim commit
* BLE Signal strenth on connect view
* Remove BLE RSSI from long press menu
* Modem lights
* minor spacing tweak
* Additional BLE logging and a scanning fix.
* Discovery and BLE RSSI improvements
* Background suspension
* Update isConnected to enable UI during db load
* update protobufs
* Replace config if statements with switches, Fix unknown module config logging, make dark mode modem circle stroke color white so they are visible
* Additional logging cleanup
* hast
* Set unmessagable to true if the longname has the unmessagable emoji
* Connect error handling improvements
* Admin popup list icon and activity lights updates
* Revert use of .toolbar back to .navigationBarItems
* More public logging
* Better BLE error handling
* Node DB progress meter
* minor tweak to activity light interaction timing
* Fix comment linting, remove stale keys
* Remove stale keys
* Easy linting fixes
* Two more simple linting fixes
* clean up meshtasticapp
* More public logging
* Replay config
* Logging
* Fix for unselected node on Settings
* Tweak to progress meter based on device idiom
* Update protos
* Session replay redaction of messages
* Serial fix for old devices, and a let statement
* Mask text too
* Fix typo
* BLE poweredOff is now an auto-reconnectable error
* Update logging
* Fix for peerRemovedPairingInformation
* Logging for BLE peripheral:didUpdateValueFor errors.
* Fix for inconsistent swipe disconnect behavior
* periperal:didUpdateValueFor error handling
* Fix for BLEConnection continuation guarding
* BLEConnection actor deadlock on disconnect
* Heartbeat nonce
* Fix for swipe disconnect and task cancellation
* Fix for swipe actions not honoring .disabled()
* Tell BLETransport when BLEConnection is cancelled
* Update navigation logging
* Logging updates
* Bump version to 2.7.0
* Organize into folders and heartbeat stuff
* Minor improvements to manual TCP connection
* Auto-connect toggle
* Possible BLE bug, still waiting to see in logs
* Concurrency tweaks
* Concurrency improvements
* requestDeviceMetadata fix. fixes remote admin
* Minor typo fixes
* "All" button for log filters: category and level
* More robust continuation handling for BLE
* @FetchRequest based ChannelMessageList
* Update info.plist and device hardware file
* Move auto connect toggle to app settings and debug mode, tint properly with the accent color
* Add label to auto connect toggle
* Update log for node info received from ourselves over the mesh
* Remove unused scrollViewProxy
* Update Meshtastic/Views/Onboarding/DeviceOnboarding.swift
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update target for connect view
* Properly Set datadog environment
* Comment out ble manager
* Adjust cyclomatic complexity thresholds in .swiftlint.yml
* Linting fixes, delete ble manager
* Make session replay debug only
---------
Co-authored-by: jake-b <jake-b@users.noreply.github.com>
Co-authored-by: jake <jake@jakes-Mac-mini.local>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-27 08:09:02 -07:00
|
|
|
Task {
|
|
|
|
|
do {
|
|
|
|
|
Logger.mesh.info("⚙️ Empty or expired security config requesting via PKI admin")
|
|
|
|
|
try await accessoryManager.requestSecurityConfig(fromUser: connectedNode.user!, toUser: node.user!)
|
|
|
|
|
} catch {
|
|
|
|
|
Logger.mesh.info("🚨 Security config request failed")
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-09-05 19:31:29 -07:00
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
if node.deviceConfig == nil {
|
|
|
|
|
/// Legacy Administration
|
2025-05-13 06:19:27 -07:00
|
|
|
Logger.mesh.info("☠️ Using insecure legacy admin that is no longer supported, please upgrade your firmware.")
|
2024-09-05 19:31:29 -07:00
|
|
|
}
|
2024-09-04 10:06:34 -07:00
|
|
|
}
|
|
|
|
|
}
|
2024-08-19 00:27:26 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-07 09:43:48 -07:00
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2024-08-07 09:43:48 -07:00
|
|
|
func setSecurityValues() {
|
2024-08-08 07:33:31 -07:00
|
|
|
self.publicKey = node?.securityConfig?.publicKey?.base64EncodedString() ?? ""
|
|
|
|
|
self.privateKey = node?.securityConfig?.privateKey?.base64EncodedString() ?? ""
|
2025-03-14 07:28:58 -07:00
|
|
|
self.adminKey = node?.securityConfig?.adminKey?.base64EncodedString(options: .lineLength64Characters) ?? ""
|
|
|
|
|
self.adminKey2 = node?.securityConfig?.adminKey2?.base64EncodedString(options: .lineLength64Characters) ?? ""
|
|
|
|
|
self.adminKey3 = node?.securityConfig?.adminKey3?.base64EncodedString(options: .lineLength64Characters) ?? ""
|
2024-08-07 09:43:48 -07:00
|
|
|
self.isManaged = node?.securityConfig?.isManaged ?? false
|
|
|
|
|
self.serialEnabled = node?.securityConfig?.serialEnabled ?? false
|
2024-08-08 10:39:45 -07:00
|
|
|
self.debugLogApiEnabled = node?.securityConfig?.debugLogApiEnabled ?? false
|
2024-08-07 09:43:48 -07:00
|
|
|
self.hasChanges = false
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2025-06-14 14:25:46 -07:00
|
|
|
func generatePrivateKey(count: Int) -> Data? {
|
|
|
|
|
var randomBytes = Data(count: count)
|
|
|
|
|
let status = randomBytes.withUnsafeMutableBytes { (mutableBytes: UnsafeMutableRawBufferPointer) -> Int32 in
|
|
|
|
|
guard let pointer = mutableBytes.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
|
|
|
|
|
return -1 // Indicate an error
|
|
|
|
|
}
|
|
|
|
|
return SecRandomCopyBytes(kSecRandomDefault, count, pointer)
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2025-06-14 14:25:46 -07:00
|
|
|
if status == errSecSuccess {
|
2025-06-18 16:44:21 -07:00
|
|
|
// Generate a random "f" value and then adjust the value to make
|
|
|
|
|
// it valid as an "s" value for eval(). According to the specification
|
|
|
|
|
// we need to mask off the 3 right-most bits of f[0], mask off the
|
|
|
|
|
// left-most bit of f[31], and set the second to left-most bit of f[31].
|
|
|
|
|
var f = randomBytes
|
|
|
|
|
f[0] &= 0xF8
|
|
|
|
|
f[31] = (f[31] & 0x7F) | 0x40
|
|
|
|
|
return f
|
2025-06-14 14:25:46 -07:00
|
|
|
} else {
|
|
|
|
|
// Handle error, perhaps by logging or throwing an exception
|
2025-06-16 17:24:54 -04:00
|
|
|
Logger.mesh.debug("Error generating random bytes: \(status)")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2025-06-17 09:11:51 -07:00
|
|
|
// Generate a new public key for display purposes to show the user what will be changed after the new private key is saved to the device
|
|
|
|
|
func generatePublicKeyDisplay(from privateKeyData: Data) -> Data? {
|
2025-06-16 17:24:54 -04:00
|
|
|
guard privateKeyData.count == 32 else {
|
|
|
|
|
Logger.mesh.debug("Invalid private key length. Must be 32 bytes for Curve25519.")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2025-10-05 17:51:18 -07:00
|
|
|
|
2025-06-16 17:24:54 -04:00
|
|
|
do {
|
|
|
|
|
// Create a Curve25519 private key from raw representation
|
|
|
|
|
let privateKey = try Curve25519.KeyAgreement.PrivateKey(rawRepresentation: privateKeyData)
|
|
|
|
|
let publicKey = privateKey.publicKey
|
|
|
|
|
return publicKey.rawRepresentation
|
|
|
|
|
} catch {
|
|
|
|
|
Logger.mesh.debug("Failed to create Curve25519 key: \(error)")
|
2025-06-14 14:25:46 -07:00
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
}
|
2024-08-07 09:43:48 -07:00
|
|
|
}
|