Add Complete XOR Patch Functionality (#255)
Co-authored-by: Davide De Rosa <keeshux@gmail.com>
This commit is contained in:
parent
e225ca15ff
commit
5ecd732cc2
|
@ -6,12 +6,18 @@ on:
|
||||||
- "master"
|
- "master"
|
||||||
paths-ignore:
|
paths-ignore:
|
||||||
- "*.md"
|
- "*.md"
|
||||||
|
pull_request:
|
||||||
|
types: [ opened, synchronize ]
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: ${{ github.ref }}
|
||||||
|
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
run_tests:
|
run_tests:
|
||||||
name: Run tests
|
name: Run tests
|
||||||
runs-on: macos-12
|
runs-on: macos-12
|
||||||
timeout-minutes: 10
|
timeout-minutes: 5
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- uses: maxim-lobanov/setup-xcode@v1
|
- uses: maxim-lobanov/setup-xcode@v1
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
"repositoryURL": "https://github.com/passepartoutvpn/wireguard-apple",
|
"repositoryURL": "https://github.com/passepartoutvpn/wireguard-apple",
|
||||||
"state": {
|
"state": {
|
||||||
"branch": null,
|
"branch": null,
|
||||||
"revision": "b4f74b7bcba9004a1852e615298f9cbc68fb7f67",
|
"revision": "d3b8f1ac6f3361d69bd3daf8aee3c43012c6ec0b",
|
||||||
"version": "1.0.16"
|
"version": "1.0.16"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -161,6 +161,11 @@ let package = Package(
|
||||||
name: "TunnelKitCoreTests",
|
name: "TunnelKitCoreTests",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"TunnelKitCore"
|
"TunnelKitCore"
|
||||||
|
],
|
||||||
|
exclude: [
|
||||||
|
"RandomTests.swift",
|
||||||
|
"RawPerformanceTests.swift",
|
||||||
|
"RoutingTests.swift"
|
||||||
]),
|
]),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "TunnelKitOpenVPNTests",
|
name: "TunnelKitOpenVPNTests",
|
||||||
|
@ -169,6 +174,10 @@ let package = Package(
|
||||||
"TunnelKitOpenVPNAppExtension",
|
"TunnelKitOpenVPNAppExtension",
|
||||||
"TunnelKitLZO"
|
"TunnelKitLZO"
|
||||||
],
|
],
|
||||||
|
exclude: [
|
||||||
|
"DataPathPerformanceTests.swift",
|
||||||
|
"EncryptionPerformanceTests.swift"
|
||||||
|
],
|
||||||
resources: [
|
resources: [
|
||||||
.process("Resources")
|
.process("Resources")
|
||||||
]),
|
]),
|
||||||
|
|
21
README.md
21
README.md
|
@ -46,10 +46,20 @@ TunnelKit can parse .ovpn configuration files. Below are a few details worth men
|
||||||
|
|
||||||
#### Non-standard
|
#### Non-standard
|
||||||
|
|
||||||
- Single-byte XOR masking
|
- XOR-patch functionality:
|
||||||
- Via `--scramble xormask <character>`
|
- Multi-byte XOR Masking
|
||||||
- XOR all incoming and outgoing bytes by the ASCII value of the character argument
|
- Via `--scramble xormask <passphrase>`
|
||||||
- See [Tunnelblick website][about-tunnelblick-xor] for more details
|
- XOR all incoming and outgoing bytes by the passphrase given
|
||||||
|
- XOR Position Masking
|
||||||
|
- Via `--scramble xorptrpos`
|
||||||
|
- XOR all bytes by their position in the array
|
||||||
|
- Packet Reverse Scramble
|
||||||
|
- Via `--scramble reverse`
|
||||||
|
- Keeps the first byte and reverses the rest of the array
|
||||||
|
- XOR Scramble Obfuscate
|
||||||
|
- Via `--scramble obfuscate <passphrase>`
|
||||||
|
- Performs a combination of the three above (specifically `xormask <passphrase>` -> `xorptrpos` -> `reverse` -> `xorptrpos` for reading, and the opposite for writing)
|
||||||
|
- See [Tunnelblick website][about-tunnelblick-xor] for more details (Patch was written in accordance with Tunnelblick's patch for compatibility)
|
||||||
|
|
||||||
#### Unsupported
|
#### Unsupported
|
||||||
|
|
||||||
|
@ -221,6 +231,7 @@ A custom TunnelKit license, e.g. for use in proprietary software, may be negotia
|
||||||
- [SURFnet][ppl-surfnet]
|
- [SURFnet][ppl-surfnet]
|
||||||
- [SwiftyBeaver][dep-swiftybeaver-repo] - Copyright (c) 2015 Sebastian Kreutzberger
|
- [SwiftyBeaver][dep-swiftybeaver-repo] - Copyright (c) 2015 Sebastian Kreutzberger
|
||||||
- [XMB5][ppl-xmb5] for the [XOR patch][ppl-xmb5-xor] - Copyright (c) 2020 Sam Foxman
|
- [XMB5][ppl-xmb5] for the [XOR patch][ppl-xmb5-xor] - Copyright (c) 2020 Sam Foxman
|
||||||
|
- [tmthecoder][ppl-tmthecoder] for the complete [XOR patch][ppl-tmthecoder-xor] - Copyright (c) 2022 Tejas Mehta
|
||||||
- [eduVPN][ppl-eduvpn] for the convenient WireGuardKitGo script
|
- [eduVPN][ppl-eduvpn] for the convenient WireGuardKitGo script
|
||||||
|
|
||||||
### OpenVPN
|
### OpenVPN
|
||||||
|
@ -264,6 +275,8 @@ Website: [passepartoutvpn.app][about-website]
|
||||||
[ppl-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]: https://github.com/XMB5
|
||||||
[ppl-xmb5-xor]: https://github.com/passepartoutvpn/tunnelkit/pull/170
|
[ppl-xmb5-xor]: https://github.com/passepartoutvpn/tunnelkit/pull/170
|
||||||
|
[ppl-tmthecoder]: https://github.com/tmthecoder
|
||||||
|
[ppl-tmthecoder-xor]: https://github.com/passepartoutvpn/tunnelkit/pull/255
|
||||||
[ppl-eduvpn]: https://github.com/eduvpn/apple
|
[ppl-eduvpn]: https://github.com/eduvpn/apple
|
||||||
[about-tunnelblick-xor]: https://tunnelblick.net/cOpenvpn_xorpatch.html
|
[about-tunnelblick-xor]: https://tunnelblick.net/cOpenvpn_xorpatch.html
|
||||||
[about-pr-bitcode]: https://github.com/passepartoutvpn/tunnelkit/issues/51
|
[about-pr-bitcode]: https://github.com/passepartoutvpn/tunnelkit/issues/51
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
//
|
||||||
|
// XORMethodNative.h
|
||||||
|
// TunnelKit
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 11/4/22.
|
||||||
|
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of TunnelKit.
|
||||||
|
//
|
||||||
|
// TunnelKit is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// TunnelKit is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
|
||||||
|
typedef NS_ENUM(NSInteger, XORMethodNative) {
|
||||||
|
XORMethodNativeNone,
|
||||||
|
XORMethodNativeMask,
|
||||||
|
XORMethodNativePtrPos,
|
||||||
|
XORMethodNativeReverse,
|
||||||
|
XORMethodNativeObfuscate
|
||||||
|
};
|
|
@ -24,23 +24,16 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#import "PacketStream.h"
|
#import "PacketStream.h"
|
||||||
|
#import "XOR.h"
|
||||||
|
|
||||||
static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
||||||
|
|
||||||
@implementation PacketStream
|
@implementation PacketStream
|
||||||
|
|
||||||
+ (void)memcpyXor:(uint8_t *)dst src:(NSData *)src xorMask:(uint8_t)xorMask
|
+ (NSArray<NSData *> *)packetsFromInboundStream:(NSData *)stream
|
||||||
{
|
until:(NSInteger *)until
|
||||||
if (xorMask != 0) {
|
xorMethod:(XORMethodNative)xorMethod
|
||||||
for (int i = 0; i < src.length; ++i) {
|
xorMask:(NSData *)xorMask
|
||||||
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];
|
||||||
|
@ -54,11 +47,7 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
||||||
}
|
}
|
||||||
NSData *packet = [stream subdataWithRange:NSMakeRange(start, packlen)];
|
NSData *packet = [stream subdataWithRange:NSMakeRange(start, packlen)];
|
||||||
uint8_t* packetBytes = (uint8_t*) packet.bytes;
|
uint8_t* packetBytes = (uint8_t*) packet.bytes;
|
||||||
if (xorMask != 0) {
|
xor_memcpy(packetBytes, packet, xorMethod, xorMask, false);
|
||||||
for (int i = 0; i < packet.length; i++) {
|
|
||||||
packetBytes[i] ^= xorMask;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
[parsed addObject:packet];
|
[parsed addObject:packet];
|
||||||
ni = end;
|
ni = end;
|
||||||
}
|
}
|
||||||
|
@ -68,19 +57,23 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
||||||
return parsed;
|
return parsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSData *)streamFromPacket:(NSData *)packet xorMask:(uint8_t)xorMask
|
+ (NSData *)outboundStreamFromPacket:(NSData *)packet
|
||||||
|
xorMethod:(XORMethodNative)xorMethod
|
||||||
|
xorMask:(NSData *)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;
|
||||||
[PacketStream memcpyXor:ptr src:packet xorMask:xorMask];
|
xor_memcpy(ptr, packet, xorMethod, xorMask, true);
|
||||||
|
|
||||||
return raw;
|
return raw;
|
||||||
}
|
}
|
||||||
|
|
||||||
+ (NSData *)streamFromPackets:(NSArray<NSData *> *)packets xorMask:(uint8_t)xorMask
|
+ (NSData *)outboundStreamFromPackets:(NSArray<NSData *> *)packets
|
||||||
|
xorMethod:(XORMethodNative)xorMethod
|
||||||
|
xorMask:(NSData *)xorMask
|
||||||
{
|
{
|
||||||
NSInteger streamLength = 0;
|
NSInteger streamLength = 0;
|
||||||
for (NSData *p in packets) {
|
for (NSData *p in packets) {
|
||||||
|
@ -92,7 +85,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;
|
||||||
[PacketStream memcpyXor:ptr src:packet xorMask:xorMask];
|
xor_memcpy(ptr, packet, xorMethod, xorMask, true);
|
||||||
ptr += packet.length;
|
ptr += packet.length;
|
||||||
}
|
}
|
||||||
return raw;
|
return raw;
|
||||||
|
|
|
@ -24,14 +24,24 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
#import <Foundation/Foundation.h>
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "XORMethodNative.h"
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_BEGIN
|
NS_ASSUME_NONNULL_BEGIN
|
||||||
|
|
||||||
@interface PacketStream : NSObject
|
@interface PacketStream : NSObject
|
||||||
|
|
||||||
+ (NSArray<NSData *> *)packetsFromStream:(NSData *)stream until:(NSInteger *)until xorMask:(uint8_t)xorMask;
|
+ (NSArray<NSData *> *)packetsFromInboundStream:(NSData *)stream
|
||||||
+ (NSData *)streamFromPacket:(NSData *)packet xorMask:(uint8_t)xorMask;
|
until:(NSInteger *)until
|
||||||
+ (NSData *)streamFromPackets:(NSArray<NSData *> *)packets xorMask:(uint8_t)xorMask;
|
xorMethod:(XORMethodNative)xorMethod
|
||||||
|
xorMask:(nullable NSData *)xorMask;
|
||||||
|
|
||||||
|
+ (NSData *)outboundStreamFromPacket:(NSData *)packet
|
||||||
|
xorMethod:(XORMethodNative)xorMethod
|
||||||
|
xorMask:(nullable NSData *)xorMask;
|
||||||
|
|
||||||
|
+ (NSData *)outboundStreamFromPackets:(NSArray<NSData *> *)packets
|
||||||
|
xorMethod:(XORMethodNative)xorMethod
|
||||||
|
xorMask:(nullable NSData *)xorMask;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,99 @@
|
||||||
|
//
|
||||||
|
// XOR.h
|
||||||
|
// TunnelKit
|
||||||
|
//
|
||||||
|
// Created by Tejas Mehta on 5/24/22.
|
||||||
|
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of TunnelKit.
|
||||||
|
//
|
||||||
|
// TunnelKit is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// TunnelKit is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
#import <Foundation/Foundation.h>
|
||||||
|
#import "XORMethodNative.h"
|
||||||
|
|
||||||
|
static inline void xor_mask(uint8_t *dst, const uint8_t *src, NSData *xorMask, size_t length)
|
||||||
|
{
|
||||||
|
if (xorMask.length > 0) {
|
||||||
|
for (size_t i = 0; i < length; ++i) {
|
||||||
|
dst[i] = src[i] ^ ((uint8_t *)(xorMask.bytes))[i % xorMask.length];
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
memcpy(dst, src, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void xor_ptrpos(uint8_t *dst, const uint8_t *src, size_t length)
|
||||||
|
{
|
||||||
|
for (size_t i = 0; i < length; ++i) {
|
||||||
|
dst[i] = src[i] ^ (i + 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void xor_reverse(uint8_t *dst, const uint8_t *src, size_t length)
|
||||||
|
{
|
||||||
|
size_t start = 1;
|
||||||
|
size_t end = length - 1;
|
||||||
|
uint8_t temp = 0;
|
||||||
|
dst[0] = src[0];
|
||||||
|
while (start < end) {
|
||||||
|
temp = src[start];
|
||||||
|
dst[start] = src[end];
|
||||||
|
dst[end] = temp;
|
||||||
|
start++;
|
||||||
|
end--;
|
||||||
|
}
|
||||||
|
if (start == end) {
|
||||||
|
dst[start] = src[start];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline void xor_memcpy(uint8_t *dst, NSData *src, XORMethodNative method, NSData *mask, BOOL outbound)
|
||||||
|
{
|
||||||
|
const uint8_t *source = (uint8_t *)src.bytes;
|
||||||
|
switch (method) {
|
||||||
|
case XORMethodNativeNone:
|
||||||
|
memcpy(dst, source, src.length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XORMethodNativeMask:
|
||||||
|
xor_mask(dst, source, mask, src.length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XORMethodNativePtrPos:
|
||||||
|
xor_ptrpos(dst, source, src.length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XORMethodNativeReverse:
|
||||||
|
xor_reverse(dst, source, src.length);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case XORMethodNativeObfuscate:
|
||||||
|
if (outbound) {
|
||||||
|
xor_ptrpos(dst, source, src.length);
|
||||||
|
xor_reverse(dst, dst, src.length);
|
||||||
|
xor_ptrpos(dst, dst, src.length);
|
||||||
|
xor_mask(dst, dst, mask, src.length);
|
||||||
|
} else {
|
||||||
|
xor_mask(dst, source, mask, src.length);
|
||||||
|
xor_ptrpos(dst, dst, src.length);
|
||||||
|
xor_reverse(dst, dst, src.length);
|
||||||
|
xor_ptrpos(dst, dst, src.length);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
|
@ -32,7 +32,8 @@ public protocol LinkProducer {
|
||||||
/**
|
/**
|
||||||
Returns a `LinkInterface`.
|
Returns a `LinkInterface`.
|
||||||
|
|
||||||
- Parameter xorMask: The XOR mask.
|
- Parameter userObject: Optional user data.
|
||||||
|
- Returns: A generic `LinkInterface`.
|
||||||
**/
|
**/
|
||||||
func link(xorMask: UInt8?) -> LinkInterface
|
func link(userObject: Any?) -> LinkInterface
|
||||||
}
|
}
|
||||||
|
|
|
@ -50,7 +50,4 @@ 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 }
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,19 +27,23 @@ import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import TunnelKitCore
|
import TunnelKitCore
|
||||||
import TunnelKitAppExtension
|
import TunnelKitAppExtension
|
||||||
|
import TunnelKitOpenVPNCore
|
||||||
import CTunnelKitOpenVPNProtocol
|
import CTunnelKitOpenVPNProtocol
|
||||||
|
|
||||||
class NETCPLink: LinkInterface {
|
class NETCPLink: LinkInterface {
|
||||||
private let impl: NWTCPConnection
|
private let impl: NWTCPConnection
|
||||||
|
|
||||||
private let maxPacketSize: Int
|
private let maxPacketSize: Int
|
||||||
|
|
||||||
let xorMask: UInt8
|
private let xorMethod: OpenVPN.XORMethod?
|
||||||
|
|
||||||
init(impl: NWTCPConnection, maxPacketSize: Int? = nil, xorMask: UInt8?) {
|
private let xorMask: Data?
|
||||||
|
|
||||||
|
init(impl: NWTCPConnection, maxPacketSize: Int? = nil, xorMethod: OpenVPN.XORMethod?) {
|
||||||
self.impl = impl
|
self.impl = impl
|
||||||
self.maxPacketSize = maxPacketSize ?? (512 * 1024)
|
self.maxPacketSize = maxPacketSize ?? (512 * 1024)
|
||||||
self.xorMask = xorMask ?? 0
|
self.xorMethod = xorMethod
|
||||||
|
xorMask = xorMethod?.mask
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: LinkInterface
|
// MARK: LinkInterface
|
||||||
|
@ -81,7 +85,12 @@ 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, xorMask: self.xorMask)
|
let packets = PacketStream.packets(
|
||||||
|
fromInboundStream: newBuffer,
|
||||||
|
until: &until,
|
||||||
|
xorMethod: self.xorMethod?.native ?? .none,
|
||||||
|
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)
|
||||||
|
|
||||||
|
@ -91,14 +100,22 @@ class NETCPLink: LinkInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
|
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
|
||||||
let stream = PacketStream.stream(fromPacket: packet, xorMask: xorMask)
|
let stream = PacketStream.outboundStream(
|
||||||
|
fromPacket: packet,
|
||||||
|
xorMethod: xorMethod?.native ?? .none,
|
||||||
|
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, xorMask: xorMask)
|
let stream = PacketStream.outboundStream(
|
||||||
|
fromPackets: packets,
|
||||||
|
xorMethod: xorMethod?.native ?? .none,
|
||||||
|
xorMask: xorMask
|
||||||
|
)
|
||||||
impl.write(stream) { (error) in
|
impl.write(stream) { (error) in
|
||||||
completionHandler?(error)
|
completionHandler?(error)
|
||||||
}
|
}
|
||||||
|
@ -106,7 +123,8 @@ class NETCPLink: LinkInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NETCPSocket: LinkProducer {
|
extension NETCPSocket: LinkProducer {
|
||||||
public func link(xorMask: UInt8?) -> LinkInterface {
|
public func link(userObject: Any?) -> LinkInterface {
|
||||||
return NETCPLink(impl: impl, maxPacketSize: nil, xorMask: xorMask)
|
let xorMethod = userObject as? OpenVPN.XORMethod
|
||||||
|
return NETCPLink(impl: impl, maxPacketSize: nil, xorMethod: xorMethod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,18 +27,20 @@ import Foundation
|
||||||
import NetworkExtension
|
import NetworkExtension
|
||||||
import TunnelKitCore
|
import TunnelKitCore
|
||||||
import TunnelKitAppExtension
|
import TunnelKitAppExtension
|
||||||
|
import TunnelKitOpenVPNCore
|
||||||
|
import TunnelKitOpenVPNProtocol
|
||||||
|
|
||||||
class NEUDPLink: LinkInterface {
|
class NEUDPLink: LinkInterface {
|
||||||
private let impl: NWUDPSession
|
private let impl: NWUDPSession
|
||||||
|
|
||||||
private let maxDatagrams: Int
|
private let maxDatagrams: Int
|
||||||
|
|
||||||
let xorMask: UInt8
|
private let xor: XORProcessor
|
||||||
|
|
||||||
init(impl: NWUDPSession, maxDatagrams: Int? = nil, xorMask: UInt8?) {
|
init(impl: NWUDPSession, maxDatagrams: Int? = nil, xorMethod: OpenVPN.XORMethod?) {
|
||||||
self.impl = impl
|
self.impl = impl
|
||||||
self.maxDatagrams = maxDatagrams ?? 200
|
self.maxDatagrams = maxDatagrams ?? 200
|
||||||
self.xorMask = xorMask ?? 0
|
xor = XORProcessor(method: xorMethod)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: LinkInterface
|
// MARK: LinkInterface
|
||||||
|
@ -68,12 +70,8 @@ class NEUDPLink: LinkInterface {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var packetsToUse: [Data]?
|
var packetsToUse: [Data]?
|
||||||
if let packets = packets, self.xorMask != 0 {
|
if let packets = packets {
|
||||||
packetsToUse = packets.map { packet in
|
packetsToUse = self.xor.processPackets(packets, outbound: false)
|
||||||
Data(bytes: packet.map { $0 ^ self.xorMask }, count: packet.count)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
packetsToUse = packets
|
|
||||||
}
|
}
|
||||||
queue.sync {
|
queue.sync {
|
||||||
handler(packetsToUse, error)
|
handler(packetsToUse, error)
|
||||||
|
@ -82,26 +80,14 @@ class NEUDPLink: LinkInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
|
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
|
||||||
var dataToUse: Data
|
let dataToUse = xor.processPacket(packet, outbound: true)
|
||||||
if xorMask != 0 {
|
|
||||||
dataToUse = Data(bytes: packet.map { $0 ^ xorMask }, count: packet.count)
|
|
||||||
} else {
|
|
||||||
dataToUse = packet
|
|
||||||
}
|
|
||||||
impl.writeDatagram(dataToUse) { error in
|
impl.writeDatagram(dataToUse) { error in
|
||||||
completionHandler?(error)
|
completionHandler?(error)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
|
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
|
||||||
var packetsToUse: [Data]
|
let packetsToUse = xor.processPackets(packets, outbound: true)
|
||||||
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
|
impl.writeMultipleDatagrams(packetsToUse) { error in
|
||||||
completionHandler?(error)
|
completionHandler?(error)
|
||||||
}
|
}
|
||||||
|
@ -109,7 +95,8 @@ class NEUDPLink: LinkInterface {
|
||||||
}
|
}
|
||||||
|
|
||||||
extension NEUDPSocket: LinkProducer {
|
extension NEUDPSocket: LinkProducer {
|
||||||
public func link(xorMask: UInt8?) -> LinkInterface {
|
public func link(userObject: Any?) -> LinkInterface {
|
||||||
return NEUDPLink(impl: impl, maxDatagrams: nil, xorMask: xorMask)
|
let xorMethod = userObject as? OpenVPN.XORMethod
|
||||||
|
return NEUDPLink(impl: impl, maxDatagrams: nil, xorMethod: xorMethod)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -408,10 +408,10 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if session.canRebindLink() {
|
if session.canRebindLink() {
|
||||||
session.rebindLink(producer.link(xorMask: cfg.configuration.xorMask))
|
session.rebindLink(producer.link(userObject: cfg.configuration.xorMethod))
|
||||||
reasserting = false
|
reasserting = false
|
||||||
} else {
|
} else {
|
||||||
session.setLink(producer.link(xorMask: cfg.configuration.xorMask))
|
session.setLink(producer.link(userObject: cfg.configuration.xorMethod))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -209,9 +209,6 @@ 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 list of server endpoints.
|
/// The list of server endpoints.
|
||||||
|
@ -314,6 +311,11 @@ extension OpenVPN {
|
||||||
/// Server settings that must not be pulled.
|
/// Server settings that must not be pulled.
|
||||||
public var noPullMask: [PullMask]?
|
public var noPullMask: [PullMask]?
|
||||||
|
|
||||||
|
// MARK: Extra
|
||||||
|
|
||||||
|
/// The method to follow in regards to the XOR patch.
|
||||||
|
public var xorMethod: XORMethod?
|
||||||
|
|
||||||
/**
|
/**
|
||||||
Creates a `ConfigurationBuilder`.
|
Creates a `ConfigurationBuilder`.
|
||||||
|
|
||||||
|
@ -348,7 +350,6 @@ extension OpenVPN {
|
||||||
keepAliveInterval: keepAliveInterval,
|
keepAliveInterval: keepAliveInterval,
|
||||||
keepAliveTimeout: keepAliveTimeout,
|
keepAliveTimeout: keepAliveTimeout,
|
||||||
renegotiatesAfter: renegotiatesAfter,
|
renegotiatesAfter: renegotiatesAfter,
|
||||||
xorMask: xorMask,
|
|
||||||
remotes: remotes,
|
remotes: remotes,
|
||||||
checksEKU: checksEKU,
|
checksEKU: checksEKU,
|
||||||
checksSANHost: checksSANHost,
|
checksSANHost: checksSANHost,
|
||||||
|
@ -376,7 +377,8 @@ extension OpenVPN {
|
||||||
proxyAutoConfigurationURL: proxyAutoConfigurationURL,
|
proxyAutoConfigurationURL: proxyAutoConfigurationURL,
|
||||||
proxyBypassDomains: proxyBypassDomains,
|
proxyBypassDomains: proxyBypassDomains,
|
||||||
routingPolicies: routingPolicies,
|
routingPolicies: routingPolicies,
|
||||||
noPullMask: noPullMask
|
noPullMask: noPullMask,
|
||||||
|
xorMethod: xorMethod
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -434,9 +436,6 @@ 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.remotes`
|
/// - Seealso: `ConfigurationBuilder.remotes`
|
||||||
public let remotes: [Endpoint]?
|
public let remotes: [Endpoint]?
|
||||||
|
|
||||||
|
@ -521,6 +520,9 @@ extension OpenVPN {
|
||||||
/// - Seealso: `ConfigurationBuilder.noPullMask`
|
/// - Seealso: `ConfigurationBuilder.noPullMask`
|
||||||
public let noPullMask: [PullMask]?
|
public let noPullMask: [PullMask]?
|
||||||
|
|
||||||
|
/// - Seealso: `ConfigurationBuilder.xorMethod`
|
||||||
|
public let xorMethod: XORMethod?
|
||||||
|
|
||||||
// MARK: Shortcuts
|
// MARK: Shortcuts
|
||||||
|
|
||||||
public var fallbackCipher: Cipher {
|
public var fallbackCipher: Cipher {
|
||||||
|
@ -597,7 +599,6 @@ extension OpenVPN.Configuration {
|
||||||
builder.keepAliveInterval = keepAliveInterval
|
builder.keepAliveInterval = keepAliveInterval
|
||||||
builder.keepAliveTimeout = keepAliveTimeout
|
builder.keepAliveTimeout = keepAliveTimeout
|
||||||
builder.renegotiatesAfter = renegotiatesAfter
|
builder.renegotiatesAfter = renegotiatesAfter
|
||||||
builder.xorMask = xorMask
|
|
||||||
builder.remotes = remotes
|
builder.remotes = remotes
|
||||||
builder.checksEKU = checksEKU
|
builder.checksEKU = checksEKU
|
||||||
builder.checksSANHost = checksSANHost
|
builder.checksSANHost = checksSANHost
|
||||||
|
@ -626,6 +627,7 @@ extension OpenVPN.Configuration {
|
||||||
builder.proxyBypassDomains = proxyBypassDomains
|
builder.proxyBypassDomains = proxyBypassDomains
|
||||||
builder.routingPolicies = routingPolicies
|
builder.routingPolicies = routingPolicies
|
||||||
builder.noPullMask = noPullMask
|
builder.noPullMask = noPullMask
|
||||||
|
builder.xorMethod = xorMethod
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,8 +65,6 @@ 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\\-]+>")
|
||||||
|
@ -120,7 +118,11 @@ extension OpenVPN {
|
||||||
static let redirectGateway = NSRegularExpression("^redirect-gateway.*")
|
static let redirectGateway = NSRegularExpression("^redirect-gateway.*")
|
||||||
|
|
||||||
static let routeNoPull = NSRegularExpression("^route-nopull")
|
static let routeNoPull = NSRegularExpression("^route-nopull")
|
||||||
|
|
||||||
|
// MARK: Extra
|
||||||
|
|
||||||
|
static let xorInfo = NSRegularExpression("^scramble +(xormask|xorptrpos|reverse|obfuscate)[\\s]?([^\\s]+)?")
|
||||||
|
|
||||||
// MARK: Unsupported
|
// MARK: Unsupported
|
||||||
|
|
||||||
// static let fragment = NSRegularExpression("^fragment +\\d+")
|
// static let fragment = NSRegularExpression("^fragment +\\d+")
|
||||||
|
@ -266,7 +268,6 @@ 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?
|
||||||
|
@ -294,6 +295,8 @@ extension OpenVPN {
|
||||||
var optProxyBypass: [String]?
|
var optProxyBypass: [String]?
|
||||||
var optRedirectGateway: Set<RedirectGateway>?
|
var optRedirectGateway: Set<RedirectGateway>?
|
||||||
var optRouteNoPull: Bool?
|
var optRouteNoPull: Bool?
|
||||||
|
//
|
||||||
|
var optXorMethod: XORMethod?
|
||||||
|
|
||||||
log.verbose("Configuration file:")
|
log.verbose("Configuration file:")
|
||||||
for line in lines {
|
for line in lines {
|
||||||
|
@ -518,13 +521,6 @@ extension OpenVPN {
|
||||||
}
|
}
|
||||||
optRenegotiateAfterSeconds = TimeInterval(arg)
|
optRenegotiateAfterSeconds = TimeInterval(arg)
|
||||||
}
|
}
|
||||||
Regex.xorMask.enumerateSpacedArguments(in: line) {
|
|
||||||
isHandled = true
|
|
||||||
if $0.count != 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
optXorMask = Character($0[1]).asciiValue
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Client
|
// MARK: Client
|
||||||
|
|
||||||
|
@ -712,7 +708,37 @@ extension OpenVPN {
|
||||||
Regex.routeNoPull.enumerateSpacedComponents(in: line) { _ in
|
Regex.routeNoPull.enumerateSpacedComponents(in: line) { _ in
|
||||||
optRouteNoPull = true
|
optRouteNoPull = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Extra
|
||||||
|
|
||||||
|
Regex.xorInfo.enumerateSpacedArguments(in: line) {
|
||||||
|
isHandled = true
|
||||||
|
guard !$0.isEmpty else {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch $0[0] {
|
||||||
|
case "xormask":
|
||||||
|
if $0.count > 1, let mask = $0[1].data(using: .utf8) {
|
||||||
|
optXorMethod = .xormask(mask: mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
case "xorptrpos":
|
||||||
|
optXorMethod = .xorptrpos
|
||||||
|
|
||||||
|
case "reverse":
|
||||||
|
optXorMethod = .reverse
|
||||||
|
|
||||||
|
case "obfuscate":
|
||||||
|
if $0.count > 1, let mask = $0[1].data(using: .utf8) {
|
||||||
|
optXorMethod = .obfuscate(mask: mask)
|
||||||
|
}
|
||||||
|
|
||||||
|
default:
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
if let error = unsupportedError {
|
if let error = unsupportedError {
|
||||||
|
@ -821,7 +847,6 @@ extension OpenVPN {
|
||||||
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
|
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
|
||||||
sessionBuilder.randomizeHostnames = optRandomizeHostnames
|
sessionBuilder.randomizeHostnames = optRandomizeHostnames
|
||||||
sessionBuilder.mtu = optMTU
|
sessionBuilder.mtu = optMTU
|
||||||
sessionBuilder.xorMask = optXorMask
|
|
||||||
|
|
||||||
// MARK: Server
|
// MARK: Server
|
||||||
|
|
||||||
|
@ -938,6 +963,10 @@ extension OpenVPN {
|
||||||
}
|
}
|
||||||
sessionBuilder.routingPolicies = [RoutingPolicy](policies)
|
sessionBuilder.routingPolicies = [RoutingPolicy](policies)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MARK: Extra
|
||||||
|
|
||||||
|
sessionBuilder.xorMethod = optXorMethod
|
||||||
|
|
||||||
//
|
//
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,77 @@
|
||||||
|
//
|
||||||
|
// XORMethod.swift
|
||||||
|
// TunnelKit
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 11/4/22.
|
||||||
|
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of TunnelKit.
|
||||||
|
//
|
||||||
|
// TunnelKit is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// TunnelKit is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import CTunnelKitOpenVPNCore
|
||||||
|
|
||||||
|
extension OpenVPN {
|
||||||
|
|
||||||
|
/// The obfuscation method.
|
||||||
|
public enum XORMethod: Codable, Equatable {
|
||||||
|
|
||||||
|
/// XORs the bytes in each buffer with the given mask.
|
||||||
|
case xormask(mask: Data)
|
||||||
|
|
||||||
|
/// XORs each byte with its position in the packet.
|
||||||
|
case xorptrpos
|
||||||
|
|
||||||
|
/// Reverses the order of bytes in each buffer except for the first (abcde becomes aedcb).
|
||||||
|
case reverse
|
||||||
|
|
||||||
|
/// Performs several of the above steps (xormask -> xorptrpos -> reverse -> xorptrpos).
|
||||||
|
case obfuscate(mask: Data)
|
||||||
|
|
||||||
|
/// This method mapped to native enumeration.
|
||||||
|
public var native: XORMethodNative {
|
||||||
|
switch self {
|
||||||
|
case .xormask:
|
||||||
|
return .mask
|
||||||
|
|
||||||
|
case .xorptrpos:
|
||||||
|
return .ptrPos
|
||||||
|
|
||||||
|
case .reverse:
|
||||||
|
return .reverse
|
||||||
|
|
||||||
|
case .obfuscate:
|
||||||
|
return .obfuscate
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The optionally associated mask.
|
||||||
|
public var mask: Data? {
|
||||||
|
switch self {
|
||||||
|
case .xormask(let mask):
|
||||||
|
return mask
|
||||||
|
|
||||||
|
case .obfuscate(let mask):
|
||||||
|
return mask
|
||||||
|
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,100 @@
|
||||||
|
//
|
||||||
|
// XORProcessor.swift
|
||||||
|
// TunnelKit
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 11/4/22.
|
||||||
|
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of TunnelKit.
|
||||||
|
//
|
||||||
|
// TunnelKit is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// TunnelKit is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
import TunnelKitOpenVPNCore
|
||||||
|
|
||||||
|
/// Processes data packets according to a XOR method.
|
||||||
|
public struct XORProcessor {
|
||||||
|
private let method: OpenVPN.XORMethod?
|
||||||
|
|
||||||
|
public init(method: OpenVPN.XORMethod?) {
|
||||||
|
self.method = method
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns an array of data packets processed according to XOR method.
|
||||||
|
|
||||||
|
- Parameter packets: The array of packets.
|
||||||
|
- Parameter outbound: Set `true` if packets are outbound, `false` otherwise.
|
||||||
|
- Returns: The array of packets after XOR processing.
|
||||||
|
**/
|
||||||
|
public func processPackets(_ packets: [Data], outbound: Bool) -> [Data] {
|
||||||
|
guard let _ = method else {
|
||||||
|
return packets
|
||||||
|
}
|
||||||
|
return packets.map {
|
||||||
|
processPacket($0, outbound: outbound)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
Returns a data packet processed according to XOR method.
|
||||||
|
|
||||||
|
- Parameter packets: The packet.
|
||||||
|
- Parameter outbound: Set `true` if packet is outbound, `false` otherwise.
|
||||||
|
- Returns: The packet after XOR processing.
|
||||||
|
**/
|
||||||
|
public func processPacket(_ packet: Data, outbound: Bool) -> Data {
|
||||||
|
guard let method = method else {
|
||||||
|
return packet
|
||||||
|
}
|
||||||
|
switch method {
|
||||||
|
case .xormask(let mask):
|
||||||
|
return Self.xormask(packet: packet, mask: mask)
|
||||||
|
|
||||||
|
case .xorptrpos:
|
||||||
|
return Self.xorptrpos(packet: packet)
|
||||||
|
|
||||||
|
case .reverse:
|
||||||
|
return Self.reverse(packet: packet)
|
||||||
|
|
||||||
|
case .obfuscate(let mask):
|
||||||
|
if outbound {
|
||||||
|
return Self.xormask(packet: Self.xorptrpos(packet: Self.reverse(packet: Self.xorptrpos(packet: packet))), mask: mask)
|
||||||
|
} else {
|
||||||
|
return Self.xorptrpos(packet: Self.reverse(packet: Self.xorptrpos(packet: Self.xormask(packet: packet, mask: mask))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension XORProcessor {
|
||||||
|
private static func xormask(packet: Data, mask: Data) -> Data {
|
||||||
|
Data(packet.enumerated().map { (index, byte) in
|
||||||
|
byte ^ [UInt8](mask)[index % mask.count]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func xorptrpos(packet: Data) -> Data {
|
||||||
|
Data(packet.enumerated().map { (index, byte) in
|
||||||
|
byte ^ UInt8(truncatingIfNeeded: index &+ 1)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func reverse(packet: Data) -> Data {
|
||||||
|
Data(([UInt8](packet))[0..<1] + ([UInt8](packet)[1...]).reversed())
|
||||||
|
}
|
||||||
|
}
|
|
@ -127,11 +127,19 @@ class ConfigurationParserTests: XCTestCase {
|
||||||
func testXOR() throws {
|
func testXOR() throws {
|
||||||
let cfg = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask F"])
|
let cfg = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask F"])
|
||||||
XCTAssertNil(cfg.warning)
|
XCTAssertNil(cfg.warning)
|
||||||
XCTAssertEqual(cfg.configuration.xorMask, Character("F").asciiValue)
|
XCTAssertEqual(cfg.configuration.xorMethod, OpenVPN.XORMethod.xormask(mask: Data(repeating: Character("F").asciiValue!, count:1)))
|
||||||
|
|
||||||
let cfg2 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask FFFF"])
|
let cfg2 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble reverse"])
|
||||||
XCTAssertNil(cfg.warning)
|
XCTAssertNil(cfg.warning)
|
||||||
XCTAssertNil(cfg2.configuration.xorMask)
|
XCTAssertEqual(cfg2.configuration.xorMethod, OpenVPN.XORMethod.reverse)
|
||||||
|
|
||||||
|
let cfg3 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xorptrpos"])
|
||||||
|
XCTAssertNil(cfg.warning)
|
||||||
|
XCTAssertEqual(cfg3.configuration.xorMethod, OpenVPN.XORMethod.xorptrpos)
|
||||||
|
|
||||||
|
let cfg4 = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble obfuscate FFFF"])
|
||||||
|
XCTAssertNil(cfg.warning)
|
||||||
|
XCTAssertEqual(cfg4.configuration.xorMethod, OpenVPN.XORMethod.obfuscate(mask: Data(repeating: Character("F").asciiValue!, count:4)))
|
||||||
}
|
}
|
||||||
|
|
||||||
private func privateTestEncryptedCertificateKey(pkcs: String) throws {
|
private func privateTestEncryptedCertificateKey(pkcs: String) throws {
|
||||||
|
|
|
@ -0,0 +1,87 @@
|
||||||
|
//
|
||||||
|
// XORTests.swift
|
||||||
|
// TunnelKitOpenVPNTests
|
||||||
|
//
|
||||||
|
// Created by Davide De Rosa on 11/4/22.
|
||||||
|
// Copyright (c) 2022 Davide De Rosa. All rights reserved.
|
||||||
|
//
|
||||||
|
// https://github.com/passepartoutvpn
|
||||||
|
//
|
||||||
|
// This file is part of TunnelKit.
|
||||||
|
//
|
||||||
|
// TunnelKit is free software: you can redistribute it and/or modify
|
||||||
|
// it under the terms of the GNU General Public License as published by
|
||||||
|
// the Free Software Foundation, either version 3 of the License, or
|
||||||
|
// (at your option) any later version.
|
||||||
|
//
|
||||||
|
// TunnelKit is distributed in the hope that it will be useful,
|
||||||
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
// GNU General Public License for more details.
|
||||||
|
//
|
||||||
|
// You should have received a copy of the GNU General Public License
|
||||||
|
// along with TunnelKit. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
//
|
||||||
|
|
||||||
|
import XCTest
|
||||||
|
import TunnelKitCore
|
||||||
|
import TunnelKitOpenVPNProtocol
|
||||||
|
import CTunnelKitOpenVPNProtocol
|
||||||
|
|
||||||
|
final class XORTests: XCTestCase {
|
||||||
|
private let mask = Data(hex: "f76dab30")
|
||||||
|
|
||||||
|
override func setUpWithError() throws {
|
||||||
|
// Put setup code here. This method is called before the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
override func tearDownWithError() throws {
|
||||||
|
// Put teardown code here. This method is called after the invocation of each test method in the class.
|
||||||
|
}
|
||||||
|
|
||||||
|
func testMask() throws {
|
||||||
|
let processor = XORProcessor(method: .xormask(mask: mask))
|
||||||
|
processor.assertReversible(try SecureRandom.data(length: 1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPtrPos() throws {
|
||||||
|
let processor = XORProcessor(method: .xorptrpos)
|
||||||
|
processor.assertReversible(try SecureRandom.data(length: 1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testReverse() throws {
|
||||||
|
let processor = XORProcessor(method: .reverse)
|
||||||
|
processor.assertReversible(try SecureRandom.data(length: 1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testObfuscate() throws {
|
||||||
|
let processor = XORProcessor(method: .obfuscate(mask: mask))
|
||||||
|
processor.assertReversible(try SecureRandom.data(length: 1000))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPacketStream() throws {
|
||||||
|
let data = try SecureRandom.data(length: 10000)
|
||||||
|
PacketStream.assertReversible(data, method: .none)
|
||||||
|
PacketStream.assertReversible(data, method: .mask, mask: mask)
|
||||||
|
PacketStream.assertReversible(data, method: .ptrPos)
|
||||||
|
PacketStream.assertReversible(data, method: .reverse)
|
||||||
|
PacketStream.assertReversible(data, method: .obfuscate, mask: mask)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension XORProcessor {
|
||||||
|
func assertReversible(_ data: Data) {
|
||||||
|
let xored = processPacket(data, outbound: true)
|
||||||
|
XCTAssertEqual(processPacket(xored, outbound: false), data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extension PacketStream {
|
||||||
|
static func assertReversible(_ data: Data, method: XORMethodNative, mask: Data? = nil) {
|
||||||
|
var until = 0
|
||||||
|
let outStream = PacketStream.outboundStream(fromPacket: data, xorMethod: method, xorMask: mask)
|
||||||
|
let inStream = PacketStream.packets(fromInboundStream: outStream, until: &until, xorMethod: method, xorMask: mask)
|
||||||
|
let originalData = Data(inStream.joined())
|
||||||
|
XCTAssertEqual(data.toHex(), originalData.toHex())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue