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:
parent
ff235e2b96
commit
a7a7424257
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue