Use WireGuardKit entities directly

No need to duplicate a well-written API.

- Offer convenience accessors in Configuration[Builder]
- Make Configuration init non-optional

Sanity checks are done in Builder with throws and decoded object
is always deemed valid.
This commit is contained in:
Davide De Rosa 2022-03-12 10:09:40 +01:00
parent ff235e2b96
commit a7a7424257
6 changed files with 293 additions and 123 deletions

View File

@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Manager package completely rewritten. - Manager package completely rewritten.
- WireGuard: Use entities from WireGuardKit directly.
- Only enable on-demand if at least one rule is provided. - Only enable on-demand if at least one rule is provided.
- Dropped incomplete support for IPSec/IKEv2. - Dropped incomplete support for IPSec/IKEv2.

View File

@ -172,17 +172,19 @@ M69t86apMrAxkUxVJAWLRBd9fbYyzJgTW61tFqXWTZpiz6bhuWApSEzaHcL3/f5l
""") """)
static func make(_ title: String, appGroup: String, hostname: String, port: UInt16, socketType: SocketType) -> OpenVPN.ProviderConfiguration { static func make(_ title: String, appGroup: String, hostname: String, port: UInt16, socketType: SocketType) -> OpenVPN.ProviderConfiguration {
var sessionBuilder = OpenVPN.ConfigurationBuilder() var builder = OpenVPN.ConfigurationBuilder()
sessionBuilder.ca = ca builder.ca = ca
sessionBuilder.cipher = .aes128cbc builder.cipher = .aes128cbc
sessionBuilder.digest = .sha1 builder.digest = .sha1
sessionBuilder.compressionFraming = .compLZO builder.compressionFraming = .compLZO
sessionBuilder.renegotiatesAfter = nil builder.renegotiatesAfter = nil
sessionBuilder.remotes = [Endpoint(hostname, EndpointProtocol(socketType, port))] builder.remotes = [Endpoint(hostname, EndpointProtocol(socketType, port))]
sessionBuilder.clientCertificate = clientCertificate builder.clientCertificate = clientCertificate
sessionBuilder.clientKey = clientKey builder.clientKey = clientKey
sessionBuilder.mtu = 1350 builder.mtu = 1350
var providerConfiguration = OpenVPN.ProviderConfiguration(title, appGroup: appGroup, configuration: sessionBuilder.build()) let cfg = builder.build()
var providerConfiguration = OpenVPN.ProviderConfiguration(title, appGroup: appGroup, configuration: cfg)
providerConfiguration.shouldDebug = true providerConfiguration.shouldDebug = true
providerConfiguration.masksPrivateData = false providerConfiguration.masksPrivateData = false
return providerConfiguration return providerConfiguration
@ -201,18 +203,13 @@ extension WireGuard {
serverAddress: String, serverAddress: String,
serverPort: String serverPort: String
) -> WireGuard.ProviderConfiguration? { ) -> WireGuard.ProviderConfiguration? {
var builder = WireGuard.ConfigurationBuilder(privateKey: clientPrivateKey) var builder = try! WireGuard.ConfigurationBuilder(clientPrivateKey)
builder.addresses = [clientAddress] builder.addresses = [clientAddress]
builder.dnsServers = ["1.1.1.1", "1.0.0.1"]
try! builder.addPeer(serverPublicKey, endpoint: "\(serverAddress):\(serverPort)")
builder.addDefaultGatewayIPv4(toPeer: 0)
let cfg = builder.build()
var peer = Peer(publicKey: serverPublicKey)
peer.endpoint = "\(serverAddress):\(serverPort)"
peer.allowedIPs = ["0.0.0.0/0"]
builder.peers = [peer]
builder.dns = ["1.1.1.1", "1.0.0.1"]
guard let cfg = builder.build() else {
return nil
}
return WireGuard.ProviderConfiguration(title, appGroup: appGroup, configuration: cfg) return WireGuard.ProviderConfiguration(title, appGroup: appGroup, configuration: cfg)
} }
} }

View File

