Add Trust Revoke View

This commit is contained in:
Leon Böttger committed 2025-08-23 21:01:31 +02:00
1 parent 448cc15a7e
commit d3dc89f412
10 files changed
+2438 -1467

No files matched your search

@@ -547,9 +547,8 @@ class InboundNearbyConnection: NearbyConnection {
func submitUserConsent(accepted: Bool, trustDevice: Bool, storeInTemp: Bool = false) {
if trustDevice, let peerCertificate = peerCertificate {
TrustStore.shared.addTrusted(certificate: peerCertificate)
if trustDevice, let peerCertificate = peerCertificate, let remoteDeviceInfo = remoteDeviceInfo {
TrustStore.shared.addTrusted(certificate: peerCertificate, device: remoteDeviceInfo)
}
DispatchQueue.global(qos: .utility).async {
+3 -3
View File
@@ -46,10 +46,10 @@ public protocol OutboundAppDelegate: AnyObject {
}
public struct RemoteDeviceInfo: Identifiable, Equatable {
public struct RemoteDeviceInfo: Codable, Identifiable, Equatable {
public let name: String?
public let type: DeviceType
public let qrCodeData:Data?
public let qrCodeData: Data?
public var id: String?
init(name: String, type: DeviceType, id: String? = nil) {
@@ -67,7 +67,7 @@ public struct RemoteDeviceInfo: Identifiable, Equatable {
}
public enum DeviceType: Int32 {
public enum DeviceType: Int32, Codable {
case unknown = 0
case phone
case tablet
+66 -12
View File
@@ -8,26 +8,80 @@
import Foundation
class TrustStore {
class TrustStore: ObservableObject {
static let shared = TrustStore()
@Published private(set) var trustedCertificates: [String: TrustedCertificate] = [:]
private let trustedKeysKey = "com.leonboettger.quickdrop.identity.trustedKeys"
private func getTrustedKeys() -> [String: Data] {
return AppGroup.appGroupUD.dictionary(forKey: trustedKeysKey) as? [String: Data] ?? [:]
private init() {
loadTrustedCertificates()
}
// MARK: - Persistence
private func loadTrustedCertificates() {
guard let dict = AppGroup.appGroupUD.dictionary(forKey: trustedKeysKey) as? [String: Data] else {
trustedCertificates = [:]
return
}
var decoded: [String: TrustedCertificate] = [:]
for (key, data) in dict {
if let cert = try? JSONDecoder().decode(TrustedCertificate.self, from: data) {
decoded[key] = cert
}
}
trustedCertificates = decoded
}
private func saveTrustedCertificates() {
var dict: [String: Data] = [:]
for (key, cert) in trustedCertificates {
if let data = try? JSONEncoder().encode(cert) {
dict[key] = data
}
}
AppGroup.appGroupUD.set(dict, forKey: trustedKeysKey)
}
// MARK: - Public API
func findTrustedKey(for secretIdHash: Data) -> Data? {
return getTrustedKeys()[secretIdHash.hex]
trustedCertificates[secretIdHash.hex]?.certificateData
}
func addTrusted(certificate: Sharing_Nearby_PublicCertificate) {
func addTrusted(certificate: Sharing_Nearby_PublicCertificate, device: RemoteDeviceInfo) {
guard let publicKeyData = try? certificate.serializedData() else { return }
var keys = getTrustedKeys()
keys[certificate.secretID.hex] = publicKeyData
AppGroup.appGroupUD.set(keys, forKey: trustedKeysKey)
trustedCertificates[certificate.secretID.hex] = TrustedCertificate(
device: device,
creationDate: Date(),
certificateData: publicKeyData
)
saveTrustedCertificates()
}
func removeTrusted(secretIdHex: String) {
trustedCertificates.removeValue(forKey: secretIdHex)
saveTrustedCertificates()
}
struct TrustedCertificate: Codable, Equatable {
// warning: do not rename for persistence
let device: RemoteDeviceInfo
let creationDate: Date
let certificateData: Data
}
}
+8
View File
@@ -14,6 +14,9 @@
699DEBAE2AB0573200115D22 /* QuickDropShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 699DEBA12AB0573200115D22 /* QuickDropShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
69DA9A1229E0BF5100A442DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 69DA9A1129E0BF5100A442DA /* AppDelegate.swift */; };
69DA9A1429E0BF5200A442DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 69DA9A1329E0BF5200A442DA /* Assets.xcassets */; };
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */; };
EB16B6122E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
EB16B6132E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
EB16B6142E58B4B9007D9792 /* IdentityManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EB16B6112E58B4B9007D9792 /* IdentityManager.swift */; };
@@ -299,6 +302,7 @@
69DA9A2B29E18CB500A442DA /* offline_wire_formats.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = offline_wire_formats.pb.swift; sourceTree = "<group>"; };
69DA9A2C29E18CB500A442DA /* securegcm.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securegcm.pb.swift; sourceTree = "<group>"; };
69DA9A2D29E18CB500A442DA /* securemessage.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = securemessage.pb.swift; sourceTree = "<group>"; };
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustedDevicesView.swift; sourceTree = "<group>"; };
EB16B6112E58B4B9007D9792 /* IdentityManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IdentityManager.swift; sourceTree = "<group>"; };
EB16B6162E58B4C8007D9792 /* TrustStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TrustStore.swift; sourceTree = "<group>"; };
EB16B7762E59BA9F007D9792 /* AppGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppGroup.swift; sourceTree = "<group>"; };
@@ -590,6 +594,7 @@
EBD2A2162D2860BE00BCB8E2 /* WelcomeScreen.swift */,
EB7F54112D78E95C00989564 /* TutorialView.swift */,
EB7F54132D78E9E400989564 /* LicenseWindow.swift */,
EB05DFBD2E59FC8000EF567A /* TrustedDevicesView.swift */,
EB6F62D62D91F4AC00534EC2 /* SettingsView.swift */,
EBC05C812E33B58300EECC18 /* IssueViews */,
);
@@ -928,6 +933,7 @@
EB5C37B82E377A6F0052EFD7 /* SleepManager.swift in Sources */,
EB5C37B92E377A6F0052EFD7 /* SaveFilesManager.swift in Sources */,
EB5C37BA2E377A6F0052EFD7 /* Helpers.swift in Sources */,
EB05DFBE2E59FC8900EF567A /* TrustedDevicesView.swift in Sources */,
EB16B6182E58B4C8007D9792 /* TrustStore.swift in Sources */,
EBC05C5B2E33937200EECC18 /* ErrorAlertHandler.swift in Sources */,
EBC05C712E33B57D00EECC18 /* Converter.swift in Sources */,
@@ -974,6 +980,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
EB05DFBF2E59FD0A00EF567A /* TrustedDevicesView.swift in Sources */,
EB5C37F12E377CE70052EFD7 /* ukey.pb.swift in Sources */,
EB5C37F22E377CE70052EFD7 /* NearbyConnection.swift in Sources */,
EB5C37F32E377CE70052EFD7 /* SaveFilesManager.swift in Sources */,
@@ -1034,6 +1041,7 @@
EB950B512E520436002D68FA /* securemessage.pb.swift in Sources */,
EB950B522E520436002D68FA /* NearbyConnectionManager.swift in Sources */,
EB950B532E520436002D68FA /* ukey.pb.swift in Sources */,
EB05DFC02E5A2EAA00EF567A /* TrustedDevicesView.swift in Sources */,
EB950B542E520436002D68FA /* HKDF.swift in Sources */,
EB950B552E520436002D68FA /* SaveFilesManager.swift in Sources */,
EB950B562E520436002D68FA /* DataModel.swift in Sources */,
@@ -34,7 +34,6 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
language = "en"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
File diff suppressed because it is too large. Load diff
+34 -9
View File
@@ -45,21 +45,27 @@ struct SettingsView: View {
Toggle("OpenFinderAfterReceiving", isOn: $openFinderAfterReceiving)
.padding(.top, 5)
Text("OpenFinderAfterReceivingFooter")
.font(.footnote)
.padding(.top, 5)
if #available(macOS 13.0, *) {
Divider()
.padding(.vertical, 10)
LaunchAtLogin.Toggle {
Text("LaunchAtLogin")
}
.padding(.top, 5)
}
Divider()
.padding(.vertical, 10)
Button {
openTrustedDevicesWindow()
} label: {
Text("ManageTrustedDevices")
.underline()
.font(.subheadline)
.opacity(0.5)
.frame(maxWidth: .infinity)
.padding(.vertical, 15)
}
.buttonStyle(PlainButtonStyle())
}
}
}
@@ -98,8 +104,27 @@ struct SettingsView: View {
}
}
}
private func openTrustedDevicesWindow() {
let window = NSWindow(
contentRect: NSRect(x: 0, y: 0, width: 600, height: 400),
styleMask: [.titled, .closable, .resizable],
backing: .buffered,
defer: false
)
window.center()
window.isReleasedWhenClosed = false
window.title = "TrustedDevices".localized()
window.contentView = NSHostingView(rootView: TrustedDevicesView())
NSApp.activate(ignoringOtherApps: true)
window.makeKeyAndOrderFront(nil)
window.level = .normal
}
}
struct SettingsView_Previews: PreviewProvider {
static var previews: some View {
SettingsView()
+125
View File
@@ -0,0 +1,125 @@
//
// TrustedDevicesView.swift
// QuickDrop
//
// Created by Leon Böttger on 23.08.25.
//
import SwiftUI
#if !os(macOS)
import LUI
#endif
struct TrustedDevicesView: View {
@StateObject private var store = TrustStore.shared
@State private var deviceToRemove: String? = nil
@State private var showRemoveAlert = false
var body: some View {
VStack {
if store.trustedCertificates.isEmpty {
VStack(spacing: 12) {
Text("NoTrustedDevices")
.font(.headline)
.foregroundColor(.secondary)
Text("TrustedDevicesDescription")
.font(.subheadline)
.foregroundColor(.secondary)
}
.multilineTextAlignment(.center)
.padding()
.frame(maxHeight: .infinity)
} else {
#if !os(macOS)
CustomSection(footer: "TrustedDevicesDescription") {
devicesView
.padding(.vertical, 4)
}
#else
List {
devicesView
Text("TrustedDevicesDescription")
.font(.footnote)
.fixedSize(horizontal: false, vertical: true)
.foregroundColor(.secondary)
.padding(.top, 4)
}
#endif
}
}
.animation(.default, value: store.trustedCertificates)
.alert(isPresented: $showRemoveAlert) {
Alert(
title: Text("RemoveTrustedDeviceAlertTitle"),
message: Text("RemoveTrustedDeviceAlertDescription"),
primaryButton: .destructive(Text("Remove")) {
if let key = deviceToRemove {
store.removeTrusted(secretIdHex: key)
}
},
secondaryButton: .cancel()
)
}
}
var devicesView: some View {
ForEach(Array(store.trustedCertificates.keys), id: \.self) { key in
if let cert = store.trustedCertificates[key] {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(cert.device.name ?? "Unknown".localized())
.font(.headline)
// macOS 11: use DateFormatter instead of .formatted
Text(Self.dateFormatter.string(from: cert.creationDate))
.font(.subheadline)
.foregroundColor(.secondary)
}
Spacer()
Button(action: {
deviceToRemove = key
showRemoveAlert = true
}) {
Image(systemName: "minus.circle.fill")
.font(.system(size: 20))
.foregroundColor(.red)
}
.buttonStyle(BorderlessButtonStyle())
}
.padding(.vertical, 8)
}
}
}
// MARK: - DateFormatter for macOS 11
private static let dateFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .medium
formatter.timeStyle = .short
return formatter
}()
}
#Preview {
TrustedDevicesView()
.onAppear {
var cert1 = Sharing_Nearby_PublicCertificate()
cert1.secretID = Data([0x01, 0x02, 0x03, 0x04])
TrustStore.shared.addTrusted(certificate: cert1, device: RemoteDeviceInfo(name: "MacBook Pro", type: .computer))
var cert2 = Sharing_Nearby_PublicCertificate()
cert2.secretID = Data([0x0A, 0x0B, 0x0C, 0x0D])
TrustStore.shared.addTrusted(certificate: cert2, device: RemoteDeviceInfo(name: "iPhone", type: .phone))
}
}
+6
View File
@@ -24,6 +24,12 @@ struct ContentView: View {
} label: {
NavigationLinkLabel(imageName: "folder.fill", text: "BrowseDownloadedFiles")
}
LUILink(destination: NavigationSubView(header: "TrustedDevices") {
TrustedDevicesView()
}) {
NavigationLinkLabel(imageName: "checkmark.shield.fill", text: "ManageTrustedDevices", backgroundColor: .green)
}
}
})
}
+8 -1
View File
@@ -61,8 +61,15 @@ class ReceiveModel: ObservableObject, InboundAppDelegate {
let pinCodeMessage = String(format: "PinCode".localized(), arguments: [transfer.pinCode ?? "?"])
let transferID = transfer.id
#if os(macOS)
let isMac = true
#else
let isMac = false
#endif
if acceptAutomatically && isMac() {
if acceptAutomatically && isMac {
pressAcceptButton(transferID: transfer.id, trustDevice: false)
UNUserNotificationCenter.current().requestAuthorization(options: [.alert, .sound]) { granted, _ in