diff --git a/Coder-Desktop/Coder-Desktop/State.swift b/Coder-Desktop/Coder-Desktop/State.swift index faf15e0..3807273 100644 --- a/Coder-Desktop/Coder-Desktop/State.swift +++ b/Coder-Desktop/Coder-Desktop/State.swift @@ -10,6 +10,8 @@ class AppState: ObservableObject { private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "AppState") let appId = Bundle.main.bundleIdentifier! + let dangerousDisableCoderSignatureValidation: Bool + // Stored in UserDefaults @Published private(set) var hasSession: Bool { didSet { @@ -87,6 +89,8 @@ class AppState: ObservableObject { if useLiteralHeaders, let headers = try? JSONEncoder().encode(literalHeaders) { proto.providerConfiguration?["literalHeaders"] = headers } + // swiftlint:disable:next line_length + proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] = dangerousDisableCoderSignatureValidation proto.serverAddress = baseAccessURL!.absoluteString return proto } @@ -106,6 +110,9 @@ class AppState: ObservableObject { { self.persistent = persistent self.onChange = onChange + dangerousDisableCoderSignatureValidation = persistent + ? UserDefaults.standard.bool(forKey: Keys.dangerousDisableCoderSignatureValidation) + : false keychain = Keychain(service: Bundle.main.bundleIdentifier!) _hasSession = Published(initialValue: persistent ? UserDefaults.standard.bool(forKey: Keys.hasSession) : false) _baseAccessURL = Published( @@ -219,6 +226,7 @@ class AppState: ObservableObject { static let stopVPNOnQuit = "StopVPNOnQuit" static let startVPNOnLaunch = "StartVPNOnLaunch" + static let dangerousDisableCoderSignatureValidation = "DangerousDisableCoderSignatureValidation" static let skipHiddenIconAlert = "SkipHiddenIconAlert" } } diff --git a/Coder-Desktop/Coder-Desktop/Views/Util.swift b/Coder-Desktop/Coder-Desktop/Views/Util.swift index 10d0747..8987f54 100644 --- a/Coder-Desktop/Coder-Desktop/Views/Util.swift +++ b/Coder-Desktop/Coder-Desktop/Views/Util.swift @@ -55,7 +55,7 @@ private struct ActivationPolicyModifier: ViewModifier { NSApp.setActivationPolicy(.regular) } .onDisappear { - if NSApp.windows.filter { $0.level != .statusBar && $0.isVisible }.count <= 1 { + if NSApp.windows.filter({ $0.level != .statusBar && $0.isVisible }).count <= 1 { NSApp.setActivationPolicy(.accessory) } } diff --git a/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift b/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift index 9b65d8e..02375ef 100644 --- a/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift +++ b/Coder-Desktop/Coder-DesktopHelper/HelperXPCListeners.swift @@ -75,11 +75,13 @@ class HelperNEXPCServer: NSObject, NSXPCListenerDelegate, @unchecked Sendable { } extension HelperNEXPCServer: HelperNEXPCInterface { + // swiftlint:disable:next function_parameter_count func startDaemon( accessURL: URL, token: String, tun: FileHandle, headers: Data?, + dangerousDisableSignatureValidation: Bool, reply: @escaping (Error?) -> Void ) { logger.info("startDaemon called") @@ -92,7 +94,13 @@ extension HelperNEXPCServer: HelperNEXPCInterface { apiToken: token, serverUrl: accessURL, tunFd: tun.fileDescriptor, - literalHeaders: headers.flatMap { try? JSONDecoder().decode([HTTPHeader].self, from: $0) } ?? [] + literalHeaders: headers.flatMap { + try? JSONDecoder().decode( + [HTTPHeader].self, + from: $0 + ) + } ?? [], + dangerousDisableSignatureValidation: dangerousDisableSignatureValidation ) ) try await manager.startVPN() diff --git a/Coder-Desktop/Coder-DesktopHelper/Manager.swift b/Coder-Desktop/Coder-DesktopHelper/Manager.swift index 7ef3d61..778088d 100644 --- a/Coder-Desktop/Coder-DesktopHelper/Manager.swift +++ b/Coder-Desktop/Coder-DesktopHelper/Manager.swift @@ -25,7 +25,7 @@ actor Manager { private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "manager") - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity init(cfg: ManagerConfig) async throws(ManagerError) { self.cfg = cfg telemetryEnricher = TelemetryEnricher() @@ -74,12 +74,17 @@ actor Manager { } catch { throw .download(error) } - pushProgress(stage: .validating) + do { - try Validator.validateSignature(binaryPath: dest) + if cfg.dangerousDisableSignatureValidation { + logger.warning("Skipping code signature validation of downloaded binary (disabled by configuration)") + } else { + pushProgress(stage: .validating) + try Validator.validateSignature(binaryPath: dest) + } try await Validator.validateVersion(binaryPath: dest, serverVersion: buildInfo.version) } catch { - // Cleanup unvalid binary + // Cleanup invalid binary try? FileManager.default.removeItem(at: dest) throw .validation(error) } @@ -270,6 +275,7 @@ struct ManagerConfig { let serverUrl: URL let tunFd: Int32 let literalHeaders: [HTTPHeader] + let dangerousDisableSignatureValidation: Bool } enum ManagerError: Error { diff --git a/Coder-Desktop/VPN/NEHelperXPCClient.swift b/Coder-Desktop/VPN/NEHelperXPCClient.swift index 05737c4..2d1d77a 100644 --- a/Coder-Desktop/VPN/NEHelperXPCClient.swift +++ b/Coder-Desktop/VPN/NEHelperXPCClient.swift @@ -35,7 +35,13 @@ final class HelperXPCClient: @unchecked Sendable { return connection } - func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?) async throws { + func startDaemon( + accessURL: URL, + token: String, + tun: FileHandle, + headers: Data?, + dangerousDisableSignatureValidation: Bool + ) async throws { let conn = connect() return try await withCheckedThrowingContinuation { continuation in guard let proxy = conn.remoteObjectProxyWithErrorHandler({ err in @@ -46,7 +52,13 @@ final class HelperXPCClient: @unchecked Sendable { continuation.resume(throwing: XPCError.wrongProxyType) return } - proxy.startDaemon(accessURL: accessURL, token: token, tun: tun, headers: headers) { err in + proxy.startDaemon( + accessURL: accessURL, + token: token, + tun: tun, + headers: headers, + dangerousDisableSignatureValidation: dangerousDisableSignatureValidation + ) { err in if let error = err { self.logger.error("Failed to start daemon: \(error.localizedDescription, privacy: .public)") continuation.resume(throwing: error) diff --git a/Coder-Desktop/VPN/PacketTunnelProvider.swift b/Coder-Desktop/VPN/PacketTunnelProvider.swift index a2d3559..70b50e6 100644 --- a/Coder-Desktop/VPN/PacketTunnelProvider.swift +++ b/Coder-Desktop/VPN/PacketTunnelProvider.swift @@ -59,6 +59,7 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { throw makeNSError(suffix: "PTP", desc: "Missing Token") } let headers = proto.providerConfiguration?["literalHeaders"] as? Data + let disableSigVal = proto.providerConfiguration?["dangerousDisableCoderSignatureValidation"] as? Bool ?? false logger.debug("retrieved token & access URL") guard let tunFd = tunnelFileDescriptor else { logger.error("startTunnel called with nil tunnelFileDescriptor") @@ -68,7 +69,8 @@ class PacketTunnelProvider: NEPacketTunnelProvider, @unchecked Sendable { accessURL: .init(string: baseAccessURL)!, token: token, tun: FileHandle(fileDescriptor: tunFd), - headers: headers + headers: headers, + dangerousDisableSignatureValidation: disableSigVal ) } diff --git a/Coder-Desktop/VPNLib/XPC.swift b/Coder-Desktop/VPNLib/XPC.swift index daf902f..75d3cd2 100644 --- a/Coder-Desktop/VPNLib/XPC.swift +++ b/Coder-Desktop/VPNLib/XPC.swift @@ -26,7 +26,15 @@ public let helperNEMachServiceName = "4399GN35BJ.com.coder.Coder-Desktop.HelperN @preconcurrency @objc public protocol HelperNEXPCInterface { // headers is a JSON `[HTTPHeader]` - func startDaemon(accessURL: URL, token: String, tun: FileHandle, headers: Data?, reply: @escaping (Error?) -> Void) + // swiftlint:disable:next function_parameter_count + func startDaemon( + accessURL: URL, + token: String, + tun: FileHandle, + headers: Data?, + dangerousDisableSignatureValidation: Bool, + reply: @escaping (Error?) -> Void + ) func stopDaemon(reply: @escaping (Error?) -> Void) }