ios: PCA Phobos VPN for iPhone (source)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Бобырев Андрей
2026-10-23 03:37:06 +03:00
parent dff6af2c8e
commit 071c1dc7f1
63 changed files with 7726 additions and 0 deletions

19
ios/WireGuardKit/COPYING Normal file
View File

@@ -0,0 +1,19 @@
Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
of the Software, and to permit persons to whom the Software is furnished to do
so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,15 @@
// swift-tools-version:5.5
// Vendored WireGuardKit (github.com/WireGuard/wireguard-apple, MIT). libwg-go.a is
// prebuilt in ../WireGuardGo/<sdk>/ — the app target adds it to LIBRARY_SEARCH_PATHS.
import PackageDescription
let package = Package(
name: "WireGuardKit",
platforms: [.iOS(.v15)],
products: [.library(name: "WireGuardKit", targets: ["WireGuardKit"])],
targets: [
.target(name: "WireGuardKit", dependencies: ["WireGuardKitGo", "WireGuardKitC"]),
.target(name: "WireGuardKitC", publicHeadersPath: "."),
.target(name: "WireGuardKitGo", publicHeadersPath: ".", linkerSettings: [.linkedLibrary("wg-go")]),
]
)

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
extension Array {
/// Returns an array containing the results of mapping the given closure over the sequence’s
/// elements concurrently.
///
/// - Parameters:
/// - queue: The queue for performing concurrent computations.
/// If the given queue is serial, the values are mapped in a serial fashion.
/// Pass `nil` to perform computations on the current queue.
/// - transform: the block to perform concurrent computations over the given element.
/// - Returns: an array of concurrently computed values.
func concurrentMap<U>(queue: DispatchQueue?, _ transform: (Element) -> U) -> [U] {
var result = [U?](repeating: nil, count: self.count)
let resultQueue = DispatchQueue(label: "ConcurrentMapQueue")
let execute = queue?.sync ?? { $0() }
execute {
DispatchQueue.concurrentPerform(iterations: self.count) { index in
let value = transform(self[index])
resultQueue.sync {
result[index] = value
}
}
}
return result.map { $0! }
}
}

View File

@@ -0,0 +1,153 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Network
import Foundation
enum DNSResolver {}
extension DNSResolver {
/// Concurrent queue used for DNS resolutions
private static let resolverQueue = DispatchQueue(label: "DNSResolverQueue", qos: .default, attributes: .concurrent)
static func resolveSync(endpoints: [Endpoint?]) -> [Result<Endpoint, DNSResolutionError>?] {
let isAllEndpointsAlreadyResolved = endpoints.allSatisfy { maybeEndpoint -> Bool in
return maybeEndpoint?.hasHostAsIPAddress() ?? true
}
if isAllEndpointsAlreadyResolved {
return endpoints.map { endpoint in
return endpoint.map { .success($0) }
}
}
return endpoints.concurrentMap(queue: resolverQueue) { endpoint -> Result<Endpoint, DNSResolutionError>? in
guard let endpoint = endpoint else { return nil }
if endpoint.hasHostAsIPAddress() {
return .success(endpoint)
} else {
return Result { try DNSResolver.resolveSync(endpoint: endpoint) }
.mapError { error -> DNSResolutionError in
// swiftlint:disable:next force_cast
return error as! DNSResolutionError
}
}
}
}
private static func resolveSync(endpoint: Endpoint) throws -> Endpoint {
guard case .name(let name, _) = endpoint.host else {
return endpoint
}
var hints = addrinfo()
hints.ai_flags = AI_ALL // We set this to ALL so that we get v4 addresses even on DNS64 networks
hints.ai_family = AF_UNSPEC
hints.ai_socktype = SOCK_DGRAM
hints.ai_protocol = IPPROTO_UDP
var resultPointer: UnsafeMutablePointer<addrinfo>?
defer {
resultPointer.flatMap { freeaddrinfo($0) }
}
let errorCode = getaddrinfo(name, "\(endpoint.port)", &hints, &resultPointer)
if errorCode != 0 {
throw DNSResolutionError(errorCode: errorCode, address: name)
}
var ipv4Address: IPv4Address?
var ipv6Address: IPv6Address?
var next: UnsafeMutablePointer<addrinfo>? = resultPointer
let iterator = AnyIterator { () -> addrinfo? in
let result = next?.pointee
next = result?.ai_next
return result
}
for addrInfo in iterator {
if let maybeIpv4Address = IPv4Address(addrInfo: addrInfo) {
ipv4Address = maybeIpv4Address
break // If we found an IPv4 address, we can stop
} else if let maybeIpv6Address = IPv6Address(addrInfo: addrInfo) {
ipv6Address = maybeIpv6Address
continue // If we already have an IPv6 address, we can skip this one
}
}
// We prefer an IPv4 address over an IPv6 address
if let ipv4Address = ipv4Address {
return Endpoint(host: .ipv4(ipv4Address), port: endpoint.port)
} else if let ipv6Address = ipv6Address {
return Endpoint(host: .ipv6(ipv6Address), port: endpoint.port)
} else {
// Must never happen
fatalError()
}
}
}
extension Endpoint {
func withReresolvedIP() throws -> Endpoint {
#if os(iOS)
let hostname: String
switch host {
case .name(let name, _):
hostname = name
case .ipv4(let address):
hostname = "\(address)"
case .ipv6(let address):
hostname = "\(address)"
@unknown default:
fatalError()
}
var hints = addrinfo()
hints.ai_family = AF_UNSPEC
hints.ai_socktype = SOCK_DGRAM
hints.ai_protocol = IPPROTO_UDP
hints.ai_flags = 0 // We set this to zero so that we actually resolve this using DNS64
var result: UnsafeMutablePointer<addrinfo>?
defer {
result.flatMap { freeaddrinfo($0) }
}
let errorCode = getaddrinfo(hostname, "\(self.port)", &hints, &result)
if errorCode != 0 {
throw DNSResolutionError(errorCode: errorCode, address: hostname)
}
let addrInfo = result!.pointee
if let ipv4Address = IPv4Address(addrInfo: addrInfo) {
return Endpoint(host: .ipv4(ipv4Address), port: port)
} else if let ipv6Address = IPv6Address(addrInfo: addrInfo) {
return Endpoint(host: .ipv6(ipv6Address), port: port)
} else {
fatalError()
}
#elseif os(macOS)
return self
#else
#error("Unimplemented")
#endif
}
}
/// An error type describing DNS resolution error
public struct DNSResolutionError: LocalizedError {
public let errorCode: Int32
public let address: String
init(errorCode: Int32, address: String) {
self.errorCode = errorCode
self.address = address
}
public var errorDescription: String? {
return String(cString: gai_strerror(errorCode))
}
}

View File

