2018-08-04 19:28:19 +00:00
|
|
|
//
|
|
|
|
// Tunnel+Extension.swift
|
|
|
|
// WireGuard
|
|
|
|
//
|
|
|
|
// Created by Jeroen Leenarts on 04-08-18.
|
2018-08-05 05:48:36 +00:00
|
|
|
// Copyright © 2018 Jason A. Donenfeld <Jason@zx2c4.com>. All rights reserved.
|
2018-08-04 19:28:19 +00:00
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
2018-08-15 22:34:16 +00:00
|
|
|
import CoreData
|
2018-08-04 19:28:19 +00:00
|
|
|
|
|
|
|
extension Tunnel {
|
|
|
|
public func generateProviderConfiguration() -> [String: Any] {
|
2018-08-06 07:04:29 +00:00
|
|
|
var providerConfiguration = [String: Any]()
|
|
|
|
|
2018-08-12 19:37:56 +00:00
|
|
|
providerConfiguration[PCKeys.title.rawValue] = self.title
|
|
|
|
providerConfiguration[PCKeys.tunnelIdentifier.rawValue] = self.tunnelIdentifier
|
|
|
|
providerConfiguration[PCKeys.endpoints.rawValue] = peers?.array.compactMap {($0 as? Peer)?.endpoint}.joined(separator: ", ")
|
|
|
|
providerConfiguration[PCKeys.dns.rawValue] = interface?.dns
|
|
|
|
providerConfiguration[PCKeys.addresses.rawValue] = interface?.addresses
|
2018-08-12 19:44:53 +00:00
|
|
|
if let mtu = interface?.mtu, mtu > 0 {
|
2018-08-12 19:37:56 +00:00
|
|
|
providerConfiguration[PCKeys.mtu.rawValue] = NSNumber(value: mtu)
|
|
|
|
}
|
|
|
|
|
2018-08-06 07:04:29 +00:00
|
|
|
var settingsString = "replace_peers=true\n"
|
|
|
|
if let interface = interface {
|
|
|
|
settingsString += generateInterfaceProviderConfiguration(interface)
|
|
|
|
}
|
|
|
|
|
|
|
|
if let peers = peers?.array as? [Peer] {
|
|
|
|
peers.forEach {
|
|
|
|
settingsString += generatePeerProviderConfiguration($0)
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
providerConfiguration["settings"] = settingsString
|
|
|
|
|
|
|
|
return providerConfiguration
|
|
|
|
}
|
|
|
|
|
2018-08-15 22:34:16 +00:00
|
|
|
private func generateInterfaceProviderConfiguration(_ interface: Interface) -> String {
|
2018-08-09 20:45:28 +00:00
|
|
|
var settingsString = ""
|
2018-08-06 07:04:29 +00:00
|
|
|
|
|
|
|
if let hexPrivateKey = base64KeyToHex(interface.privateKey) {
|
|
|
|
settingsString += "private_key=\(hexPrivateKey)\n"
|
|
|
|
}
|
|
|
|
if interface.listenPort > 0 {
|
|
|
|
settingsString += "listen_port=\(interface.listenPort)\n"
|
|
|
|
}
|
|
|
|
if interface.mtu > 0 {
|
|
|
|
settingsString += "mtu=\(interface.mtu)\n"
|
|
|
|
}
|
|
|
|
|
|
|
|
return settingsString
|
|
|
|
}
|
|
|
|
|
2018-08-15 22:34:16 +00:00
|
|
|
private func generatePeerProviderConfiguration(_ peer: Peer) -> String {
|
2018-08-06 07:04:29 +00:00
|
|
|
var settingsString = ""
|
|
|
|
|
|
|
|
if let hexPublicKey = base64KeyToHex(peer.publicKey) {
|
2018-08-09 20:45:28 +00:00
|
|
|
settingsString += "public_key=\(hexPublicKey)\n"
|
2018-08-06 07:04:29 +00:00
|
|
|
}
|
|
|
|
if let presharedKey = peer.presharedKey {
|
2018-08-09 20:45:28 +00:00
|
|
|
settingsString += "preshared_key=\(presharedKey)\n"
|
2018-08-06 07:04:29 +00:00
|
|
|
}
|
|
|
|
if let endpoint = peer.endpoint {
|
2018-08-09 20:45:28 +00:00
|
|
|
settingsString += "endpoint=\(endpoint)\n"
|
2018-08-06 07:04:29 +00:00
|
|
|
}
|
|
|
|
if peer.persistentKeepalive > 0 {
|
2018-08-09 20:45:28 +00:00
|
|
|
settingsString += "persistent_keepalive_interval=\(peer.persistentKeepalive)\n"
|
2018-08-06 07:04:29 +00:00
|
|
|
}
|
2018-08-16 20:39:08 +00:00
|
|
|
if let allowedIPs = peer.allowedIPs?.commaSeparatedToArray() {
|
2018-08-07 19:31:43 +00:00
|
|
|
allowedIPs.forEach {
|
2018-08-09 20:45:28 +00:00
|
|
|
settingsString += "allowed_ip=\($0.trimmingCharacters(in: .whitespaces))\n"
|
2018-08-07 19:31:43 +00:00
|
|
|
}
|
2018-08-06 07:04:29 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
return settingsString
|
2018-08-04 19:28:19 +00:00
|
|
|
}
|
2018-08-15 22:34:16 +00:00
|
|
|
|
|
|
|
func validate() throws {
|
|
|
|
let nameRegex = "[a-zA-Z0-9_=+.-]{1,15}"
|
|
|
|
let nameTest = NSPredicate(format: "SELF MATCHES %@", nameRegex)
|
|
|
|
guard let title = title, nameTest.evaluate(with: title) else {
|
|
|
|
throw TunnelValidationError.invalidTitle
|
|
|
|
}
|
|
|
|
|
|
|
|
let fetchRequest: NSFetchRequest<Tunnel> = Tunnel.fetchRequest()
|
|
|
|
fetchRequest.predicate = NSPredicate(format: "title == %@", title)
|
|
|
|
guard (try? managedObjectContext?.count(for: fetchRequest)) == 1 else {
|
|
|
|
throw TunnelValidationError.titleExists
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let interface = interface else {
|
|
|
|
throw TunnelValidationError.nilInterface
|
|
|
|
}
|
|
|
|
|
|
|
|
try interface.validate()
|
|
|
|
|
|
|
|
guard let peers = peers else {
|
|
|
|
throw TunnelValidationError.nilPeers
|
|
|
|
}
|
|
|
|
|
|
|
|
try peers.forEach {
|
|
|
|
guard let peer = $0 as? Peer else {
|
|
|
|
throw TunnelValidationError.invalidPeer
|
|
|
|
}
|
|
|
|
|
|
|
|
try peer.validate()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-08-21 16:00:41 +00:00
|
|
|
static func fromConfig(_ text: String, context: NSManagedObjectContext) throws -> Tunnel {
|
|
|
|
let lines = text.split(separator: "\n")
|
|
|
|
|
|
|
|
var currentPeer: Peer?
|
|
|
|
var isInInterfaceSection = false
|
|
|
|
|
|
|
|
var tunnel: Tunnel!
|
|
|
|
context.performAndWait {
|
|
|
|
tunnel = Tunnel(context: context)
|
|
|
|
tunnel.interface = Interface(context: context)
|
|
|
|
}
|
|
|
|
tunnel.tunnelIdentifier = UUID().uuidString
|
|
|
|
|
|
|
|
for line in lines {
|
|
|
|
var trimmedLine: String
|
|
|
|
if let commentRange = line.range(of: "#") {
|
|
|
|
trimmedLine = String(line[..<commentRange.lowerBound])
|
|
|
|
} else {
|
|
|
|
trimmedLine = String(line)
|
|
|
|
}
|
|
|
|
|
|
|
|
trimmedLine = trimmedLine.trimmingCharacters(in: .whitespaces)
|
|
|
|
|
|
|
|
guard trimmedLine.count > 0 else { continue }
|
|
|
|
|
|
|
|
if "[interface]" == line.lowercased() {
|
|
|
|
currentPeer = nil
|
|
|
|
isInInterfaceSection = true
|
|
|
|
} else if "[peer]" == line.lowercased() {
|
|
|
|
context.performAndWait { currentPeer = Peer(context: context) }
|
|
|
|
tunnel.insertIntoPeers(currentPeer!, at: tunnel.peers?.count ?? 0)
|
|
|
|
isInInterfaceSection = false
|
|
|
|
} else if isInInterfaceSection, let attribute = Attribute.match(line: String(line)) {
|
|
|
|
try tunnel.interface!.parse(attribute: attribute)
|
|
|
|
} else if let currentPeer = currentPeer, let attribute = Attribute.match(line: String(line)) {
|
|
|
|
try currentPeer.parse(attribute: attribute)
|
|
|
|
} else {
|
|
|
|
throw TunnelParseError.invalidLine(String(line))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if !isInInterfaceSection && currentPeer == nil {
|
|
|
|
throw TunnelParseError.noConfigInfo
|
|
|
|
}
|
|
|
|
|
|
|
|
return tunnel
|
|
|
|
}
|
|
|
|
|
2018-08-29 13:01:59 +00:00
|
|
|
func export() -> String {
|
|
|
|
var exportString = ""
|
|
|
|
if let interfaceExport = self.interface?.export() {
|
|
|
|
exportString.append(interfaceExport)
|
|
|
|
}
|
|
|
|
|
|
|
|
if let peers = peers?.array as? [Peer] {
|
|
|
|
peers.forEach {
|
|
|
|
exportString.append($0.export())
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return exportString
|
|
|
|
}
|
|
|
|
|
2018-08-04 19:28:19 +00:00
|
|
|
}
|
2018-08-06 07:04:29 +00:00
|
|
|
|
|
|
|
private func base64KeyToHex(_ base64: String?) -> String? {
|
|
|
|
guard let base64 = base64 else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
guard base64.count == 44 else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
guard base64.last == "=" else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
guard let keyData = Data(base64Encoded: base64) else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
guard keyData.count == 32 else {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
let hexKey = keyData.reduce("") {$0 + String(format: "%02x", $1)}
|
|
|
|
|
|
|
|
return hexKey
|
|
|
|
}
|
2018-08-15 22:34:16 +00:00
|
|
|
|
|
|
|
enum TunnelValidationError: Error {
|
|
|
|
case invalidTitle
|
|
|
|
case titleExists
|
|
|
|
case nilInterface
|
|
|
|
case nilPeers
|
|
|
|
case invalidPeer
|
|
|
|
}
|
2018-08-21 16:00:41 +00:00
|
|
|
|
|
|
|
enum TunnelParseError: Error {
|
|
|
|
case invalidLine(_ line: String)
|
|
|
|
case noConfigInfo
|
|
|
|
}
|