@ -25,8 +25,10 @@
import Foundation import Foundation
/// Error raised by the configuration parser, with details about the line that triggered it. extension OpenVPN {
public enum ConfigurationError: Error {
/// Error raised by the configuration parser, with details about the line that triggered it.
public enum ConfigurationError: Error {
/// Option syntax is incorrect. /// Option syntax is incorrect.
case malformed(option: String) case malformed(option: String)
@ -42,4 +44,5 @@ public enum ConfigurationError: Error {
/// Encryption passphrase is incorrect or key is corrupt. /// Encryption passphrase is incorrect or key is corrupt.
case unableToDecrypt(error: Error) case unableToDecrypt(error: Error)
}
} }

View File

@ -188,7 +188,7 @@ public class OpenVPNSession: Session {
*/ */
public init(queue: DispatchQueue, configuration: OpenVPN.Configuration, cachesURL: URL) throws { public init(queue: DispatchQueue, configuration: OpenVPN.Configuration, cachesURL: URL) throws {
guard let ca = configuration.ca else { guard let ca = configuration.ca else {
throw ConfigurationError.missingConfiguration(option: "ca") throw OpenVPN.ConfigurationError.missingConfiguration(option: "ca")
} }
self.queue = queue self.queue = queue

View File

@ -27,110 +27,225 @@ import Foundation
import WireGuardKit import WireGuardKit
import NetworkExtension import NetworkExtension
public protocol WireGuardConfigurationProviding {
var interface: InterfaceConfiguration { get }
var peers: [PeerConfiguration] { get }
var privateKey: String { get }
var publicKey: String { get }
var addresses: [String] { get }
var dnsServers: [String] { get }
var dnsSearchDomains: [String] { get }
var mtu: UInt16? { get }
var peersCount: Int { get }
func publicKey(ofPeer peerIndex: Int) -> String
func preSharedKey(ofPeer peerIndex: Int) -> String?
func endpoint(ofPeer peerIndex: Int) -> String?
func allowedIPs(ofPeer peerIndex: Int) -> [String]
func keepAlive(ofPeer peerIndex: Int) -> UInt16?
}
extension WireGuard { extension WireGuard {
public struct Peer { public struct ConfigurationBuilder: WireGuardConfigurationProviding {
public var publicKey: String private static let defaultGateway4 = IPAddressRange(from: "0.0.0.0/0")!
public var preSharedKey: String? private static let defaultGateway6 = IPAddressRange(from: "::/0")!
public var endpoint: String? public private(set) var interface: InterfaceConfiguration
public var allowedIPs: [String]? public private(set) var peers: [PeerConfiguration]
public var keepAliveInterval: UInt16? public init() {
self.init(PrivateKey())
public init(publicKey: String) {
self.publicKey = publicKey
}
} }
public struct ConfigurationBuilder { public init(_ base64PrivateKey: String) throws {
public var privateKey: String guard let privateKey = PrivateKey(base64Key: base64PrivateKey) else {
throw WireGuard.ConfigurationError.invalidKey
public var publicKey: String? { }
return PrivateKey(base64Key: privateKey)?.publicKey.base64Key self.init(privateKey)
} }
public var addresses: [String]? private init(_ privateKey: PrivateKey) {
interface = InterfaceConfiguration(privateKey: privateKey)
public var dns: [String]?
public var mtu: UInt16?
public var peers: [Peer]
public init(privateKey: String) {
self.privateKey = privateKey
peers = [] peers = []
} }
public func build() -> Configuration? { public init(_ tunnelConfiguration: TunnelConfiguration) {
guard let clientPrivateKey = PrivateKey(base64Key: privateKey) else { interface = tunnelConfiguration.interface
return nil peers = tunnelConfiguration.peers
} }
var interfaceConfiguration = InterfaceConfiguration(privateKey: clientPrivateKey) // MARK: WireGuardConfigurationProviding
if let clientAddresses = addresses?.mapOptional({ IPAddressRange(from: $0) }) {
interfaceConfiguration.addresses = clientAddresses
}
if let dnsServers = dns?.mapOptional({ DNSServer(from: $0) }) {
interfaceConfiguration.dns = dnsServers
}
interfaceConfiguration.mtu = mtu
var peerConfigurations: [PeerConfiguration] = [] public var privateKey: String {
for peer in peers { get {
guard let publicKey = PublicKey(base64Key: peer.publicKey) else { return interface.privateKey.base64Key
continue }
set {
guard let key = PrivateKey(base64Key: newValue) else {
return
}
interface.privateKey = key
} }
// XXX: this is actually optional in WireGuard
guard let endpointString = peer.endpoint, let endpoint = Endpoint(from: endpointString) else {
return nil
} }
var cfg = PeerConfiguration(publicKey: publicKey) public var addresses: [String] {
if let preSharedKey = peer.preSharedKey { get {
cfg.preSharedKey = PreSharedKey(base64Key: preSharedKey) return interface.addresses.map(\.stringRepresentation)
} }
if let allowedIPs = peer.allowedIPs?.mapOptional(IPAddressRange.init(from:)) { set {
cfg.allowedIPs = allowedIPs interface.addresses = newValue.compactMap(IPAddressRange.init)
} }
cfg.endpoint = endpoint
cfg.persistentKeepAlive = peer.keepAliveInterval
peerConfigurations.append(cfg)
}
guard !peers.isEmpty else {
return nil
} }
let tunnelConfiguration = TunnelConfiguration(name: nil, interface: interfaceConfiguration, peers: peerConfigurations) public var dnsServers: [String] {
get {
return interface.dns.map(\.stringRepresentation)
}
set {
interface.dns = newValue.compactMap(DNSServer.init)
}
}
public var dnsSearchDomains: [String] {
get {
return interface.dnsSearch
}
set {
interface.dnsSearch = newValue
}
}
public var mtu: UInt16? {
get {
return interface.mtu
}
set {
interface.mtu = newValue
}
}
// MARK: Modification
public mutating func addPeer(_ base64PublicKey: String, endpoint: String, allowedIPs: [String] = []) throws {
guard let publicKey = PublicKey(base64Key: base64PublicKey) else {
throw WireGuard.ConfigurationError.invalidKey
}
var peer = PeerConfiguration(publicKey: publicKey)
peer.endpoint = Endpoint(from: endpoint)
peer.allowedIPs = allowedIPs.compactMap(IPAddressRange.init)
peers.append(peer)
}
public mutating func setPreSharedKey(_ base64Key: String, ofPeer peerIndex: Int) throws {
guard let preSharedKey = PreSharedKey(base64Key: base64Key) else {
throw WireGuard.ConfigurationError.invalidKey
}
peers[peerIndex].preSharedKey = preSharedKey
}
public mutating func addDefaultGatewayIPv4(toPeer peerIndex: Int) {
peers[peerIndex].allowedIPs.append(Self.defaultGateway4)
}
public mutating func addDefaultGatewayIPv6(toPeer peerIndex: Int) {
peers[peerIndex].allowedIPs.append(Self.defaultGateway6)
}
public mutating func removeDefaultGatewayIPv4(fromPeer peerIndex: Int) {
peers[peerIndex].allowedIPs.removeAll {
$0 == Self.defaultGateway4
}
}
public mutating func removeDefaultGatewayIPv6(fromPeer peerIndex: Int) {
peers[peerIndex].allowedIPs.removeAll {
$0 == Self.defaultGateway6
}
}
public mutating func addAllowedIP(_ allowedIP: String, toPeer peerIndex: Int) {
guard let addr = IPAddressRange(from: allowedIP) else {
return
}
peers[peerIndex].allowedIPs.append(addr)
}
public mutating func removeAllowedIP(_ allowedIP: String, fromPeer peerIndex: Int) {
guard let addr = IPAddressRange(from: allowedIP) else {
return
}
peers[peerIndex].allowedIPs.removeAll {
$0 == addr
}
}
public mutating func setKeepAlive(_ keepAlive: UInt16, forPeer peerIndex: Int) {
peers[peerIndex].persistentKeepAlive = keepAlive
}
public func build() -> Configuration {
let tunnelConfiguration = TunnelConfiguration(name: nil, interface: interface, peers: peers)
return Configuration(tunnelConfiguration: tunnelConfiguration) return Configuration(tunnelConfiguration: tunnelConfiguration)
} }
} }
public struct Configuration: Codable { public struct Configuration: Codable, WireGuardConfigurationProviding {
public let tunnelConfiguration: TunnelConfiguration public let tunnelConfiguration: TunnelConfiguration
public var interface: InterfaceConfiguration {
return tunnelConfiguration.interface
}
public var peers: [PeerConfiguration] {
return tunnelConfiguration.peers
}
public init(tunnelConfiguration: TunnelConfiguration) { public init(tunnelConfiguration: TunnelConfiguration) {
self.tunnelConfiguration = tunnelConfiguration self.tunnelConfiguration = tunnelConfiguration
} }
public func builder() -> WireGuard.ConfigurationBuilder { public func builder() -> WireGuard.ConfigurationBuilder {
let privateKey = tunnelConfiguration.interface.privateKey.base64Key return WireGuard.ConfigurationBuilder(tunnelConfiguration)
var builder = WireGuard.ConfigurationBuilder(privateKey: privateKey)
builder.addresses = tunnelConfiguration.interface.addresses.map(\.stringRepresentation)
builder.dns = tunnelConfiguration.interface.dns.map(\.stringRepresentation)
builder.mtu = tunnelConfiguration.interface.mtu
builder.peers = tunnelConfiguration.peers.map {
var peer = Peer(publicKey: $0.publicKey.base64Key)
peer.preSharedKey = $0.preSharedKey?.base64Key
peer.endpoint = $0.endpoint?.stringRepresentation
peer.allowedIPs = $0.allowedIPs.map(\.stringRepresentation)
peer.keepAliveInterval = $0.persistentKeepAlive
return peer
} }
return builder
// MARK: WireGuardConfigurationProviding
public var privateKey: String {
return interface.privateKey.base64Key
}
public var publicKey: String {
return interface.privateKey.publicKey.base64Key
}
public var addresses: [String] {
return interface.addresses.map(\.stringRepresentation)
}
public var dnsServers: [String] {
return interface.dns.map(\.stringRepresentation)
}
public var dnsSearchDomains: [String] {
return interface.dnsSearch
}
public var mtu: UInt16? {
return interface.mtu
} }
// MARK: Codable // MARK: Codable
@ -150,10 +265,32 @@ extension WireGuard {
} }
} }
private extension Array { extension WireGuardConfigurationProviding {
func mapOptional<V>(_ transform: (Self.Element) throws -> V?) rethrows -> [V] { public var publicKey: String {
return try map(transform) return interface.privateKey.publicKey.base64Key
.filter { $0 != nil } }
.map { $0! }
public var peersCount: Int {
return peers.count
}
public func publicKey(ofPeer peerIndex: Int) -> String {
return peers[peerIndex].publicKey.base64Key
}
public func preSharedKey(ofPeer peerIndex: Int) -> String? {
return peers[peerIndex].preSharedKey?.base64Key
}
public func endpoint(ofPeer peerIndex: Int) -> String? {
return peers[peerIndex].endpoint?.stringRepresentation
}
public func allowedIPs(ofPeer peerIndex: Int) -> [String] {
return peers[peerIndex].allowedIPs.map(\.stringRepresentation)
}
public func keepAlive(ofPeer peerIndex: Int) -> UInt16? {
return peers[peerIndex].persistentKeepAlive
} }
} }

View File

@ -0,0 +1,32 @@
//
// ConfigurationError.swift
// TunnelKit
//
// Created by Davide De Rosa on 3/12/22.
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of TunnelKit.
//
// TunnelKit is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// TunnelKit is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
extension WireGuard {
public enum ConfigurationError: Error {
case invalidKey
}
}