@@ -0,0 +1,35 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
import Network
public struct DNSServer {
public let address: IPAddress
public init(address: IPAddress) {
self.address = address
}
}
extension DNSServer: Equatable {
public static func == (lhs: DNSServer, rhs: DNSServer) -> Bool {
return lhs.address.rawValue == rhs.address.rawValue
}
}
extension DNSServer {
public var stringRepresentation: String {
return "\(address)"
}
public init?(from addressString: String) {
if let addr = IPv4Address(addressString) {
address = addr
} else if let addr = IPv6Address(addressString) {
address = addr
} else {
return nil
}
}
}

View File

@@ -0,0 +1,101 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
import Network
public struct Endpoint {
public let host: NWEndpoint.Host
public let port: NWEndpoint.Port
public init(host: NWEndpoint.Host, port: NWEndpoint.Port) {
self.host = host
self.port = port
}
}
extension Endpoint: Equatable {
public static func == (lhs: Endpoint, rhs: Endpoint) -> Bool {
return lhs.host == rhs.host && lhs.port == rhs.port
}
}
extension Endpoint: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(host)
hasher.combine(port)
}
}
extension Endpoint {
public 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)"
@unknown default:
fatalError()
}
}
public init?(from string: String) {
// Separation of host and port is based on 'parse_endpoint' function in
// https://git.zx2c4.com/wireguard-tools/tree/src/config.c
guard !string.isEmpty else { return nil }
let startOfPort: String.Index
let hostString: String
if string.first! == "[" {
// Look for IPv6-style endpoint, like [::1]:80
let startOfHost = string.index(after: string.startIndex)
guard let endOfHost = string.dropFirst().firstIndex(of: "]") else { return nil }
let afterEndOfHost = string.index(after: endOfHost)
if afterEndOfHost == string.endIndex { return nil }
guard string[afterEndOfHost] == ":" else { return nil }
startOfPort = string.index(after: afterEndOfHost)
hostString = String(string[startOfHost ..< endOfHost])
} else {
// Look for an IPv4-style endpoint, like 127.0.0.1:80
guard let endOfHost = string.firstIndex(of: ":") else { return nil }
startOfPort = string.index(after: endOfHost)
hostString = String(string[string.startIndex ..< endOfHost])
}
guard let endpointPort = NWEndpoint.Port(String(string[startOfPort ..< string.endIndex])) else { return nil }
let invalidCharacterIndex = hostString.unicodeScalars.firstIndex { char in
return !CharacterSet.urlHostAllowed.contains(char)
}
guard invalidCharacterIndex == nil else { return nil }
host = NWEndpoint.Host(hostString)
port = endpointPort
}
}
extension Endpoint {
public func hasHostAsIPAddress() -> Bool {
switch host {
case .name:
return false
case .ipv4:
return true
case .ipv6:
return true
@unknown default:
fatalError()
}
}
public func hostname() -> String? {
switch host {
case .name(let hostname, _):
return hostname
case .ipv4:
return nil
case .ipv6:
return nil
@unknown default:
fatalError()
}
}
}

View File

@@ -0,0 +1,29 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
import Network
extension IPv4Address {
init?(addrInfo: addrinfo) {
guard addrInfo.ai_family == AF_INET else { return nil }
let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in.self, capacity: 1) { ptr -> Data in
return Data(bytes: &ptr.pointee.sin_addr, count: MemoryLayout<in_addr>.size)
}
self.init(addressData)
}
}
extension IPv6Address {
init?(addrInfo: addrinfo) {
guard addrInfo.ai_family == AF_INET6 else { return nil }
let addressData = addrInfo.ai_addr.withMemoryRebound(to: sockaddr_in6.self, capacity: 1) { ptr -> Data in
return Data(bytes: &ptr.pointee.sin6_addr, count: MemoryLayout<in6_addr>.size)
}
self.init(addressData)
}
}

View File

@@ -0,0 +1,115 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
import Network
public struct IPAddressRange {
public let address: IPAddress
public let networkPrefixLength: UInt8
init(address: IPAddress, networkPrefixLength: UInt8) {
self.address = address
self.networkPrefixLength = networkPrefixLength
}
}
extension IPAddressRange: Equatable {
public static func == (lhs: IPAddressRange, rhs: IPAddressRange) -> Bool {
return lhs.address.rawValue == rhs.address.rawValue && lhs.networkPrefixLength == rhs.networkPrefixLength
}
}
extension IPAddressRange: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(address.rawValue)
hasher.combine(networkPrefixLength)
}
}
extension IPAddressRange {
public var stringRepresentation: String {
return "\(address)/\(networkPrefixLength)"
}
public 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
if let addr = IPv4Address(addressString) {
address = addr
} else if let addr = IPv6Address(addressString) {
address = addr
} else {
return nil
}
let maxNetworkPrefixLength: UInt8 = address is IPv4Address ? 32 : 128
var networkPrefixLength: UInt8
if endOfIPAddress < string.endIndex { // "/" was located
let indexOfNetworkPrefixLength = string.index(after: endOfIPAddress)
guard indexOfNetworkPrefixLength < string.endIndex else { return nil }
let networkPrefixLengthSubstring = string[indexOfNetworkPrefixLength ..< string.endIndex]
guard let npl = UInt8(networkPrefixLengthSubstring) else { return nil }
networkPrefixLength = min(npl, maxNetworkPrefixLength)
} else {
networkPrefixLength = maxNetworkPrefixLength
}
return (address, networkPrefixLength)
}
public func subnetMask() -> IPAddress {
if address is IPv4Address {
let mask = networkPrefixLength > 0 ? ~UInt32(0) << (32 - networkPrefixLength) : UInt32(0)
let bytes = Data([
UInt8(truncatingIfNeeded: mask >> 24),
UInt8(truncatingIfNeeded: mask >> 16),
UInt8(truncatingIfNeeded: mask >> 8),
UInt8(truncatingIfNeeded: mask >> 0)
])
return IPv4Address(bytes)!
}
if address is IPv6Address {
var bytes = Data(repeating: 0, count: 16)
for i in 0..<Int(networkPrefixLength/8) {
bytes[i] = 0xff
}
let nibble = networkPrefixLength % 32
if nibble != 0 {
let mask = ~UInt32(0) << (32 - nibble)
let i = Int(networkPrefixLength / 32 * 4)
bytes[i + 0] = UInt8(truncatingIfNeeded: mask >> 24)
bytes[i + 1] = UInt8(truncatingIfNeeded: mask >> 16)
bytes[i + 2] = UInt8(truncatingIfNeeded: mask >> 8)
bytes[i + 3] = UInt8(truncatingIfNeeded: mask >> 0)
}
return IPv6Address(bytes)!
}
fatalError()
}
public func maskedAddress() -> IPAddress {
let subnet = subnetMask().rawValue
var masked = Data(address.rawValue)
if subnet.count != masked.count {
fatalError()
}
for i in 0..<subnet.count {
masked[i] &= subnet[i]
}
if subnet.count == 4 {
return IPv4Address(masked)!
}
if subnet.count == 16 {
return IPv6Address(masked)!
}
fatalError()
}
}

View File

