Files
moltbot/apps/ios/Tests/GatewayConnectionSecurityTests.swift
2026-02-24 15:16:11 +00:00

133 lines
6.0 KiB
Swift

import Foundation
import Network
import OpenClawKit
import Testing
@testable import OpenClaw
@Suite(.serialized) struct GatewayConnectionSecurityTests {
private func clearTLSFingerprint(stableID: String) {
let suite = UserDefaults(suiteName: "ai.openclaw.shared") ?? .standard
suite.removeObject(forKey: "gateway.tls.\(stableID)")
}
@Test @MainActor func discoveredTLSParams_prefersStoredPinOverAdvertisedTXT() async {
let stableID = "test|\(UUID().uuidString)"
defer { clearTLSFingerprint(stableID: stableID) }
clearTLSFingerprint(stableID: stableID)
GatewayTLSStore.saveFingerprint("11", stableID: stableID)
let endpoint: NWEndpoint = .service(name: "Test", type: "_openclaw-gw._tcp", domain: "local.", interface: nil)
let gateway = GatewayDiscoveryModel.DiscoveredGateway(
name: "Test",
endpoint: endpoint,
stableID: stableID,
debugID: "debug",
lanHost: "evil.example.com",
tailnetDns: "evil.example.com",
gatewayPort: 12345,
canvasPort: nil,
tlsEnabled: true,
tlsFingerprintSha256: "22",
cliPath: nil)
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let params = controller._test_resolveDiscoveredTLSParams(gateway: gateway, allowTOFU: true)
#expect(params?.expectedFingerprint == "11")
#expect(params?.allowTOFU == false)
}
@Test @MainActor func discoveredTLSParams_doesNotTrustAdvertisedFingerprint() async {
let stableID = "test|\(UUID().uuidString)"
defer { clearTLSFingerprint(stableID: stableID) }
clearTLSFingerprint(stableID: stableID)
let endpoint: NWEndpoint = .service(name: "Test", type: "_openclaw-gw._tcp", domain: "local.", interface: nil)
let gateway = GatewayDiscoveryModel.DiscoveredGateway(
name: "Test",
endpoint: endpoint,
stableID: stableID,
debugID: "debug",
lanHost: nil,
tailnetDns: nil,
gatewayPort: nil,
canvasPort: nil,
tlsEnabled: true,
tlsFingerprintSha256: "22",
cliPath: nil)
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
let params = controller._test_resolveDiscoveredTLSParams(gateway: gateway, allowTOFU: true)
#expect(params?.expectedFingerprint == nil)
#expect(params?.allowTOFU == false)
}
@Test @MainActor func autoconnectRequiresStoredPinForDiscoveredGateways() async {
let stableID = "test|\(UUID().uuidString)"
defer { clearTLSFingerprint(stableID: stableID) }
clearTLSFingerprint(stableID: stableID)
let defaults = UserDefaults.standard
defaults.set(true, forKey: "gateway.autoconnect")
defaults.set(false, forKey: "gateway.manual.enabled")
defaults.removeObject(forKey: "gateway.last.host")
defaults.removeObject(forKey: "gateway.last.port")
defaults.removeObject(forKey: "gateway.last.tls")
defaults.removeObject(forKey: "gateway.last.stableID")
defaults.removeObject(forKey: "gateway.last.kind")
defaults.removeObject(forKey: "gateway.preferredStableID")
defaults.set(stableID, forKey: "gateway.lastDiscoveredStableID")
let endpoint: NWEndpoint = .service(name: "Test", type: "_openclaw-gw._tcp", domain: "local.", interface: nil)
let gateway = GatewayDiscoveryModel.DiscoveredGateway(
name: "Test",
endpoint: endpoint,
stableID: stableID,
debugID: "debug",
lanHost: "test.local",
tailnetDns: nil,
gatewayPort: 18789,
canvasPort: nil,
tlsEnabled: true,
tlsFingerprintSha256: nil,
cliPath: nil)
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
controller._test_setGateways([gateway])
controller._test_triggerAutoConnect()
#expect(controller._test_didAutoConnect() == false)
}
@Test @MainActor func manualConnectionsForceTLSForNonLoopbackHosts() async {
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
#expect(controller._test_resolveManualUseTLS(host: "gateway.example.com", useTLS: false) == true)
#expect(controller._test_resolveManualUseTLS(host: "openclaw.local", useTLS: false) == true)
#expect(controller._test_resolveManualUseTLS(host: "127.attacker.example", useTLS: false) == true)
#expect(controller._test_resolveManualUseTLS(host: "localhost", useTLS: false) == false)
#expect(controller._test_resolveManualUseTLS(host: "127.0.0.1", useTLS: false) == false)
#expect(controller._test_resolveManualUseTLS(host: "::1", useTLS: false) == false)
#expect(controller._test_resolveManualUseTLS(host: "[::1]", useTLS: false) == false)
#expect(controller._test_resolveManualUseTLS(host: "::ffff:127.0.0.1", useTLS: false) == false)
#expect(controller._test_resolveManualUseTLS(host: "0.0.0.0", useTLS: false) == false)
}
@Test @MainActor func manualDefaultPortUses443OnlyForTailnetTLSHosts() async {
let appModel = NodeAppModel()
let controller = GatewayConnectionController(appModel: appModel, startDiscovery: false)
#expect(controller._test_resolveManualPort(host: "gateway.example.com", port: 0, useTLS: true) == 18789)
#expect(controller._test_resolveManualPort(host: "device.sample.ts.net", port: 0, useTLS: true) == 443)
#expect(controller._test_resolveManualPort(host: "device.sample.ts.net.", port: 0, useTLS: true) == 443)
#expect(controller._test_resolveManualPort(host: "device.sample.ts.net", port: 18789, useTLS: true) == 18789)
}
}