macOS: Refactor config file parsing

- To report more fine grained errors
- To make the parse errors conform to WireGuardAppError

Signed-off-by: Roopesh Chander <roop@roopc.net>
This commit is contained in:
Roopesh Chander
2019-01-14 14:52:34 +05:30
parent 96fa6d3ba6
commit c2a6241b5c
4 changed files with 147 additions and 26 deletions
@@ -12,16 +12,28 @@ extension TunnelConfiguration {
} }
enum ParseError: Error { enum ParseError: Error {
case invalidLine(_ line: String.SubSequence) case invalidLine(String.SubSequence)
case noInterface case noInterface
case invalidInterface
case multipleInterfaces 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 multiplePeersWithSamePublicKey
case invalidPeer
} }
//swiftlint:disable:next function_body_length cyclomatic_complexity //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 interfaceConfiguration: InterfaceConfiguration?
var peerConfigurations = [PeerConfiguration]() var peerConfigurations = [PeerConfiguration]()
@@ -45,7 +57,8 @@ extension TunnelConfiguration {
if let equalsIndex = line.firstIndex(of: "=") { if let equalsIndex = line.firstIndex(of: "=") {
// Line contains an attribute // 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 value = line[line.index(equalsIndex, offsetBy: 1)...].trimmingCharacters(in: .whitespaces)
let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"] let keysWithMultipleEntriesAllowed: Set<String> = ["address", "allowedips", "dns"]
if let presentValue = attributes[key], keysWithMultipleEntriesAllowed.contains(key) { if let presentValue = attributes[key], keysWithMultipleEntriesAllowed.contains(key) {
@@ -53,6 +66,19 @@ extension TunnelConfiguration {
} else { } else {
attributes[key] = value 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]" { } else if lowercasedLine != "[interface]" && lowercasedLine != "[peer]" {
throw ParseError.invalidLine(line) throw ParseError.invalidLine(line)
} }
@@ -62,11 +88,11 @@ extension TunnelConfiguration {
if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" { if isLastLine || lowercasedLine == "[interface]" || lowercasedLine == "[peer]" {
// Previous section has ended; process the attributes collected so far // Previous section has ended; process the attributes collected so far
if parserState == .inInterfaceSection { 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 } guard interfaceConfiguration == nil else { throw ParseError.multipleInterfaces }
interfaceConfiguration = interface interfaceConfiguration = interface
} else if parserState == .inPeerSection { } 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) peerConfigurations.append(peer)
} }
} }
@@ -133,21 +159,26 @@ extension TunnelConfiguration {
} }
//swiftlint:disable:next cyclomatic_complexity //swiftlint:disable:next cyclomatic_complexity
private static func collate(interfaceAttributes attributes: [String: String]) -> InterfaceConfiguration? { private static func collate(interfaceAttributes attributes: [String: String]) throws -> InterfaceConfiguration {
// required wg fields guard let privateKeyString = attributes["privatekey"] else {
guard let privateKeyString = attributes["privatekey"] else { return nil } throw ParseError.interfaceHasNoPrivateKey
guard let privateKey = Data(base64Encoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else { return nil } }
guard let privateKey = Data(base64Encoded: privateKeyString), privateKey.count == TunnelConfiguration.keyLength else {
throw ParseError.interfaceHasInvalidPrivateKey(privateKeyString)
}
var interface = InterfaceConfiguration(privateKey: privateKey) var interface = InterfaceConfiguration(privateKey: privateKey)
// other wg fields
if let listenPortString = attributes["listenport"] { 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 interface.listenPort = listenPort
} }
// wg-quick fields
if let addressesString = attributes["address"] { if let addressesString = attributes["address"] {
var addresses = [IPAddressRange]() var addresses = [IPAddressRange]()
for addressString in addressesString.splitToArray(trimmingCharacters: .whitespaces) { 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) addresses.append(address)
} }
interface.addresses = addresses interface.addresses = addresses
@@ -155,43 +186,57 @@ extension TunnelConfiguration {
if let dnsString = attributes["dns"] { if let dnsString = attributes["dns"] {
var dnsServers = [DNSServer]() var dnsServers = [DNSServer]()
for dnsServerString in dnsString.splitToArray(trimmingCharacters: .whitespaces) { 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) dnsServers.append(dnsServer)
} }
interface.dns = dnsServers interface.dns = dnsServers
} }
if let mtuString = attributes["mtu"] { 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 interface.mtu = mtu
} }
return interface return interface
} }
//swiftlint:disable:next cyclomatic_complexity //swiftlint:disable:next cyclomatic_complexity
private static func collate(peerAttributes attributes: [String: String]) -> PeerConfiguration? { private static func collate(peerAttributes attributes: [String: String]) throws -> PeerConfiguration {
// required wg fields guard let publicKeyString = attributes["publickey"] else {
guard let publicKeyString = attributes["publickey"] else { return nil } throw ParseError.peerHasNoPublicKey
guard let publicKey = Data(base64Encoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else { return nil } }
guard let publicKey = Data(base64Encoded: publicKeyString), publicKey.count == TunnelConfiguration.keyLength else {
throw ParseError.peerHasInvalidPublicKey(publicKeyString)
}
var peer = PeerConfiguration(publicKey: publicKey) var peer = PeerConfiguration(publicKey: publicKey)
// wg fields
if let preSharedKeyString = attributes["presharedkey"] { 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 peer.preSharedKey = preSharedKey
} }
if let allowedIPsString = attributes["allowedips"] { if let allowedIPsString = attributes["allowedips"] {
var allowedIPs = [IPAddressRange]() var allowedIPs = [IPAddressRange]()
for allowedIPString in allowedIPsString.splitToArray(trimmingCharacters: .whitespacesAndNewlines) { 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) allowedIPs.append(allowedIP)
} }
peer.allowedIPs = allowedIPs peer.allowedIPs = allowedIPs
} }
if let endpointString = attributes["endpoint"] { 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 peer.endpoint = endpoint
} }
if let persistentKeepAliveString = attributes["persistentkeepalive"] { 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 peer.persistentKeepAlive = persistentKeepAlive
} }
return peer return peer
@@ -153,6 +153,7 @@
6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; }; 6FFA5D96219446380001E2F7 /* NETunnelProviderProtocol+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */; };
6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; }; 6FFA5DA021958ECC0001E2F7 /* ErrorNotifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5D9F21958ECC0001E2F7 /* ErrorNotifier.swift */; };
6FFA5DA42197085D0001E2F7 /* ActivateOnDemandSetting.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6FFA5DA32197085D0001E2F7 /* ActivateOnDemandSetting.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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -313,6 +314,7 @@
6FFA5D942194454A0001E2F7 /* NETunnelProviderProtocol+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NETunnelProviderProtocol+Extension.swift"; sourceTree = "<group>"; }; 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>"; }; 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>"; }; 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 */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -517,6 +519,7 @@
5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */, 5F52D0BE21E3788900283CEA /* NSColor+Hex.swift */,
5F52D0C021E378C000283CEA /* highlighter.h */, 5F52D0C021E378C000283CEA /* highlighter.h */,
5F52D0C121E378C000283CEA /* highlighter.c */, 5F52D0C121E378C000283CEA /* highlighter.c */,
6FFACD1E21E4D89600E9A2A5 /* ParseError+WireGuardAppError.swift */,
); );
path = macOS; path = macOS;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -1108,6 +1111,7 @@
5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */, 5F52D0BF21E3788900283CEA /* NSColor+Hex.swift in Sources */,
6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */, 6FB1BDBE21D50F0200A991BF /* Logger.swift in Sources */,
6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */, 6FB1BDBF21D50F0200A991BF /* TunnelConfiguration+WgQuickConfig.swift in Sources */,
6FFACD2021E4D8D500E9A2A5 /* ParseError+WireGuardAppError.swift in Sources */,
6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */, 6FB1BDC021D50F0200A991BF /* NETunnelProviderProtocol+Extension.swift in Sources */,
6FBA101821D656000051C35F /* StatusMenu.swift in Sources */, 6FBA101821D656000051C35F /* StatusMenu.swift in Sources */,
6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */, 6F613D9B21DE33B8004B217A /* KeyValueRow.swift in Sources */,
@@ -256,3 +256,25 @@
"macEditDiscard" = "Discard"; "macEditDiscard" = "Discard";
"macEditSave" = "Save"; "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'";
@@ -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"), "")
}
}
}