Skip to content
This repository has been archived by the owner on Sep 29, 2024. It is now read-only.

Add scramble xormask #170

Merged
merged 3 commits into from
Jul 23, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## Unreleased

### Added

- Support for XOR patch (Sam Foxman). [#170](https://github.com/passepartoutvpn/tunnelkit/pull/170)

## 3.3.3 (2021-07-19)

### Added
Expand Down
21 changes: 16 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,9 +39,16 @@ The library therefore supports compression framing, just not newer compression.

### Support for .ovpn configuration

TunnelKit can parse .ovpn configuration files. Below are a few limitations worth mentioning.
TunnelKit can parse .ovpn configuration files. Below are a few details worth mentioning.

Unsupported:
#### Non-standard

- Single-byte XOR masking
- Via `--scramble xormask <character>`
- XOR all incoming and outgoing bytes by the ASCII value of the character argument
- See [Tunnelblick website][about-tunnelblick-xor] for more details

#### Unsupported

- UDP fragmentation, i.e. `--fragment`
- Compression via `--compress` other than empty or `lzo`
Expand All @@ -51,7 +58,7 @@ Unsupported:
- `<connection>` blocks
- `vpn_gateway` and `net_gateway` literals in routes

Ignored:
#### Ignored

- Some MTU overrides
- `--link-mtu` and variants
Expand Down Expand Up @@ -201,8 +208,9 @@ For more details please see [CONTRIBUTING][contrib-readme].

- [lzo][dep-lzo-website] - Copyright (c) 1996-2017 Markus F.X.J. Oberhumer
- [PIATunnel][dep-piatunnel-repo] - Copyright (c) 2018-Present Private Internet Access
- [SURFnet][surfnet]
- [SURFnet][ppl-surfnet]
- [SwiftyBeaver][dep-swiftybeaver-repo] - Copyright (c) 2015 Sebastian Kreutzberger
- [XMB5][ppl-xmb5] for the [XOR patch][ppl-xmb5-xor] - Copyright (c) 2020 Sam Foxman

This product includes software developed by the OpenSSL Project for use in the OpenSSL Toolkit. ([https://www.openssl.org/][dep-openssl])

Expand Down Expand Up @@ -236,7 +244,10 @@ Website: [passepartoutvpn.app][about-website]
[dep-piatunnel-repo]: https://github.com/pia-foss/tunnel-apple
[dep-swiftybeaver-repo]: https://github.com/SwiftyBeaver/SwiftyBeaver
[dep-lzo-website]: http://www.oberhumer.com/opensource/lzo/
[surfnet]: https://www.surf.nl/en/about-surf/subsidiaries/surfnet
[ppl-surfnet]: https://www.surf.nl/en/about-surf/subsidiaries/surfnet
[ppl-xmb5]: https://github.com/XMB5
[ppl-xmb5-xor]: https://github.com/passepartoutvpn/tunnelkit/pull/170
[about-tunnelblick-xor]: https://tunnelblick.net/cOpenvpn_xorpatch.html

[about-twitter]: https://twitter.com/keeshux
[about-website]: https://passepartoutvpn.app
4 changes: 3 additions & 1 deletion TunnelKit/Sources/AppExtension/LinkProducer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public protocol LinkProducer {

/**
Returns a `LinkInterface`.

- Parameter xorMask: The XOR mask.
**/
func link() -> LinkInterface
func link(xorMask: UInt8?) -> LinkInterface
}
3 changes: 3 additions & 0 deletions TunnelKit/Sources/Core/LinkInterface.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
}
19 changes: 11 additions & 8 deletions TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NETCPLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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 {
Expand All @@ -69,24 +72,24 @@ 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..<newBuffer.count)
self?.loopReadPackets(queue, newBuffer, handler)
self.loopReadPackets(queue, newBuffer, handler)

handler(packets, nil)
}
}
}

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
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)
}
Expand All @@ -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)
}
}
43 changes: 34 additions & 9 deletions TunnelKit/Sources/Protocols/OpenVPN/AppExtension/NEUDPLink.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -51,32 +54,54 @@ 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
}
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
}
queue.sync {
handler(packets, error)
handler(packetsToUse, error)
}
}, maxDatagrams: maxDatagrams)
}, 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)
}
}
}

/// :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)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
}

Expand Down
8 changes: 8 additions & 0 deletions TunnelKit/Sources/Protocols/OpenVPN/Configuration.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand Down Expand Up @@ -324,6 +327,7 @@ extension OpenVPN {
keepAliveInterval: keepAliveInterval,
keepAliveTimeout: keepAliveTimeout,
renegotiatesAfter: renegotiatesAfter,
xorMask: xorMask,
hostname: hostname,
endpointProtocols: endpointProtocols,
checksEKU: checksEKU,
Expand Down Expand Up @@ -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?

Expand Down Expand Up @@ -545,6 +552,7 @@ extension OpenVPN.Configuration {
builder.proxyAutoConfigurationURL = proxyAutoConfigurationURL
builder.proxyBypassDomains = proxyBypassDomains
builder.routingPolicies = routingPolicies
builder.xorMask = xorMask
return builder
}
}
Expand Down
11 changes: 11 additions & 0 deletions TunnelKit/Sources/Protocols/OpenVPN/ConfigurationParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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\\-]+>")
Expand Down Expand Up @@ -218,6 +220,7 @@ extension OpenVPN {
var optKeepAliveSeconds: TimeInterval?
var optKeepAliveTimeoutSeconds: TimeInterval?
var optRenegotiateAfterSeconds: TimeInterval?
var optXorMask: UInt8?
//
var optDefaultProto: SocketType?
var optDefaultPort: UInt16?
Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -726,6 +736,7 @@ extension OpenVPN {
sessionBuilder.checksEKU = optChecksEKU
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
sessionBuilder.mtu = optMTU
sessionBuilder.xorMask = optXorMask

// MARK: Server

Expand Down
6 changes: 3 additions & 3 deletions TunnelKit/Sources/Protocols/OpenVPN/PacketStream.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,9 +29,9 @@ NS_ASSUME_NONNULL_BEGIN

@interface PacketStream : NSObject

+ (NSArray<NSData *> *)packetsFromStream:(NSData *)stream until:(nullable NSInteger *)until;
+ (NSData *)streamFromPacket:(NSData *)packet;
+ (NSData *)streamFromPackets:(NSArray<NSData *> *)packets;
+ (NSArray<NSData *> *)packetsFromStream:(NSData *)stream until:(NSInteger *)until xorMask:(uint8_t)xorMask;
+ (NSData *)streamFromPacket:(NSData *)packet xorMask:(uint8_t)xorMask;
+ (NSData *)streamFromPackets:(NSArray<NSData *> *)packets xorMask:(uint8_t)xorMask;

@end

Expand Down
27 changes: 22 additions & 5 deletions TunnelKit/Sources/Protocols/OpenVPN/PacketStream.m
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,18 @@

@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;
NSMutableArray<NSData *> *parsed = [[NSMutableArray alloc] init];
Expand All @@ -42,6 +53,12 @@ @implementation PacketStream
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;
}
Expand All @@ -51,19 +68,19 @@ @implementation PacketStream
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<NSData *> *)packets;
+ (NSData *)streamFromPackets:(NSArray<NSData *> *)packets xorMask:(uint8_t)xorMask
{
NSInteger streamLength = 0;
for (NSData *p in packets) {
Expand All @@ -75,7 +92,7 @@ + (NSData *)streamFromPackets:(NSArray<NSData *> *)packets;
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;
Expand Down
Loading