diff --git a/TunnelKit/Sources/AppExtension/LinkProducer.swift b/TunnelKit/Sources/AppExtension/LinkProducer.swift index 0c76bc9..71cd11c 100644 --- a/TunnelKit/Sources/AppExtension/LinkProducer.swift +++ b/TunnelKit/Sources/AppExtension/LinkProducer.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 5/23/19. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -30,6 +30,8 @@ public protocol LinkProducer { /** Returns a `LinkInterface`. + + - Parameter xorMask: The XOR mask. **/ - func link() -> LinkInterface + func link(xorMask: UInt8?) -> LinkInterface } diff --git a/TunnelKit/Sources/Core/LinkInterface.swift b/TunnelKit/Sources/Core/LinkInterface.swift index 077047a..7a95708 100644 --- a/TunnelKit/Sources/Core/LinkInterface.swift +++ b/TunnelKit/Sources/Core/LinkInterface.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 8/27/17. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -47,4 +47,7 @@ public protocol LinkInterface: IOInterface { /// The number of packets that this interface is able to bufferize. var packetBufferSize: Int { get } + + /// A byte to xor all packet payloads with. + var xorMask: UInt8 { get } } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NETCPLink.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NETCPLink.swift index b15e2f9..b633cf3 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NETCPLink.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NETCPLink.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 5/23/19. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -32,9 +32,12 @@ class NETCPLink: LinkInterface { private let maxPacketSize: Int - init(impl: NWTCPConnection, maxPacketSize: Int? = nil) { + let xorMask: UInt8 + + init(impl: NWTCPConnection, maxPacketSize: Int? = nil, xorMask: UInt8?) { self.impl = impl self.maxPacketSize = maxPacketSize ?? (512 * 1024) + self.xorMask = xorMask ?? 0 } // MARK: LinkInterface @@ -57,7 +60,7 @@ class NETCPLink: LinkInterface { // WARNING: runs in Network.framework queue impl.readMinimumLength(2, maximumLength: packetBufferSize) { [weak self] (data, error) in - guard let _ = self else { + guard let self = self else { return } queue.sync { @@ -69,9 +72,9 @@ class NETCPLink: LinkInterface { var newBuffer = buffer newBuffer.append(contentsOf: data) var until = 0 - let packets = PacketStream.packets(fromStream: newBuffer, until: &until) + let packets = PacketStream.packets(fromStream: newBuffer, until: &until, xorMask: self.xorMask) newBuffer = newBuffer.subdata(in: until.. Void)?) { - let stream = PacketStream.stream(fromPacket: packet) + let stream = PacketStream.stream(fromPacket: packet, xorMask: xorMask) impl.write(stream) { (error) in completionHandler?(error) } } func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { - let stream = PacketStream.stream(fromPackets: packets) + let stream = PacketStream.stream(fromPackets: packets, xorMask: xorMask) impl.write(stream) { (error) in completionHandler?(error) } @@ -95,7 +98,7 @@ class NETCPLink: LinkInterface { /// :nodoc: extension NETCPSocket: LinkProducer { - public func link() -> LinkInterface { - return NETCPLink(impl: impl) + public func link(xorMask: UInt8?) -> LinkInterface { + return NETCPLink(impl: impl, maxPacketSize: nil, xorMask: xorMask) } } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NEUDPLink.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NEUDPLink.swift index 73d7b3c..451471f 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NEUDPLink.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NEUDPLink.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 5/23/19. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -31,9 +31,12 @@ class NEUDPLink: LinkInterface { private let maxDatagrams: Int - init(impl: NWUDPSession, maxDatagrams: Int? = nil) { + let xorMask: UInt8 + + init(impl: NWUDPSession, maxDatagrams: Int? = nil, xorMask: UInt8?) { self.impl = impl self.maxDatagrams = maxDatagrams ?? 200 + self.xorMask = xorMask ?? 0 } // MARK: LinkInterface @@ -51,24 +54,46 @@ class NEUDPLink: LinkInterface { func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { // WARNING: runs in Network.framework queue - impl.setReadHandler({ [weak self] (packets, error) in - guard let _ = self else { + impl.setReadHandler({ [weak self] packets, error in + guard let self = self else { return } - queue.sync { - handler(packets, error) + var packetsToUse: [Data]? + if let packets = packets, self.xorMask != 0 { + packetsToUse = packets.map { packet in + Data(bytes: packet.map { $0 ^ self.xorMask }, count: packet.count) + } + } else { + packetsToUse = packets } - }, maxDatagrams: maxDatagrams) + queue.sync { + handler(packetsToUse, error) + } + }, maxDatagrams: maxDatagrams) } func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { - impl.writeDatagram(packet) { (error) in + var dataToUse: Data + if xorMask != 0 { + dataToUse = Data(bytes: packet.map { $0 ^ xorMask }, count: packet.count) + } else { + dataToUse = packet + } + impl.writeDatagram(dataToUse) { error in completionHandler?(error) } } func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { - impl.writeMultipleDatagrams(packets) { (error) in + var packetsToUse: [Data] + if xorMask != 0 { + packetsToUse = packets.map { packet in + Data(bytes: packet.map { $0 ^ xorMask }, count: packet.count) + } + } else { + packetsToUse = packets + } + impl.writeMultipleDatagrams(packetsToUse) { error in completionHandler?(error) } } @@ -76,7 +101,7 @@ class NEUDPLink: LinkInterface { /// :nodoc: extension NEUDPSocket: LinkProducer { - public func link() -> LinkInterface { - return NEUDPLink(impl: impl) + public func link(xorMask: UInt8?) -> LinkInterface { + return NEUDPLink(impl: impl, maxDatagrams: nil, xorMask: xorMask) } } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift index 3558718..b3b59e3 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider+Configuration.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 10/23/17. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // diff --git a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift index 9aef1b1..2c34bd5 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/AppExtension/OpenVPNTunnelProvider.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 2/1/17. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -454,10 +454,10 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate { return } if session.canRebindLink() { - session.rebindLink(producer.link()) + session.rebindLink(producer.link(xorMask: cfg.sessionConfiguration.xorMask)) reasserting = false } else { - session.setLink(producer.link()) + session.setLink(producer.link(xorMask: cfg.sessionConfiguration.xorMask)) } } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift b/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift index f3a2873..9aa2909 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 8/23/18. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -216,6 +216,9 @@ extension OpenVPN { /// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation. public var renegotiatesAfter: TimeInterval? + /// A byte to xor all packet payloads with. + public var xorMask: UInt8? + // MARK: Client /// The server hostname (picked from first remote). @@ -324,6 +327,7 @@ extension OpenVPN { keepAliveInterval: keepAliveInterval, keepAliveTimeout: keepAliveTimeout, renegotiatesAfter: renegotiatesAfter, + xorMask: xorMask, hostname: hostname, endpointProtocols: endpointProtocols, checksEKU: checksEKU, @@ -414,6 +418,9 @@ extension OpenVPN { /// - Seealso: `ConfigurationBuilder.renegotiatesAfter` public let renegotiatesAfter: TimeInterval? + /// - Seealso: `ConfigurationBuilder.xorMask` + public let xorMask: UInt8? + /// - Seealso: `ConfigurationBuilder.hostname` public let hostname: String? @@ -545,6 +552,7 @@ extension OpenVPN.Configuration { builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL builder.proxyBypassDomains = proxyBypassDomains builder.routingPolicies = routingPolicies + builder.xorMask = xorMask return builder } } diff --git a/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift b/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift index fbf4293..de17780 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift +++ b/TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 9/5/18. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -60,6 +60,8 @@ extension OpenVPN { static let renegSec = NSRegularExpression("^reneg-sec +\\d+") + static let xorMask = NSRegularExpression("^scramble +xormask +.$") + static let blockBegin = NSRegularExpression("^<[\\w\\-]+>") static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>") @@ -218,6 +220,7 @@ extension OpenVPN { var optKeepAliveSeconds: TimeInterval? var optKeepAliveTimeoutSeconds: TimeInterval? var optRenegotiateAfterSeconds: TimeInterval? + var optXorMask: UInt8? // var optDefaultProto: SocketType? var optDefaultPort: UInt16? @@ -459,6 +462,13 @@ extension OpenVPN { } optRenegotiateAfterSeconds = TimeInterval(arg) } + Regex.xorMask.enumerateArguments(in: line) { + isHandled = true + if $0.count != 2 { + return + } + optXorMask = Character($0[1]).asciiValue + } // MARK: Client @@ -726,6 +736,7 @@ extension OpenVPN { sessionBuilder.checksEKU = optChecksEKU sessionBuilder.randomizeEndpoint = optRandomizeEndpoint sessionBuilder.mtu = optMTU + sessionBuilder.xorMask = optXorMask // MARK: Server diff --git a/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.h b/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.h index 7ef3c98..8c5568b 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.h +++ b/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.h @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 4/25/19. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -29,9 +29,9 @@ NS_ASSUME_NONNULL_BEGIN @interface PacketStream : NSObject -+ (NSArray *)packetsFromStream:(NSData *)stream until:(nullable NSInteger *)until; -+ (NSData *)streamFromPacket:(NSData *)packet; -+ (NSData *)streamFromPackets:(NSArray *)packets; ++ (NSArray *)packetsFromStream:(NSData *)stream until:(NSInteger *)until xorMask:(uint8_t)xorMask; ++ (NSData *)streamFromPacket:(NSData *)packet xorMask:(uint8_t)xorMask; ++ (NSData *)streamFromPackets:(NSArray *)packets xorMask:(uint8_t)xorMask; @end diff --git a/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.m b/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.m index 07e1865..3ac34af 100644 --- a/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.m +++ b/TunnelKit/Sources/Protocols/OpenVPN/PacketStream.m @@ -3,7 +3,7 @@ // TunnelKit // // Created by Davide De Rosa on 4/25/19. -// Copyright (c) 2021 Davide De Rosa. All rights reserved. +// Copyright (c) 2021 Davide De Rosa, Sam Foxman. All rights reserved. // // https://github.com/passepartoutvpn // @@ -29,7 +29,18 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t); @implementation PacketStream -+ (NSArray *)packetsFromStream:(NSData *)stream until:(NSInteger *)until ++ (void)memcpyXor:(uint8_t *)dst src:(NSData *)src xorMask:(uint8_t)xorMask +{ + if (xorMask != 0) { + for (int i = 0; i < src.length; ++i) { + dst[i] = ((uint8_t *)(src.bytes))[i] ^ xorMask; + } + return; + } + memcpy(dst, src.bytes, src.length); +} + ++ (NSArray *)packetsFromStream:(NSData *)stream until:(NSInteger *)until xorMask:(uint8_t)xorMask { NSInteger ni = 0; NSMutableArray *parsed = [[NSMutableArray alloc] init]; @@ -42,6 +53,12 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t); break; } NSData *packet = [stream subdataWithRange:NSMakeRange(start, packlen)]; + uint8_t* packetBytes = (uint8_t*) packet.bytes; + if (xorMask != 0) { + for (int i = 0; i < packet.length; i++) { + packetBytes[i] ^= xorMask; + } + } [parsed addObject:packet]; ni = end; } @@ -51,19 +68,19 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t); return parsed; } -+ (NSData *)streamFromPacket:(NSData *)packet ++ (NSData *)streamFromPacket:(NSData *)packet xorMask:(uint8_t)xorMask { NSMutableData *raw = [[NSMutableData alloc] initWithLength:(PacketStreamHeaderLength + packet.length)]; uint8_t *ptr = raw.mutableBytes; *(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length); ptr += PacketStreamHeaderLength; - memcpy(ptr, packet.bytes, packet.length); + [PacketStream memcpyXor:ptr src:packet xorMask:xorMask]; return raw; } -+ (NSData *)streamFromPackets:(NSArray *)packets; ++ (NSData *)streamFromPackets:(NSArray *)packets xorMask:(uint8_t)xorMask { NSInteger streamLength = 0; for (NSData *p in packets) { @@ -75,7 +92,7 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t); for (NSData *packet in packets) { *(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length); ptr += PacketStreamHeaderLength; - memcpy(ptr, packet.bytes, packet.length); + [PacketStream memcpyXor:ptr src:packet xorMask:xorMask]; ptr += packet.length; } return raw; diff --git a/TunnelKit/Tests/OpenVPN/ConfigurationParserTests.swift b/TunnelKit/Tests/OpenVPN/ConfigurationParserTests.swift index 320982c..920e36b 100644 --- a/TunnelKit/Tests/OpenVPN/ConfigurationParserTests.swift +++ b/TunnelKit/Tests/OpenVPN/ConfigurationParserTests.swift @@ -114,6 +114,16 @@ class ConfigurationParserTests: XCTestCase { try privateTestEncryptedCertificateKey(pkcs: "8") } + func testXOR() throws { + let cfg = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask F"]) + XCTAssertNil(cfg.warning) + XCTAssertEqual(cfg.configuration.xorMask, Character("F").asciiValue) + + let cfg2 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask FFFF"]) + XCTAssertNil(cfg.warning) + XCTAssertNil(cfg2.configuration.xorMask) + } + private func privateTestEncryptedCertificateKey(pkcs: String) throws { let cfgURL = url(withName: "tunnelbear.enc.\(pkcs)") XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromURL: cfgURL))