Minimize dependencies of VPN implementations (#1057)

### OpenVPN

- Make CPassepartoutCryptoOpenSSL agnostic of PassepartoutKit
- Move Allocation/ZeroingData from PassepartoutKit to package for
internal use
  - Rename ZeroingData.count to .length for consistency with NSData
  - Duplicate some Data manipulation code in CryptoOpenSSL
- Retain a simplified version of ZeroingData in PassepartoutKit
(AutoerasingData)

### WireGuard

- Make WireGuardKit imports `internal`
This commit is contained in:
Davide 2025-01-15 09:39:58 +01:00 committed by GitHub
parent 53ee7ba457
commit 7b8dbfe84a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
54 changed files with 1019 additions and 270 deletions

View File

@ -133,10 +133,10 @@ extension OpenVPN.XORMethod: StyledLocalizableEntity {
private var longDescription: String {
switch self {
case .xormask(let mask):
return "\(shortDescription) \(mask.zData.toHex())"
return "\(shortDescription) \(mask.toHex())"
case .obfuscate(let mask):
return "\(shortDescription) \(mask.zData.toHex())"
return "\(shortDescription) \(mask.toHex())"
default:
return shortDescription

@ -1 +1 @@
Subproject commit 358cd688354a7ab4f6ad46a471b4ab192525a692
Subproject commit c4901c59970d8314ba75fa7453c016c3508f4074

View File

@ -31,14 +31,14 @@ let package = Package(
targets: [
.target(
name: "CPassepartoutCryptoOpenSSL",
dependencies: [
"openssl-apple",
"PassepartoutKit-Framework"
]
dependencies: ["openssl-apple",]
),
.target(
name: "CPassepartoutOpenVPNOpenSSL",
dependencies: ["CPassepartoutCryptoOpenSSL"],
dependencies: [
"CPassepartoutCryptoOpenSSL",
"PassepartoutKit-Framework"
],
exclude: [
"lib/COPYING",
"lib/Makefile",
@ -58,7 +58,7 @@ let package = Package(
]
),
.testTarget(
name: "PassepartoutCryptoOpenSSLTests",
name: "CPassepartoutCryptoOpenSSLTests",
dependencies: ["PassepartoutCryptoOpenSSL"]
),
.testTarget(

View File

@ -25,10 +25,4 @@
#import "Crypto.h"
#define MAX_BLOCK_SIZE 16 // AES only, block is 128-bit
size_t pp_alloc_crypto_capacity(size_t size, size_t overhead) {
// encryption, byte-alignment, overhead (e.g. IV, digest)
return 2 * size + MAX_BLOCK_SIZE + overhead;
}
NSString *const PassepartoutCryptoErrorDomain = @"PassepartoutCrypto";

View File

@ -34,11 +34,12 @@
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <PassepartoutKit/PassepartoutKit.h>
#import <openssl/evp.h>
#import "Allocation.h"
#import "Crypto.h"
#import "CryptoAEAD.h"
#import "CryptoMacros.h"
#import "ZeroingData.h"
@interface CryptoAEAD ()
@ -75,8 +76,8 @@
self.cipherCtxEnc = EVP_CIPHER_CTX_new();
self.cipherCtxDec = EVP_CIPHER_CTX_new();
self.cipherIVEnc = pp_alloc(self.cipherIVLength);
self.cipherIVDec = pp_alloc(self.cipherIVLength);
self.cipherIVEnc = pp_alloc_crypto(self.cipherIVLength);
self.cipherIVDec = pp_alloc_crypto(self.cipherIVLength);
self.mappedError = ^NSError *(CryptoAEADError errorCode) {
return [NSError errorWithDomain:PassepartoutCryptoErrorDomain code:0 userInfo:nil];
@ -116,7 +117,7 @@
- (void)configureEncryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey
{
NSParameterAssert(cipherKey.count >= self.cipherKeyLength);
NSParameterAssert(cipherKey.length >= self.cipherKeyLength);
NSParameterAssert(hmacKey);
EVP_CIPHER_CTX_reset(self.cipherCtxEnc);
@ -157,7 +158,7 @@
- (void)configureDecryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey
{
NSParameterAssert(cipherKey.count >= self.cipherKeyLength);
NSParameterAssert(cipherKey.length >= self.cipherKeyLength);
NSParameterAssert(hmacKey);
EVP_CIPHER_CTX_reset(self.cipherCtxDec);

View File

@ -34,12 +34,13 @@
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <PassepartoutKit/PassepartoutKit.h>
#import <openssl/evp.h>
#import <openssl/rand.h>
#import "Allocation.h"
#import "Crypto.h"
#import "CryptoCBC.h"
#import "CryptoMacros.h"
#import "ZeroingData.h"
const NSInteger CryptoCBCMaxHMACLength = 100;
@ -103,7 +104,7 @@ const NSInteger CryptoCBCMaxHMACLength = 100;
macParams[1] = OSSL_PARAM_construct_end();
self.macParams = macParams;
self.bufferDecHMAC = pp_alloc(CryptoCBCMaxHMACLength);
self.bufferDecHMAC = pp_alloc_crypto(CryptoCBCMaxHMACLength);
self.mappedError = ^NSError *(CryptoCBCError errorCode) {
return [NSError errorWithDomain:PassepartoutCryptoErrorDomain code:0 userInfo:nil];
@ -147,16 +148,16 @@ const NSInteger CryptoCBCMaxHMACLength = 100;
- (void)configureEncryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey
{
NSParameterAssert(hmacKey);
NSParameterAssert(hmacKey.count >= self.hmacKeyLength);
NSParameterAssert(hmacKey.length >= self.hmacKeyLength);
if (self.cipher) {
NSParameterAssert(cipherKey.count >= self.cipherKeyLength);
NSParameterAssert(cipherKey.length >= self.cipherKeyLength);
EVP_CIPHER_CTX_reset(self.cipherCtxEnc);
EVP_CipherInit(self.cipherCtxEnc, self.cipher, cipherKey.bytes, NULL, 1);
}
self.hmacKeyEnc = [[ZeroingData alloc] initWithBytes:hmacKey.bytes count:self.hmacKeyLength];
self.hmacKeyEnc = [[ZeroingData alloc] initWithBytes:hmacKey.bytes length:self.hmacKeyLength];
}
- (BOOL)encryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags * _Nullable)flags error:(NSError * _Nullable __autoreleasing * _Nullable)error
@ -188,7 +189,7 @@ const NSInteger CryptoCBCMaxHMACLength = 100;
l1 = (int)length;
}
EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(self.mac);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyEnc.bytes, self.hmacKeyEnc.count, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyEnc.bytes, self.hmacKeyEnc.length, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_update(ctx, outIV, l1 + l2 + self.cipherIVLength);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_final(ctx, dest, &l3, self.digestLength);
EVP_MAC_CTX_free(ctx);
@ -203,16 +204,16 @@ const NSInteger CryptoCBCMaxHMACLength = 100;
- (void)configureDecryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey
{
NSParameterAssert(hmacKey);
NSParameterAssert(hmacKey.count >= self.hmacKeyLength);
NSParameterAssert(hmacKey.length >= self.hmacKeyLength);
if (self.cipher) {
NSParameterAssert(cipherKey.count >= self.cipherKeyLength);
NSParameterAssert(cipherKey.length >= self.cipherKeyLength);
EVP_CIPHER_CTX_reset(self.cipherCtxDec);
EVP_CipherInit(self.cipherCtxDec, self.cipher, cipherKey.bytes, NULL, 0);
}
self.hmacKeyDec = [[ZeroingData alloc] initWithBytes:hmacKey.bytes count:self.hmacKeyLength];
self.hmacKeyDec = [[ZeroingData alloc] initWithBytes:hmacKey.bytes length:self.hmacKeyLength];
}
- (BOOL)decryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags * _Nullable)flags error:(NSError * _Nullable __autoreleasing * _Nullable)error
@ -223,7 +224,7 @@ const NSInteger CryptoCBCMaxHMACLength = 100;
int code = 1;
EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(self.mac);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyDec.bytes, self.hmacKeyDec.count, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyDec.bytes, self.hmacKeyDec.length, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_update(ctx, bytes + self.digestLength, length - self.digestLength);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_final(ctx, self.bufferDecHMAC, &l1, self.digestLength);
EVP_MAC_CTX_free(ctx);
@ -257,7 +258,7 @@ const NSInteger CryptoCBCMaxHMACLength = 100;
int code = 1;
EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(self.mac);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyDec.bytes, self.hmacKeyDec.count, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyDec.bytes, self.hmacKeyDec.length, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_update(ctx, bytes + self.digestLength, length - self.digestLength);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_final(ctx, self.bufferDecHMAC, &l1, self.digestLength);
EVP_MAC_CTX_free(ctx);

View File

@ -23,11 +23,12 @@
// along with PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
#import <PassepartoutKit/PassepartoutKit.h>
#import <openssl/evp.h>
#import "Allocation.h"
#import "Crypto.h"
#import "CryptoCTR.h"
#import "CryptoMacros.h"
#import "ZeroingData.h"
@interface CryptoCTR ()
@ -92,7 +93,7 @@
macParams[1] = OSSL_PARAM_construct_end();
self.macParams = macParams;
self.bufferDecHMAC = pp_alloc(self.tagLength);
self.bufferDecHMAC = pp_alloc_crypto(self.tagLength);
self.mappedError = ^NSError *(CryptoCTRError errorCode) {
return [NSError errorWithDomain:PassepartoutCryptoErrorDomain code:0 userInfo:nil];
@ -137,13 +138,13 @@
- (void)configureEncryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey
{
NSParameterAssert(hmacKey);
NSParameterAssert(hmacKey.count >= self.hmacKeyLength);
NSParameterAssert(cipherKey.count >= self.cipherKeyLength);
NSParameterAssert(hmacKey.length >= self.hmacKeyLength);
NSParameterAssert(cipherKey.length >= self.cipherKeyLength);
EVP_CIPHER_CTX_reset(self.cipherCtxEnc);
EVP_CipherInit(self.cipherCtxEnc, self.cipher, cipherKey.bytes, NULL, 1);
self.hmacKeyEnc = [[ZeroingData alloc] initWithBytes:hmacKey.bytes count:self.hmacKeyLength];
self.hmacKeyEnc = [[ZeroingData alloc] initWithBytes:hmacKey.bytes length:self.hmacKeyLength];
}
- (BOOL)encryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags * _Nullable)flags error:(NSError * _Nullable __autoreleasing * _Nullable)error
@ -156,7 +157,7 @@
int code = 1;
EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(self.mac);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyEnc.bytes, self.hmacKeyEnc.count, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyEnc.bytes, self.hmacKeyEnc.length, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_update(ctx, flags->ad, flags->adLength);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_update(ctx, bytes, length);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_final(ctx, dest, &l3, self.digestLength);
@ -178,13 +179,13 @@
- (void)configureDecryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey
{
NSParameterAssert(hmacKey);
NSParameterAssert(hmacKey.count >= self.hmacKeyLength);
NSParameterAssert(cipherKey.count >= self.cipherKeyLength);
NSParameterAssert(hmacKey.length >= self.hmacKeyLength);
NSParameterAssert(cipherKey.length >= self.cipherKeyLength);
EVP_CIPHER_CTX_reset(self.cipherCtxDec);
EVP_CipherInit(self.cipherCtxDec, self.cipher, cipherKey.bytes, NULL, 0);
self.hmacKeyDec = [[ZeroingData alloc] initWithBytes:hmacKey.bytes count:self.hmacKeyLength];
self.hmacKeyDec = [[ZeroingData alloc] initWithBytes:hmacKey.bytes length:self.hmacKeyLength];
}
- (BOOL)decryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags * _Nullable)flags error:(NSError * _Nullable __autoreleasing * _Nullable)error
@ -205,7 +206,7 @@
*destLength = l1 + l2;
EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(self.mac);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyDec.bytes, self.hmacKeyDec.count, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_init(ctx, self.hmacKeyDec.bytes, self.hmacKeyDec.length, self.macParams);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_update(ctx, flags->ad, flags->adLength);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_update(ctx, dest, *destLength);
CRYPTO_OPENSSL_TRACK_STATUS(code) EVP_MAC_final(ctx, self.bufferDecHMAC, &l3, self.digestLength);

View File

@ -0,0 +1,319 @@
//
// ZeroingData.m
// PassepartoutKit
//
// Created by Davide De Rosa on 4/28/17.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of PassepartoutKit.
//
// PassepartoutKit 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.
//
// PassepartoutKit 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 PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright (c) 2018-Present Private Internet Access
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "ZeroingData.h"
#import "Allocation.h"
@interface ZeroingData () {
uint8_t *_bytes;
}
@end
@implementation ZeroingData
- (instancetype)init
{
return [self initWithBytes:NULL length:0];
}
- (instancetype)initWithLength:(NSInteger)length
{
if ((self = [super init])) {
_length = length;
_bytes = pp_alloc_crypto(length);
bzero(_bytes, _length);
}
return self;
}
- (instancetype)initWithBytes:(const uint8_t *)bytes length:(NSInteger)length
{
// NSParameterAssert(bytes);
if ((self = [super init])) {
_length = length;
_bytes = pp_alloc_crypto(length);
memcpy(_bytes, bytes, length);
}
return self;
}
- (instancetype)initWithBytesNoCopy:(uint8_t *)bytes length:(NSInteger)length
{
NSParameterAssert(bytes);
if ((self = [super init])) {
_length = length;
_bytes = bytes;
}
return self;
}
- (instancetype)initWithUInt8:(uint8_t)uint8
{
if ((self = [super init])) {
_length = 1;
_bytes = pp_alloc_crypto(_length);
_bytes[0] = uint8;
}
return self;
}
- (instancetype)initWithUInt16:(uint16_t)uint16
{
if ((self = [super init])) {
_length = 2;
_bytes = pp_alloc_crypto(_length);
_bytes[0] = (uint16 & 0xff);
_bytes[1] = (uint16 >> 8);
}
return self;
}
- (instancetype)initWithData:(NSData *)data
{
return [self initWithData:data offset:0 length:data.length];
}
- (instancetype)initWithData:(NSData *)data offset:(NSInteger)offset length:(NSInteger)length
{
NSParameterAssert(data);
NSParameterAssert(length >= 0);
NSParameterAssert(offset + length <= data.length);
if ((self = [super init])) {
_length = length;
_bytes = pp_alloc_crypto(length);
memcpy(_bytes, data.bytes + offset, length);
}
return self;
}
- (instancetype)initWithString:(NSString *)string nullTerminated:(BOOL)nullTerminated
{
NSParameterAssert(string);
if ((self = [super init])) {
const int stringLength = (int)string.length;
_length = stringLength + (nullTerminated ? 1 : 0);
_bytes = pp_alloc_crypto(_length);
const char *stringBytes = [string cStringUsingEncoding:NSUTF8StringEncoding];
if (stringBytes) {
memcpy(_bytes, stringBytes, stringLength);
} else {
NSAssert(stringBytes != NULL, @"Cannot encode string to UTF-8");
bzero(_bytes, stringLength);
}
if (nullTerminated) {
_bytes[stringLength] = '\0';
}
}
return self;
}
- (instancetype)copy
{
return [[ZeroingData alloc] initWithBytes:_bytes length:_length];
}
- (void)dealloc
{
bzero(_bytes, _length);
free(_bytes);
}
- (const uint8_t *)bytes
{
return _bytes;
}
- (uint8_t *)mutableBytes
{
return _bytes;
}
- (void)appendData:(ZeroingData *)other
{
NSParameterAssert(other);
const NSInteger newLength = _length + other.length;
uint8_t *newBytes = pp_alloc_crypto(newLength);
memcpy(newBytes, _bytes, _length);
memcpy(newBytes + _length, other.bytes, other.length);
bzero(_bytes, _length);
free(_bytes);
_bytes = newBytes;
_length = newLength;
}
- (void)truncateToSize:(NSInteger)size
{
NSParameterAssert(size <= _length);
uint8_t *newBytes = pp_alloc_crypto(size);
memcpy(newBytes, _bytes, size);
bzero(_bytes, _length);
free(_bytes);
_bytes = newBytes;
_length = size;
}
- (void)removeUntilOffset:(NSInteger)until
{
NSParameterAssert(until <= _length);
const NSInteger newLength = _length - until;
uint8_t *newBytes = pp_alloc_crypto(newLength);
memcpy(newBytes, _bytes + until, newLength);
bzero(_bytes, _length);
free(_bytes);
_bytes = newBytes;
_length = newLength;
}
- (void)zero
{
bzero(_bytes, _length);
}
- (ZeroingData *)appendingData:(ZeroingData *)other
{
NSParameterAssert(other);
const NSInteger newLength = _length + other.length;
uint8_t *newBytes = pp_alloc_crypto(newLength);
memcpy(newBytes, _bytes, _length);
memcpy(newBytes + _length, other.bytes, other.length);
return [[ZeroingData alloc] initWithBytesNoCopy:newBytes length:newLength];
}
- (ZeroingData *)withOffset:(NSInteger)offset length:(NSInteger)length
{
NSParameterAssert(offset + length <= _length);
uint8_t *newBytes = pp_alloc_crypto(length);
memcpy(newBytes, _bytes + offset, length);
return [[ZeroingData alloc] initWithBytesNoCopy:newBytes length:length];
}
- (uint16_t)UInt16ValueFromOffset:(NSInteger)from
{
NSParameterAssert(from + 2 <= _length);
uint16_t value = 0;
value |= _bytes[from];
value |= _bytes[from + 1] << 8;
return value;
}
- (uint16_t)networkUInt16ValueFromOffset:(NSInteger)from
{
NSParameterAssert(from + 2 <= _length);
uint16_t value = 0;
value |= _bytes[from];
value |= _bytes[from + 1] << 8;
return CFSwapInt16BigToHost(value);
}
- (NSString *)nullTerminatedStringFromOffset:(NSInteger)from
{
NSParameterAssert(from <= _length);
NSInteger nullOffset = NSNotFound;
for (NSInteger i = from; i < _length; ++i) {
if (_bytes[i] == 0) {
nullOffset = i;
break;
}
}
if (nullOffset == NSNotFound) {
return nil;
}
const NSInteger stringLength = nullOffset - from;
return [[NSString alloc] initWithBytes:_bytes length:stringLength encoding:NSUTF8StringEncoding];
}
- (BOOL)isEqual:(id)object
{
NSParameterAssert(object);
if (![object isKindOfClass:[ZeroingData class]]) {
return NO;
}
ZeroingData *other = (ZeroingData *)object;
if (other.length != _length) {
return NO;
}
return !memcmp(_bytes, other.bytes, _length);
}
- (BOOL)isEqualToData:(NSData *)data
{
NSParameterAssert(data);
if (data.length != _length) {
return NO;
}
return !memcmp(_bytes, data.bytes, _length);
}
- (NSData *)toData
{
return [NSData dataWithBytes:_bytes length:_length];
}
- (NSString *)toHex
{
const NSUInteger capacity = _length * 2;
NSMutableString *hexString = [[NSMutableString alloc] initWithCapacity:capacity];
for (int i = 0; i < _length; ++i) {
[hexString appendFormat:@"%02x", _bytes[i]];
}
return hexString;
}
@end

View File

@ -1,8 +1,8 @@
//
// CryptoMacros.h
// Allocation.h
// PassepartoutKit
//
// Created by Davide De Rosa on 7/6/18.
// Created by Davide De Rosa on 3/3/17.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
@ -36,13 +36,24 @@
#import <Foundation/Foundation.h>
#define CRYPTO_OPENSSL_SUCCESS(ret) (ret > 0)
#define CRYPTO_OPENSSL_TRACK_STATUS(ret) if (ret > 0) ret =
#define CRYPTO_OPENSSL_RETURN_STATUS(ret, raised)\
if (ret <= 0) {\
if (error) {\
*error = raised;\
}\
return NO;\
}\
return YES;
static inline void *_Nullable pp_alloc_crypto(size_t size) {
void *memory = malloc(size);
if (!memory) {
NSCAssert(NO, @"pp_alloc_crypto: malloc() call failed");
abort();
return NULL;
}
return memory;
}
#define MAX_BLOCK_SIZE 16 // AES only, block is 128-bit
/// - Parameters:
/// - size: The base number of bytes.
/// - overhead: The extra number of bytes.
/// - Returns: The number of bytes to store a crypto buffer safely.
static inline size_t pp_alloc_crypto_capacity(size_t size, size_t overhead) {
// encryption, byte-alignment, overhead (e.g. IV, digest)
return 2 * size + MAX_BLOCK_SIZE + overhead;
}

View File

@ -36,17 +36,18 @@
#import <Foundation/Foundation.h>
@class ZeroingData;
extern NSString *_Nonnull const PassepartoutCryptoErrorDomain;
NS_ASSUME_NONNULL_BEGIN
extern NSString *const PassepartoutCryptoErrorDomain;
/// - Parameters:
/// - size: The base number of bytes.
/// - overhead: The extra number of bytes.
/// - Returns: The number of bytes to store a crypto buffer safely.
size_t pp_alloc_crypto_capacity(size_t size, size_t overhead);
#define CRYPTO_OPENSSL_SUCCESS(ret) (ret > 0)
#define CRYPTO_OPENSSL_TRACK_STATUS(ret) if (ret > 0) ret =
#define CRYPTO_OPENSSL_RETURN_STATUS(ret, raised)\
if (ret <= 0) {\
if (error) {\
*error = raised;\
}\
return NO;\
}\
return YES;
/// Custom flags for encryption routines.
typedef struct {
@ -66,64 +67,3 @@ typedef struct {
/// Enable testable (predictable) behavior.
BOOL forTesting;
} CryptoFlags;
@protocol Crypto
/// The digest length or 0.
- (int)digestLength;
/// The tag length or 0.
- (int)tagLength;
/// The preferred encryption capacity.
/// - Parameter length: The number of bytes to encrypt.
- (NSInteger)encryptionCapacityWithLength:(NSInteger)length;
@end
@protocol Encrypter <Crypto>
/// Configures the object.
/// - Parameters:
/// - cipherKey: The cipher key data.
/// - hmacKey: The HMAC key data.
- (void)configureEncryptionWithCipherKey:(nullable ZeroingData *)cipherKey hmacKey:(nullable ZeroingData *)hmacKey;
/// Encrypts a buffer.
/// - Parameters:
/// - bytes: Bytes to encrypt.
/// - length: The number of bytes.
/// - dest: The destination buffer.
/// - destLength: The number of bytes written to ``dest``.
/// - flags: The optional encryption flags.
- (BOOL)encryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags *_Nullable)flags error:(NSError **)error;
@end
@protocol Decrypter <Crypto>
/// Configures the object.
/// - Parameters:
/// - cipherKey: The cipher key data.
/// - hmacKey: The HMAC key data.
- (void)configureDecryptionWithCipherKey:(nullable ZeroingData *)cipherKey hmacKey:(nullable ZeroingData *)hmacKey;
/// Decrypts a buffer.
/// - Parameters:
/// - bytes: Bytes to decrypt.
/// - length: The number of bytes.
/// - dest: The destination buffer.
/// - destLength: The number of bytes written to ``dest``.
/// - flags: The optional encryption flags.
- (BOOL)decryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags *_Nullable)flags error:(NSError **)error;
/// Verifies an encrypted buffer.
/// - Parameters:
/// - bytes: Bytes to decrypt.
/// - length: The number of bytes.
/// - flags: The optional encryption flags.
- (BOOL)verifyBytes:(const uint8_t *)bytes length:(NSInteger)length flags:(const CryptoFlags *_Nullable)flags error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END

View File

@ -36,6 +36,7 @@
#import <Foundation/Foundation.h>
#import "Crypto.h"
#import "CryptoProtocols.h"
NS_ASSUME_NONNULL_BEGIN

View File

@ -36,6 +36,7 @@
#import <Foundation/Foundation.h>
#import "Crypto.h"
#import "CryptoProtocols.h"
NS_ASSUME_NONNULL_BEGIN

View File

@ -25,6 +25,7 @@
#import <Foundation/Foundation.h>
#import "Crypto.h"
#import "CryptoProtocols.h"
NS_ASSUME_NONNULL_BEGIN

View File

@ -0,0 +1,91 @@
//
// CryptoProtocols.swift
// PassepartoutKit
//
// Created by Davide De Rosa on 1/14/25.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of PassepartoutKit.
//
// PassepartoutKit 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.
//
// PassepartoutKit 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 PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
#import <Foundation/Foundation.h>
@class ZeroingData;
NS_ASSUME_NONNULL_BEGIN
@protocol Crypto
/// The digest length or 0.
- (int)digestLength;
/// The tag length or 0.
- (int)tagLength;
/// The preferred encryption capacity.
/// - Parameter length: The number of bytes to encrypt.
- (NSInteger)encryptionCapacityWithLength:(NSInteger)length;
@end
@protocol Encrypter <Crypto>
/// Configures the object.
/// - Parameters:
/// - cipherKey: The cipher key data.
/// - hmacKey: The HMAC key data.
- (void)configureEncryptionWithCipherKey:(nullable ZeroingData *)cipherKey hmacKey:(nullable ZeroingData *)hmacKey;
/// Encrypts a buffer.
/// - Parameters:
/// - bytes: Bytes to encrypt.
/// - length: The number of bytes.
/// - dest: The destination buffer.
/// - destLength: The number of bytes written to ``dest``.
/// - flags: The optional encryption flags.
- (BOOL)encryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags *_Nullable)flags error:(NSError **)error;
@end
@protocol Decrypter <Crypto>
/// Configures the object.
/// - Parameters:
/// - cipherKey: The cipher key data.
/// - hmacKey: The HMAC key data.
- (void)configureDecryptionWithCipherKey:(nullable ZeroingData *)cipherKey hmacKey:(nullable ZeroingData *)hmacKey;
/// Decrypts a buffer.
/// - Parameters:
/// - bytes: Bytes to decrypt.
/// - length: The number of bytes.
/// - dest: The destination buffer.
/// - destLength: The number of bytes written to ``dest``.
/// - flags: The optional encryption flags.
- (BOOL)decryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength flags:(const CryptoFlags *_Nullable)flags error:(NSError **)error;
/// Verifies an encrypted buffer.
/// - Parameters:
/// - bytes: Bytes to decrypt.
/// - length: The number of bytes.
/// - flags: The optional encryption flags.
- (BOOL)verifyBytes:(const uint8_t *)bytes length:(NSInteger)length flags:(const CryptoFlags *_Nullable)flags error:(NSError **)error;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,76 @@
//
// ZeroingData.h
// PassepartoutKit
//
// Created by Davide De Rosa on 4/28/17.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of PassepartoutKit.
//
// PassepartoutKit 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.
//
// PassepartoutKit 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 PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
// This file incorporates work covered by the following copyright and
// permission notice:
//
// Copyright (c) 2018-Present Private Internet Access
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/// A wrapper to handle data buffers safely. Any formerly allocated bytes are erased before release.
@interface ZeroingData : NSObject
@property (nonatomic, readonly) const uint8_t *bytes;
@property (nonatomic, readonly) uint8_t *mutableBytes;
@property (nonatomic, readonly) NSInteger length;
- (instancetype)initWithLength:(NSInteger)length;
- (instancetype)initWithBytes:(nullable const uint8_t *)bytes length:(NSInteger)length;
- (instancetype)initWithUInt8:(uint8_t)uint8;
- (instancetype)initWithUInt16:(uint16_t)uint16;
- (instancetype)initWithData:(NSData *)data;
- (instancetype)initWithData:(NSData *)data offset:(NSInteger)offset length:(NSInteger)length;
- (instancetype)initWithString:(NSString *)string nullTerminated:(BOOL)nullTerminated;
- (instancetype)copy;
- (void)appendData:(ZeroingData *)other;
- (void)truncateToSize:(NSInteger)size;
- (void)removeUntilOffset:(NSInteger)until;
- (void)zero;
- (ZeroingData *)appendingData:(ZeroingData *)other;
- (ZeroingData *)withOffset:(NSInteger)offset length:(NSInteger)length;
- (uint16_t)UInt16ValueFromOffset:(NSInteger)from;
- (uint16_t)networkUInt16ValueFromOffset:(NSInteger)from;
- (nullable NSString *)nullTerminatedStringFromOffset:(NSInteger)from;
- (BOOL)isEqualToData:(NSData *)data;
- (NSData *)toData; // XXX: unsafe
- (NSString *)toHex;
@end
NS_ASSUME_NONNULL_END

View File

@ -37,7 +37,7 @@
#import <Foundation/Foundation.h>
#import "CryptoCBC+OpenVPN.h"
#import "CryptoMacros.h"
#import "Crypto.h"
#import "Errors.h"
#import "PacketMacros.h"

View File

@ -37,6 +37,7 @@
#import <PassepartoutKit/PassepartoutKit.h>
#import <arpa/inet.h>
#import "Allocation.h"
#import "DataPath.h"
#import "DataPathCrypto.h"
#import "Errors.h"
@ -107,11 +108,11 @@
self.outPackets = [[NSMutableArray alloc] initWithCapacity:maxPackets];
self.outPacketId = 0;
self.encBufferCapacity = 65000;
self.encBuffer = pp_alloc(self.encBufferCapacity);
self.encBuffer = pp_alloc_crypto(self.encBufferCapacity);
self.inPackets = [[NSMutableArray alloc] initWithCapacity:maxPackets];
self.decBufferCapacity = 65000;
self.decBuffer = pp_alloc(self.decBufferCapacity);
self.decBuffer = pp_alloc_crypto(self.decBufferCapacity);
if (usesReplayProtection) {
self.inReplay = [[ReplayProtector alloc] init];
}
@ -144,7 +145,7 @@
bzero(self.encBuffer, self.encBufferCapacity);
free(self.encBuffer);
self.encBufferCapacity = neededCapacity;
self.encBuffer = pp_alloc(self.encBufferCapacity);
self.encBuffer = pp_alloc_crypto(self.encBufferCapacity);
}
- (void)adjustDecBufferToPacketSize:(int)size
@ -156,7 +157,7 @@
bzero(self.decBuffer, self.decBufferCapacity);
free(self.decBuffer);
self.decBufferCapacity = neededCapacity;
self.decBuffer = pp_alloc(self.decBufferCapacity);
self.decBuffer = pp_alloc_crypto(self.decBufferCapacity);
}
- (uint8_t *)encBufferAligned

View File

@ -65,7 +65,7 @@ static const NSInteger CryptoCTRPayloadLength = PacketOpcodeLength + PacketSessi
#pragma mark Initialization
- (instancetype)initWithSeed:(const uint8_t *)seed length:(NSInteger)length
- (instancetype)initWithSeed:(ZeroingData *)seed
{
if ((self = [super init])) {
unsigned char x[1];
@ -73,7 +73,7 @@ static const NSInteger CryptoCTRPayloadLength = PacketOpcodeLength + PacketSessi
if (RAND_bytes(x, 1) != 1) {
return nil;
}
RAND_seed(seed, (int)length);
RAND_seed(seed.bytes, (int)seed.length);
}
return self;
}

View File

@ -39,6 +39,7 @@
#import <openssl/x509v3.h>
#import <openssl/err.h>
#import "Allocation.h"
#import "Errors.h"
#import "OSSLTLSBox.h"
@ -128,7 +129,7 @@ static BIO *create_BIO_from_PEM(NSString *pem) {
NSAssert(self.options == nil, @"Already configured");
self.options = options;
self.onFailure = onFailure;
self.bufferCipherText = pp_alloc(self.options.bufferLength);
self.bufferCipherText = pp_alloc_crypto(self.options.bufferLength);
return YES;
}

View File

@ -24,6 +24,7 @@
//
#import <Foundation/Foundation.h>
#import <PassepartoutKit/PassepartoutKit.h>
#import "OpenVPNCryptoProtocol.h"

View File

@ -34,8 +34,7 @@
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import <PassepartoutKit/PassepartoutKit.h>
#import "Allocation.h"
#import "ReplayProtector.h"
@import CPassepartoutCryptoOpenSSL;
@ -61,7 +60,7 @@
{
if ((self = [super init])) {
self.highestPacketId = 0;
self.bitmap = pp_alloc(BITMAP_LEN * sizeof(uint32_t));
self.bitmap = pp_alloc_crypto(BITMAP_LEN * sizeof(uint32_t));
bzero(self.bitmap, BITMAP_LEN * sizeof(uint32_t));
}
return self;

View File

@ -38,12 +38,14 @@
#import "OpenVPNCryptoProtocol.h"
@class ZeroingData;
NS_ASSUME_NONNULL_BEGIN
// WARNING: not thread-safe!
@interface OSSLCryptoBox : NSObject <OpenVPNCryptoProtocol>
- (nullable instancetype)initWithSeed:(const uint8_t *)seed length:(NSInteger)length;
- (nullable instancetype)initWithSeed:(ZeroingData *)seed;
@end

View File

@ -24,8 +24,8 @@
//
#import <Foundation/Foundation.h>
#import <PassepartoutKit/PassepartoutKit.h>
#import "Crypto.h"
#import "CryptoProvider.h"
NS_ASSUME_NONNULL_BEGIN

View File

@ -23,10 +23,10 @@
// along with PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
#import <PassepartoutKit/PassepartoutKit.h>
#import <Foundation/Foundation.h>
#import "XORMethodNative.h"
#import "ZeroingData.h"
NS_ASSUME_NONNULL_BEGIN

View File

@ -30,9 +30,9 @@
static inline void xor_mask(uint8_t *dst, const uint8_t *src, ZeroingData *xorMask, size_t length)
{
if (xorMask.count > 0) {
if (xorMask.length > 0) {
for (size_t i = 0; i < length; ++i) {
dst[i] = src[i] ^ ((uint8_t *)(xorMask.bytes))[i % xorMask.count];
dst[i] = src[i] ^ ((uint8_t *)(xorMask.bytes))[i % xorMask.length];
}
return;
}

View File

@ -36,7 +36,6 @@
internal import CPassepartoutCryptoOpenSSL
import Foundation
import PassepartoutKit
extension Encrypter {
@ -75,3 +74,12 @@ extension Decrypter {
}
}
}
private extension UnsafeRawBufferPointer {
var bytePointer: UnsafePointer<Element> {
guard let address = bindMemory(to: Element.self).baseAddress else {
fatalError("Cannot bind to self")
}
return address
}
}

View File

@ -40,7 +40,7 @@ import PassepartoutKit
fileprivate extension ZeroingData {
func appendSized(_ buf: ZeroingData) {
append(Z(UInt16(buf.count).bigEndian))
append(Z(UInt16(buf.length).bigEndian))
append(buf)
}
}
@ -67,9 +67,9 @@ final class Authenticator {
var sslVersion: String?
init(prng: PRNGProtocol, _ username: String?, _ password: String?) {
preMaster = prng.safeData(length: Constants.preMasterLength).zData
random1 = prng.safeData(length: Constants.randomLength).zData
random2 = prng.safeData(length: Constants.randomLength).zData
preMaster = prng.safeData(length: Constants.preMasterLength)
random1 = prng.safeData(length: Constants.randomLength)
random2 = prng.safeData(length: Constants.randomLength)
// XXX: not 100% secure, can't erase input username/password
if let username = username, let password = password {
@ -162,7 +162,7 @@ final class Authenticator {
pp_log(.openvpn, .info, "TLS.auth: Put plaintext \(raw.asSensitiveBytes)")
try into.putRawPlainText(raw.bytes, length: raw.count)
try into.putRawPlainText(raw.bytes, length: raw.length)
}
// MARK: Server replies
@ -175,30 +175,30 @@ final class Authenticator {
let prefixLength = ProtocolMacros.tlsPrefix.count
// TLS prefix + random (x2) + opts length [+ opts]
guard controlBuffer.count >= prefixLength + 2 * Constants.randomLength + 2 else {
guard controlBuffer.length >= prefixLength + 2 * Constants.randomLength + 2 else {
return false
}
let prefix = controlBuffer.withOffset(0, count: prefixLength)
let prefix = controlBuffer.withOffset(0, length: prefixLength)
guard prefix.isEqual(to: ProtocolMacros.tlsPrefix) else {
throw OpenVPNSessionError.wrongControlDataPrefix
}
var offset = ProtocolMacros.tlsPrefix.count
let serverRandom1 = controlBuffer.withOffset(offset, count: Constants.randomLength)
let serverRandom1 = controlBuffer.withOffset(offset, length: Constants.randomLength)
offset += Constants.randomLength
let serverRandom2 = controlBuffer.withOffset(offset, count: Constants.randomLength)
let serverRandom2 = controlBuffer.withOffset(offset, length: Constants.randomLength)
offset += Constants.randomLength
let serverOptsLength = Int(controlBuffer.networkUInt16Value(fromOffset: offset))
offset += 2
guard controlBuffer.count >= offset + serverOptsLength else {
guard controlBuffer.length >= offset + serverOptsLength else {
return false
}
let serverOpts = controlBuffer.withOffset(offset, count: serverOptsLength)
let serverOpts = controlBuffer.withOffset(offset, length: serverOptsLength)
offset += serverOptsLength
pp_log(.openvpn, .info, "TLS.auth: Parsed server random [\(serverRandom1.asSensitiveBytes), \(serverRandom2.asSensitiveBytes)]")

View File

@ -81,7 +81,7 @@ actor ControlChannel {
queue = BidirectionalState(withResetValue: [])
currentPacketId = BidirectionalState(withResetValue: 0)
pendingAcks = []
plainBuffer = Z(count: OpenVPNTLSOptionsDefaultBufferLength)
plainBuffer = Z(length: OpenVPNTLSOptionsDefaultBufferLength)
sentDates = [:]
}
}
@ -245,6 +245,6 @@ extension ControlChannel {
func currentControlData(withTLS tls: OpenVPNTLSProtocol) throws -> ZeroingData {
var length = 0
try tls.pullRawPlainText(plainBuffer.mutableBytes, length: &length)
return plainBuffer.withOffset(0, count: length)
return plainBuffer.withOffset(0, length: length)
}
}

View File

@ -23,6 +23,7 @@
// along with PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
internal import CPassepartoutCryptoOpenSSL
internal import CPassepartoutOpenVPNOpenSSL
import Foundation
import PassepartoutKit

View File

@ -129,7 +129,7 @@ private extension OpenVPNCryptoProtocol {
var keysArray = [ZeroingData]()
for i in 0..<Constants.keysCount {
let offset = i * Constants.keyLength
let zbuf = keysData.withOffset(offset, count: Constants.keyLength)
let zbuf = keysData.withOffset(offset, length: Constants.keyLength)
keysArray.append(zbuf)
}
@ -156,16 +156,16 @@ private extension OpenVPNCryptoProtocol {
if let ssi = parameters.serverSessionId {
seed.append(Z(ssi))
}
let len = parameters.secret.count / 2
let lenx = len + (parameters.secret.count & 1)
let secret1 = parameters.secret.withOffset(0, count: lenx)
let secret2 = parameters.secret.withOffset(len, count: lenx)
let len = parameters.secret.length / 2
let lenx = len + (parameters.secret.length & 1)
let secret1 = parameters.secret.withOffset(0, length: lenx)
let secret2 = parameters.secret.withOffset(len, length: lenx)
let hash1 = try keysHash("md5", secret1, seed, parameters.size)
let hash2 = try keysHash("sha1", secret2, seed, parameters.size)
let prf = Z()
for i in 0..<hash1.count {
for i in 0..<hash1.length {
let h1 = hash1.bytes[i]
let h2 = hash2.bytes[i]
@ -176,13 +176,13 @@ private extension OpenVPNCryptoProtocol {
func keysHash(_ digestName: String, _ secret: ZeroingData, _ seed: ZeroingData, _ size: Int) throws -> ZeroingData {
let out = Z()
let buffer = Z(count: maxHmacLength)
let buffer = Z(length: maxHmacLength)
var chain = try hmac(buffer, digestName, secret, seed)
while out.count < size {
while out.length < size {
out.append(try hmac(buffer, digestName, secret, chain.appending(seed)))
chain = try hmac(buffer, digestName, secret, chain)
}
return out.withOffset(0, count: size)
return out.withOffset(0, length: size)
}
func hmac(_ buffer: ZeroingData, _ digestName: String, _ secret: ZeroingData, _ data: ZeroingData) throws -> ZeroingData {
@ -191,13 +191,13 @@ private extension OpenVPNCryptoProtocol {
try hmac(
withDigestName: digestName,
secret: secret.bytes,
secretLength: secret.count,
secretLength: secret.length,
data: data.bytes,
dataLength: data.count,
dataLength: data.length,
hmac: buffer.mutableBytes,
hmacLength: &length
)
return buffer.withOffset(0, count: length)
return buffer.withOffset(0, length: length)
}
}

View File

@ -24,6 +24,7 @@
//
import Combine
internal import CPassepartoutCryptoOpenSSL
internal import CPassepartoutOpenVPNOpenSSL
import Foundation
import PassepartoutKit

View File

@ -59,7 +59,7 @@ struct XORProcessor {
- Returns: The packet after XOR processing.
**/
func processPacket(_ packet: Data, outbound: Bool) -> Data {
guard let method = method else {
guard let method else {
return packet
}
switch method {
@ -85,7 +85,7 @@ struct XORProcessor {
extension XORProcessor {
private static func xormask(packet: Data, mask: ZeroingData) -> Data {
Data(packet.enumerated().map { (index, byte) in
byte ^ mask.bytes[index % mask.count]
byte ^ mask.bytes[index % mask.length]
})
}

View File

@ -0,0 +1,57 @@
//
// ZeroingData+Extensions.swift
// PassepartoutKit
//
// Created by Davide De Rosa on 1/14/25.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of PassepartoutKit.
//
// PassepartoutKit 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.
//
// PassepartoutKit 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 PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
internal import CPassepartoutCryptoOpenSSL
import Foundation
import PassepartoutKit
extension PRNGProtocol {
func safeData(length: Int) -> ZeroingData {
precondition(length > 0)
let randomBytes = UnsafeMutablePointer<UInt8>.allocate(capacity: length)
defer {
bzero(randomBytes, length)
randomBytes.deallocate()
}
guard SecRandomCopyBytes(kSecRandomDefault, length, randomBytes) == errSecSuccess else {
fatalError("SecRandomCopyBytes failed")
}
return Z(Data(bytes: randomBytes, count: length))
}
}
extension SecureData {
var zData: ZeroingData {
Z(innerData.toData())
}
}
extension ZeroingData: @retroactive SensitiveDebugStringConvertible {
func debugDescription(withSensitiveData: Bool) -> String {
withSensitiveData ? "[\(length) bytes, \(toHex())]" : "[\(length) bytes]"
}
}

View File

@ -0,0 +1,59 @@
//
// ZeroingData.swift
// PassepartoutKit
//
// Created by Davide De Rosa on 1/8/25.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of PassepartoutKit.
//
// PassepartoutKit 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.
//
// PassepartoutKit 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 PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
internal import CPassepartoutCryptoOpenSSL
import Foundation
func Z() -> ZeroingData {
ZeroingData()
}
func Z(length: Int) -> ZeroingData {
ZeroingData(length: length)
}
func Z(bytes: UnsafePointer<UInt8>, length: Int) -> ZeroingData {
ZeroingData(bytes: bytes, length: length)
}
func Z(_ uint8: UInt8) -> ZeroingData {
ZeroingData(uInt8: uint8)
}
func Z(_ uint16: UInt16) -> ZeroingData {
ZeroingData(uInt16: uint16)
}
func Z(_ data: Data) -> ZeroingData {
ZeroingData(data: data)
}
func Z(_ data: Data, _ offset: Int, _ length: Int) -> ZeroingData {
ZeroingData(data: data, offset: offset, length: length)
}
func Z(_ string: String, nullTerminated: Bool) -> ZeroingData {
ZeroingData(string: string, nullTerminated: nullTerminated)
}

View File

@ -46,7 +46,7 @@ extension OpenVPNConnection {
}
let cryptoFactory = { @Sendable in
let seed = prng.safeData(length: 64)
guard let box = OSSLCryptoBox(seed: seed.zData.bytes, length: seed.zData.count) else {
guard let box = OSSLCryptoBox(seed: seed) else {
fatalError("Unable to create OSSLCryptoBox")
}
return box

View File

@ -25,7 +25,6 @@
internal import CPassepartoutCryptoOpenSSL
@testable import PassepartoutCryptoOpenSSL
import PassepartoutKit
import XCTest
final class CryptoAEADTests: XCTestCase {
@ -54,11 +53,11 @@ final class CryptoAEADTests: XCTestCase {
private extension CryptoAEADTests {
var cipherKey: ZeroingData {
ZeroingData(count: 32)
ZeroingData(length: 32)
}
var hmacKey: ZeroingData {
ZeroingData(count: 32)
ZeroingData(length: 32)
}
var plainData: Data {

View File

@ -25,7 +25,6 @@
internal import CPassepartoutCryptoOpenSSL
@testable import PassepartoutCryptoOpenSSL
import PassepartoutKit
import XCTest
final class CryptoCBCTests: XCTestCase {
@ -93,11 +92,11 @@ final class CryptoCBCTests: XCTestCase {
private extension CryptoCBCTests {
var cipherKey: ZeroingData {
ZeroingData(count: 32)
ZeroingData(length: 32)
}
var hmacKey: ZeroingData {
ZeroingData(count: 32)
ZeroingData(length: 32)
}
var plainData: Data {

View File

@ -25,7 +25,6 @@
internal import CPassepartoutCryptoOpenSSL
@testable import PassepartoutCryptoOpenSSL
import PassepartoutKit
import XCTest
final class CryptoCTRTests: XCTestCase {
@ -57,11 +56,11 @@ final class CryptoCTRTests: XCTestCase {
private extension CryptoCTRTests {
var cipherKey: ZeroingData {
ZeroingData(count: 32)
ZeroingData(length: 32)
}
var hmacKey: ZeroingData {
ZeroingData(count: 32)
ZeroingData(length: 32)
}
var plainData: Data {

View File

@ -1,8 +1,8 @@
//
// CryptoMacros.m
// Extensions.swift
// PassepartoutKit
//
// Created by Davide De Rosa on 3/1/24.
// Created by Davide De Rosa on 1/14/25.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
@ -23,6 +23,22 @@
// along with PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
#import <Foundation/Foundation.h>
import Foundation
NSString *const PassepartoutCryptoErrorDomain = @"PassepartoutCrypto";
extension Data {
init(hex: String) {
assert(hex.count & 1 == 0)
var data = Data()
var index = hex.startIndex
while index < hex.endIndex {
let nextIndex = hex.index(index, offsetBy: 2)
if let byte = UInt8(hex[index..<nextIndex], radix: 16) {
data.append(byte)
} else {
break
}
index = nextIndex
}
self.init(data)
}
}

View File

@ -0,0 +1,114 @@
//
// ZeroingDataTests.swift
// PassepartoutKit
//
// Created by Davide De Rosa on 4/9/24.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of PassepartoutKit.
//
// PassepartoutKit 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.
//
// PassepartoutKit 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 PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
internal import CPassepartoutCryptoOpenSSL
import Foundation
import PassepartoutCryptoOpenSSL
import XCTest
final class ZeroingDataTests: XCTestCase {
func test_givenInput_whenInit_thenReturnsExpected() {
XCTAssertEqual(ZeroingData(length: 123).length, 123)
XCTAssertEqual(ZeroingData(bytes: [0x11, 0x22, 0x33, 0x44, 0x55], length: 3).length, 3)
XCTAssertEqual(ZeroingData(uInt8: UInt8(78)).length, 1)
XCTAssertEqual(ZeroingData(uInt16: UInt16(4756)).length, 2)
XCTAssertEqual(ZeroingData(data: Data(count: 12)).length, 12)
XCTAssertEqual(ZeroingData(data: Data(count: 12), offset: 3, length: 7).length, 7)
XCTAssertEqual(ZeroingData(string: "hello", nullTerminated: false).length, 5)
XCTAssertEqual(ZeroingData(string: "hello", nullTerminated: true).length, 6)
}
func test_givenData_whenOffset_thenReturnsExpected() {
let sut = ZeroingData(string: "Hello", nullTerminated: true)
XCTAssertEqual(sut.networkUInt16Value(fromOffset: 3), 0x6c6f)
XCTAssertEqual(sut.nullTerminatedString(fromOffset: 0), "Hello")
XCTAssertEqual(sut.withOffset(3, length: 2), ZeroingData(string: "lo", nullTerminated: false))
}
func test_givenData_whenAppend_thenIsAppended() {
let sut = ZeroingData(string: "this_data", nullTerminated: false)
let other = ZeroingData(string: "that_data", nullTerminated: false)
let merged = sut.copy()
merged.append(other)
XCTAssertEqual(merged, ZeroingData(string: "this_datathat_data", nullTerminated: false))
XCTAssertEqual(merged, sut.appending(other))
}
func test_givenData_whenTruncate_thenIsTruncated() {
let data = Data(hex: "438ac4729847fb3975345983")
let sut = ZeroingData(data: data)
sut.truncate(toSize: 5)
XCTAssertEqual(sut.length, 5)
XCTAssertEqual(sut.toData(), data.subdata(in: 0..<5))
}
func test_givenData_whenRemove_thenIsRemoved() {
let data = Data(hex: "438ac4729847fb3975345983")
let sut = ZeroingData(data: data)
sut.remove(untilOffset: 5)
XCTAssertEqual(sut.length, data.count - 5)
XCTAssertEqual(sut.toData(), data.subdata(in: 5..<data.count))
}
func test_givenData_whenZero_thenIsZeroedOut() {
let data = Data(hex: "438ac4729847fb3975345983")
let sut = ZeroingData(data: data)
sut.zero()
XCTAssertEqual(sut.length, data.count)
XCTAssertEqual(sut.toData(), Data(repeating: 0, count: data.count))
}
func test_givenData_whenCompareEqual_thenIsEqual() {
let data = Data(hex: "438ac4729847fb3975345983")
let sut = ZeroingData(data: data)
let other = ZeroingData(data: data)
XCTAssertEqual(sut, other)
XCTAssertEqual(sut, sut.copy())
XCTAssertEqual(other, other.copy())
XCTAssertEqual(sut.copy(), other.copy())
sut.append(ZeroingData(length: 1))
XCTAssertNotEqual(sut, other)
other.append(ZeroingData(length: 1))
XCTAssertEqual(sut, other)
}
func test_givenData_whenManipulate_thenDataIsExpected() {
let z1 = ZeroingData()
z1.append(ZeroingData(data: Data(hex: "12345678")))
z1.append(ZeroingData(data: Data(hex: "abcdef")))
let z2 = z1.withOffset(2, length: 3) // 5678ab
let z3 = z2.appending(ZeroingData(data: Data(hex: "aaddcc"))) // 5678abaaddcc
XCTAssertEqual(z1.toData(), Data(hex: "12345678abcdef"))
XCTAssertEqual(z2.toData(), Data(hex: "5678ab"))
XCTAssertEqual(z3.toData(), Data(hex: "5678abaaddcc"))
}
}

View File

@ -0,0 +1,42 @@
//
// ZeroingDataExtensionsTests.swift
// PassepartoutKit
//
// Created by Davide De Rosa on 1/14/25.
// Copyright (c) 2024 Davide De Rosa. All rights reserved.
//
// https://github.com/passepartoutvpn
//
// This file is part of PassepartoutKit.
//
// PassepartoutKit 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.
//
// PassepartoutKit 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 PassepartoutKit. If not, see <http://www.gnu.org/licenses/>.
//
import Foundation
import PassepartoutKit
@testable import PassepartoutOpenVPNOpenSSL
import XCTest
final class ZeroingDataExtensionsTests: XCTestCase {
func test_givenPRNG_whenGenerateSafeData_thenHasGivenLength() {
let sut = SecureRandom()
XCTAssertEqual(sut.safeData(length: 500).length, 500)
}
func test_givenZeroingData_whenAsSensitive_thenOmitsSensitiveData() throws {
let sut = Z(Data(hex: "12345678abcdef"))
XCTAssertEqual(sut.debugDescription(withSensitiveData: true), "[7 bytes, 12345678abcdef]")
XCTAssertEqual(sut.debugDescription(withSensitiveData: false), "[7 bytes]")
}
}

View File

@ -24,7 +24,7 @@
//
import Foundation
import WireGuardKit
internal import WireGuardKit
extension TunnelConfiguration.ParseError: LocalizedError {
public var errorDescription: String? {

View File

@ -1,4 +1,4 @@
import WireGuardKit
internal import WireGuardKit
// SPDX-License-Identifier: MIT
// Copyright © 2018-2021 WireGuard LLC. All Rights Reserved.

View File

@ -25,7 +25,7 @@
import Foundation
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
extension WireGuard.Configuration {
init(wgQuickConfig: String) throws {

View File

@ -25,7 +25,7 @@
import Foundation
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
extension PassepartoutKit.Endpoint {
init?(wg: WireGuardKit.Endpoint) {

View File

@ -25,7 +25,7 @@
import Foundation
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
extension WireGuard.LocalInterface {
init(wg: InterfaceConfiguration) throws {

View File

@ -25,7 +25,7 @@
import Foundation
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
// MARK: - Mapping

View File

@ -26,7 +26,7 @@
import Foundation
import Network
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
extension WireGuard.RemoteInterface {
init(wg: PeerConfiguration) throws {

View File

@ -26,7 +26,7 @@
import Foundation
import Network
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
extension Subnet {
init?(wg: IPAddressRange) {

View File

@ -25,7 +25,7 @@
import Foundation
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
public final class StandardWireGuardKeyGenerator: WireGuardKeyGenerator {
public init() {

View File

@ -25,7 +25,7 @@
import Foundation
import PassepartoutKit
import WireGuardKit
internal import WireGuardKit
/// Parses WireGuard configurations in `wg-quick` format.
public final class StandardWireGuardParser {

View File

@ -33,8 +33,8 @@ import Foundation
import NetworkExtension
import PassepartoutKit
import os
import WireGuardKit
import WireGuardKitGo
internal import WireGuardKit
internal import WireGuardKitGo
public final class WireGuardConnection: Connection {
private let statusSubject: CurrentValueSubject<ConnectionStatus, Error>
@ -50,7 +50,7 @@ public final class WireGuardConnection: Connection {
private var dataCountTimer: AnyCancellable?
private lazy var adapter: WireGuardAdapter = {
WireGuardAdapter(with: self, backend: WireGuardBackendGo()) { logLevel, message in
WireGuardAdapter(with: AdapterDelegate(connection: self), backend: WireGuardBackendGo()) { logLevel, message in
pp_log(.wireguard, osLogLevel: logLevel.osLogLevel, message)
}
}()
@ -156,34 +156,47 @@ public final class WireGuardConnection: Connection {
}
}
extension WireGuardConnection: WireGuardAdapterDelegate {
public func adapterShouldReassert(_ adapter: WireGuardAdapter, reasserting: Bool) {
if reasserting {
statusSubject.send(.connecting)
}
}
// MARK: - WireGuardAdapterDelegate
public func adapterShouldSetNetworkSettings(_ adapter: WireGuardAdapter, settings: NEPacketTunnelNetworkSettings, completionHandler: ((Error?) -> Void)?) {
let module = NESettingsModule(fullSettings: settings)
let addressObject = Address(rawValue: settings.tunnelRemoteAddress)
if addressObject == nil {
pp_log(.wireguard, .error, "Unable to parse remote tunnel address")
private extension WireGuardConnection {
final class AdapterDelegate: WireGuardAdapterDelegate {
private weak var connection: WireGuardConnection?
init(connection: WireGuardConnection) {
self.connection = connection
}
Task {
do {
try await controller.setTunnelSettings(with: TunnelRemoteInfo(
originalModuleId: moduleId,
address: addressObject,
modules: [module]
))
completionHandler?(nil)
pp_log(.wireguard, .info, "Tunnel interface is now UP")
statusSubject.send(.connected)
} catch {
completionHandler?(error)
pp_log(.wireguard, .error, "Unable to configure tunnel settings: \(error)")
statusSubject.send(.disconnected)
func adapterShouldReassert(_ adapter: WireGuardAdapter, reasserting: Bool) {
if reasserting {
connection?.statusSubject.send(.connecting)
}
}
func adapterShouldSetNetworkSettings(_ adapter: WireGuardAdapter, settings: NEPacketTunnelNetworkSettings, completionHandler: ((Error?) -> Void)?) {
guard let connection else {
return
}
let module = NESettingsModule(fullSettings: settings)
let addressObject = Address(rawValue: settings.tunnelRemoteAddress)
if addressObject == nil {
pp_log(.wireguard, .error, "Unable to parse remote tunnel address")
}
Task {
do {
try await connection.controller.setTunnelSettings(with: TunnelRemoteInfo(
originalModuleId: connection.moduleId,
address: addressObject,
modules: [module]
))
completionHandler?(nil)
pp_log(.wireguard, .info, "Tunnel interface is now UP")
connection.statusSubject.send(.connected)
} catch {
completionHandler?(error)
pp_log(.wireguard, .error, "Unable to configure tunnel settings: \(error)")
connection.statusSubject.send(.disconnected)
}
}
}
}

View File

@ -25,7 +25,7 @@
import PassepartoutKit
@testable import PassepartoutWireGuardGo
import WireGuardKit
internal import WireGuardKit
import XCTest
final class ParseErrorTests: XCTestCase {

View File

@ -12,13 +12,6 @@
"testTimeoutsEnabled" : true
},
"testTargets" : [
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutOpenVPNOpenSSL",
"identifier" : "PassepartoutCryptoOpenSSLTests",
"name" : "PassepartoutCryptoOpenSSLTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutKit",
@ -36,9 +29,9 @@
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutWireGuardGo",
"identifier" : "PassepartoutWireGuardGoTests",
"name" : "PassepartoutWireGuardGoTests"
"containerPath" : "container:Packages\/PassepartoutOpenVPNOpenSSL",
"identifier" : "PassepartoutOpenVPNOpenSSLTests",
"name" : "PassepartoutOpenVPNOpenSSLTests"
}
},
{
@ -51,36 +44,8 @@
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutKit",
"identifier" : "PassepartoutAPITests",
"name" : "PassepartoutAPITests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/App",
"identifier" : "CommonLibraryTests",
"name" : "CommonLibraryTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/App",
"identifier" : "LegacyV2Tests",
"name" : "LegacyV2Tests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutKit",
"identifier" : "PassepartoutWireGuardTests",
"name" : "PassepartoutWireGuardTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutKit",
"identifier" : "PassepartoutCoreTests",
"name" : "PassepartoutCoreTests"
"identifier" : "PassepartoutNETests",
"name" : "PassepartoutNETests"
}
},
{
@ -90,11 +55,25 @@
"name" : "AppUIMainTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/App",
"identifier" : "CommonLibraryTests",
"name" : "CommonLibraryTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutKit",
"identifier" : "PassepartoutNETests",
"name" : "PassepartoutNETests"
"identifier" : "PassepartoutCoreTests",
"name" : "PassepartoutCoreTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutKit",
"identifier" : "PassepartoutAPITests",
"name" : "PassepartoutAPITests"
}
},
{
@ -104,11 +83,32 @@
"name" : "UILibraryTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/App",
"identifier" : "LegacyV2Tests",
"name" : "LegacyV2Tests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutWireGuardGo",
"identifier" : "PassepartoutWireGuardGoTests",
"name" : "PassepartoutWireGuardGoTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutKit",
"identifier" : "PassepartoutWireGuardTests",
"name" : "PassepartoutWireGuardTests"
}
},
{
"target" : {
"containerPath" : "container:Packages\/PassepartoutOpenVPNOpenSSL",
"identifier" : "PassepartoutOpenVPNOpenSSLTests",
"name" : "PassepartoutOpenVPNOpenSSLTests"
"identifier" : "CPassepartoutCryptoOpenSSLTests",
"name" : "CPassepartoutCryptoOpenSSLTests"
}
}
],