diff --git a/FrameworkTests/PersistenceCompatibilityTests.swift b/FrameworkTests/PersistenceCompatibilityTests.swift index 12fc05aa..b6c54b0c 100644 --- a/FrameworkTests/PersistenceCompatibilityTests.swift +++ b/FrameworkTests/PersistenceCompatibilityTests.swift @@ -72,6 +72,8 @@ struct PersistenceCompatibilityTests { #expect(TermindMigrationPlan.schemas.contains { $0.versionIdentifier == TermindSchemaV26.versionIdentifier }) #expect(TermindMigrationPlan.schemas.contains { $0.versionIdentifier == TermindSchemaV27.versionIdentifier }) #expect(TermindMigrationPlan.schemas.contains { $0.versionIdentifier == TermindSchemaV28.versionIdentifier }) + #expect(TermindMigrationPlan.schemas.contains { $0.versionIdentifier == TermindSchemaV29.versionIdentifier }) + #expect(TermindMigrationPlan.schemas.contains { $0.versionIdentifier == TermindSchemaV30.versionIdentifier }) #expect(TermindSchemaV22.models.contains { $0 == TermindSchemaV22HostModels.PersistentHost.self }) #expect(!TermindSchemaV22.models.contains { $0 == TermindSchemaV24HostModels.PersistentHost.self }) #expect(TermindSchemaV25.models.contains { $0 == TermindSchemaV24HostModels.PersistentHost.self }) @@ -86,6 +88,34 @@ struct PersistenceCompatibilityTests { #expect(first.schema.entities.contains { $0.name == "PersistentSFTPPreferences" }) } + @Test + func version29StoreDropsMismatchStateWithoutLosingKnownHosts() throws { + let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) + try FileManager.default.createDirectory(at: directory, withIntermediateDirectories: true) + defer { try? FileManager.default.removeItem(at: directory) } + let url = directory.appendingPathComponent("Termind.store") + let oldSchema = Schema(versionedSchema: TermindSchemaV29.self) + let oldConfiguration = ModelConfiguration( + "Termind", schema: oldSchema, url: url, cloudKitDatabase: .none + ) + let knownHost = KnownHostEntry( + hostname: "server.invalid", algorithm: .ed25519, + fingerprint: "SHA256:known-host" + ) + var oldContainer: ModelContainer? = try ModelContainer( + for: oldSchema, configurations: [oldConfiguration] + ) + let oldRecord = TermindSchemaV29KnownHostModels.PersistentKnownHost(knownHost) + oldRecord.isMismatched = true + oldContainer?.mainContext.insert(oldRecord) + try oldContainer?.mainContext.save() + oldContainer = nil + + let migrated = try TermindPersistence.makeContainer(url: url) + let records = try migrated.mainContext.fetch(FetchDescriptor()) + #expect(records.map(\.value) == [knownHost]) + } + @Test func version25StoreMigratesWithNoStartupSnippet() throws { let directory = FileManager.default.temporaryDirectory.appendingPathComponent(UUID().uuidString) diff --git a/Frameworks/Resources/Localizable.xcstrings b/Frameworks/Resources/Localizable.xcstrings index 44287005..56bd9cb6 100644 --- a/Frameworks/Resources/Localizable.xcstrings +++ b/Frameworks/Resources/Localizable.xcstrings @@ -12242,22 +12242,6 @@ } } }, - "Host key changed" : { - "localizations" : { - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "主机密钥已更改" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "主機金鑰已更改" - } - } - } - }, "Host key confirmation required" : { "extractionState" : "stale", "localizations" : { diff --git a/Frameworks/TermindApplication/Connections/RemoteSessionConnectionController.swift b/Frameworks/TermindApplication/Connections/RemoteSessionConnectionController.swift index 2a43e1d7..fdde254b 100644 --- a/Frameworks/TermindApplication/Connections/RemoteSessionConnectionController.swift +++ b/Frameworks/TermindApplication/Connections/RemoteSessionConnectionController.swift @@ -131,12 +131,6 @@ public final class RemoteSessionConnectionController { return .accept } finishHostKeyAuthorization(.reject) - if !challenge.expectedHostKeys.isEmpty { - store.markKnownHostMismatched( - hostname: challenge.observation.hostname, - port: challenge.observation.port - ) - } let authorizationID = UUID() hostKeyAuthorizationID = authorizationID waitForHostKey( diff --git a/Frameworks/TermindApplication/Connections/RemoteSessionHostStore.swift b/Frameworks/TermindApplication/Connections/RemoteSessionHostStore.swift index e442276d..012a5e3f 100644 --- a/Frameworks/TermindApplication/Connections/RemoteSessionHostStore.swift +++ b/Frameworks/TermindApplication/Connections/RemoteSessionHostStore.swift @@ -64,7 +64,6 @@ public protocol RemoteSessionHostStore: AnyObject, Sendable { func setConnectionStatus(_ status: ConnectionStatus, connectionID: UUID, hostID: Host.ID) func recordRemoteSystemInfo(_ systemInfo: RemoteSystemInfo?, for hostID: Host.ID) func trust(_ entry: KnownHostEntry) - func markKnownHostMismatched(hostname: String, port: Int) @discardableResult func upsert(_ identity: Identity) -> Bool func upsert(_ identity: Identity, passwordUpdate: IdentityPasswordUpdate) throws func upsert(_ host: Host) diff --git a/Frameworks/TermindApplication/FeatureModels/HostLibraryModel.swift b/Frameworks/TermindApplication/FeatureModels/HostLibraryModel.swift index 4e738969..5461809f 100644 --- a/Frameworks/TermindApplication/FeatureModels/HostLibraryModel.swift +++ b/Frameworks/TermindApplication/FeatureModels/HostLibraryModel.swift @@ -408,9 +408,6 @@ public final class HostLibraryModel: RemoteSessionHostStore { guard !knownHosts.isEmpty else { return String(localized: "Connect once to trust this host") } - guard !knownHosts.contains(where: \.isMismatched) else { - return String(localized: "Host key changed") - } return nil } @@ -1218,9 +1215,6 @@ public final class HostLibraryModel: RemoteSessionHostStore { let endpointMatches: (KnownHostEntry) -> Bool = { $0.matches(hostname: entry.hostname, port: entry.port) } - for index in knownHosts.indices where endpointMatches(knownHosts[index]) { - knownHosts[index].isMismatched = false - } if let index = knownHosts.firstIndex(where: { endpointMatches($0) && $0.fingerprint == entry.fingerprint }) { @@ -1237,16 +1231,6 @@ public final class HostLibraryModel: RemoteSessionHostStore { persistCore() } - public func markKnownHostMismatched(hostname: String, port: Int) { - var changed = false - for index in knownHosts.indices - where knownHosts[index].matches(hostname: hostname, port: port) { - knownHosts[index].isMismatched = true - changed = true - } - if changed { persistCore() } - } - public func setExpanded(_ expanded: Bool, for groupID: HostGroup.ID) { guard group(groupID) != nil else { return } expansionState.setHostGroupExpanded(expanded, for: groupID) @@ -2130,12 +2114,10 @@ public final class HostLibraryModel: RemoteSessionHostStore { } let previous = knownHosts if let index = knownHosts.firstIndex(where: { $0.id == entry.id }) { - // Editing the address or key explicitly creates a new trust decision. if knownHosts[index].identity != entry.identity || knownHosts[index].hostname != entry.hostname || knownHosts[index].port != entry.port { entry.addedAt = Date() - entry.isMismatched = false } knownHosts[index] = entry } else { diff --git a/Frameworks/TermindApplication/Metrics/ServerMetricsModel.swift b/Frameworks/TermindApplication/Metrics/ServerMetricsModel.swift index 92a1f8bb..54df43a6 100644 --- a/Frameworks/TermindApplication/Metrics/ServerMetricsModel.swift +++ b/Frameworks/TermindApplication/Metrics/ServerMetricsModel.swift @@ -969,12 +969,6 @@ public final class ServerMetricsModel: MetricsSnapshotReading { ) -> HostKeyAuthorization? { guard current(serverID, token), let server = servers.first(where: { $0.id == serverID }) else { return nil } finishHostKeyAuthorization(for: serverID, decision: .reject) - if !challenge.expectedHostKeys.isEmpty { - hosts.markKnownHostMismatched( - hostname: challenge.observation.hostname, - port: challenge.observation.port - ) - } server.hostKey = challenge.observation server.hostKeyHostID = challenge.hostID server.hostKeyPrompt = SSHHostKeyPrompt( diff --git a/Frameworks/TermindDomain/Hosts/KnownHost.swift b/Frameworks/TermindDomain/Hosts/KnownHost.swift index 1c223608..8fc18873 100644 --- a/Frameworks/TermindDomain/Hosts/KnownHost.swift +++ b/Frameworks/TermindDomain/Hosts/KnownHost.swift @@ -13,17 +13,13 @@ public nonisolated struct KnownHostEntry: Identifiable, Codable, Hashable, Senda public var algorithm: SSHKeyAlgorithm = .ed25519 public var fingerprint: String = "" public var addedAt: Date = Date(timeIntervalSince1970: 0) - /// True once the key the server presented stopped matching the pinned one. - /// Kept as data rather than a transient alert so the warning survives the - /// dialog the user dismissed at 3am. - public var isMismatched: Bool = false public var identity: SSHHostKeyIdentity { SSHHostKeyIdentity(algorithm: algorithm, fingerprint: fingerprint) } public init( id: UUID = UUID(), hostname: String, port: Int = 22, algorithm: SSHKeyAlgorithm = .ed25519, - fingerprint: String = "", addedAt: Date = Date(timeIntervalSince1970: 0), isMismatched: Bool = false + fingerprint: String = "", addedAt: Date = Date(timeIntervalSince1970: 0) ) { self.id = id self.hostname = hostname @@ -31,7 +27,6 @@ public nonisolated struct KnownHostEntry: Identifiable, Codable, Hashable, Senda self.algorithm = algorithm self.fingerprint = fingerprint self.addedAt = addedAt - self.isMismatched = isMismatched } } diff --git a/Frameworks/TermindPersistence/HostPersistence.swift b/Frameworks/TermindPersistence/HostPersistence.swift index 4ba2efed..f63f8a27 100644 --- a/Frameworks/TermindPersistence/HostPersistence.swift +++ b/Frameworks/TermindPersistence/HostPersistence.swift @@ -383,36 +383,26 @@ public enum TermindSchemaV7Models { } } -@Model -public final class PersistentKnownHost { - @Attribute(.unique) public var id: UUID = UUID() - public var hostname = "" - public var port = 22 - public var algorithm = SSHKeyAlgorithm.ed25519.rawValue - public var fingerprint = "" - public var addedAt = Date(timeIntervalSince1970: 0) - public var isMismatched = false +/// Frozen through V29. V30 removes the persisted mismatch flag from the live model. +public enum TermindSchemaV29KnownHostModels { + @Model + public final class PersistentKnownHost { + @Attribute(.unique) public var id: UUID = UUID() + public var hostname = "" + public var port = 22 + public var algorithm = SSHKeyAlgorithm.ed25519.rawValue + public var fingerprint = "" + public var addedAt = Date(timeIntervalSince1970: 0) + public var isMismatched = false - public init(_ value: KnownHostEntry) { - id = value.id - apply(value) - } - - public func apply(_ value: KnownHostEntry) { - hostname = value.hostname - port = value.port - algorithm = value.algorithm.rawValue - fingerprint = value.fingerprint - addedAt = value.addedAt - isMismatched = value.isMismatched - } - - public var value: KnownHostEntry { - KnownHostEntry( - id: id, hostname: hostname, port: port, - algorithm: SSHKeyAlgorithm(rawValue: algorithm) ?? .ed25519, - fingerprint: fingerprint, addedAt: addedAt, isMismatched: isMismatched - ) + public init(_ value: KnownHostEntry) { + id = value.id + hostname = value.hostname + port = value.port + algorithm = value.algorithm.rawValue + fingerprint = value.fingerprint + addedAt = value.addedAt + } } } @@ -503,7 +493,7 @@ public enum TermindSchemaV1: VersionedSchema { PersistentHostGroup.self, TermindSchemaV6Models.PersistentIdentity.self, TermindSchemaV6Models.PersistentSSHKey.self, - PersistentKnownHost.self, + TermindSchemaV29KnownHostModels.PersistentKnownHost.self, PersistentSnippetPackage.self, PersistentSnippet.self, TermindSchemaV19ForwardingModels.PersistentPortForwardingRule.self, @@ -782,6 +772,15 @@ public enum TermindSchemaV29: VersionedSchema { } } +public enum TermindSchemaV30: VersionedSchema { + public static let versionIdentifier = Schema.Version(30, 0, 0) + public static var models: [any PersistentModel.Type] { + TermindSchemaV29.models.filter { + $0 != TermindSchemaV29KnownHostModels.PersistentKnownHost.self + } + [TermindSchemaV30KnownHostModels.PersistentKnownHost.self] + } +} + public enum TermindMigrationPlan: SchemaMigrationPlan { public static var schemas: [any VersionedSchema.Type] { [ @@ -793,6 +792,7 @@ public enum TermindMigrationPlan: SchemaMigrationPlan { TermindSchemaV18.self, TermindSchemaV19.self, TermindSchemaV20.self, TermindSchemaV21.self, TermindSchemaV22.self, TermindSchemaV23.self, TermindSchemaV24.self, TermindSchemaV25.self, TermindSchemaV26.self, TermindSchemaV27.self, TermindSchemaV28.self, TermindSchemaV29.self, + TermindSchemaV30.self, ] } @@ -863,6 +863,7 @@ public enum TermindMigrationPlan: SchemaMigrationPlan { MigrationStage.lightweight(fromVersion: TermindSchemaV26.self, toVersion: TermindSchemaV27.self), MigrationStage.lightweight(fromVersion: TermindSchemaV27.self, toVersion: TermindSchemaV28.self), MigrationStage.lightweight(fromVersion: TermindSchemaV28.self, toVersion: TermindSchemaV29.self), + MigrationStage.lightweight(fromVersion: TermindSchemaV29.self, toVersion: TermindSchemaV30.self), ] } } @@ -870,3 +871,4 @@ public enum TermindMigrationPlan: SchemaMigrationPlan { public typealias PersistentHost = TermindSchemaV29HostModels.PersistentHost public typealias PersistentIdentity = TermindSchemaV25IdentityModels.PersistentIdentity public typealias PersistentSSHKey = TermindSchemaV7Models.PersistentSSHKey +public typealias PersistentKnownHost = TermindSchemaV30KnownHostModels.PersistentKnownHost diff --git a/Frameworks/TermindPersistence/TermindPersistence.swift b/Frameworks/TermindPersistence/TermindPersistence.swift index c5c19810..2186a2eb 100644 --- a/Frameworks/TermindPersistence/TermindPersistence.swift +++ b/Frameworks/TermindPersistence/TermindPersistence.swift @@ -5,7 +5,7 @@ import SwiftData /// choose storage and own the container lifetime, never the model set. @MainActor public enum TermindPersistence { - public static let schema = Schema(versionedSchema: TermindSchemaV29.self) + public static let schema = Schema(versionedSchema: TermindSchemaV30.self) public static func makeContainer(inMemory: Bool = false, url: URL? = nil) throws -> ModelContainer { precondition(!inMemory || url == nil, "An in-memory store cannot also have a file URL") diff --git a/Frameworks/TermindPersistence/TermindSchemaV30KnownHostModels.swift b/Frameworks/TermindPersistence/TermindSchemaV30KnownHostModels.swift new file mode 100644 index 00000000..b4b3ad1c --- /dev/null +++ b/Frameworks/TermindPersistence/TermindSchemaV30KnownHostModels.swift @@ -0,0 +1,37 @@ +import Foundation +import SwiftData +import TermindDomain + +/// V30 removes the historical host-key mismatch marker while preserving Known Host trust records. +public enum TermindSchemaV30KnownHostModels { + @Model + public final class PersistentKnownHost { + @Attribute(.unique) public var id: UUID = UUID() + public var hostname = "" + public var port = 22 + public var algorithm = SSHKeyAlgorithm.ed25519.rawValue + public var fingerprint = "" + public var addedAt = Date(timeIntervalSince1970: 0) + + public init(_ value: KnownHostEntry) { + id = value.id + apply(value) + } + + public func apply(_ value: KnownHostEntry) { + hostname = value.hostname + port = value.port + algorithm = value.algorithm.rawValue + fingerprint = value.fingerprint + addedAt = value.addedAt + } + + public var value: KnownHostEntry { + KnownHostEntry( + id: id, hostname: hostname, port: port, + algorithm: SSHKeyAlgorithm(rawValue: algorithm) ?? .ed25519, + fingerprint: fingerprint, addedAt: addedAt + ) + } + } +} diff --git a/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift b/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift index fe6235be..01952212 100644 --- a/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift +++ b/Frameworks/TermindRemoteInfrastructure/Agent/AgentConnections.swift @@ -380,12 +380,6 @@ public final class AgentHostResolver: Sendable { ?? (challenge.observation.port == 22 ? challenge.observation.hostname : "\(challenge.observation.hostname):\(challenge.observation.port)") - if !challenge.expectedHostKeys.isEmpty { - store.markKnownHostMismatched( - hostname: challenge.observation.hostname, - port: challenge.observation.port - ) - } let response = await interaction.confirmHostKey( AgentSSHHostKeyRequest( invocationID: invocationID, hostID: challenge.hostID, diff --git a/Memory/ARCHITECTURE.md b/Memory/ARCHITECTURE.md index fd8020b6..bc6ee6db 100644 --- a/Memory/ARCHITECTURE.md +++ b/Memory/ARCHITECTURE.md @@ -77,7 +77,7 @@ sibling working tree is not the app dependency. Follow `AGENTS.md` for package r - SSH import previews are window-owned indexed public metadata. Host selection never selects credentials; imported identities require selected keys. The actor retains scoped descriptors/digests, not private-key text/passphrases; reread selected keys at unlock/commit and fail closed if changed. Picking/drop share validation; config parsing - executes nothing. Create credentials before one Vault save, clean up failure, never overwrite records/clear mismatch. + executes nothing. Create credentials before one Vault save, clean up failure, and never overwrite records. - macOS appearance/language are local; System appearance clears `NSApplication.appearance`. Login launch reads `SMAppService`, not mirrored preferences; quiet launch hides the app without suppressing its window. Mobile uses Apple's per-app language settings without an injected locale override. diff --git a/TFI/Features/Vault/MobileVaultView.swift b/TFI/Features/Vault/MobileVaultView.swift index 6010aed6..53128797 100644 --- a/TFI/Features/Vault/MobileVaultView.swift +++ b/TFI/Features/Vault/MobileVaultView.swift @@ -572,10 +572,6 @@ struct MobileVaultView: View { .frame(maxWidth: .infinity, alignment: .leading) } HStack(spacing: 6) { - if app.library.knownHosts(for: host).contains(where: \.isMismatched) { - MobileCardChip( - title: "Host key changed", symbol: "exclamationmark.shield", tint: .orange) - } ForEach(MobileCardLayout.visibleTags(host.tags), id: \.self) { tag in MobileCardChip(text: tag) } diff --git a/TFITests/MobileCardRenderingTests.swift b/TFITests/MobileCardRenderingTests.swift index 22e92540..8a74d1e8 100644 --- a/TFITests/MobileCardRenderingTests.swift +++ b/TFITests/MobileCardRenderingTests.swift @@ -36,8 +36,7 @@ struct MobileCardRenderingTests { if index == 3 { _ = app.knownHosts.save( KnownHostEntry( - hostname: host.host, fingerprint: "SHA256:0123456789abcdefghijklmnopqrstuvwxyzABCDE", - isMismatched: true)) + hostname: host.host, fingerprint: "SHA256:0123456789abcdefghijklmnopqrstuvwxyzABCDE")) } } let editableKnownHost = KnownHostEntry( diff --git a/TFM/Application/FeatureModels/HostLibraryModel+Demo.swift b/TFM/Application/FeatureModels/HostLibraryModel+Demo.swift index 18ff38b2..c0162344 100644 --- a/TFM/Application/FeatureModels/HostLibraryModel+Demo.swift +++ b/TFM/Application/FeatureModels/HostLibraryModel+Demo.swift @@ -174,13 +174,10 @@ extension HostLibraryModel { fingerprint: "SHA256:2mF8sK1pQ7xR4vB9nL3yT6wC0dH5jA2uZgE7iOxNbVr", addedAt: now.addingTimeInterval(-604_800) ), - // One deliberately broken pin, so the warning state is visible at P0 - // instead of first appearing the day it matters. KnownHostEntry( hostname: "staging.internal", algorithm: .rsa, fingerprint: "SHA256:5vT2nB8xQ1mL7kR4pW9yC3sD6fH0jG5uZaE8iXoNbVr", - addedAt: now.addingTimeInterval(-15_552_000), - isMismatched: true + addedAt: now.addingTimeInterval(-15_552_000) ), ] diff --git a/TFM/Application/FeatureModels/TransferLibraryModel.swift b/TFM/Application/FeatureModels/TransferLibraryModel.swift index 27745745..bde6e730 100644 --- a/TFM/Application/FeatureModels/TransferLibraryModel.swift +++ b/TFM/Application/FeatureModels/TransferLibraryModel.swift @@ -123,9 +123,6 @@ extension TransferLibraryModel: RemoteSessionHostStore { library.recordRemoteSystemInfo(systemInfo, for: hostID) } func trust(_ entry: KnownHostEntry) { library.trust(entry) } - func markKnownHostMismatched(hostname: String, port: Int) { - library.markKnownHostMismatched(hostname: hostname, port: port) - } @discardableResult func upsert(_ identity: Identity) -> Bool { library.upsert(identity) } func upsert(_ identity: Identity, passwordUpdate: IdentityPasswordUpdate) throws { try library.upsert(identity, passwordUpdate: passwordUpdate) diff --git a/TFM/Features/Hosts/HostsPageView.swift b/TFM/Features/Hosts/HostsPageView.swift index 80633239..29553976 100644 --- a/TFM/Features/Hosts/HostsPageView.swift +++ b/TFM/Features/Hosts/HostsPageView.swift @@ -421,13 +421,6 @@ private struct HostCard: View { private var status: ConnectionStatus { app.hostLibrary.status(of: host.id) } - /// A pinned key that stopped matching. Shown here, on the host, because that - /// is what the fingerprint belongs to — there is no separate Known Hosts - /// list to go and check. - private var keyMismatched: Bool { - app.hostLibrary.knownHosts(for: host).contains(where: \.isMismatched) - } - var body: some View { card.clearAllHostHistoryConfirmation(hostIDs: $clearHistoryHostIDs) } @@ -486,13 +479,6 @@ private struct HostCard: View { private var footer: some View { HStack(spacing: Metrics.Space.xs) { - if keyMismatched { - CardChip( - text: String(localized: "Host key changed"), - symbol: "exclamationmark.triangle.fill", - tint: .orange - ) - } ForEach(host.tags.prefix(2), id: \.self) { tag in CardChip(text: tag) } diff --git a/TFMTests/AgentHostAuthenticationTests.swift b/TFMTests/AgentHostAuthenticationTests.swift index 38aeb63e..00db0256 100644 --- a/TFMTests/AgentHostAuthenticationTests.swift +++ b/TFMTests/AgentHostAuthenticationTests.swift @@ -864,7 +864,6 @@ struct AgentHostAuthenticationTests { #expect(decision == .accept) #expect(attempt.configuration.expectedHostKeys == [old.identity]) #expect(Set(store.knownHosts(for: host).map(\.identity)) == [old.identity, observation.identity]) - #expect(store.knownHosts(for: host).allSatisfy { !$0.isMismatched }) } @Test diff --git a/TFMTests/ConnectionJourneyTests.swift b/TFMTests/ConnectionJourneyTests.swift index ff2d1679..306044c6 100644 --- a/TFMTests/ConnectionJourneyTests.swift +++ b/TFMTests/ConnectionJourneyTests.swift @@ -278,12 +278,11 @@ struct ConnectionJourneyTests { } @Test - func trustingTheSameFingerprintIsIdempotentAndClearsMismatch() { + func trustingTheSameFingerprintIsIdempotent() { let host = Host(name: "Server", host: "server.example") let original = KnownHostEntry( hostname: host.host, algorithm: .ed25519, - fingerprint: "SHA256:key", addedAt: Date(timeIntervalSince1970: 1), - isMismatched: true + fingerprint: "SHA256:key", addedAt: Date(timeIntervalSince1970: 1) ) let store = HostLibraryModel(hosts: [host], knownHosts: [original]) @@ -298,28 +297,19 @@ struct ConnectionJourneyTests { #expect(entries.count == 1) #expect(entries[0].id == original.id) #expect(entries[0].addedAt == Date(timeIntervalSince1970: 2)) - #expect(!entries[0].isMismatched) } @Test - func mismatchAndForgetOperationsApplyToTheHostKeySet() { + func trustAndForgetOperationsApplyToTheHostKeySet() { let host = Host(name: "Server", host: "server.example") let first = KnownHostEntry(hostname: host.host, fingerprint: "SHA256:first") let second = KnownHostEntry(hostname: host.host, fingerprint: "SHA256:second") let store = HostLibraryModel(hosts: [host], knownHosts: [first, second]) - store.markKnownHostMismatched(hostname: host.host, port: host.port) - for entry in store.knownHosts(for: host) { - #expect(entry.isMismatched) - } - store.trust( KnownHostEntry(hostname: host.host, fingerprint: "SHA256:third") ) #expect(store.knownHosts(for: host).count == 3) - for entry in store.knownHosts(for: host) { - #expect(!entry.isMismatched) - } store.forget(knownHost: first.id) #expect(store.knownHosts(for: host).count == 2) diff --git a/TFMTests/SSHImportTests.swift b/TFMTests/SSHImportTests.swift index 88fe7a87..1cc13ae1 100644 --- a/TFMTests/SSHImportTests.swift +++ b/TFMTests/SSHImportTests.swift @@ -56,8 +56,6 @@ struct SSHImportTests { let store = HostLibraryModel(knownHosts: [decoded]) let host = Host(name: "IPv6", host: "2001:db8::1", port: 2222) #expect(try store.expectedHostKeys(for: host) == [entry.identity]) - store.markKnownHostMismatched(hostname: host.host, port: host.port) - #expect(store.knownHosts[0].isMismatched) store.forgetKnownHosts(for: host) #expect(store.knownHosts.isEmpty) } @@ -427,8 +425,8 @@ struct SSHImportTests { } @Test - func importRetainsMismatchWarningsAndRequiresConflictReview() async { - let original = KnownHostEntry(hostname: "host", fingerprint: "SHA256:old", isMismatched: true) + func importRequiresKnownHostConflictReview() async { + let original = KnownHostEntry(hostname: "host", fingerprint: "SHA256:old") let store = HostLibraryModel(knownHosts: [original]) let model = SSHImportModel(library: store, reader: SSHImportReader()) model.items = [ @@ -441,7 +439,7 @@ struct SSHImportTests { #expect(model.canImport) await model.commit() #expect(model.completed) - #expect(store.knownHosts.first?.isMismatched == true) + #expect(store.knownHosts.first?.fingerprint == original.fingerprint) } @Test diff --git a/TFMTests/VaultSyncTests.swift b/TFMTests/VaultSyncTests.swift index 409213fd..eaadaa4d 100644 --- a/TFMTests/VaultSyncTests.swift +++ b/TFMTests/VaultSyncTests.swift @@ -115,10 +115,10 @@ struct VaultSyncTests { #expect(Set(received.knownHosts) == Set([plain, hashed])) var edited = plain - edited.isMismatched = true + edited.algorithm = .ecdsa256 #expect(library.saveKnownHost(edited)) try transfer(plain.id) - #expect(try destinationPersistence.load().knownHosts.first { $0.id == plain.id }?.isMismatched == true) + #expect(try destinationPersistence.load().knownHosts.first { $0.id == plain.id }?.algorithm == .ecdsa256) #expect(library.deleteKnownHosts([plain.id])) try transfer(plain.id)