diff options
4 files changed, 147 insertions, 26 deletions
diff --git a/WireGuard/Shared/Model/TunnelConfiguration+WgQuickConfig.swift b/WireGuard/Shared/Model/TunnelConfiguration+WgQuickConfig.swift index 3225979..e438e29 100644 --- a/WireGuard/Shared/Model/TunnelConfiguration+WgQuickConfig.swift +++ b/WireGuard/Shared/Model/TunnelConfiguration+WgQuickConfig.swift @@ -12,16 +12,28 @@ extension TunnelConfiguration { } enum ParseError: Error { - case invalidLine(_ line: String.SubSequence) + case invalidLine(String.SubSequence) case noInterface - case invalidInterface case multipleInterfaces + case interfaceHasNoPrivateKey + case interfaceHasInvalidPrivateKey(String) + case interfaceHasInvalidListenPort(String) + case interfaceHasInvalidAddress(String) + case interfaceHasInvalidDNS(String) + case interfaceHasInvalidMTU(String) + case interfaceHasUnrecognizedKey(String) + case peerHasNoPublicKey + case peerHasInvalidPublicKey(String) + case peerHasInvalidPreSharedKey(String) + case peerHasInvalidAllowedIP(String) + case peerHasInvalidEndpoint(String) + case peerHasInvalidPersistentKeepAlive(String) + case peerHasUnrecognizedKey(String) case multiplePeersWithSamePublicKey - case invalidPeer } //swiftlint:disable:next function_body_length cyclomatic_complexity - convenience init(fromWgQuickConfig wgQuickConfig: String, called name: String? = nil) throws { + convenience init(fromWgQuickConfig wgQuickConfig: String, called name: String? = nil, ignoreUnrecognizedKeys: Bool = true) throws { var interfaceConfiguration: InterfaceConfiguration? var peerConfigurations = [PeerConfiguration]() @@ -45,7 +57,8 @@ extension TunnelConfiguration { if let equalsIndex = line.firstIndex(of: "=") { // Line contains an attribute - let key = line[..<equalsIndex].trimmingCharacters(in: .whitespaces).lowercased() + let keyWithCase = line[..<equalsIndex].trimmingCharacters(in: .whitespaces) + let key = keyWithCase.lowercased() let value = line[line.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespaces) let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"] if let presentValue = attributes[key], keysWithMultipleEntriesAllowed.contains(key) { @@ -53,6 +66,19 @@ extension TunnelConfiguration { } else { attributes[key] = value } + if !ignoreUnrecognizedKeys { + let interfaceSectionKeys: Set<String> = ["privatekey", "listenport", "address", "dns", "mtu"] + let peerSectionKeys: Set<String> = ["publickey", "presharedkey", "allowedips", "endpoint", "persistentkeepalive"] + if parserState == .inInterfaceSection { + guard interfaceSectionKeys.contains(key) else { + throw ParseError.interfaceHasUnrecognizedKey(keyWithCase) + } + } else if parserState == .inPeerSection { + guard peerSectionKeys.contains(key) else { + throw ParseError.peerHasUnrecognizedKey(keyWithCase) + } + } + } } else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" { throw ParseError.invalidLine(line) } @@ -62,11 +88,11 @@ extension TunnelConfiguration { if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" { // Previous section has ended; process the attributes collected so far if parserState == .inInterfaceSection { - guard let interface = TunnelConfiguration.collate(interfaceAttributes: attributes) else { throw ParseError.invalidInterface } + let interface = try TunnelConfiguration.collate(interfaceAttributes: attributes) guard interfaceConfiguration == nil else { throw ParseError.multipleInterfaces } interfaceConfiguration = interface } else if parserState == .inPeerSection { - guard let peer = TunnelConfiguration.collate(peerAttributes: attributes) else { throw ParseError.invalidPeer } + let peer = try TunnelConfiguration.collate(peerAttributes: attributes) peerConfigurations.append(peer) } } @@ -133,21 +159,26 @@ extension TunnelConfiguration { } //swiftlint:disable:next cyclomatic_complexity - private static func collate(interfaceAttributes attributes: [String: String]) -> InterfaceConfiguration? { - // required wg fields - guard let privateKeyString = attributes["privatekey"] else { return nil } - guard let privateKey = Data(base64Encoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else { return nil } + private static func collate(interfaceAttributes attributes: [String: String]) throws -> InterfaceConfiguration { + guard let privateKeyString = attributes["privatekey"] else { + throw ParseError.interfaceHasNoPrivateKey + } + guard let privateKey = Data(base64Encoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else { + throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString) + } var interface = InterfaceConfiguration(privateKey: privateKey) - // other wg fields if let listenPortString = attributes["listenport"] { - guard let listenPort = UInt16(listenPortString) else { return nil } + guard let listenPort = UInt16(listenPortString) else { + throw ParseError.interfaceHasInvalidListenPort(listenPortString) + } interface.listenPort = listenPort } - // wg-quick fields if let addressesString = attributes["address"] { var addresses = [IPAddressRange]() for addressString in addressesString.splitToArray(trimmingCharacters: .whitespaces) { - guard let address = IPAddressRange(from: addressString) else { return nil } + guard let address = IPAddressRange(from: addressString) else { + throw ParseError.interfaceHasInvalidAddress(addressString) + } addresses.append(address) } interface.addresses = addresses @@ -155,43 +186,57 @@ extension TunnelConfiguration { if let dnsString = attributes["dns"] { var dnsServers = [DNSServer]() for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespaces) { - guard let dnsServer = DNSServer(from: dnsServerString) else { return nil } + guard let dnsServer = DNSServer(from: dnsServerString) else { + throw ParseError.interfaceHasInvalidDNS(dnsServerString) + } dnsServers.append(dnsServer) } interface.dns = dnsServers } if let mtuString = attributes["mtu"] { - guard let mtu = UInt16(mtuString) else { return nil } + guard let mtu = UInt16(mtuString) else { + throw ParseError.interfaceHasInvalidMTU(mtuString) + } interface.mtu = mtu } return interface } //swiftlint:disable:next cyclomatic_complexity - private static func collate(peerAttributes attributes: [String: String]) -> PeerConfiguration? { - // required wg fields - guard let publicKeyString = attributes["publickey"] else { return nil } - guard let publicKey = Data(base64Encoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else { return nil } + private static func collate(peerAttributes attributes: [String: String]) throws -> PeerConfiguration { + guard let publicKeyString = attributes["publickey"] else { + throw ParseError.peerHasNoPublicKey + } + guard let publicKey = Data(base64Encoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else { + throw ParseError.peerHasInvalidPublicKey(publicKeyString) + } var peer = PeerConfiguration(publicKey: publicKey) - // wg fields if let preSharedKeyString = attributes["presharedkey"] { - guard let preSharedKey = Data(base64Encoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength else { return nil } + guard let preSharedKey = Data(base64Encoded: preSharedKeyString), preSharedKey.count == TunnelConfiguration.keyLength else { + throw ParseError.peerHasInvalidPreSharedKey(preSharedKeyString) + } peer.preSharedKey = preSharedKey } if let allowedIPsString = attributes["allowedips"] { var allowedIPs = [IPAddressRange]() for allowedIPString in allowedIPsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { - guard let allowedIP = IPAddressRange(from: allowedIPString) else { return nil } + guard let allowedIP = IPAddressRange(from: allowedIPString) else { + throw ParseError.peerHasInvalidAllowedIP(allowedIPString) + } allowedIPs.append(allowedIP) } peer.allowedIPs = allowedIPs } if let endpointString = attributes["endpoint"] { - guard let endpoint = Endpoint(from: endpointString) else { return nil } + guard let endpoint = Endpoint(from: endpointString) else { + throw ParseError.peerHasInvalidEndpoint(endpointString) + } peer.endpoint = endpoint } if let persistentKeepAliveString = attributes["persistentkeepalive"] { - guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { return nil } + guard let persistentKeepAlive = UInt16(persistentKeepAliveString) else { + throw ParseError.peerHasInvalidPersistentKeepAlive(persistentKeepAliveString) + } peer.persistentKeepAlive = persistentKeepAlive } return peer diff --git a/WireGuard/WireGuard.xcodeproj/project.pbxproj b/WireGuard/WireGuard.xcodeproj/project.pbxproj index 44785c0..0cc3579 100644 --- a/WireGuard/WireGuard.xcodeproj/project.pbxproj +++ b/WireGuard/WireGuard.xcodeproj/project.pbxproj @@ -153,6 +153,7 @@ 6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; }; 6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; }; 6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */; }; + 6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -313,6 +314,7 @@ 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; }; 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorNotifier.swift; sourceTree = "<group>"; }; 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivateOnDemandSetting.swift; sourceTree = "<group>"; }; + 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ParseError+WireGuardAppError.swift"; sourceTree = "<group>"; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -517,6 +519,7 @@ 5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */, 5F52D0C021E378C000283CEA /* highlighter.h */, 5F52D0C121E378C000283CEA /* highlighter.c */, + 6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */, ); path = macOS; sourceTree = "<group>"; @@ -1108,6 +1111,7 @@ 5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */, 6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */, 6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */, + 6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */, 6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */, 6FBA101821D656000051C35F /* StatusMenu.swift in Sources */, 6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */, diff --git a/WireGuard/WireGuard/Base.lproj/Localizable.strings b/WireGuard/WireGuard/Base.lproj/Localizable.strings index 8cae396..897d13d 100644 --- a/WireGuard/WireGuard/Base.lproj/Localizable.strings +++ b/WireGuard/WireGuard/Base.lproj/Localizable.strings @@ -256,3 +256,25 @@ "macEditDiscard" = "Discard"; "macEditSave" = "Save"; + +"macAlertInvalidLine (%@)" = "Invalid line: '%@'."; + +"macAlertNoInterface" = "Configuration must have an 'Interface' section."; +"macAlertMultipleInterfaces" = "Configuration must have only one 'Interface' section."; +"macAlertPrivateKeyInvalid" = "Private key is invalid."; +"macAlertListenPortInvalid (%@)" = "Listen port '%@' is invalid."; +"macAlertAddressInvalid (%@)" = "Address '%@' is invalid."; +"macAlertDNSInvalid (%@)" = "DNS '%@' is invalid."; +"macAlertMTUInvalid (%@)" = "MTU '%@' is invalid."; + +"macAlertUnrecognizedInterfaceKey (%@)" = "Interface contains unrecognized key '%@'"; +"macAlertInfoUnrecognizedInterfaceKey" = "Valid keys are: 'PrivateKey', 'ListenPort', 'Address', 'DNS' and 'MTU'."; + +"macAlertPublicKeyInvalid" = "Public key is invalid"; +"macAlertPreSharedKeyInvalid" = "Preshared key is invalid"; +"macAlertAllowedIPInvalid (%@)" = "Allowed IP '%@' is invalid"; +"macAlertEndpointInvalid (%@)" = "Endpoint '%@' is invalid"; +"macAlertPersistentKeepliveInvalid (%@)" = "Persistent keepalive value '%@' is invalid"; + +"macAlertUnrecognizedPeerKey (%@)" = "Peer contains unrecognized key '%@'"; +"macAlertInfoUnrecognizedPeerKey" = "Valid keys are: 'PublicKey', 'PresharedKey', 'AllowedIPs', 'Endpoint' and 'PersistentKeepalive'"; diff --git a/WireGuard/WireGuard/UI/macOS/ParseError+WireGuardAppError.swift b/WireGuard/WireGuard/UI/macOS/ParseError+WireGuardAppError.swift new file mode 100644 index 0000000..4a1c890 --- /dev/null +++ b/WireGuard/WireGuard/UI/macOS/ParseError+WireGuardAppError.swift @@ -0,0 +1,50 @@ +// SPDX-License-Identifier: MIT +// Copyright © 2018 WireGuard LLC. All Rights Reserved. + +import Cocoa + +// We have this in a separate file because we don't want the network extension +// code to see WireGuardAppError and tr(). Also, this extension is used only on macOS. + +extension TunnelConfiguration.ParseError: WireGuardAppError { + var alertText: AlertText { + switch self { + case .invalidLine(let line): + return (tr(format: "macAlertInvalidLine (%@)", String(line)), "") + case .noInterface: + return (tr("macAlertNoInterface"), "") + case .multipleInterfaces: + return (tr("macAlertMultipleInterfaces"), "") + case .interfaceHasNoPrivateKey: + return (tr("alertInvalidInterfaceMessagePrivateKeyRequired"), tr("alertInvalidInterfaceMessagePrivateKeyInvalid")) + case .interfaceHasInvalidPrivateKey: + return (tr("macAlertPrivateKeyInvalid"), tr("alertInvalidInterfaceMessagePrivateKeyInvalid")) + case .interfaceHasInvalidListenPort(let value): + return (tr(format: "macAlertListenPortInvalid (%@)", value), tr("alertInvalidInterfaceMessageListenPortInvalid")) + case .interfaceHasInvalidAddress(let value): + return (tr(format: "macAlertAddressInvalid (%@)", value), tr("alertInvalidInterfaceMessageAddressInvalid")) + case .interfaceHasInvalidDNS(let value): + return (tr(format: "macAlertDNSInvalid (%@)", value), tr("alertInvalidInterfaceMessageDNSInvalid")) + case .interfaceHasInvalidMTU(let value): + return (tr(format: "macAlertMTUInvalid (%@)", value), tr("alertInvalidInterfaceMessageMTUInvalid")) + case .interfaceHasUnrecognizedKey(let value): + return (tr(format: "macAlertUnrecognizedInterfaceKey (%@)", value), tr("macAlertInfoUnrecognizedInterfaceKey")) + case .peerHasNoPublicKey: + return (tr("alertInvalidPeerMessagePublicKeyRequired"), tr("alertInvalidPeerMessagePublicKeyInvalid")) + case .peerHasInvalidPublicKey: + return (tr("macAlertPublicKeyInvalid"), tr("alertInvalidPeerMessagePublicKeyInvalid")) + case .peerHasInvalidPreSharedKey: + return (tr("macAlertPreSharedKeyInvalid"), tr("alertInvalidPeerMessagePreSharedKeyInvalid")) + case .peerHasInvalidAllowedIP(let value): + return (tr(format: "macAlertAllowedIPInvalid (%@)", value), tr("alertInvalidPeerMessageAllowedIPsInvalid")) + case .peerHasInvalidEndpoint(let value): + return (tr(format: "macAlertEndpointInvalid (%@)", value), tr("alertInvalidPeerMessageEndpointInvalid")) + case .peerHasInvalidPersistentKeepAlive(let value): + return (tr(format: "macAlertPersistentKeepliveInvalid (%@)", value), tr("alertInvalidPeerMessagePersistentKeepaliveInvalid")) + case .peerHasUnrecognizedKey(let value): + return (tr(format: "macAlertUnrecognizedPeerKey (%@)", value), tr("macAlertInfoUnrecognizedPeerKey")) + case .multiplePeersWithSamePublicKey: + return (tr("alertInvalidPeerMessagePublicKeyDuplicated"), "") + } + } +} |