macOS: Refactor config file parsing

- To report more fine grained errors
 - To make the parse errors conform to WireGuardAppError
This commit is contained in:
Roopesh Chander 2019-01-08 15:44:17 +05:30
parent e0aaf0661f
commit 309d06217f
4 changed files with 147 additions and 26 deletions

View File

@ -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

View File

@ -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 */,

View File

@ -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'";

View File

@ -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"), "")
}
}
}