@@ -0,0 +1,32 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
import Network
public struct InterfaceConfiguration {
public var privateKey: PrivateKey
public var addresses = [IPAddressRange]()
public var listenPort: UInt16?
public var mtu: UInt16?
public var dns = [DNSServer]()
public var dnsSearch = [String]()
public init(privateKey: PrivateKey) {
self.privateKey = privateKey
}
}
extension InterfaceConfiguration: Equatable {
public static func == (lhs: InterfaceConfiguration, rhs: InterfaceConfiguration) -> Bool {
let lhsAddresses = lhs.addresses.filter { $0.address is IPv4Address } + lhs.addresses.filter { $0.address is IPv6Address }
let rhsAddresses = rhs.addresses.filter { $0.address is IPv4Address } + rhs.addresses.filter { $0.address is IPv6Address }
return lhs.privateKey == rhs.privateKey &&
lhsAddresses == rhsAddresses &&
lhs.listenPort == rhs.listenPort &&
lhs.mtu == rhs.mtu &&
lhs.dns == rhs.dns &&
lhs.dnsSearch == rhs.dnsSearch
}
}

View File

@@ -0,0 +1,183 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
import Network
import NetworkExtension
#if SWIFT_PACKAGE
import WireGuardKitC
#endif
/// A type alias for `Result` type that holds a tuple with source and resolved endpoint.
typealias EndpointResolutionResult = Result<(Endpoint, Endpoint), DNSResolutionError>
class PacketTunnelSettingsGenerator {
let tunnelConfiguration: TunnelConfiguration
let resolvedEndpoints: [Endpoint?]
init(tunnelConfiguration: TunnelConfiguration, resolvedEndpoints: [Endpoint?]) {
self.tunnelConfiguration = tunnelConfiguration
self.resolvedEndpoints = resolvedEndpoints
}
func endpointUapiConfiguration() -> (String, [EndpointResolutionResult?]) {
var resolutionResults = [EndpointResolutionResult?]()
var wgSettings = ""
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) {
wgSettings.append("public_key=\(peer.publicKey.hexKey)\n")
let result = resolvedEndpoint.map(Self.reresolveEndpoint)
if case .success((_, let resolvedEndpoint)) = result {
if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") }
wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n")
}
resolutionResults.append(result)
}
return (wgSettings, resolutionResults)
}
func uapiConfiguration() -> (String, [EndpointResolutionResult?]) {
var resolutionResults = [EndpointResolutionResult?]()
var wgSettings = ""
wgSettings.append("private_key=\(tunnelConfiguration.interface.privateKey.hexKey)\n")
if let listenPort = tunnelConfiguration.interface.listenPort {
wgSettings.append("listen_port=\(listenPort)\n")
}
if !tunnelConfiguration.peers.isEmpty {
wgSettings.append("replace_peers=true\n")
}
assert(tunnelConfiguration.peers.count == resolvedEndpoints.count)
for (peer, resolvedEndpoint) in zip(self.tunnelConfiguration.peers, self.resolvedEndpoints) {
wgSettings.append("public_key=\(peer.publicKey.hexKey)\n")
if let preSharedKey = peer.preSharedKey?.hexKey {
wgSettings.append("preshared_key=\(preSharedKey)\n")
}
let result = resolvedEndpoint.map(Self.reresolveEndpoint)
if case .success((_, let resolvedEndpoint)) = result {
if case .name = resolvedEndpoint.host { assert(false, "Endpoint is not resolved") }
wgSettings.append("endpoint=\(resolvedEndpoint.stringRepresentation)\n")
}
resolutionResults.append(result)
let persistentKeepAlive = peer.persistentKeepAlive ?? 0
wgSettings.append("persistent_keepalive_interval=\(persistentKeepAlive)\n")
if !peer.allowedIPs.isEmpty {
wgSettings.append("replace_allowed_ips=true\n")
peer.allowedIPs.forEach { wgSettings.append("allowed_ip=\($0.stringRepresentation)\n") }
}
}
return (wgSettings, resolutionResults)
}
func generateNetworkSettings() -> NEPacketTunnelNetworkSettings {
/* iOS requires a tunnel endpoint, whereas in WireGuard it's valid for
* a tunnel to have no endpoint, or for there to be many endpoints, in
* which case, displaying a single one in settings doesn't really
* make sense. So, we fill it in with this placeholder, which is not
* a valid IP address that will actually route over the Internet.
*/
let networkSettings = NEPacketTunnelNetworkSettings(tunnelRemoteAddress: "127.0.0.1")
if !tunnelConfiguration.interface.dnsSearch.isEmpty || !tunnelConfiguration.interface.dns.isEmpty {
let dnsServerStrings = tunnelConfiguration.interface.dns.map { $0.stringRepresentation }
let dnsSettings = NEDNSSettings(servers: dnsServerStrings)
dnsSettings.searchDomains = tunnelConfiguration.interface.dnsSearch
if !tunnelConfiguration.interface.dns.isEmpty {
dnsSettings.matchDomains = [""] // All DNS queries must first go through the tunnel's DNS
}
networkSettings.dnsSettings = dnsSettings
}
let mtu = tunnelConfiguration.interface.mtu ?? 0
/* 0 means automatic MTU. In theory, we should just do
* `networkSettings.tunnelOverheadBytes = 80` but in
* practice there are too many broken networks out there.
* Instead set it to 1280. Boohoo. Maybe someday we'll
* add a nob, maybe, or iOS will do probing for us.
*/
if mtu == 0 {
#if os(iOS)
networkSettings.mtu = NSNumber(value: 1280)
#elseif os(macOS)
networkSettings.tunnelOverheadBytes = 80
#else
#error("Unimplemented")
#endif
} else {
networkSettings.mtu = NSNumber(value: mtu)
}
let (ipv4Addresses, ipv6Addresses) = addresses()
let (ipv4IncludedRoutes, ipv6IncludedRoutes) = includedRoutes()
let ipv4Settings = NEIPv4Settings(addresses: ipv4Addresses.map { $0.destinationAddress }, subnetMasks: ipv4Addresses.map { $0.destinationSubnetMask })
ipv4Settings.includedRoutes = ipv4IncludedRoutes
networkSettings.ipv4Settings = ipv4Settings
let ipv6Settings = NEIPv6Settings(addresses: ipv6Addresses.map { $0.destinationAddress }, networkPrefixLengths: ipv6Addresses.map { $0.destinationNetworkPrefixLength })
ipv6Settings.includedRoutes = ipv6IncludedRoutes
networkSettings.ipv6Settings = ipv6Settings
return networkSettings
}
private func addresses() -> ([NEIPv4Route], [NEIPv6Route]) {
var ipv4Routes = [NEIPv4Route]()
var ipv6Routes = [NEIPv6Route]()
for addressRange in tunnelConfiguration.interface.addresses {
if addressRange.address is IPv4Address {
ipv4Routes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())"))
} else if addressRange.address is IPv6Address {
/* Big fat ugly hack for broken iOS networking stack: the smallest prefix that will have
* any effect on iOS is a /120, so we clamp everything above to /120. This is potentially
* very bad, if various network parameters were actually relying on that subnet being
* intentionally small. TODO: talk about this with upstream iOS devs.
*/
ipv6Routes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: min(120, addressRange.networkPrefixLength))))
}
}
return (ipv4Routes, ipv6Routes)
}
private func includedRoutes() -> ([NEIPv4Route], [NEIPv6Route]) {
var ipv4IncludedRoutes = [NEIPv4Route]()
var ipv6IncludedRoutes = [NEIPv6Route]()
for addressRange in tunnelConfiguration.interface.addresses {
if addressRange.address is IPv4Address {
let route = NEIPv4Route(destinationAddress: "\(addressRange.maskedAddress())", subnetMask: "\(addressRange.subnetMask())")
route.gatewayAddress = "\(addressRange.address)"
ipv4IncludedRoutes.append(route)
} else if addressRange.address is IPv6Address {
let route = NEIPv6Route(destinationAddress: "\(addressRange.maskedAddress())", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength))
route.gatewayAddress = "\(addressRange.address)"
ipv6IncludedRoutes.append(route)
}
}
for peer in tunnelConfiguration.peers {
for addressRange in peer.allowedIPs {
if addressRange.address is IPv4Address {
ipv4IncludedRoutes.append(NEIPv4Route(destinationAddress: "\(addressRange.address)", subnetMask: "\(addressRange.subnetMask())"))
} else if addressRange.address is IPv6Address {
ipv6IncludedRoutes.append(NEIPv6Route(destinationAddress: "\(addressRange.address)", networkPrefixLength: NSNumber(value: addressRange.networkPrefixLength)))
}
}
}
return (ipv4IncludedRoutes, ipv6IncludedRoutes)
}
private class func reresolveEndpoint(endpoint: Endpoint) -> EndpointResolutionResult {
return Result { (endpoint, try endpoint.withReresolvedIP()) }
.mapError { error -> DNSResolutionError in
// swiftlint:disable:next force_cast
return error as! DNSResolutionError
}
}
}

