Updated NETunnelProvider save format
Signed-off-by: Eric Kuck <eric@bluelinelabs.com>
This commit is contained in:
committed by
Jason A. Donenfeld
parent
38445114e0
commit
8553723e04
@@ -1,68 +0,0 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
final class TunnelConfiguration: Codable {
|
||||
var interface: InterfaceConfiguration
|
||||
let peers: [PeerConfiguration]
|
||||
|
||||
static let keyLength: Int = 32
|
||||
|
||||
init(interface: InterfaceConfiguration, peers: [PeerConfiguration]) {
|
||||
self.interface = interface
|
||||
self.peers = peers
|
||||
|
||||
let peerPublicKeysArray = peers.map { $0.publicKey }
|
||||
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
|
||||
if peerPublicKeysArray.count != peerPublicKeysSet.count {
|
||||
fatalError("Two or more peers cannot have the same public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
struct InterfaceConfiguration: Codable {
|
||||
var name: String
|
||||
var privateKey: Data
|
||||
var addresses = [IPAddressRange]()
|
||||
var listenPort: UInt16?
|
||||
var mtu: UInt16?
|
||||
var dns = [DNSServer]()
|
||||
|
||||
init(name: String, privateKey: Data) {
|
||||
self.name = name
|
||||
self.privateKey = privateKey
|
||||
if name.isEmpty {
|
||||
fatalError("Empty name")
|
||||
}
|
||||
if privateKey.count != TunnelConfiguration.keyLength {
|
||||
fatalError("Invalid private key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
struct PeerConfiguration: Codable {
|
||||
var publicKey: Data
|
||||
var preSharedKey: Data? {
|
||||
didSet(value) {
|
||||
if let value = value {
|
||||
if value.count != TunnelConfiguration.keyLength {
|
||||
fatalError("Invalid preshared key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
var endpoint: Endpoint?
|
||||
var persistentKeepAlive: UInt16?
|
||||
|
||||
init(publicKey: Data) {
|
||||
self.publicKey = publicKey
|
||||
if publicKey.count != TunnelConfiguration.keyLength {
|
||||
fatalError("Invalid public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,14 +4,43 @@
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
struct DNSServer {
|
||||
let address: IPAddress
|
||||
|
||||
init(address: IPAddress) {
|
||||
self.address = address
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Converting to and from String
|
||||
extension DNSServer: Codable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(stringRepresentation)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let values = try decoder.singleValueContainer()
|
||||
let addressString = try values.decode(String.self)
|
||||
|
||||
if let address = IPv4Address(addressString) {
|
||||
self.address = address
|
||||
} else if let address = IPv6Address(addressString) {
|
||||
self.address = address
|
||||
} else {
|
||||
throw DecodingError.invalidData
|
||||
}
|
||||
}
|
||||
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
}
|
||||
|
||||
extension DNSServer {
|
||||
var stringRepresentation: String {
|
||||
return "\(address)"
|
||||
}
|
||||
|
||||
init?(from addressString: String) {
|
||||
if let addr = IPv4Address(addressString) {
|
||||
address = addr
|
||||
@@ -21,35 +50,4 @@ extension DNSServer {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
func stringRepresentation() -> String {
|
||||
return "\(address)"
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Codable
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
extension DNSServer: Codable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(address.rawValue)
|
||||
}
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
var data = try container.decode(Data.self)
|
||||
let ipAddressFromData: IPAddress? = {
|
||||
switch data.count {
|
||||
case 4: return IPv4Address(data)
|
||||
case 16: return IPv6Address(data)
|
||||
default: return nil
|
||||
}
|
||||
}()
|
||||
guard let ipAddress = ipAddressFromData else {
|
||||
throw DecodingError.invalidData
|
||||
}
|
||||
address = ipAddress
|
||||
}
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,15 +4,48 @@
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
struct Endpoint {
|
||||
let host: NWEndpoint.Host
|
||||
let port: NWEndpoint.Port
|
||||
|
||||
init(host: NWEndpoint.Host, port: NWEndpoint.Port) {
|
||||
self.host = host
|
||||
self.port = port
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Converting to and from String
|
||||
extension Endpoint: Codable {
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let endpointString = try container.decode(String.self)
|
||||
guard let endpoint = Endpoint(from: endpointString) else {
|
||||
throw DecodingError.invalidData
|
||||
}
|
||||
self = endpoint
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(stringRepresentation)
|
||||
}
|
||||
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint {
|
||||
var stringRepresentation: String {
|
||||
switch host {
|
||||
case .name(let hostname, _):
|
||||
return "\(hostname):\(port)"
|
||||
case .ipv4(let address):
|
||||
return "\(address):\(port)"
|
||||
case .ipv6(let address):
|
||||
return "[\(address)]:\(port)"
|
||||
}
|
||||
}
|
||||
|
||||
init?(from string: String) {
|
||||
// Separation of host and port is based on 'parse_endpoint' function in
|
||||
// https://git.zx2c4.com/WireGuard/tree/src/tools/config.c
|
||||
@@ -41,37 +74,6 @@ extension Endpoint {
|
||||
host = NWEndpoint.Host(hostString)
|
||||
port = endpointPort
|
||||
}
|
||||
func stringRepresentation() -> String {
|
||||
switch host {
|
||||
case .name(let hostname, _):
|
||||
return "\(hostname):\(port)"
|
||||
case .ipv4(let address):
|
||||
return "\(address):\(port)"
|
||||
case .ipv6(let address):
|
||||
return "[\(address)]:\(port)"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Codable
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
extension Endpoint: Codable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(stringRepresentation())
|
||||
}
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let endpointString = try container.decode(String.self)
|
||||
guard let endpoint = Endpoint(from: endpointString) else {
|
||||
throw DecodingError.invalidData
|
||||
}
|
||||
self = endpoint
|
||||
}
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
}
|
||||
|
||||
extension Endpoint {
|
||||
|
||||
@@ -4,16 +4,28 @@
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
struct IPAddressRange {
|
||||
let address: IPAddress
|
||||
var networkPrefixLength: UInt8
|
||||
|
||||
init(address: IPAddress, networkPrefixLength: UInt8) {
|
||||
self.address = address
|
||||
self.networkPrefixLength = networkPrefixLength
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Converting to and from String
|
||||
|
||||
extension IPAddressRange {
|
||||
var stringRepresentation: String {
|
||||
return "\(address)/\(networkPrefixLength)"
|
||||
}
|
||||
|
||||
init?(from string: String) {
|
||||
guard let parsed = IPAddressRange.parseAddressString(string) else { return nil }
|
||||
address = parsed.0
|
||||
networkPrefixLength = parsed.1
|
||||
}
|
||||
|
||||
private static func parseAddressString(_ string: String) -> (IPAddress, UInt8)? {
|
||||
let endOfIPAddress = string.lastIndex(of: "/") ?? string.endIndex
|
||||
let addressString = String(string[string.startIndex ..< endOfIPAddress])
|
||||
let address: IPAddress
|
||||
@@ -24,7 +36,8 @@ extension IPAddressRange {
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
let maxNetworkPrefixLength: UInt8 = (address is IPv4Address) ? 32 : 128
|
||||
|
||||
let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128
|
||||
var networkPrefixLength: UInt8
|
||||
if endOfIPAddress < string.endIndex { // "/" was located
|
||||
let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress)
|
||||
@@ -35,47 +48,25 @@ extension IPAddressRange {
|
||||
} else {
|
||||
networkPrefixLength = maxNetworkPrefixLength
|
||||
}
|
||||
self.address = address
|
||||
self.networkPrefixLength = networkPrefixLength
|
||||
}
|
||||
func stringRepresentation() -> String {
|
||||
return "\(address)/\(networkPrefixLength)"
|
||||
|
||||
return (address, networkPrefixLength)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Codable
|
||||
|
||||
@available(OSX 10.14, iOS 12.0, *)
|
||||
extension IPAddressRange: Codable {
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
let addressDataLength: Int
|
||||
if address is IPv4Address {
|
||||
addressDataLength = 4
|
||||
} else if address is IPv6Address {
|
||||
addressDataLength = 16
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
var data = Data(capacity: addressDataLength + 1)
|
||||
data.append(address.rawValue)
|
||||
data.append(networkPrefixLength)
|
||||
try container.encode(data)
|
||||
try container.encode(stringRepresentation)
|
||||
}
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
var data = try container.decode(Data.self)
|
||||
networkPrefixLength = data.removeLast()
|
||||
let ipAddressFromData: IPAddress? = {
|
||||
switch data.count {
|
||||
case 4: return IPv4Address(data)
|
||||
case 16: return IPv6Address(data)
|
||||
default: return nil
|
||||
}
|
||||
}()
|
||||
guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
|
||||
address = ipAddress
|
||||
let values = try decoder.singleValueContainer()
|
||||
let addressString = try values.decode(String.self)
|
||||
guard let parsed = IPAddressRange.parseAddressString(addressString) else { throw DecodingError.invalidData }
|
||||
address = parsed.0
|
||||
networkPrefixLength = parsed.1
|
||||
}
|
||||
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
struct InterfaceConfiguration {
|
||||
var name: String
|
||||
var privateKey: Data
|
||||
var addresses = [IPAddressRange]()
|
||||
var listenPort: UInt16?
|
||||
var mtu: UInt16?
|
||||
var dns = [DNSServer]()
|
||||
|
||||
init(name: String, privateKey: Data) {
|
||||
self.name = name
|
||||
self.privateKey = privateKey
|
||||
if name.isEmpty {
|
||||
fatalError("Empty name")
|
||||
}
|
||||
if privateKey.count != TunnelConfiguration.keyLength {
|
||||
fatalError("Invalid private key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension InterfaceConfiguration: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case name = "Name"
|
||||
case privateKey = "PrivateKey"
|
||||
case addresses = "Address"
|
||||
case listenPort = "ListenPort"
|
||||
case mtu = "MTU"
|
||||
case dns = "DNS"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
name = try values.decode(String.self, forKey: .name)
|
||||
privateKey = try Data(base64Encoded: values.decode(String.self, forKey: .privateKey))!
|
||||
addresses = try values.decode([IPAddressRange].self, forKey: .addresses)
|
||||
listenPort = try? values.decode(UInt16.self, forKey: .listenPort)
|
||||
mtu = try? values.decode(UInt16.self, forKey: .mtu)
|
||||
dns = try values.decode([DNSServer].self, forKey: .dns)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(name, forKey: .name)
|
||||
try container.encode(privateKey.base64EncodedString(), forKey: .privateKey)
|
||||
try container.encode(addresses, forKey: .addresses)
|
||||
if let listenPort = listenPort {
|
||||
try container.encode(listenPort, forKey: .listenPort)
|
||||
}
|
||||
if let mtu = mtu {
|
||||
try container.encode(mtu, forKey: .mtu)
|
||||
}
|
||||
try container.encode(dns, forKey: .dns)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
struct LegacyDNSServer: Codable {
|
||||
let address: IPAddress
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
var data = try container.decode(Data.self)
|
||||
let ipAddressFromData: IPAddress? = {
|
||||
switch data.count {
|
||||
case 4: return IPv4Address(data)
|
||||
case 16: return IPv6Address(data)
|
||||
default: return nil
|
||||
}
|
||||
}()
|
||||
guard let ipAddress = ipAddressFromData else {
|
||||
throw DecodingError.invalidData
|
||||
}
|
||||
address = ipAddress
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(address.rawValue)
|
||||
}
|
||||
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
}
|
||||
|
||||
extension LegacyDNSServer {
|
||||
var migrated: DNSServer {
|
||||
return DNSServer(address: address)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == LegacyDNSServer {
|
||||
var migrated: [DNSServer] {
|
||||
return map { $0.migrated }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
struct LegacyEndpoint: Codable {
|
||||
let host: NWEndpoint.Host
|
||||
let port: NWEndpoint.Port
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
let endpointString = try container.decode(String.self)
|
||||
guard !endpointString.isEmpty else { throw DecodingError.invalidData }
|
||||
let startOfPort: String.Index
|
||||
let hostString: String
|
||||
if endpointString.first! == "[" {
|
||||
// Look for IPv6-style endpoint, like [::1]:80
|
||||
let startOfHost = endpointString.index(after: endpointString.startIndex)
|
||||
guard let endOfHost = endpointString.dropFirst().firstIndex(of: "]") else { throw DecodingError.invalidData }
|
||||
let afterEndOfHost = endpointString.index(after: endOfHost)
|
||||
guard endpointString[afterEndOfHost] == ":" else { throw DecodingError.invalidData }
|
||||
startOfPort = endpointString.index(after: afterEndOfHost)
|
||||
hostString = String(endpointString[startOfHost ..< endOfHost])
|
||||
} else {
|
||||
// Look for an IPv4-style endpoint, like 127.0.0.1:80
|
||||
guard let endOfHost = endpointString.firstIndex(of: ":") else { throw DecodingError.invalidData }
|
||||
startOfPort = endpointString.index(after: endOfHost)
|
||||
hostString = String(endpointString[endpointString.startIndex ..< endOfHost])
|
||||
}
|
||||
guard let endpointPort = NWEndpoint.Port(String(endpointString[startOfPort ..< endpointString.endIndex])) else { throw DecodingError.invalidData }
|
||||
let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { char in
|
||||
return !CharacterSet.urlHostAllowed.contains(char)
|
||||
}
|
||||
guard invalidCharacterIndex == nil else { throw DecodingError.invalidData }
|
||||
host = NWEndpoint.Host(hostString)
|
||||
port = endpointPort
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
let stringRepresentation: String
|
||||
switch host {
|
||||
case .name(let hostname, _):
|
||||
stringRepresentation = "\(hostname):\(port)"
|
||||
case .ipv4(let address):
|
||||
stringRepresentation = "\(address):\(port)"
|
||||
case .ipv6(let address):
|
||||
stringRepresentation = "[\(address)]:\(port)"
|
||||
}
|
||||
|
||||
var container = encoder.singleValueContainer()
|
||||
try container.encode(stringRepresentation)
|
||||
}
|
||||
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
}
|
||||
|
||||
extension LegacyEndpoint {
|
||||
var migrated: Endpoint {
|
||||
return Endpoint(host: host, port: port)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
import Network
|
||||
|
||||
struct LegacyIPAddressRange: Codable {
|
||||
let address: IPAddress
|
||||
let networkPrefixLength: UInt8
|
||||
|
||||
public init(from decoder: Decoder) throws {
|
||||
let container = try decoder.singleValueContainer()
|
||||
var data = try container.decode(Data.self)
|
||||
networkPrefixLength = data.removeLast()
|
||||
let ipAddressFromData: IPAddress? = {
|
||||
switch data.count {
|
||||
case 4: return IPv4Address(data)
|
||||
case 16: return IPv6Address(data)
|
||||
default: return nil
|
||||
}
|
||||
}()
|
||||
guard let ipAddress = ipAddressFromData else { throw DecodingError.invalidData }
|
||||
address = ipAddress
|
||||
}
|
||||
|
||||
public func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.singleValueContainer()
|
||||
let addressDataLength: Int
|
||||
if address is IPv4Address {
|
||||
addressDataLength = 4
|
||||
} else if address is IPv6Address {
|
||||
addressDataLength = 16
|
||||
} else {
|
||||
fatalError()
|
||||
}
|
||||
var data = Data(capacity: addressDataLength + 1)
|
||||
data.append(address.rawValue)
|
||||
data.append(networkPrefixLength)
|
||||
try container.encode(data)
|
||||
}
|
||||
|
||||
enum DecodingError: Error {
|
||||
case invalidData
|
||||
}
|
||||
}
|
||||
|
||||
extension LegacyIPAddressRange {
|
||||
var migrated: IPAddressRange {
|
||||
return IPAddressRange(address: address, networkPrefixLength: networkPrefixLength)
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == LegacyIPAddressRange {
|
||||
var migrated: [IPAddressRange] {
|
||||
return map { $0.migrated }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
struct LegacyInterfaceConfiguration: Codable {
|
||||
let name: String
|
||||
let privateKey: Data
|
||||
let addresses: [LegacyIPAddressRange]
|
||||
let listenPort: UInt16?
|
||||
let mtu: UInt16?
|
||||
let dns: [LegacyDNSServer]
|
||||
}
|
||||
|
||||
extension LegacyInterfaceConfiguration {
|
||||
var migrated: InterfaceConfiguration {
|
||||
var interface = InterfaceConfiguration(name: name, privateKey: privateKey)
|
||||
interface.addresses = addresses.migrated
|
||||
interface.listenPort = listenPort
|
||||
interface.mtu = mtu
|
||||
interface.dns = dns.migrated
|
||||
return interface
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
struct LegacyPeerConfiguration: Codable {
|
||||
let publicKey: Data
|
||||
let preSharedKey: Data?
|
||||
let allowedIPs: [LegacyIPAddressRange]
|
||||
let endpoint: LegacyEndpoint?
|
||||
let persistentKeepAlive: UInt16?
|
||||
}
|
||||
|
||||
extension LegacyPeerConfiguration {
|
||||
var migrated: PeerConfiguration {
|
||||
var configuration = PeerConfiguration(publicKey: publicKey)
|
||||
configuration.preSharedKey = preSharedKey
|
||||
configuration.allowedIPs = allowedIPs.migrated
|
||||
configuration.endpoint = endpoint?.migrated
|
||||
configuration.persistentKeepAlive = persistentKeepAlive
|
||||
return configuration
|
||||
}
|
||||
}
|
||||
|
||||
extension Array where Element == LegacyPeerConfiguration {
|
||||
var migrated: [PeerConfiguration] {
|
||||
return map { $0.migrated }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
final class LegacyTunnelConfiguration: Codable {
|
||||
let interface: LegacyInterfaceConfiguration
|
||||
let peers: [LegacyPeerConfiguration]
|
||||
}
|
||||
|
||||
extension LegacyTunnelConfiguration {
|
||||
var migrated: TunnelConfiguration {
|
||||
return TunnelConfiguration(interface: interface.migrated, peers: peers.migrated)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,66 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
struct PeerConfiguration {
|
||||
var publicKey: Data
|
||||
var preSharedKey: Data? {
|
||||
didSet(value) {
|
||||
if let value = value {
|
||||
if value.count != TunnelConfiguration.keyLength {
|
||||
fatalError("Invalid preshared key")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
var allowedIPs = [IPAddressRange]()
|
||||
var endpoint: Endpoint?
|
||||
var persistentKeepAlive: UInt16?
|
||||
|
||||
init(publicKey: Data) {
|
||||
self.publicKey = publicKey
|
||||
if publicKey.count != TunnelConfiguration.keyLength {
|
||||
fatalError("Invalid public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension PeerConfiguration: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case publicKey = "PublicKey"
|
||||
case preSharedKey = "PreSharedKey"
|
||||
case allowedIPs = "AllowedIPs"
|
||||
case endpoint = "Endpoint"
|
||||
case persistentKeepAlive = "PersistentKeepAlive"
|
||||
}
|
||||
|
||||
init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
publicKey = try Data(base64Encoded: values.decode(String.self, forKey: .publicKey))!
|
||||
if let base64PreSharedKey = try? values.decode(Data.self, forKey: .preSharedKey) {
|
||||
preSharedKey = Data(base64Encoded: base64PreSharedKey)
|
||||
} else {
|
||||
preSharedKey = nil
|
||||
}
|
||||
allowedIPs = try values.decode([IPAddressRange].self, forKey: .allowedIPs)
|
||||
endpoint = try? values.decode(Endpoint.self, forKey: .endpoint)
|
||||
persistentKeepAlive = try? values.decode(UInt16.self, forKey: .persistentKeepAlive)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(publicKey.base64EncodedString(), forKey: .publicKey)
|
||||
if let preSharedKey = preSharedKey {
|
||||
try container.encode(preSharedKey.base64EncodedString(), forKey: .preSharedKey)
|
||||
}
|
||||
|
||||
try container.encode(allowedIPs, forKey: .allowedIPs)
|
||||
if let endpoint = endpoint {
|
||||
try container.encode(endpoint, forKey: .endpoint)
|
||||
}
|
||||
if let persistentKeepAlive = persistentKeepAlive {
|
||||
try container.encode(persistentKeepAlive, forKey: .persistentKeepAlive)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
// SPDX-License-Identifier: MIT
|
||||
// Copyright © 2018 WireGuard LLC. All Rights Reserved.
|
||||
|
||||
import Foundation
|
||||
|
||||
final class TunnelConfiguration {
|
||||
var interface: InterfaceConfiguration
|
||||
let peers: [PeerConfiguration]
|
||||
|
||||
static let keyLength = 32
|
||||
|
||||
init(interface: InterfaceConfiguration, peers: [PeerConfiguration]) {
|
||||
self.interface = interface
|
||||
self.peers = peers
|
||||
|
||||
let peerPublicKeysArray = peers.map { $0.publicKey }
|
||||
let peerPublicKeysSet = Set<Data>(peerPublicKeysArray)
|
||||
if peerPublicKeysArray.count != peerPublicKeysSet.count {
|
||||
fatalError("Two or more peers cannot have the same public key")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension TunnelConfiguration: Codable {
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case interface = "Interface"
|
||||
case peers = "Peer"
|
||||
}
|
||||
|
||||
convenience init(from decoder: Decoder) throws {
|
||||
let values = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let interface = try values.decode(InterfaceConfiguration.self, forKey: .interface)
|
||||
let peers = try values.decode([PeerConfiguration].self, forKey: .peers)
|
||||
self.init(interface: interface, peers: peers)
|
||||
}
|
||||
|
||||
func encode(to encoder: Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
try container.encode(interface, forKey: .interface)
|
||||
try container.encode(peers, forKey: .peers)
|
||||
}
|
||||
}
|
||||
@@ -3,24 +3,53 @@
|
||||
|
||||
import NetworkExtension
|
||||
|
||||
let tunnelConfigurationVersion = 2
|
||||
|
||||
extension NETunnelProviderProtocol {
|
||||
|
||||
enum Keys: String {
|
||||
case tunnelConfiguration = "TunnelConfiguration"
|
||||
case tunnelConfigurationVersion = "TunnelConfigurationVersion"
|
||||
case isActivateOnDemandEnabled = "IsActivateOnDemandEnabled"
|
||||
}
|
||||
|
||||
var tunnelConfiguration: TunnelConfiguration? {
|
||||
migrateConfigurationIfNeeded()
|
||||
|
||||
let tunnelConfigurationData: Data?
|
||||
if let configurationDictionary = providerConfiguration?[Keys.tunnelConfiguration.rawValue] {
|
||||
tunnelConfigurationData = try? JSONSerialization.data(withJSONObject: configurationDictionary, options: [])
|
||||
} else {
|
||||
tunnelConfigurationData = nil
|
||||
}
|
||||
|
||||
guard tunnelConfigurationData != nil else { return nil }
|
||||
return try? JSONDecoder().decode(TunnelConfiguration.self, from: tunnelConfigurationData!)
|
||||
}
|
||||
|
||||
var isActivateOnDemandEnabled: Bool {
|
||||
return providerConfiguration?[Keys.isActivateOnDemandEnabled.rawValue] as? Bool ?? false
|
||||
}
|
||||
|
||||
convenience init?(tunnelConfiguration: TunnelConfiguration, isActivateOnDemandEnabled: Bool) {
|
||||
assert(!tunnelConfiguration.interface.name.isEmpty)
|
||||
guard let serializedTunnelConfiguration = try? JSONEncoder().encode(tunnelConfiguration) else { return nil }
|
||||
|
||||
|
||||
guard let tunnelConfigData = try? JSONEncoder().encode(tunnelConfiguration) else { return nil }
|
||||
guard let tunnelConfigDictionary = try? JSONSerialization.jsonObject(with: tunnelConfigData, options: .allowFragments) else { return nil }
|
||||
|
||||
self.init()
|
||||
|
||||
let appId = Bundle.main.bundleIdentifier!
|
||||
providerBundleIdentifier = "\(appId).network-extension"
|
||||
providerConfiguration = [
|
||||
"tunnelConfiguration": serializedTunnelConfiguration,
|
||||
"tunnelConfigurationVersion": 1,
|
||||
"isActivateOnDemandEnabled": isActivateOnDemandEnabled
|
||||
Keys.tunnelConfiguration.rawValue: tunnelConfigDictionary,
|
||||
Keys.tunnelConfigurationVersion.rawValue: tunnelConfigurationVersion,
|
||||
Keys.isActivateOnDemandEnabled.rawValue: isActivateOnDemandEnabled
|
||||
]
|
||||
|
||||
let endpoints = tunnelConfiguration.peers.compactMap {$0.endpoint}
|
||||
let endpoints = tunnelConfiguration.peers.compactMap { $0.endpoint }
|
||||
if endpoints.count == 1 {
|
||||
serverAddress = endpoints.first!.stringRepresentation()
|
||||
serverAddress = endpoints[0].stringRepresentation
|
||||
} else if endpoints.isEmpty {
|
||||
serverAddress = "Unspecified"
|
||||
} else {
|
||||
@@ -29,18 +58,42 @@ extension NETunnelProviderProtocol {
|
||||
username = tunnelConfiguration.interface.name
|
||||
}
|
||||
|
||||
func tunnelConfiguration() -> TunnelConfiguration? {
|
||||
guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return nil }
|
||||
return try? JSONDecoder().decode(TunnelConfiguration.self, from: serializedTunnelConfiguration)
|
||||
}
|
||||
|
||||
var isActivateOnDemandEnabled: Bool {
|
||||
return (providerConfiguration?["isActivateOnDemandEnabled"] as? Bool) ?? false
|
||||
}
|
||||
|
||||
func hasTunnelConfiguration(tunnelConfiguration otherTunnelConfiguration: TunnelConfiguration) -> Bool {
|
||||
guard let serializedThisTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return false }
|
||||
guard let serializedThisTunnelConfiguration = try? JSONEncoder().encode(tunnelConfiguration) else { return false }
|
||||
guard let serializedOtherTunnelConfiguration = try? JSONEncoder().encode(otherTunnelConfiguration) else { return false }
|
||||
return serializedThisTunnelConfiguration == serializedOtherTunnelConfiguration
|
||||
}
|
||||
|
||||
@discardableResult
|
||||
func migrateConfigurationIfNeeded() -> Bool {
|
||||
guard let providerConfiguration = providerConfiguration else { return false }
|
||||
guard let configurationVersion = providerConfiguration[Keys.tunnelConfigurationVersion.rawValue] as? Int ?? providerConfiguration["tunnelConfigurationVersion"] as? Int else { return false }
|
||||
|
||||
if configurationVersion < tunnelConfigurationVersion {
|
||||
switch configurationVersion {
|
||||
case 1:
|
||||
migrateFromConfigurationV1()
|
||||
default:
|
||||
fatalError("No migration from configuration version \(configurationVersion) exists.")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private func migrateFromConfigurationV1() {
|
||||
guard let serializedTunnelConfiguration = providerConfiguration?["tunnelConfiguration"] as? Data else { return }
|
||||
guard let configuration = try? JSONDecoder().decode(LegacyTunnelConfiguration.self, from: serializedTunnelConfiguration) else { return }
|
||||
guard let isActivateOnDemandEnabled = providerConfiguration?["isActivateOnDemandEnabled"] as? Bool else { return }
|
||||
guard let tunnelConfigData = try? JSONEncoder().encode(configuration.migrated) else { return }
|
||||
guard let tunnelConfigDictionary = try? JSONSerialization.jsonObject(with: tunnelConfigData, options: .allowFragments) else { return }
|
||||
|
||||
providerConfiguration = [
|
||||
Keys.tunnelConfiguration.rawValue: tunnelConfigDictionary,
|
||||
Keys.tunnelConfigurationVersion.rawValue: tunnelConfigurationVersion,
|
||||
Keys.isActivateOnDemandEnabled.rawValue: isActivateOnDemandEnabled
|
||||
]
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user