// Trust-on-first-use prompt: shown over the live-but-blurred stream when connecting to an // unpinned host. The user compares the fingerprint with the one the host logged at startup, // or drops this and runs the PIN pairing ceremony instead. import Foundation import SwiftUI struct TrustCardView: View { let fingerprint: Data let hostName: String let onCancel: () -> Void let onTrust: () -> Void let onPairInstead: () -> Void var body: some View { VStack(spacing: 14) { Image(systemName: "lock.shield") .font(.system(size: 36, weight: .light)) .foregroundStyle(.tint) Text("Verify \(hostName)") .font(.title3.weight(.semibold)) Text("First connection. Compare this fingerprint with the one " + "punktfunk-host logged at startup (\u{201C}clients pin this " + "fingerprint\u{201D}):") .font(.callout) .foregroundStyle(.secondary) .multilineTextAlignment(.center) Text(Self.format(fingerprint: fingerprint)) .font(.system(.callout, design: .monospaced)) #if !os(tvOS) .textSelection(.enabled) #endif .padding(10) .background(.quaternary, in: RoundedRectangle(cornerRadius: 8)) HStack(spacing: 12) { Button("Cancel", role: .cancel, action: onCancel) #if !os(tvOS) .keyboardShortcut(.cancelAction) #endif Button("Trust & Connect", action: onTrust) .buttonStyle(.borderedProminent) #if !os(tvOS) .keyboardShortcut(.defaultAction) #endif } #if os(iOS) .controlSize(.large) #endif // The verified alternative to eyeballing hex: drop this session (the host // serves one connection at a time) and run the SPAKE2 PIN ceremony instead. Button("Pair with PIN instead…", action: onPairInstead) #if os(macOS) .buttonStyle(.link) #else .buttonStyle(.borderless) #endif .font(.callout) } .padding(28) .frame(maxWidth: 440) .background(.regularMaterial, in: RoundedRectangle(cornerRadius: 18)) } /// 64 hex chars → four groups per line, two lines — easy to eyeball against the log. private static func format(fingerprint: Data) -> String { let hex = fingerprint.map { String(format: "%02x", $0) }.joined() let groups = stride(from: 0, to: hex.count, by: 8).map { i -> String in let start = hex.index(hex.startIndex, offsetBy: i) let end = hex.index(start, offsetBy: min(8, hex.count - i)) return String(hex[start.. [[Element]] { stride(from: 0, to: count, by: size).map { Array(self[$0..