View File

@@ -0,0 +1,40 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
public struct PeerConfiguration {
public var publicKey: PublicKey
public var preSharedKey: PreSharedKey?
public var allowedIPs = [IPAddressRange]()
public var endpoint: Endpoint?
public var persistentKeepAlive: UInt16?
public var rxBytes: UInt64?
public var txBytes: UInt64?
public var lastHandshakeTime: Date?
public init(publicKey: PublicKey) {
self.publicKey = publicKey
}
}
extension PeerConfiguration: Equatable {
public static func == (lhs: PeerConfiguration, rhs: PeerConfiguration) -> Bool {
return lhs.publicKey == rhs.publicKey &&
lhs.preSharedKey == rhs.preSharedKey &&
Set(lhs.allowedIPs) == Set(rhs.allowedIPs) &&
lhs.endpoint == rhs.endpoint &&
lhs.persistentKeepAlive == rhs.persistentKeepAlive
}
}
extension PeerConfiguration: Hashable {
public func hash(into hasher: inout Hasher) {
hasher.combine(publicKey)
hasher.combine(preSharedKey)
hasher.combine(Set(allowedIPs))
hasher.combine(endpoint)
hasher.combine(persistentKeepAlive)
}
}

View File

@@ -0,0 +1,114 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
#if SWIFT_PACKAGE
import WireGuardKitC
#endif
/// The class describing a private key used by WireGuard.
public class PrivateKey: BaseKey {
/// Derived public key
public var publicKey: PublicKey {
return rawValue.withUnsafeBytes { (privateKeyBufferPointer: UnsafeRawBufferPointer) -> PublicKey in
var publicKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN))
let privateKeyBytes = privateKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
publicKeyData.withUnsafeMutableBytes { (publicKeyBufferPointer: UnsafeMutableRawBufferPointer) in
let publicKeyBytes = publicKeyBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
curve25519_derive_public_key(publicKeyBytes, privateKeyBytes)
}
return PublicKey(rawValue: publicKeyData)!
}
}
/// Initialize new private key
convenience public init() {
var privateKeyData = Data(repeating: 0, count: Int(WG_KEY_LEN))
privateKeyData.withUnsafeMutableBytes { (rawBufferPointer: UnsafeMutableRawBufferPointer) in
let privateKeyBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
curve25519_generate_private_key(privateKeyBytes)
}
self.init(rawValue: privateKeyData)!
}
}
/// The class describing a public key used by WireGuard.
public class PublicKey: BaseKey {}
/// The class describing a pre-shared key used by WireGuard.
public class PreSharedKey: BaseKey {}
/// The base key implementation. Should not be used directly.
public class BaseKey: RawRepresentable, Equatable, Hashable {
/// Raw key representation
public let rawValue: Data
/// Hex encoded representation
public var hexKey: String {
return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in
let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_HEX))
key_to_hex(&outBytes, inBytes)
return String(cString: outBytes, encoding: .ascii)!
}
}
/// Base64 encoded representation
public var base64Key: String {
return rawValue.withUnsafeBytes { (rawBufferPointer: UnsafeRawBufferPointer) -> String in
let inBytes = rawBufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self)
var outBytes = [CChar](repeating: 0, count: Int(WG_KEY_LEN_BASE64))
key_to_base64(&outBytes, inBytes)
return String(cString: outBytes, encoding: .ascii)!
}
}
/// Initialize the key with existing raw representation
required public init?(rawValue: Data) {
if rawValue.count == WG_KEY_LEN {
self.rawValue = rawValue
} else {
return nil
}
}
/// Initialize the key with hex representation
public convenience init?(hexKey: String) {
var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN))
let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in
return key_from_hex(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), hexKey)
}
if success {
self.init(rawValue: bytes)
} else {
return nil
}
}
/// Initialize the key with base64 representation
public convenience init?(base64Key: String) {
var bytes = Data(repeating: 0, count: Int(WG_KEY_LEN))
let success = bytes.withUnsafeMutableBytes { (bufferPointer: UnsafeMutableRawBufferPointer) -> Bool in
return key_from_base64(bufferPointer.baseAddress!.assumingMemoryBound(to: UInt8.self), base64Key)
}
if success {
self.init(rawValue: bytes)
} else {
return nil
}
}
public static func == (lhs: BaseKey, rhs: BaseKey) -> Bool {
return lhs.rawValue.withUnsafeBytes { (lhsBytes: UnsafeRawBufferPointer) -> Bool in
return rhs.rawValue.withUnsafeBytes { (rhsBytes: UnsafeRawBufferPointer) -> Bool in
return key_eq(
lhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self),
rhsBytes.baseAddress!.assumingMemoryBound(to: UInt8.self)
)
}
}
}
}

View File

