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"
|
||||
paths-ignore:
|
||||
- "*.md"
|
||||
pull_request:
|
||||
types: [ opened, synchronize ]
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.ref }}
|
||||
cancel-in-progress: ${{ github.ref != 'refs/heads/master' }}
|
||||
|
||||
jobs:
|
||||
run_tests:
|
||||
name: Run tests
|
||||
runs-on: macos-12
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 5
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: maxim-lobanov/setup-xcode@v1
|
||||
|
|
|
@ -24,7 +24,7 @@
|
|||
"repositoryURL": "https://github.com/passepartoutvpn/wireguard-apple",
|
||||
"state": {
|
||||
"branch": null,
|
||||
"revision": "b4f74b7bcba9004a1852e615298f9cbc68fb7f67",
|
||||
"revision": "d3b8f1ac6f3361d69bd3daf8aee3c43012c6ec0b",
|
||||
"version": "1.0.16"
|
||||
}
|
||||
}
|
||||
|
|
|
@ -161,6 +161,11 @@ let package = Package(
|
|||
name: "TunnelKitCoreTests",
|
||||
dependencies: [
|
||||
"TunnelKitCore"
|
||||
],
|
||||
exclude: [
|
||||
"RandomTests.swift",
|
||||
"RawPerformanceTests.swift",
|
||||
"RoutingTests.swift"
|
||||
]),
|
||||
.testTarget(
|
||||
name: "TunnelKitOpenVPNTests",
|
||||
|
@ -169,6 +174,10 @@ let package = Package(
|
|||
"TunnelKitOpenVPNAppExtension",
|
||||
"TunnelKitLZO"
|
||||
],
|
||||
exclude: [
|
||||
"DataPathPerformanceTests.swift",
|
||||
"EncryptionPerformanceTests.swift"
|
||||
],
|
||||
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
|
||||
|
||||
- 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
|
||||
- XOR-patch functionality:
|
||||
- Multi-byte XOR Masking
|
||||
- Via `--scramble xormask <passphrase>`
|
||||
- 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
|
||||
|
||||
|
@ -221,6 +231,7 @@ A custom TunnelKit license, e.g. for use in proprietary software, may be negotia
|
|||
- [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
|
||||
- [tmthecoder][ppl-tmthecoder] for the complete [XOR patch][ppl-tmthecoder-xor] - Copyright (c) 2022 Tejas Mehta
|
||||
- [eduVPN][ppl-eduvpn] for the convenient WireGuardKitGo script
|
||||
|
||||
### OpenVPN
|
||||
|
@ -264,6 +275,8 @@ Website: [passepartoutvpn.app][about-website]
|
|||
[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
|
||||
[ppl-tmthecoder]: https://github.com/tmthecoder
|
||||
[ppl-tmthecoder-xor]: https://github.com/passepartoutvpn/tunnelkit/pull/255
|
||||
[ppl-eduvpn]: https://github.com/eduvpn/apple
|
||||
[about-tunnelblick-xor]: https://tunnelblick.net/cOpenvpn_xorpatch.html
|
||||
[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 "XOR.h"
|
||||
|
||||
static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
||||
|
||||
@implementation PacketStream
|
||||
|
||||
+ (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
|
||||
+ (NSArray<NSData *> *)packetsFromInboundStream:(NSData *)stream
|
||||
until:(NSInteger *)until
|
||||
xorMethod:(XORMethodNative)xorMethod
|
||||
xorMask:(NSData *)xorMask
|
||||
{
|
||||
NSInteger ni = 0;
|
||||
NSMutableArray<NSData *> *parsed = [[NSMutableArray alloc] init];
|
||||
|
@ -54,11 +47,7 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
|||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
xor_memcpy(packetBytes, packet, xorMethod, xorMask, false);
|
||||
[parsed addObject:packet];
|
||||
ni = end;
|
||||
}
|
||||
|
@ -68,19 +57,23 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
|||
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)];
|
||||
|
||||
uint8_t *ptr = raw.mutableBytes;
|
||||
*(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length);
|
||||
ptr += PacketStreamHeaderLength;
|
||||
[PacketStream memcpyXor:ptr src:packet xorMask:xorMask];
|
||||
xor_memcpy(ptr, packet, xorMethod, xorMask, true);
|
||||
|
||||
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;
|
||||
for (NSData *p in packets) {
|
||||
|
@ -92,7 +85,7 @@ static const NSInteger PacketStreamHeaderLength = sizeof(uint16_t);
|
|||
for (NSData *packet in packets) {
|
||||
*(uint16_t *)ptr = CFSwapInt16HostToBig(packet.length);
|
||||
ptr += PacketStreamHeaderLength;
|
||||
[PacketStream memcpyXor:ptr src:packet xorMask:xorMask];
|
||||
xor_memcpy(ptr, packet, xorMethod, xorMask, true);
|
||||
ptr += packet.length;
|
||||
}
|
||||
return raw;
|
||||
|
|
|
@ -24,14 +24,24 @@
|
|||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "XORMethodNative.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface PacketStream : NSObject
|
||||
|
||||
+ (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;
|
||||
+ (NSArray<NSData *> *)packetsFromInboundStream:(NSData *)stream
|
||||
until:(NSInteger *)until
|
||||
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
|
||||
|
||||
|
|
|
@ -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`.
|
||||
|
||||
- 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.
|
||||
var packetBufferSize: Int { get }
|
||||
|
||||
/// A byte to xor all packet payloads with.
|
||||
var xorMask: UInt8 { get }
|
||||
}
|
||||
|
|
|
@ -27,6 +27,7 @@ import Foundation
|
|||
import NetworkExtension
|
||||
import TunnelKitCore
|
||||
import TunnelKitAppExtension
|
||||
import TunnelKitOpenVPNCore
|
||||
import CTunnelKitOpenVPNProtocol
|
||||
|
||||
class NETCPLink: LinkInterface {
|
||||
|
@ -34,12 +35,15 @@ class NETCPLink: LinkInterface {
|
|||
|
||||
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.maxPacketSize = maxPacketSize ?? (512 * 1024)
|
||||
self.xorMask = xorMask ?? 0
|
||||
self.xorMethod = xorMethod
|
||||
xorMask = xorMethod?.mask
|
||||
}
|
||||
|
||||
// MARK: LinkInterface
|
||||
|
@ -81,7 +85,12 @@ class NETCPLink: LinkInterface {
|
|||
var newBuffer = buffer
|
||||
newBuffer.append(contentsOf: data)
|
||||
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)
|
||||
self.loopReadPackets(queue, newBuffer, handler)
|
||||
|
||||
|
@ -91,14 +100,22 @@ class NETCPLink: LinkInterface {
|
|||
}
|
||||
|
||||
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
|
||||
completionHandler?(error)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
completionHandler?(error)
|
||||
}
|
||||
|
@ -106,7 +123,8 @@ class NETCPLink: LinkInterface {
|
|||
}
|
||||
|
||||
extension NETCPSocket: LinkProducer {
|
||||
public func link(xorMask: UInt8?) -> LinkInterface {
|
||||
return NETCPLink(impl: impl, maxPacketSize: nil, xorMask: xorMask)
|
||||
public func link(userObject: Any?) -> LinkInterface {
|
||||
let xorMethod = userObject as? OpenVPN.XORMethod
|
||||
return NETCPLink(impl: impl, maxPacketSize: nil, xorMethod: xorMethod)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,18 +27,20 @@ import Foundation
|
|||
import NetworkExtension
|
||||
import TunnelKitCore
|
||||
import TunnelKitAppExtension
|
||||
import TunnelKitOpenVPNCore
|
||||
import TunnelKitOpenVPNProtocol
|
||||
|
||||
class NEUDPLink: LinkInterface {
|
||||
private let impl: NWUDPSession
|
||||
|
||||
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.maxDatagrams = maxDatagrams ?? 200
|
||||
self.xorMask = xorMask ?? 0
|
||||
xor = XORProcessor(method: xorMethod)
|
||||
}
|
||||
|
||||
// MARK: LinkInterface
|
||||
|
@ -68,12 +70,8 @@ class NEUDPLink: LinkInterface {
|
|||
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
|
||||
if let packets = packets {
|
||||
packetsToUse = self.xor.processPackets(packets, outbound: false)
|
||||
}
|
||||
queue.sync {
|
||||
handler(packetsToUse, error)
|
||||
|
@ -82,26 +80,14 @@ class NEUDPLink: LinkInterface {
|
|||
}
|
||||
|
||||
func writePacket(_ packet: Data, completionHandler: ((Error?) -> Void)?) {
|
||||
var dataToUse: Data
|
||||
if xorMask != 0 {
|
||||
dataToUse = Data(bytes: packet.map { $0 ^ xorMask }, count: packet.count)
|
||||
} else {
|
||||
dataToUse = packet
|
||||
}
|
||||
let dataToUse = xor.processPacket(packet, outbound: true)
|
||||
impl.writeDatagram(dataToUse) { error in
|
||||
completionHandler?(error)
|
||||
}
|
||||
}
|
||||
|
||||
func writePackets(_ packets: [Data], completionHandler: ((Error?) -> Void)?) {
|
||||
var packetsToUse: [Data]
|
||||
if xorMask != 0 {
|
||||
packetsToUse = packets.map { packet in
|
||||
Data(bytes: packet.map { $0 ^ xorMask }, count: packet.count)
|
||||
}
|
||||
} else {
|
||||
packetsToUse = packets
|
||||
}
|
||||
let packetsToUse = xor.processPackets(packets, outbound: true)
|
||||
impl.writeMultipleDatagrams(packetsToUse) { error in
|
||||
completionHandler?(error)
|
||||
}
|
||||
|
@ -109,7 +95,8 @@ class NEUDPLink: LinkInterface {
|
|||
}
|
||||
|
||||
extension NEUDPSocket: LinkProducer {
|
||||
public func link(xorMask: UInt8?) -> LinkInterface {
|
||||
return NEUDPLink(impl: impl, maxDatagrams: nil, xorMask: xorMask)
|
||||
public func link(userObject: Any?) -> LinkInterface {
|
||||
let xorMethod = userObject as? OpenVPN.XORMethod
|
||||
return NEUDPLink(impl: impl, maxDatagrams: nil, xorMethod: xorMethod)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -408,10 +408,10 @@ extension OpenVPNTunnelProvider: GenericSocketDelegate {
|
|||
return
|
||||
}
|
||||
if session.canRebindLink() {
|
||||
session.rebindLink(producer.link(xorMask: cfg.configuration.xorMask))
|
||||
session.rebindLink(producer.link(userObject: cfg.configuration.xorMethod))
|
||||
reasserting = false
|
||||
} 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.
|
||||
public var renegotiatesAfter: TimeInterval?
|
||||
|
||||
/// A byte to xor all packet payloads with.
|
||||
public var xorMask: UInt8?
|
||||
|
||||
// MARK: Client
|
||||
|
||||
/// The list of server endpoints.
|
||||
|
@ -314,6 +311,11 @@ extension OpenVPN {
|
|||
/// Server settings that must not be pulled.
|
||||
public var noPullMask: [PullMask]?
|
||||
|
||||
// MARK: Extra
|
||||
|
||||
/// The method to follow in regards to the XOR patch.
|
||||
public var xorMethod: XORMethod?
|
||||
|
||||
/**
|
||||
Creates a `ConfigurationBuilder`.
|
||||
|
||||
|
@ -348,7 +350,6 @@ extension OpenVPN {
|
|||
keepAliveInterval: keepAliveInterval,
|
||||
keepAliveTimeout: keepAliveTimeout,
|
||||
renegotiatesAfter: renegotiatesAfter,
|
||||
xorMask: xorMask,
|
||||
remotes: remotes,
|
||||
checksEKU: checksEKU,
|
||||
checksSANHost: checksSANHost,
|
||||
|
@ -376,7 +377,8 @@ extension OpenVPN {
|
|||
proxyAutoConfigurationURL: proxyAutoConfigurationURL,
|
||||
proxyBypassDomains: proxyBypassDomains,
|
||||
routingPolicies: routingPolicies,
|
||||
noPullMask: noPullMask
|
||||
noPullMask: noPullMask,
|
||||
xorMethod: xorMethod
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -434,9 +436,6 @@ extension OpenVPN {
|
|||
/// - Seealso: `ConfigurationBuilder.renegotiatesAfter`
|
||||
public let renegotiatesAfter: TimeInterval?
|
||||
|
||||
/// - Seealso: `ConfigurationBuilder.xorMask`
|
||||
public let xorMask: UInt8?
|
||||
|
||||
/// - Seealso: `ConfigurationBuilder.remotes`
|
||||
public let remotes: [Endpoint]?
|
||||
|
||||
|
@ -521,6 +520,9 @@ extension OpenVPN {
|
|||
/// - Seealso: `ConfigurationBuilder.noPullMask`
|
||||
public let noPullMask: [PullMask]?
|
||||
|
||||
/// - Seealso: `ConfigurationBuilder.xorMethod`
|
||||
public let xorMethod: XORMethod?
|
||||
|
||||
// MARK: Shortcuts
|
||||
|
||||
public var fallbackCipher: Cipher {
|
||||
|
@ -597,7 +599,6 @@ extension OpenVPN.Configuration {
|
|||
builder.keepAliveInterval = keepAliveInterval
|
||||
builder.keepAliveTimeout = keepAliveTimeout
|
||||
builder.renegotiatesAfter = renegotiatesAfter
|
||||
builder.xorMask = xorMask
|
||||
builder.remotes = remotes
|
||||
builder.checksEKU = checksEKU
|
||||
builder.checksSANHost = checksSANHost
|
||||
|
@ -626,6 +627,7 @@ extension OpenVPN.Configuration {
|
|||
builder.proxyBypassDomains = proxyBypassDomains
|
||||
builder.routingPolicies = routingPolicies
|
||||
builder.noPullMask = noPullMask
|
||||
builder.xorMethod = xorMethod
|
||||
return builder
|
||||
}
|
||||
}
|
||||
|
|
|
@ -65,8 +65,6 @@ 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\\-]+>")
|
||||
|
@ -121,6 +119,10 @@ extension OpenVPN {
|
|||
|
||||
static let routeNoPull = NSRegularExpression("^route-nopull")
|
||||
|
||||
// MARK: Extra
|
||||
|
||||
static let xorInfo = NSRegularExpression("^scramble +(xormask|xorptrpos|reverse|obfuscate)[\\s]?([^\\s]+)?")
|
||||
|
||||
// MARK: Unsupported
|
||||
|
||||
// static let fragment = NSRegularExpression("^fragment +\\d+")
|
||||
|
@ -266,7 +268,6 @@ extension OpenVPN {
|
|||
var optKeepAliveSeconds: TimeInterval?
|
||||
var optKeepAliveTimeoutSeconds: TimeInterval?
|
||||
var optRenegotiateAfterSeconds: TimeInterval?
|
||||
var optXorMask: UInt8?
|
||||
//
|
||||
var optDefaultProto: SocketType?
|
||||
var optDefaultPort: UInt16?
|
||||
|
@ -294,6 +295,8 @@ extension OpenVPN {
|
|||
var optProxyBypass: [String]?
|
||||
var optRedirectGateway: Set<RedirectGateway>?
|
||||
var optRouteNoPull: Bool?
|
||||
//
|
||||
var optXorMethod: XORMethod?
|
||||
|
||||
log.verbose("Configuration file:")
|
||||
for line in lines {
|
||||
|
@ -518,13 +521,6 @@ extension OpenVPN {
|
|||
}
|
||||
optRenegotiateAfterSeconds = TimeInterval(arg)
|
||||
}
|
||||
Regex.xorMask.enumerateSpacedArguments(in: line) {
|
||||
isHandled = true
|
||||
if $0.count != 2 {
|
||||
return
|
||||
}
|
||||
optXorMask = Character($0[1]).asciiValue
|
||||
}
|
||||
|
||||
// MARK: Client
|
||||
|
||||
|
@ -713,6 +709,36 @@ extension OpenVPN {
|
|||
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 {
|
||||
|
@ -821,7 +847,6 @@ extension OpenVPN {
|
|||
sessionBuilder.randomizeEndpoint = optRandomizeEndpoint
|
||||
sessionBuilder.randomizeHostnames = optRandomizeHostnames
|
||||
sessionBuilder.mtu = optMTU
|
||||
sessionBuilder.xorMask = optXorMask
|
||||
|
||||
// MARK: Server
|
||||
|
||||
|
@ -939,6 +964,10 @@ extension OpenVPN {
|
|||
sessionBuilder.routingPolicies = [RoutingPolicy](policies)
|
||||
}
|
||||
|
||||
// MARK: Extra
|
||||
|
||||
sessionBuilder.xorMethod = optXorMethod
|
||||
|
||||
//
|
||||
|
||||
return Result(
|
||||
|
|
|
@ -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 {
|
||||
let cfg = try OpenVPN.ConfigurationParser.parsed(fromLines: ["scramble xormask F"])
|
||||
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(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 {
|
||||
|
|
|
@ -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