Add xormask option

This commit is contained in:
sam 2019-09-03 16:11:53 -04:00 committed by Davide De Rosa
parent 642a6763d1
commit e69079d1f6
11 changed files with 118 additions and 39 deletions

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 5/23/19. // 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 // https://github.com/passepartoutvpn
// //
@ -30,6 +30,8 @@ public protocol LinkProducer {
/** /**
Returns a `LinkInterface`. Returns a `LinkInterface`.
- Parameter xorMask: The XOR mask.
**/ **/
func link() -> LinkInterface func link(xorMask: UInt8?) -> LinkInterface
} }

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 8/27/17. // 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 // https://github.com/passepartoutvpn
// //
@ -47,4 +47,7 @@ public protocol LinkInterface: IOInterface {
/// The number of packets that this interface is able to bufferize. /// The number of packets that this interface is able to bufferize.
var packetBufferSize: Int { get } var packetBufferSize: Int { get }
/// A byte to xor all packet payloads with.
var xorMask: UInt8 { get }
} }

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 5/23/19. // 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 // https://github.com/passepartoutvpn
// //
@ -32,9 +32,12 @@ class NETCPLink: LinkInterface {
private let maxPacketSize: Int 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.impl = impl
self.maxPacketSize = maxPacketSize ?? (512 * 1024) self.maxPacketSize = maxPacketSize ?? (512 * 1024)
self.xorMask = xorMask ?? 0
} }
// MARK: LinkInterface // MARK: LinkInterface
@ -57,7 +60,7 @@ class NETCPLink: LinkInterface {
// WARNING: runs in Network.framework queue // WARNING: runs in Network.framework queue
impl.readMinimumLength(2, maximumLength: packetBufferSize) { [weak self] (data, error) in impl.readMinimumLength(2, maximumLength: packetBufferSize) { [weak self] (data, error) in
guard let _ = self else { guard let self = self else {
return return
} }
queue.sync { queue.sync {
@ -69,9 +72,9 @@ class NETCPLink: LinkInterface {
var newBuffer = buffer var newBuffer = buffer
newBuffer.append(contentsOf: data) newBuffer.append(contentsOf: data)
var until = 0 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..<newBuffer.count) newBuffer = newBuffer.subdata(in: until..<newBuffer.count)
self?.loopReadPackets(queue, newBuffer, handler) self.loopReadPackets(queue, newBuffer, handler)
handler(packets, nil) handler(packets, nil)
} }
@ -79,14 +82,14 @@ class NETCPLink: LinkInterface {
} }
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) { func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
let stream = PacketStream.stream(fromPacket: packet) let stream = PacketStream.stream(fromPacket: packet, xorMask: xorMask)
impl.write(stream) { (error) in impl.write(stream) { (error) in
completionHandler?(error) completionHandler?(error)
} }
} }
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { 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 impl.write(stream) { (error) in
completionHandler?(error) completionHandler?(error)
} }
@ -95,7 +98,7 @@ class NETCPLink: LinkInterface {
/// :nodoc: /// :nodoc:
extension NETCPSocket: LinkProducer { extension NETCPSocket: LinkProducer {
public func link() -> LinkInterface { public func link(xorMask: UInt8?) -> LinkInterface {
return NETCPLink(impl: impl) return NETCPLink(impl: impl, maxPacketSize: nil, xorMask: xorMask)
} }
} }

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 5/23/19. // 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 // https://github.com/passepartoutvpn
// //
@ -31,9 +31,12 @@ class NEUDPLink: LinkInterface {
private let maxDatagrams: Int 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.impl = impl
self.maxDatagrams = maxDatagrams ?? 200 self.maxDatagrams = maxDatagrams ?? 200
self.xorMask = xorMask ?? 0
} }
// MARK: LinkInterface // MARK: LinkInterface
@ -51,24 +54,46 @@ class NEUDPLink: LinkInterface {
func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) { func setReadHandler(queue: DispatchQueue, _ handler: @escaping ([Data]?, Error?) -> Void) {
// WARNING: runs in Network.framework queue // WARNING: runs in Network.framework queue
impl.setReadHandler({ [weak self] (packets, error) in impl.setReadHandler({ [weak self] packets, error in
guard let _ = self else { guard let self = self else {
return return
} }
queue.sync { var packetsToUse: [Data]?
handler(packets, error) 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)?) { 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) completionHandler?(error)
} }
} }
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) { 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) completionHandler?(error)
} }
} }
@ -76,7 +101,7 @@ class NEUDPLink: LinkInterface {
/// :nodoc: /// :nodoc:
extension NEUDPSocket: LinkProducer { extension NEUDPSocket: LinkProducer {
public func link() -> LinkInterface { public func link(xorMask: UInt8?) -> LinkInterface {
return NEUDPLink(impl: impl) return NEUDPLink(impl: impl, maxDatagrams: nil, xorMask: xorMask)
} }
} }

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 10/23/17. // 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 // https://github.com/passepartoutvpn
// //

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 2/1/17. // 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 // https://github.com/passepartoutvpn
// //
@ -454,10 +454,10 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
return return
} }
if session.canRebindLink() { if session.canRebindLink() {
session.rebindLink(producer.link()) session.rebindLink(producer.link(xorMask: cfg.sessionConfiguration.xorMask))
reasserting = false reasserting = false
} else { } else {
session.setLink(producer.link()) session.setLink(producer.link(xorMask: cfg.sessionConfiguration.xorMask))
} }
} }

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 8/23/18. // 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 // 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. /// The number of seconds after which a renegotiation should be initiated. If `nil`, the client will never initiate a renegotiation.
public var renegotiatesAfter: TimeInterval? public var renegotiatesAfter: TimeInterval?
/// A byte to xor all packet payloads with.
public var xorMask: UInt8?
// MARK: Client // MARK: Client
/// The server hostname (picked from first remote). /// The server hostname (picked from first remote).
@ -324,6 +327,7 @@ extension OpenVPN {
keepAliveInterval: keepAliveInterval, keepAliveInterval: keepAliveInterval,
keepAliveTimeout: keepAliveTimeout, keepAliveTimeout: keepAliveTimeout,
renegotiatesAfter: renegotiatesAfter, renegotiatesAfter: renegotiatesAfter,
xorMask: xorMask,
hostname: hostname, hostname: hostname,
endpointProtocols: endpointProtocols, endpointProtocols: endpointProtocols,
checksEKU: checksEKU, checksEKU: checksEKU,
@ -414,6 +418,9 @@ extension OpenVPN {
/// - Seealso: `ConfigurationBuilder.renegotiatesAfter` /// - Seealso: `ConfigurationBuilder.renegotiatesAfter`
public let renegotiatesAfter: TimeInterval? public let renegotiatesAfter: TimeInterval?
/// - Seealso: `ConfigurationBuilder.xorMask`
public let xorMask: UInt8?
/// - Seealso: `ConfigurationBuilder.hostname` /// - Seealso: `ConfigurationBuilder.hostname`
public let hostname: String? public let hostname: String?
@ -545,6 +552,7 @@ extension OpenVPN.Configuration {
builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL
builder.proxyBypassDomains = proxyBypassDomains builder.proxyBypassDomains = proxyBypassDomains
builder.routingPolicies = routingPolicies builder.routingPolicies = routingPolicies
builder.xorMask = xorMask
return builder return builder
} }
} }

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 9/5/18. // 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 // https://github.com/passepartoutvpn
// //
@ -60,6 +60,8 @@ extension OpenVPN {
static let renegSec = NSRegularExpression("^reneg-sec +\\d+") static let renegSec = NSRegularExpression("^reneg-sec +\\d+")
static let xorMask = NSRegularExpression("^scramble +xormask +.$")
static let blockBegin = NSRegularExpression("^<[\\w\\-]+>") static let blockBegin = NSRegularExpression("^<[\\w\\-]+>")
static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>") static let blockEnd = NSRegularExpression("^<\\/[\\w\\-]+>")
@ -218,6 +220,7 @@ extension OpenVPN {
var optKeepAliveSeconds: TimeInterval? var optKeepAliveSeconds: TimeInterval?
var optKeepAliveTimeoutSeconds: TimeInterval? var optKeepAliveTimeoutSeconds: TimeInterval?
var optRenegotiateAfterSeconds: TimeInterval? var optRenegotiateAfterSeconds: TimeInterval?
var optXorMask: UInt8?
// //
var optDefaultProto: SocketType? var optDefaultProto: SocketType?
var optDefaultPort: UInt16? var optDefaultPort: UInt16?
@ -459,6 +462,13 @@ extension OpenVPN {
} }
optRenegotiateAfterSeconds = TimeInterval(arg) optRenegotiateAfterSeconds = TimeInterval(arg)
} }
Regex.xorMask.enumerateArguments(in: line) {
isHandled = true
if $0.count != 2 {
return
}
optXorMask = Character($0[1]).asciiValue
}
// MARK: Client // MARK: Client
@ -726,6 +736,7 @@ extension OpenVPN {
sessionBuilder.checksEKU = optChecksEKU sessionBuilder.checksEKU = optChecksEKU
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
sessionBuilder.mtu = optMTU sessionBuilder.mtu = optMTU
sessionBuilder.xorMask = optXorMask
// MARK: Server // MARK: Server

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 4/25/19. // 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 // https://github.com/passepartoutvpn
// //
@ -29,9 +29,9 @@ NS_ASSUME_NONNULL_BEGIN
@interface PacketStream : NSObject @interface PacketStream : NSObject
+ (NSArray<NSData *> *)packetsFromStream:(NSData *)stream until:(nullable NSInteger *)until; + (NSArray<NSData *> *)packetsFromStream:(NSData *)stream until:(NSInteger *)until xorMask:(uint8_t)xorMask;
+ (NSData *)streamFromPacket:(NSData *)packet; + (NSData *)streamFromPacket:(NSData *)packet xorMask:(uint8_t)xorMask;
+ (NSData *)streamFromPackets:(NSArray<NSData *> *)packets; + (NSData *)streamFromPackets:(NSArray<NSData *> *)packets xorMask:(uint8_t)xorMask;
@end @end

View File

@ -3,7 +3,7 @@
// TunnelKit // TunnelKit
// //
// Created by Davide De Rosa on 4/25/19. // 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 // https://github.com/passepartoutvpn
// //
@ -29,7 +29,18 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
@implementation PacketStream @implementation PacketStream
+ (NSArray<NSData *> *)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<NSData *> *)packetsFromStream:(NSData *)stream until:(NSInteger *)until xorMask:(uint8_t)xorMask
{ {
NSInteger ni = 0; NSInteger ni = 0;
NSMutableArray<NSData *> *parsed = [[NSMutableArray alloc] init]; NSMutableArray<NSData *> *parsed = [[NSMutableArray alloc] init];
@ -42,6 +53,12 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
break; break;
} }
NSData *packet = [stream subdataWithRange:NSMakeRange(start, packlen)]; 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]; [parsed addObject:packet];
ni = end; ni = end;
} }
@ -51,19 +68,19 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
return parsed; return parsed;
} }
+ (NSData *)streamFromPacket:(NSData *)packet + (NSData *)streamFromPacket:(NSData *)packet xorMask:(uint8_t)xorMask
{ {
NSMutableData *raw = [[NSMutableData alloc] initWithLength:(PacketStreamHeaderLength + packet.length)]; NSMutableData *raw = [[NSMutableData alloc] initWithLength:(PacketStreamHeaderLength + packet.length)];
uint8_t *ptr = raw.mutableBytes; uint8_t *ptr = raw.mutableBytes;
*(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length); *(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length);
ptr += PacketStreamHeaderLength; ptr += PacketStreamHeaderLength;
memcpy(ptr, packet.bytes, packet.length); [PacketStream memcpyXor:ptr src:packet xorMask:xorMask];
return raw; return raw;
} }
+ (NSData *)streamFromPackets:(NSArray<NSData *> *)packets; + (NSData *)streamFromPackets:(NSArray<NSData *> *)packets xorMask:(uint8_t)xorMask
{ {
NSInteger streamLength = 0; NSInteger streamLength = 0;
for (NSData *p in packets) { for (NSData *p in packets) {
@ -75,7 +92,7 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
for (NSData *packet in packets) { for (NSData *packet in packets) {
*(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length); *(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length);
ptr += PacketStreamHeaderLength; ptr += PacketStreamHeaderLength;
memcpy(ptr, packet.bytes, packet.length); [PacketStream memcpyXor:ptr src:packet xorMask:xorMask];
ptr += packet.length; ptr += packet.length;
} }
return raw; return raw;

View File

@ -114,6 +114,16 @@ class ConfigurationParserTests: XCTestCase {
try privateTestEncryptedCertificateKey(pkcs: "8") 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 { private func privateTestEncryptedCertificateKey(pkcs: String) throws {
let cfgURL = url(withName: "tunnelbear.enc.\(pkcs)") let cfgURL = url(withName: "tunnelbear.enc.\(pkcs)")
XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromURL: cfgURL)) XCTAssertThrowsError(try OpenVPN.ConfigurationParser.parsed(fromURL: cfgURL))