@@ -0,0 +1,30 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
public final class TunnelConfiguration {
public var name: String?
public var interface: InterfaceConfiguration
public let peers: [PeerConfiguration]
public init(name: String?, interface: InterfaceConfiguration, peers: [PeerConfiguration]) {
self.interface = interface
self.peers = peers
self.name = name
let peerPublicKeysArray = peers.map { $0.publicKey }
let peerPublicKeysSet = Set<PublicKey>(peerPublicKeysArray)
if peerPublicKeysArray.count != peerPublicKeysSet.count {
fatalError("Two or more peers cannot have the same public key")
}
}
}
extension TunnelConfiguration: Equatable {
public static func == (lhs: TunnelConfiguration, rhs: TunnelConfiguration) -> Bool {
return lhs.name == rhs.name &&
lhs.interface == rhs.interface &&
Set(lhs.peers) == Set(rhs.peers)
}
}

View File

@@ -0,0 +1,487 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
import Foundation
import NetworkExtension
#if SWIFT_PACKAGE
import WireGuardKitGo
import WireGuardKitC
#endif
public enum WireGuardAdapterError: Error {
/// Failure to locate tunnel file descriptor.
case cannotLocateTunnelFileDescriptor
/// Failure to perform an operation in such state.
case invalidState
/// Failure to resolve endpoints.
case dnsResolution([DNSResolutionError])
/// Failure to set network settings.
case setNetworkSettings(Error)
/// Failure to start WireGuard backend.
case startWireGuardBackend(Int32)
}
/// Enum representing internal state of the `WireGuardAdapter`
private enum State {
/// The tunnel is stopped
case stopped
/// The tunnel is up and running
case started(_ handle: Int32, _ settingsGenerator: PacketTunnelSettingsGenerator)
/// The tunnel is temporarily shutdown due to device going offline
case temporaryShutdown(_ settingsGenerator: PacketTunnelSettingsGenerator)
}
public class WireGuardAdapter {
public typealias LogHandler = (WireGuardLogLevel, String) -> Void
/// Network routes monitor.
private var networkMonitor: NWPathMonitor?
/// Packet tunnel provider.
private weak var packetTunnelProvider: NEPacketTunnelProvider?
/// Log handler closure.
private let logHandler: LogHandler
/// Private queue used to synchronize access to `WireGuardAdapter` members.
private let workQueue = DispatchQueue(label: "WireGuardAdapterWorkQueue")
/// Adapter state.
private var state: State = .stopped
/// Tunnel device file descriptor.
private var tunnelFileDescriptor: Int32? {
var ctlInfo = ctl_info()
withUnsafeMutablePointer(to: &ctlInfo.ctl_name) {
$0.withMemoryRebound(to: CChar.self, capacity: MemoryLayout.size(ofValue: $0.pointee)) {
_ = strcpy($0, "com.apple.net.utun_control")
}
}
for fd: Int32 in 0...1024 {
var addr = sockaddr_ctl()
var ret: Int32 = -1
var len = socklen_t(MemoryLayout.size(ofValue: addr))
withUnsafeMutablePointer(to: &addr) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
ret = getpeername(fd, $0, &len)
}
}
if ret != 0 || addr.sc_family != AF_SYSTEM {
continue
}
if ctlInfo.ctl_id == 0 {
ret = ioctl(fd, CTLIOCGINFO, &ctlInfo)
if ret != 0 {
continue
}
}
if addr.sc_id == ctlInfo.ctl_id {
return fd
}
}
return nil
}
/// Returns a WireGuard version.
class var backendVersion: String {
guard let ver = wgVersion() else { return "unknown" }
let str = String(cString: ver)
free(UnsafeMutableRawPointer(mutating: ver))
return str
}
/// Returns the tunnel device interface name, or nil on error.
/// - Returns: String.
public var interfaceName: String? {
guard let tunnelFileDescriptor = self.tunnelFileDescriptor else { return nil }
var buffer = [UInt8](repeating: 0, count: Int(IFNAMSIZ))
return buffer.withUnsafeMutableBufferPointer { mutableBufferPointer in
guard let baseAddress = mutableBufferPointer.baseAddress else { return nil }
var ifnameSize = socklen_t(IFNAMSIZ)
let result = getsockopt(
tunnelFileDescriptor,
2 /* SYSPROTO_CONTROL */,
2 /* UTUN_OPT_IFNAME */,
baseAddress,
&ifnameSize)
if result == 0 {
return String(cString: baseAddress)
} else {
return nil
}
}
}
// MARK: - Initialization
/// Designated initializer.
/// - Parameter packetTunnelProvider: an instance of `NEPacketTunnelProvider`. Internally stored
/// as a weak reference.
/// - Parameter logHandler: a log handler closure.
public init(with packetTunnelProvider: NEPacketTunnelProvider, logHandler: @escaping LogHandler) {
self.packetTunnelProvider = packetTunnelProvider
self.logHandler = logHandler
setupLogHandler()
}
deinit {
// Force remove logger to make sure that no further calls to the instance of this class
// can happen after deallocation.
wgSetLogger(nil, nil)
// Cancel network monitor
networkMonitor?.cancel()
// Shutdown the tunnel
if case .started(let handle, _) = self.state {
wgTurnOff(handle)
}
}
// MARK: - Public methods
/// Returns a runtime configuration from WireGuard.
/// - Parameter completionHandler: completion handler.
public func getRuntimeConfiguration(completionHandler: @escaping (String?) -> Void) {
workQueue.async {
guard case .started(let handle, _) = self.state else {
completionHandler(nil)
return
}
if let settings = wgGetConfig(handle) {
completionHandler(String(cString: settings))
free(settings)
} else {
completionHandler(nil)
}
}
}
/// Start the tunnel tunnel.
/// - Parameters:
/// - tunnelConfiguration: tunnel configuration.
/// - completionHandler: completion handler.
public func start(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) {
workQueue.async {
guard case .stopped = self.state else {
completionHandler(.invalidState)
return
}
let networkMonitor = NWPathMonitor()
networkMonitor.pathUpdateHandler = { [weak self] path in
self?.didReceivePathUpdate(path: path)
}
networkMonitor.start(queue: self.workQueue)
do {
let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration)
try self.setNetworkSettings(settingsGenerator.generateNetworkSettings())
let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration()
self.logEndpointResolutionResults(resolutionResults)
self.state = .started(
try self.startWireGuardBackend(wgConfig: wgConfig),
settingsGenerator
)
self.networkMonitor = networkMonitor
completionHandler(nil)
} catch let error as WireGuardAdapterError {
networkMonitor.cancel()
completionHandler(error)
} catch {
fatalError()
}
}
}
/// Stop the tunnel.
/// - Parameter completionHandler: completion handler.
public func stop(completionHandler: @escaping (WireGuardAdapterError?) -> Void) {
workQueue.async {
switch self.state {
case .started(let handle, _):
wgTurnOff(handle)
case .temporaryShutdown:
break
case .stopped:
completionHandler(.invalidState)
return
}
self.networkMonitor?.cancel()
self.networkMonitor = nil
self.state = .stopped
completionHandler(nil)
}
}
/// Update runtime configuration.
/// - Parameters:
/// - tunnelConfiguration: tunnel configuration.
/// - completionHandler: completion handler.
public func update(tunnelConfiguration: TunnelConfiguration, completionHandler: @escaping (WireGuardAdapterError?) -> Void) {
workQueue.async {
if case .stopped = self.state {
completionHandler(.invalidState)
return
}
// Tell the system that the tunnel is going to reconnect using new WireGuard
// configuration.
// This will broadcast the `NEVPNStatusDidChange` notification to the GUI process.
self.packetTunnelProvider?.reasserting = true
defer {
self.packetTunnelProvider?.reasserting = false
}
do {
let settingsGenerator = try self.makeSettingsGenerator(with: tunnelConfiguration)
try self.setNetworkSettings(settingsGenerator.generateNetworkSettings())
switch self.state {
case .started(let handle, _):
let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration()
self.logEndpointResolutionResults(resolutionResults)
wgSetConfig(handle, wgConfig)
#if os(iOS)
wgDisableSomeRoamingForBrokenMobileSemantics(handle)
#endif
self.state = .started(handle, settingsGenerator)
case .temporaryShutdown:
self.state = .temporaryShutdown(settingsGenerator)
case .stopped:
fatalError()
}
completionHandler(nil)
} catch let error as WireGuardAdapterError {
completionHandler(error)
} catch {
fatalError()
}
}
}
// MARK: - Private methods
/// Setup WireGuard log handler.
private func setupLogHandler() {
let context = Unmanaged.passUnretained(self).toOpaque()
wgSetLogger(context) { context, logLevel, message in
guard let context = context, let message = message else { return }
let unretainedSelf = Unmanaged<WireGuardAdapter>.fromOpaque(context)
.takeUnretainedValue()
let swiftString = String(cString: message).trimmingCharacters(in: .newlines)
let tunnelLogLevel = WireGuardLogLevel(rawValue: logLevel) ?? .verbose
unretainedSelf.logHandler(tunnelLogLevel, swiftString)
}
}
/// Set network tunnel configuration.
/// This method ensures that the call to `setTunnelNetworkSettings` does not time out, as in
/// certain scenarios the completion handler given to it may not be invoked by the system.
///
/// - Parameters:
/// - networkSettings: an instance of type `NEPacketTunnelNetworkSettings`.
/// - Throws: an error of type `WireGuardAdapterError`.
/// - Returns: `PacketTunnelSettingsGenerator`.
private func setNetworkSettings(_ networkSettings: NEPacketTunnelNetworkSettings) throws {
var systemError: Error?
let condition = NSCondition()
// Activate the condition
condition.lock()
defer { condition.unlock() }
self.packetTunnelProvider?.setTunnelNetworkSettings(networkSettings) { error in
systemError = error
condition.signal()
}
// Packet tunnel's `setTunnelNetworkSettings` times out in certain
// scenarios & never calls the given callback.
let setTunnelNetworkSettingsTimeout: TimeInterval = 5 // seconds
if condition.wait(until: Date().addingTimeInterval(setTunnelNetworkSettingsTimeout)) {
if let systemError = systemError {
throw WireGuardAdapterError.setNetworkSettings(systemError)
}
} else {
self.logHandler(.error, "setTunnelNetworkSettings timed out after 5 seconds; proceeding anyway")
}
}
/// Resolve peers of the given tunnel configuration.
/// - Parameter tunnelConfiguration: tunnel configuration.
/// - Throws: an error of type `WireGuardAdapterError`.
/// - Returns: The list of resolved endpoints.
private func resolvePeers(for tunnelConfiguration: TunnelConfiguration) throws -> [Endpoint?] {
let endpoints = tunnelConfiguration.peers.map { $0.endpoint }
let resolutionResults = DNSResolver.resolveSync(endpoints: endpoints)
let resolutionErrors = resolutionResults.compactMap { result -> DNSResolutionError? in
if case .failure(let error) = result {
return error
} else {
return nil
}
}
assert(endpoints.count == resolutionResults.count)
guard resolutionErrors.isEmpty else {
throw WireGuardAdapterError.dnsResolution(resolutionErrors)
}
let resolvedEndpoints = resolutionResults.map { result -> Endpoint? in
// swiftlint:disable:next force_try
return try! result?.get()
}
return resolvedEndpoints
}
/// Start WireGuard backend.
/// - Parameter wgConfig: WireGuard configuration
/// - Throws: an error of type `WireGuardAdapterError`
/// - Returns: tunnel handle
private func startWireGuardBackend(wgConfig: String) throws -> Int32 {
guard let tunnelFileDescriptor = self.tunnelFileDescriptor else {
throw WireGuardAdapterError.cannotLocateTunnelFileDescriptor
}
let handle = wgTurnOn(wgConfig, tunnelFileDescriptor)
if handle < 0 {
throw WireGuardAdapterError.startWireGuardBackend(handle)
}
#if os(iOS)
wgDisableSomeRoamingForBrokenMobileSemantics(handle)
#endif
return handle
}
/// Resolves the hostnames in the given tunnel configuration and return settings generator.
/// - Parameter tunnelConfiguration: an instance of type `TunnelConfiguration`.
/// - Throws: an error of type `WireGuardAdapterError`.
/// - Returns: an instance of type `PacketTunnelSettingsGenerator`.
private func makeSettingsGenerator(with tunnelConfiguration: TunnelConfiguration) throws -> PacketTunnelSettingsGenerator {
return PacketTunnelSettingsGenerator(
tunnelConfiguration: tunnelConfiguration,
resolvedEndpoints: try self.resolvePeers(for: tunnelConfiguration)
)
}
/// Log DNS resolution results.
/// - Parameter resolutionErrors: an array of type `[DNSResolutionError]`.
private func logEndpointResolutionResults(_ resolutionResults: [EndpointResolutionResult?]) {
for case .some(let result) in resolutionResults {
switch result {
case .success((let sourceEndpoint, let resolvedEndpoint)):
if sourceEndpoint.host == resolvedEndpoint.host {
self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to itself.")
} else {
self.logHandler(.verbose, "DNS64: mapped \(sourceEndpoint.host) to \(resolvedEndpoint.host)")
}
case .failure(let resolutionError):
self.logHandler(.error, "Failed to resolve endpoint \(resolutionError.address): \(resolutionError.errorDescription ?? "(nil)")")
}
}
}
/// Helper method used by network path monitor.
/// - Parameter path: new network path
private func didReceivePathUpdate(path: Network.NWPath) {
self.logHandler(.verbose, "Network change detected with \(path.status) route and interface order \(path.availableInterfaces)")
#if os(macOS)
if case .started(let handle, _) = self.state {
wgBumpSockets(handle)
}
#elseif os(iOS)
switch self.state {
case .started(let handle, let settingsGenerator):
if path.status.isSatisfiable {
let (wgConfig, resolutionResults) = settingsGenerator.endpointUapiConfiguration()
self.logEndpointResolutionResults(resolutionResults)
wgSetConfig(handle, wgConfig)
wgDisableSomeRoamingForBrokenMobileSemantics(handle)
wgBumpSockets(handle)
} else {
self.logHandler(.verbose, "Connectivity offline, pausing backend.")
self.state = .temporaryShutdown(settingsGenerator)
wgTurnOff(handle)
}
case .temporaryShutdown(let settingsGenerator):
guard path.status.isSatisfiable else { return }
self.logHandler(.verbose, "Connectivity online, resuming backend.")
do {
try self.setNetworkSettings(settingsGenerator.generateNetworkSettings())
let (wgConfig, resolutionResults) = settingsGenerator.uapiConfiguration()
self.logEndpointResolutionResults(resolutionResults)
self.state = .started(
try self.startWireGuardBackend(wgConfig: wgConfig),
settingsGenerator
)
} catch {
self.logHandler(.error, "Failed to restart backend: \(error.localizedDescription)")
}
case .stopped:
// no-op
break
}
#else
#error("Unsupported")
#endif
}
}
/// A enum describing WireGuard log levels defined in `api-apple.go`.
public enum WireGuardLogLevel: Int32 {
case verbose = 0
case error = 1
}
private extension Network.NWPath.Status {
/// Returns `true` if the path is potentially satisfiable.
var isSatisfiable: Bool {
switch self {
case .requiresConnection, .satisfied:
return true
case .unsatisfied:
return false
@unknown default:
return true
}
}
}

