Revisit WireGuard.Configuration

- Make Configuration Codable
- Expose WireGuard ConfigurationError
- Produce ConfigurationBuilder from Configuration
- Support multiple peers
- Make private key a requirement
This commit is contained in:
Davide De Rosa 2022-02-11 18:45:40 +01:00
parent c019cecbe0
commit 2bcd11fd7e
4 changed files with 111 additions and 38 deletions

View File

@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Parse OpenVPN authentication requirement from `--auth-user-pass`.
### Changed
- Support multiple peers in WireGuard.
## 4.1.0 (2022-02-09)
### Added

View File

@ -200,13 +200,14 @@ extension WireGuard {
serverAddress: String,
serverPort: String
) -> WireGuardProvider.Configuration? {
var builder = WireGuard.ConfigurationBuilder()
builder.privateKey = clientPrivateKey
var builder = WireGuard.ConfigurationBuilder(privateKey: clientPrivateKey)
builder.addresses = [clientAddress]
builder.peerPublicKey = serverPublicKey
builder.peerAddress = serverAddress
builder.peerPort = UInt16(serverPort)
builder.allowedIPs = ["0.0.0.0/0"]
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

View File

@ -26,9 +26,17 @@
import Foundation
import WireGuardKit
public struct ConfigurationError: Error {
let parseError: TunnelConfiguration.ParseError
}
extension WireGuard.Configuration {
public init(wgQuickConfig: String) throws {
do {
tunnelConfiguration = try TunnelConfiguration(fromWgQuickConfig: wgQuickConfig)
} catch let parseError as TunnelConfiguration.ParseError {
throw ConfigurationError(parseError: parseError)
}
}
public func asWgQuickConfig() -> String {

View File

@ -25,10 +25,31 @@
import Foundation
import WireGuardKit
import NetworkExtension
extension WireGuard {
public struct Peer {
public var publicKey: String
public var preSharedKey: String?
public var endpoint: String?
public var allowedIPs: [String]?
public var keepAliveInterval: UInt16?
public init(publicKey: String) {
self.publicKey = publicKey
}
}
public struct ConfigurationBuilder {
public var privateKey: String?
public var privateKey: String
public var publicKey: String? {
return PrivateKey(base64Key: privateKey)?.publicKey.base64Key
}
public var addresses: [String]?
@ -36,29 +57,15 @@ extension WireGuard {
public var mtu: UInt16?
public var peerPublicKey: String?
public var peers: [Peer]
public var peerPreSharedKey: String?
public var peerAddress: String?
public var peerPort: UInt16?
public var allowedIPs: [String]?
public var keepAliveInterval: UInt16?
public init() {
public init(privateKey: String) {
self.privateKey = privateKey
peers = []
}
public func build() -> Configuration? {
guard let privateKey = privateKey, let clientPrivateKey = PrivateKey(base64Key: privateKey) else {
return nil
}
guard let peerPublicKey = peerPublicKey, let serverPublicKey = PublicKey(base64Key: peerPublicKey) else {
return nil
}
guard let peerAddress = peerAddress, let peerPort = peerPort, let endpoint = Endpoint(from: "\(peerAddress):\(peerPort)") else {
guard let clientPrivateKey = PrivateKey(base64Key: privateKey) else {
return nil
}
@ -70,23 +77,76 @@ extension WireGuard {
interfaceConfiguration.dns = dnsServers
}
interfaceConfiguration.mtu = mtu
var peerConfiguration = PeerConfiguration(publicKey: serverPublicKey)
if let peerPreSharedKey = peerPreSharedKey {
peerConfiguration.preSharedKey = PreSharedKey(base64Key: peerPreSharedKey)
}
if let peerAllowedIPs = allowedIPs?.mapOptional({ IPAddressRange(from: $0) }) {
peerConfiguration.allowedIPs = peerAllowedIPs
}
peerConfiguration.endpoint = endpoint
peerConfiguration.persistentKeepAlive = keepAliveInterval
let tunnelConfiguration = TunnelConfiguration(name: nil, interface: interfaceConfiguration, peers: [peerConfiguration])
var peerConfigurations: [PeerConfiguration] = []
for peer in peers {
guard let publicKey = PublicKey(base64Key: peer.publicKey) else {
continue
}
// 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)
if let preSharedKey = peer.preSharedKey {
cfg.preSharedKey = PreSharedKey(base64Key: preSharedKey)
}
if let allowedIPs = peer.allowedIPs?.mapOptional(IPAddressRange.init(from:)) {
cfg.allowedIPs = allowedIPs
}
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)
return Configuration(tunnelConfiguration: tunnelConfiguration)
}
}
public struct Configuration {
public struct Configuration: Codable {
public let tunnelConfiguration: TunnelConfiguration
public init(tunnelConfiguration: TunnelConfiguration) {
self.tunnelConfiguration = tunnelConfiguration
}
public func builder() -> WireGuard.ConfigurationBuilder {
let privateKey = tunnelConfiguration.interface.privateKey.base64Key
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: Codable
public init(from decoder: Decoder) throws {
let container = try decoder.singleValueContainer()
let wg = try container.decode(String.self)
let cfg = try TunnelConfiguration(fromWgQuickConfig: wg, called: nil)
self.init(tunnelConfiguration: cfg)
}
public func encode(to encoder: Encoder) throws {
let wg = tunnelConfiguration.asWgQuickConfig()
var container = encoder.singleValueContainer()
try container.encode(wg)
}
}
}