View File

@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
// Copyright © 2018-2023 WireGuard LLC. All Rights Reserved.
#include <sys/types.h>
#include "key.h"
#include "x25519.h"
/* From <sys/kern_control.h> */
#define CTLIOCGINFO 0xc0644e03UL
struct ctl_info {
u_int32_t ctl_id;
char ctl_name[96];
};
struct sockaddr_ctl {
u_char sc_len;
u_char sc_family;
u_int16_t ss_sysaddr;
u_int32_t sc_id;
u_int32_t sc_unit;
u_int32_t sc_reserved[5];
};

View File

@@ -0,0 +1,124 @@
// SPDX-License-Identifier: MIT
/*
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
* This is a specialized constant-time base64/hex implementation that resists side-channel attacks.
*/
#include <string.h>
#include "key.h"
static inline void encode_base64(char dest[static 4], const uint8_t src[static 3])
{
const uint8_t input[] = { (src[0] >> 2) & 63, ((src[0] << 4) | (src[1] >> 4)) & 63, ((src[1] << 2) | (src[2] >> 6)) & 63, src[2] & 63 };
for (unsigned int i = 0; i < 4; ++i)
dest[i] = input[i] + 'A'
+ (((25 - input[i]) >> 8) & 6)
- (((51 - input[i]) >> 8) & 75)
- (((61 - input[i]) >> 8) & 15)
+ (((62 - input[i]) >> 8) & 3);
}
void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN])
{
unsigned int i;
for (i = 0; i < WG_KEY_LEN / 3; ++i)
encode_base64(&base64[i * 4], &key[i * 3]);
encode_base64(&base64[i * 4], (const uint8_t[]){ key[i * 3 + 0], key[i * 3 + 1], 0 });
base64[WG_KEY_LEN_BASE64 - 2] = '=';
base64[WG_KEY_LEN_BASE64 - 1] = '\0';
}
static inline int decode_base64(const char src[static 4])
{
int val = 0;
for (unsigned int i = 0; i < 4; ++i)
val |= (-1
+ ((((('A' - 1) - src[i]) & (src[i] - ('Z' + 1))) >> 8) & (src[i] - 64))
+ ((((('a' - 1) - src[i]) & (src[i] - ('z' + 1))) >> 8) & (src[i] - 70))
+ ((((('0' - 1) - src[i]) & (src[i] - ('9' + 1))) >> 8) & (src[i] + 5))
+ ((((('+' - 1) - src[i]) & (src[i] - ('+' + 1))) >> 8) & 63)
+ ((((('/' - 1) - src[i]) & (src[i] - ('/' + 1))) >> 8) & 64)
) << (18 - 6 * i);
return val;
}
bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64)
{
unsigned int i;
volatile uint8_t ret = 0;
int val;
if (strlen(base64) != WG_KEY_LEN_BASE64 - 1 || base64[WG_KEY_LEN_BASE64 - 2] != '=')
return false;
for (i = 0; i < WG_KEY_LEN / 3; ++i) {
val = decode_base64(&base64[i * 4]);
ret |= (uint32_t)val >> 31;
key[i * 3 + 0] = (val >> 16) & 0xff;
key[i * 3 + 1] = (val >> 8) & 0xff;
key[i * 3 + 2] = val & 0xff;
}
val = decode_base64((const char[]){ base64[i * 4 + 0], base64[i * 4 + 1], base64[i * 4 + 2], 'A' });
ret |= ((uint32_t)val >> 31) | (val & 0xff);
key[i * 3 + 0] = (val >> 16) & 0xff;
key[i * 3 + 1] = (val >> 8) & 0xff;
return 1 & ((ret - 1) >> 8);
}
void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN])
{
unsigned int i;
for (i = 0; i < WG_KEY_LEN; ++i) {
hex[i * 2] = 87U + (key[i] >> 4) + ((((key[i] >> 4) - 10U) >> 8) & ~38U);
hex[i * 2 + 1] = 87U + (key[i] & 0xf) + ((((key[i] & 0xf) - 10U) >> 8) & ~38U);
}
hex[i * 2] = '\0';
}
bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex)
{
uint8_t c, c_acc, c_alpha0, c_alpha, c_num0, c_num, c_val;
volatile uint8_t ret = 0;
if (strlen(hex) != WG_KEY_LEN_HEX - 1)
return false;
for (unsigned int i = 0; i < WG_KEY_LEN_HEX - 1; i += 2) {
c = (uint8_t)hex[i];
c_num = c ^ 48U;
c_num0 = (c_num - 10U) >> 8;
c_alpha = (c & ~32U) - 55U;
c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8;
ret |= ((c_num0 | c_alpha0) - 1) >> 8;
c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
c_acc = c_val * 16U;
c = (uint8_t)hex[i + 1];
c_num = c ^ 48U;
c_num0 = (c_num - 10U) >> 8;
c_alpha = (c & ~32U) - 55U;
c_alpha0 = ((c_alpha - 10U) ^ (c_alpha - 16U)) >> 8;
ret |= ((c_num0 | c_alpha0) - 1) >> 8;
c_val = (c_num0 & c_num) | (c_alpha0 & c_alpha);
key[i / 2] = c_acc | c_val;
}
return 1 & ((ret - 1) >> 8);
}
bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN])
{
volatile uint8_t acc = 0;
for (unsigned int i = 0; i < WG_KEY_LEN; ++i) {
acc |= key1[i] ^ key2[i];
asm volatile("" : "=r"(acc) : "0"(acc));
}
return 1 & ((acc - 1) >> 8);
}

View File

@@ -0,0 +1,24 @@
/* SPDX-License-Identifier: MIT */
/*
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*/
#ifndef KEY_H
#define KEY_H
#include <stdbool.h>
#include <stdint.h>
#define WG_KEY_LEN (32)
#define WG_KEY_LEN_BASE64 (45)
#define WG_KEY_LEN_HEX (65)
void key_to_base64(char base64[static WG_KEY_LEN_BASE64], const uint8_t key[static WG_KEY_LEN]);
bool key_from_base64(uint8_t key[static WG_KEY_LEN], const char *base64);
void key_to_hex(char hex[static WG_KEY_LEN_HEX], const uint8_t key[static WG_KEY_LEN]);
bool key_from_hex(uint8_t key[static WG_KEY_LEN], const char *hex);
bool key_eq(const uint8_t key1[static WG_KEY_LEN], const uint8_t key2[static WG_KEY_LEN]);
#endif

View File

@@ -0,0 +1,4 @@
module WireGuardKitC {
umbrella header "WireGuardKitC.h"
export *
}

View File

@@ -0,0 +1,178 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2015-2019 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
*
* Curve25519 ECDH functions, based on TweetNaCl but cleaned up.
*/
#include <stdint.h>
#include <string.h>
#include <assert.h>
#include <CommonCrypto/CommonRandom.h>
#include "x25519.h"
typedef int64_t fe[16];
static inline void carry(fe o)
{
int i;
for (i = 0; i < 16; ++i) {
o[(i + 1) % 16] += (i == 15 ? 38 : 1) * (o[i] >> 16);
o[i] &= 0xffff;
}
}
static inline void cswap(fe p, fe q, int b)
{
int i;
int64_t t, c = ~(b - 1);
for (i = 0; i < 16; ++i) {
t = c & (p[i] ^ q[i]);
p[i] ^= t;
q[i] ^= t;
}
}
static inline void pack(uint8_t *o, const fe n)
{
int i, j, b;
fe m, t;
memcpy(t, n, sizeof(t));
carry(t);
carry(t);
carry(t);
for (j = 0; j < 2; ++j) {
m[0] = t[0] - 0xffed;
for (i = 1; i < 15; ++i) {
m[i] = t[i] - 0xffff - ((m[i - 1] >> 16) & 1);
m[i - 1] &= 0xffff;
}
m[15] = t[15] - 0x7fff - ((m[14] >> 16) & 1);
b = (m[15] >> 16) & 1;
m[14] &= 0xffff;
cswap(t, m, 1 - b);
}
for (i = 0; i < 16; ++i) {
o[2 * i] = t[i] & 0xff;
o[2 * i + 1] = t[i] >> 8;
}
}
static inline void unpack(fe o, const uint8_t *n)
{
int i;
for (i = 0; i < 16; ++i)
o[i] = n[2 * i] + ((int64_t)n[2 * i + 1] << 8);
o[15] &= 0x7fff;
}
static inline void add(fe o, const fe a, const fe b)
{
int i;
for (i = 0; i < 16; ++i)
o[i] = a[i] + b[i];
}
static inline void subtract(fe o, const fe a, const fe b)
{
int i;
for (i = 0; i < 16; ++i)
o[i] = a[i] - b[i];
}
static inline void multmod(fe o, const fe a, const fe b)
{
int i, j;
int64_t t[31] = { 0 };
for (i = 0; i < 16; ++i) {
for (j = 0; j < 16; ++j)
t[i + j] += a[i] * b[j];
}
for (i = 0; i < 15; ++i)
t[i] += 38 * t[i + 16];
memcpy(o, t, sizeof(fe));
carry(o);
carry(o);
}
static inline void invert(fe o, const fe i)
{
fe c;
int a;
memcpy(c, i, sizeof(c));
for (a = 253; a >= 0; --a) {
multmod(c, c, c);
if (a != 2 && a != 4)
multmod(c, c, i);
}
memcpy(o, c, sizeof(fe));
}
static void curve25519_shared_secret(uint8_t shared_secret[32], const uint8_t private_key[32], const uint8_t public_key[32])
{
static const fe a24 = { 0xdb41, 1 };
uint8_t z[32];
int64_t r;
int i;
fe a = { 1 }, b, c = { 0 }, d = { 1 }, e, f, x;
memcpy(z, private_key, sizeof(z));
z[31] = (z[31] & 127) | 64;
z[0] &= 248;
unpack(x, public_key);
memcpy(b, x, sizeof(b));
for (i = 254; i >= 0; --i) {
r = (z[i >> 3] >> (i & 7)) & 1;
cswap(a, b, (int)r);
cswap(c, d, (int)r);
add(e, a, c);
subtract(a, a, c);
add(c, b, d);
subtract(b, b, d);
multmod(d, e, e);
multmod(f, a, a);
multmod(a, c, a);
multmod(c, b, e);
add(e, a, c);
subtract(a, a, c);
multmod(b, a, a);
subtract(c, d, f);
multmod(a, c, a24);
add(a, a, d);
multmod(c, c, a);
multmod(a, d, f);
multmod(d, b, x);
multmod(b, e, e);
cswap(a, b, (int)r);
cswap(c, d, (int)r);
}
invert(c, c);
multmod(a, a, c);
pack(shared_secret, a);
}
void curve25519_derive_public_key(uint8_t public_key[32], const uint8_t private_key[32])
{
static const uint8_t basepoint[32] = { 9 };
curve25519_shared_secret(public_key, private_key, basepoint);
}
void curve25519_generate_private_key(uint8_t private_key[32])
{
assert(CCRandomGenerateBytes(private_key, 32) == kCCSuccess);
private_key[31] = (private_key[31] & 127) | 64;
private_key[0] &= 248;
}

View File

@@ -0,0 +1,7 @@
#ifndef X25519_H
#define X25519_H
void curve25519_derive_public_key(unsigned char public_key[32], const unsigned char private_key[32]);
void curve25519_generate_private_key(unsigned char private_key[32]);
#endif

View File

@@ -0,0 +1 @@
// Empty

View File

@@ -0,0 +1,5 @@
module WireGuardKitGo {
umbrella header "wireguard.h"
link "wg-go"
export *
}

View File

@@ -0,0 +1,23 @@
/* SPDX-License-Identifier: MIT
*
* Copyright (C) 2018-2023 WireGuard LLC. All Rights Reserved.
*/
#ifndef WIREGUARD_H
#define WIREGUARD_H
#include <sys/types.h>
#include <stdint.h>
#include <stdbool.h>
typedef void(*logger_fn_t)(void *context, int level, const char *msg);
extern void wgSetLogger(void *context, logger_fn_t logger_fn);
extern int wgTurnOn(const char *settings, int32_t tun_fd);
extern void wgTurnOff(int handle);
extern int64_t wgSetConfig(int handle, const char *settings);
extern char *wgGetConfig(int handle);
extern void wgBumpSockets(int handle);
extern void wgDisableSomeRoamingForBrokenMobileSemantics(int handle);
extern const char *wgVersion();
#endif