From a430beb35f160b81cebddbc40e31c26b623a4792 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Fri, 19 Oct 2018 16:51:54 +0200 Subject: [PATCH 1/5] Improve Swift bridging of CryptoFlags --- TunnelKit/Sources/Core/Crypto.h | 8 ++++---- TunnelKit/Sources/Core/CryptoAEAD.m | 4 ++-- TunnelKitTests/EncryptionPerformanceTests.swift | 3 +-- TunnelKitTests/EncryptionTests.swift | 2 +- 4 files changed, 8 insertions(+), 9 deletions(-) diff --git a/TunnelKit/Sources/Core/Crypto.h b/TunnelKit/Sources/Core/Crypto.h index 5b71c19..109758c 100644 --- a/TunnelKit/Sources/Core/Crypto.h +++ b/TunnelKit/Sources/Core/Crypto.h @@ -44,10 +44,10 @@ NS_ASSUME_NONNULL_BEGIN @protocol DataPathDecrypter; typedef struct { - const uint8_t *iv; - int ivLength; - const uint8_t *ad; - int adLength; + const uint8_t *_Nullable iv; + NSInteger ivLength; + const uint8_t *_Nullable ad; + NSInteger adLength; } CryptoFlags; // WARNING: dest must be able to hold ciphertext diff --git a/TunnelKit/Sources/Core/CryptoAEAD.m b/TunnelKit/Sources/Core/CryptoAEAD.m index 0ee5627..d5f4e06 100644 --- a/TunnelKit/Sources/Core/CryptoAEAD.m +++ b/TunnelKit/Sources/Core/CryptoAEAD.m @@ -127,7 +127,7 @@ const NSInteger CryptoAEADTagLength = 16; memcpy(self.cipherIVEnc, flags->iv, flags->ivLength); TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxEnc, NULL, NULL, self.cipherIVEnc, -1); - TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, NULL, &x, flags->ad, flags->adLength); + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, NULL, &x, flags->ad, (int)flags->adLength); TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, dest + CryptoAEADTagLength, &l1, bytes, (int)length); TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxEnc, dest + CryptoAEADTagLength + l1, &l2); TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CIPHER_CTX_ctrl(self.cipherCtxEnc, EVP_CTRL_GCM_GET_TAG, CryptoAEADTagLength, dest); @@ -174,7 +174,7 @@ const NSInteger CryptoAEADTagLength = 16; TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxDec, NULL, NULL, self.cipherIVDec, -1); TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CIPHER_CTX_ctrl(self.cipherCtxDec, EVP_CTRL_GCM_SET_TAG, CryptoAEADTagLength, (uint8_t *)bytes); - TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, NULL, &x, flags->ad, flags->adLength); + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, NULL, &x, flags->ad, (int)flags->adLength); TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, dest, &l1, bytes + CryptoAEADTagLength, (int)length - CryptoAEADTagLength); TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxDec, dest + l1, &l2); diff --git a/TunnelKitTests/EncryptionPerformanceTests.swift b/TunnelKitTests/EncryptionPerformanceTests.swift index 75e1c89..b08a7f9 100644 --- a/TunnelKitTests/EncryptionPerformanceTests.swift +++ b/TunnelKitTests/EncryptionPerformanceTests.swift @@ -80,9 +80,8 @@ class EncryptionPerformanceTests: XCTestCase { // 0.684s func testGCMEncryption() { let suite = TestUtils.generateDataSuite(1000, 100000) - let iv: [UInt8] = [0x11, 0x22, 0x33, 0x44] let ad: [UInt8] = [0x11, 0x22, 0x33, 0x44] - var flags = CryptoFlags(iv: iv, ivLength: 4, ad: ad, adLength: 4) + var flags = CryptoFlags(iv: nil, ivLength: 0, ad: ad, adLength: ad.count) measure { for data in suite { let _ = try! self.gcmEncrypter.encryptData(data, flags: &flags) diff --git a/TunnelKitTests/EncryptionTests.swift b/TunnelKitTests/EncryptionTests.swift index 43d2132..90370ab 100644 --- a/TunnelKitTests/EncryptionTests.swift +++ b/TunnelKitTests/EncryptionTests.swift @@ -81,7 +81,7 @@ class EncryptionTests: XCTestCase { let packetId: [UInt8] = [0x56, 0x34, 0x12, 0x00] let ad: [UInt8] = [0x00, 0x12, 0x34, 0x56] - var flags = CryptoFlags(iv: packetId, ivLength: 4, ad: ad, adLength: 4) + var flags = CryptoFlags(iv: packetId, ivLength: packetId.count, ad: ad, adLength: ad.count) let plain = Data(hex: "00112233445566778899") let encrypted = try! client.encrypter().encryptData(plain, flags: &flags) let decrypted = try! server.decrypter().decryptData(encrypted, flags: &flags) From 3ec4a7d292c3dc59415dbdd741381362de56e784 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Thu, 20 Sep 2018 10:58:24 +0200 Subject: [PATCH 2/5] Implement AES-CTR encryption --- TunnelKit.xcodeproj/project.pbxproj | 12 ++ TunnelKit/Sources/Core/CryptoBox.m | 6 + TunnelKit/Sources/Core/CryptoCBC.m | 5 - TunnelKit/Sources/Core/CryptoCTR.h | 41 ++++++ TunnelKit/Sources/Core/CryptoCTR.m | 211 +++++++++++++++++++++++++++ TunnelKitTests/EncryptionTests.swift | 17 +++ 6 files changed, 287 insertions(+), 5 deletions(-) create mode 100644 TunnelKit/Sources/Core/CryptoCTR.h create mode 100644 TunnelKit/Sources/Core/CryptoCTR.m diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index 2dbb3a3..e287381 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -44,6 +44,10 @@ 0E39BCE9214B2AB60035E9DE /* ControlPacket.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */; }; 0E39BCEA214B2AB60035E9DE /* ControlPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCE7214B2AB60035E9DE /* ControlPacket.m */; }; 0E39BCEB214B2AB60035E9DE /* ControlPacket.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E39BCE7214B2AB60035E9DE /* ControlPacket.m */; }; + 0E3B15C72152B05E00984B17 /* CryptoCTR.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E3B15C52152B05E00984B17 /* CryptoCTR.h */; }; + 0E3B15C82152B05E00984B17 /* CryptoCTR.h in Headers */ = {isa = PBXBuildFile; fileRef = 0E3B15C52152B05E00984B17 /* CryptoCTR.h */; }; + 0E3B15C92152B05E00984B17 /* CryptoCTR.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B15C62152B05E00984B17 /* CryptoCTR.m */; }; + 0E3B15CA2152B05E00984B17 /* CryptoCTR.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B15C62152B05E00984B17 /* CryptoCTR.m */; }; 0E3E0F212108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; }; 0E3E0F222108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; }; 0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */; }; @@ -218,6 +222,8 @@ 0E3251C51F95770D00C108D9 /* TunnelKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = TunnelKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 0E39BCE6214B2AB60035E9DE /* ControlPacket.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ControlPacket.h; sourceTree = ""; }; 0E39BCE7214B2AB60035E9DE /* ControlPacket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ControlPacket.m; sourceTree = ""; }; + 0E3B15C52152B05E00984B17 /* CryptoCTR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoCTR.h; sourceTree = ""; }; + 0E3B15C62152B05E00984B17 /* CryptoCTR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoCTR.m; sourceTree = ""; }; 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PushReply.swift"; sourceTree = ""; }; 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSTests.swift; sourceTree = ""; }; 0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -459,6 +465,8 @@ 0EFEB4322006D3C800F81029 /* CryptoBox.m */, 0E07596D20EF79B400F38FD8 /* CryptoCBC.h */, 0E07595C20EF6D1400F38FD8 /* CryptoCBC.m */, + 0E3B15C52152B05E00984B17 /* CryptoCTR.h */, + 0E3B15C62152B05E00984B17 /* CryptoCTR.m */, 0E07596120EF733F00F38FD8 /* CryptoMacros.h */, 0EFEB4432006D3C800F81029 /* Data+Manipulation.swift */, 0EFEB4352006D3C800F81029 /* DataPath.h */, @@ -559,6 +567,7 @@ 0EFEB4592006D3C800F81029 /* Allocation.h in Headers */, 0EFEB4582006D3C800F81029 /* MSS.h in Headers */, 0E245D6C2137F73600B012A2 /* CompressionFramingNative.h in Headers */, + 0E3B15C72152B05E00984B17 /* CryptoCTR.h in Headers */, 0EFEB4602006D3C800F81029 /* DataPath.h in Headers */, 0E39BCE8214B2AB60035E9DE /* ControlPacket.h in Headers */, 0E07597E20F0060E00F38FD8 /* CryptoAEAD.h in Headers */, @@ -581,6 +590,7 @@ 0EEC49E320B5F7F6008FEB91 /* DataPath.h in Headers */, 0EF5CF282141E183004FF1BD /* CompressionFramingNative.h in Headers */, 0EEC49E820B5F7F6008FEB91 /* ReplayProtector.h in Headers */, + 0E3B15C82152B05E00984B17 /* CryptoCTR.h in Headers */, 0EEC49E920B5F7F6008FEB91 /* TLSBox.h in Headers */, 0E39BCE9214B2AB60035E9DE /* ControlPacket.h in Headers */, 0E07597F20F0060E00F38FD8 /* CryptoAEAD.h in Headers */, @@ -890,6 +900,7 @@ 0EE3B3E421471C3A0027AB17 /* StaticKey.swift in Sources */, 0EFEB4622006D3C800F81029 /* SecureRandom.swift in Sources */, 0EFEB45D2006D3C800F81029 /* CryptoBox.m in Sources */, + 0E3B15C92152B05E00984B17 /* CryptoCTR.m in Sources */, 0EBBF2FA2085061600E36B40 /* NETCPInterface.swift in Sources */, 0E0C2125212ED29D008AB282 /* SessionError.swift in Sources */, 0E12B2A821456C0200B4BAE9 /* ControlChannel.swift in Sources */, @@ -948,6 +959,7 @@ 0EE3B3E521471C3A0027AB17 /* StaticKey.swift in Sources */, 0EBBF3012085196000E36B40 /* NWTCPConnectionState+Description.swift in Sources */, 0EFEB4962006D7F300F81029 /* ProtocolMacros.swift in Sources */, + 0E3B15CA2152B05E00984B17 /* CryptoCTR.m in Sources */, 0EFEB48A2006D7C400F81029 /* TunnelKitProvider.swift in Sources */, 0E0C2126212ED29D008AB282 /* SessionError.swift in Sources */, 0E12B2A921456C0200B4BAE9 /* ControlChannel.swift in Sources */, diff --git a/TunnelKit/Sources/Core/CryptoBox.m b/TunnelKit/Sources/Core/CryptoBox.m index c1ddb1f..bbdd825 100644 --- a/TunnelKit/Sources/Core/CryptoBox.m +++ b/TunnelKit/Sources/Core/CryptoBox.m @@ -45,6 +45,7 @@ #import "CryptoCBC.h" #import "CryptoAEAD.h" +#import "CryptoCTR.h" @interface CryptoBox () @@ -122,6 +123,11 @@ self.encrypter = gcm; self.decrypter = gcm; } + else if ([self.cipherAlgorithm hasSuffix:@"-ctr"]) { + CryptoCTR *ctr = [[CryptoCTR alloc] initWithCipherName:self.cipherAlgorithm digestName:self.digestAlgorithm]; + self.encrypter = ctr; + self.decrypter = ctr; + } // not supported else { if (error) { diff --git a/TunnelKit/Sources/Core/CryptoCBC.m b/TunnelKit/Sources/Core/CryptoCBC.m index 7a3ddde..b9fc3b9 100644 --- a/TunnelKit/Sources/Core/CryptoCBC.m +++ b/TunnelKit/Sources/Core/CryptoCBC.m @@ -113,11 +113,6 @@ const NSInteger CryptoCBCMaxHMACLength = 100; self.digest = NULL; } -- (int)extraLength -{ - return 0; -} - - (NSInteger)encryptionCapacityWithLength:(NSInteger)length { return safe_crypto_capacity(length, self.digestLength + self.cipherIVLength); diff --git a/TunnelKit/Sources/Core/CryptoCTR.h b/TunnelKit/Sources/Core/CryptoCTR.h new file mode 100644 index 0000000..a627078 --- /dev/null +++ b/TunnelKit/Sources/Core/CryptoCTR.h @@ -0,0 +1,41 @@ +// +// CryptoCTR.h +// TunnelKit +// +// Created by Davide De Rosa on 9/18/18. +// Copyright (c) 2018 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// 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 . +// + +#import + +#import "Crypto.h" +#import "DataPathCrypto.h" + +NS_ASSUME_NONNULL_BEGIN + +extern const NSInteger CryptoCTRADLength; + +@interface CryptoCTR : NSObject + +- (instancetype)initWithCipherName:(nullable NSString *)cipherName digestName:(NSString *)digestName; + +@end + +NS_ASSUME_NONNULL_END diff --git a/TunnelKit/Sources/Core/CryptoCTR.m b/TunnelKit/Sources/Core/CryptoCTR.m new file mode 100644 index 0000000..e321fc8 --- /dev/null +++ b/TunnelKit/Sources/Core/CryptoCTR.m @@ -0,0 +1,211 @@ +// +// CryptoCTR.m +// TunnelKit +// +// Created by Davide De Rosa on 9/18/18. +// Copyright (c) 2018 Davide De Rosa. All rights reserved. +// +// https://github.com/keeshux +// +// 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 . +// + +#import +#import +#import + +#import "CryptoCTR.h" +#import "CryptoMacros.h" +#import "PacketMacros.h" +#import "Allocation.h" +#import "Errors.h" + +const NSInteger CryptoCTRADLength = PacketOpcodeLength + PacketSessionIdLength + PacketReplayIdLength + PacketReplayTimestampLength; +const NSInteger CryptoCTRTagLength = 32; + +@interface CryptoCTR () + +@property (nonatomic, unsafe_unretained) const EVP_CIPHER *cipher; +@property (nonatomic, unsafe_unretained) const EVP_MD *digest; +@property (nonatomic, assign) int cipherKeyLength; +@property (nonatomic, assign) int cipherIVLength; +@property (nonatomic, assign) int hmacKeyLength; +@property (nonatomic, assign) int digestLength; + +@property (nonatomic, unsafe_unretained) EVP_CIPHER_CTX *cipherCtxEnc; +@property (nonatomic, unsafe_unretained) EVP_CIPHER_CTX *cipherCtxDec; +@property (nonatomic, unsafe_unretained) HMAC_CTX *hmacCtxEnc; +@property (nonatomic, unsafe_unretained) HMAC_CTX *hmacCtxDec; +@property (nonatomic, unsafe_unretained) uint8_t *bufferDecHMAC; + +@end + +@implementation CryptoCTR + +- (instancetype)initWithCipherName:(NSString *)cipherName digestName:(NSString *)digestName +{ + NSParameterAssert(cipherName && [[cipherName uppercaseString] hasSuffix:@"CTR"]); + NSParameterAssert(digestName); + + self = [super init]; + if (self) { + self.cipher = EVP_get_cipherbyname([cipherName cStringUsingEncoding:NSASCIIStringEncoding]); + NSAssert(self.cipher, @"Unknown cipher '%@'", cipherName); + self.digest = EVP_get_digestbyname([digestName cStringUsingEncoding:NSASCIIStringEncoding]); + NSAssert(self.digest, @"Unknown digest '%@'", digestName); + + self.cipherKeyLength = EVP_CIPHER_key_length(self.cipher); + self.cipherIVLength = EVP_CIPHER_iv_length(self.cipher); + // as seen in OpenVPN's crypto_openssl.c:md_kt_size() + self.hmacKeyLength = EVP_MD_size(self.digest); + self.digestLength = EVP_MD_size(self.digest); + NSAssert(self.digestLength == CryptoCTRTagLength, @"Expected digest size to be tag length (%ld)", CryptoCTRTagLength); + + self.cipherCtxEnc = EVP_CIPHER_CTX_new(); + self.cipherCtxDec = EVP_CIPHER_CTX_new(); + self.hmacCtxEnc = HMAC_CTX_new(); + self.hmacCtxDec = HMAC_CTX_new(); + self.bufferDecHMAC = allocate_safely(self.digestLength); + } + return self; +} + +- (void)dealloc +{ + EVP_CIPHER_CTX_free(self.cipherCtxEnc); + EVP_CIPHER_CTX_free(self.cipherCtxDec); + HMAC_CTX_free(self.hmacCtxEnc); + HMAC_CTX_free(self.hmacCtxDec); + bzero(self.bufferDecHMAC, self.digestLength); + free(self.bufferDecHMAC); + + self.cipher = NULL; + self.digest = NULL; +} + +- (NSInteger)encryptionCapacityWithLength:(NSInteger)length +{ + return safe_crypto_capacity(length, PacketOpcodeLength + PacketSessionIdLength + PacketReplayIdLength + PacketReplayTimestampLength + CryptoCTRTagLength); +} + +#pragma mark Encrypter + +- (void)configureEncryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey +{ + NSParameterAssert(hmacKey); + NSParameterAssert(hmacKey.count >= self.hmacKeyLength); + NSParameterAssert(cipherKey.count >= self.cipherKeyLength); + + EVP_CIPHER_CTX_reset(self.cipherCtxEnc); + EVP_CipherInit(self.cipherCtxEnc, self.cipher, cipherKey.bytes, NULL, 1); + + HMAC_CTX_reset(self.hmacCtxEnc); + HMAC_Init_ex(self.hmacCtxEnc, hmacKey.bytes, self.hmacKeyLength, self.digest, NULL); +} + +- (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 +{ + NSParameterAssert(flags); + + uint8_t *outEncrypted = dest + CryptoCTRTagLength; + int l1 = 0, l2 = 0; + unsigned int l3 = 0; + int code = 1; + + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Init_ex(self.hmacCtxEnc, NULL, 0, NULL, NULL); + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxEnc, flags->ad, flags->adLength); + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxEnc, bytes, length); + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Final(self.hmacCtxEnc, dest, &l3); + + NSAssert(l3 == CryptoCTRTagLength, @"Incorrect digest size"); + + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxEnc, NULL, NULL, dest, -1); + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, outEncrypted, &l1, bytes, (int)length); + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxEnc, outEncrypted + l1, &l2); + + *destLength = CryptoCTRTagLength + l1 + l2; + + TUNNEL_CRYPTO_RETURN_STATUS(code) +} + +- (id)dataPathEncrypter +{ + [NSException raise:NSInvalidArgumentException format:@"DataPathEncryption not supported"]; + return nil; +} + +#pragma mark Decrypter + +- (void)configureDecryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey +{ + NSParameterAssert(hmacKey); + NSParameterAssert(hmacKey.count >= self.hmacKeyLength); + NSParameterAssert(cipherKey.count >= self.cipherKeyLength); + + EVP_CIPHER_CTX_reset(self.cipherCtxDec); + EVP_CipherInit(self.cipherCtxDec, self.cipher, cipherKey.bytes, NULL, 0); + + HMAC_CTX_reset(self.hmacCtxDec); + HMAC_Init_ex(self.hmacCtxDec, hmacKey.bytes, self.hmacKeyLength, self.digest, NULL); +} + +- (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 +{ + NSParameterAssert(flags); + NSAssert(self.cipher, @"No cipher provided"); + + const uint8_t *iv = bytes; + const uint8_t *encrypted = bytes + CryptoCTRTagLength; + int l1 = 0, l2 = 0; + unsigned int l3 = 0; + int code = 1; + + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxDec, NULL, NULL, iv, -1); + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, dest, &l1, encrypted, (int)length - CryptoCTRTagLength); + TUNNEL_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxDec, dest + l1, &l2); + + *destLength = l1 + l2; + + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Init_ex(self.hmacCtxDec, NULL, 0, NULL, NULL); + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxDec, flags->ad, flags->adLength); + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxDec, dest, *destLength); + TUNNEL_CRYPTO_TRACK_STATUS(code) HMAC_Final(self.hmacCtxDec, self.bufferDecHMAC, &l3); + + NSAssert(l3 == CryptoCTRTagLength, @"Incorrect digest size"); + + if (TUNNEL_CRYPTO_SUCCESS(code) && CRYPTO_memcmp(self.bufferDecHMAC, bytes, CryptoCTRTagLength) != 0) { + if (error) { + *error = TunnelKitErrorWithCode(TunnelKitErrorCodeCryptoBoxHMAC); + } + return NO; + } + + TUNNEL_CRYPTO_RETURN_STATUS(code) +} + +- (BOOL)verifyBytes:(const uint8_t *)bytes length:(NSInteger)length flags:(const CryptoFlags * _Nullable)flags error:(NSError * _Nullable __autoreleasing * _Nullable)error +{ + [NSException raise:NSInvalidArgumentException format:@"Verification not supported"]; + return NO; +} + +- (id)dataPathDecrypter +{ + [NSException raise:NSInvalidArgumentException format:@"DataPathEncryption not supported"]; + return nil; +} + +@end diff --git a/TunnelKitTests/EncryptionTests.swift b/TunnelKitTests/EncryptionTests.swift index 90370ab..7136934 100644 --- a/TunnelKitTests/EncryptionTests.swift +++ b/TunnelKitTests/EncryptionTests.swift @@ -96,6 +96,23 @@ class EncryptionTests: XCTestCase { XCTAssertEqual(md5, exp) } + func testCTR() { + let (client, server) = clientServer("aes-256-ctr", "sha256") + + let original = Data(hex: "0000000000") + let ad: [UInt8] = [UInt8](Data(hex: "38afa8f1162096081e000000015ba35373")) + var flags = CryptoFlags(iv: nil, ivLength: 0, ad: ad, adLength: ad.count) + +// let expEncrypted = Data(hex: "319bb8e7f8f7930cc4625079dd32a6ef9540c2fc001c53f909f712037ae9818af840b88714") + let encrypted = try! client.encrypter().encryptData(original, flags: &flags) + print(encrypted.toHex()) +// XCTAssertEqual(encrypted, expEncrypted) + + let decrypted = try! server.decrypter().decryptData(encrypted, flags: &flags) + print(decrypted.toHex()) + XCTAssertEqual(decrypted, original) + } + private func clientServer(_ c: String?, _ d: String?) -> (CryptoBox, CryptoBox) { let client = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d) let server = CryptoBox(cipherAlgorithm: c, digestAlgorithm: d) From 55e0aa5c5a17c8ce09a8053eb598f6f0e4ed554e Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Fri, 19 Oct 2018 16:52:37 +0200 Subject: [PATCH 3/5] Implement and test crypt serializer --- TunnelKit.xcodeproj/project.pbxproj | 4 + TunnelKit/Sources/Core/ControlChannel.swift | 4 + .../Core/ControlChannelSerializer.swift | 77 +++++++++++++++++++ TunnelKit/Sources/Core/ControlPacket.h | 8 +- TunnelKit/Sources/Core/ControlPacket.m | 49 ++++++++++++ TunnelKit/Sources/Core/CryptoCTR.m | 1 - TunnelKitTests/ControlChannelTests.swift | 2 +- 7 files changed, 141 insertions(+), 4 deletions(-) diff --git a/TunnelKit.xcodeproj/project.pbxproj b/TunnelKit.xcodeproj/project.pbxproj index e287381..2464ef3 100644 --- a/TunnelKit.xcodeproj/project.pbxproj +++ b/TunnelKit.xcodeproj/project.pbxproj @@ -50,6 +50,7 @@ 0E3B15CA2152B05E00984B17 /* CryptoCTR.m in Sources */ = {isa = PBXBuildFile; fileRef = 0E3B15C62152B05E00984B17 /* CryptoCTR.m */; }; 0E3E0F212108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; }; 0E3E0F222108A8CC00B371C1 /* SessionProxy+PushReply.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */; }; + 0E50D57521634E0A00FC87A8 /* ControlChannelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */; }; 0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */; }; 0E749F622178911D00BB2701 /* pia-2048.pem in Resources */ = {isa = PBXBuildFile; fileRef = 0E749F612178911C00BB2701 /* pia-2048.pem */; }; 0E749F5F2178885500BB2701 /* SessionProxy+PIA.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0E749F5E2178885500BB2701 /* SessionProxy+PIA.swift */; }; @@ -225,6 +226,7 @@ 0E3B15C52152B05E00984B17 /* CryptoCTR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoCTR.h; sourceTree = ""; }; 0E3B15C62152B05E00984B17 /* CryptoCTR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoCTR.m; sourceTree = ""; }; 0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PushReply.swift"; sourceTree = ""; }; + 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlChannelTests.swift; sourceTree = ""; }; 0E58F12F2138AC2F00A49F27 /* DNSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSTests.swift; sourceTree = ""; }; 0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 0E6479E0212EACD6008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; @@ -345,6 +347,7 @@ children = ( 0E11089E1F77B9E800A92462 /* Info.plist */, 0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */, + 0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */, 0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */, 0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */, 0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */, @@ -859,6 +862,7 @@ 0EB2B45720F0BD16004233D7 /* RandomTests.swift in Sources */, 0EB2B45920F0BD9A004233D7 /* LinkTests.swift in Sources */, 0EB2B45520F0BB53004233D7 /* DataManipulationTests.swift in Sources */, + 0E50D57521634E0A00FC87A8 /* ControlChannelTests.swift in Sources */, 0EB2B45320F0BB44004233D7 /* EncryptionTests.swift in Sources */, 0EB2B45B20F0BE4C004233D7 /* TestUtils.swift in Sources */, 0E58F1302138AC2F00A49F27 /* DNSTests.swift in Sources */, diff --git a/TunnelKit/Sources/Core/ControlChannel.swift b/TunnelKit/Sources/Core/ControlChannel.swift index 2d51b31..3268088 100644 --- a/TunnelKit/Sources/Core/ControlChannel.swift +++ b/TunnelKit/Sources/Core/ControlChannel.swift @@ -68,6 +68,10 @@ class ControlChannel { self.init(serializer: try AuthSerializer(withKey: key, digest: digest)) } + convenience init(withCryptKey key: StaticKey) throws { + self.init(serializer: try CryptSerializer(withKey: key)) + } + private init(serializer: ControlChannelSerializer) { self.serializer = serializer sessionId = nil diff --git a/TunnelKit/Sources/Core/ControlChannelSerializer.swift b/TunnelKit/Sources/Core/ControlChannelSerializer.swift index 0ecf65f..4714820 100644 --- a/TunnelKit/Sources/Core/ControlChannelSerializer.swift +++ b/TunnelKit/Sources/Core/ControlChannelSerializer.swift @@ -203,3 +203,80 @@ extension ControlChannel { } } } + +extension ControlChannel { + class CryptSerializer: ControlChannelSerializer { + private let encrypter: Encrypter + + private let decrypter: Decrypter + + private let headerLength: Int + + private var adLength: Int + + private let tagLength: Int + + private var currentReplayId: BidirectionalState + + private let plain: PlainSerializer + + init(withKey key: StaticKey) throws { + let crypto = CryptoBox(cipherAlgorithm: "AES-256-CTR", digestAlgorithm: "SHA256") + try crypto.configure( + withCipherEncKey: key.cipherEncryptKey, + cipherDecKey: key.cipherDecryptKey, + hmacEncKey: key.hmacSendKey, + hmacDecKey: key.hmacReceiveKey + ) + encrypter = crypto.encrypter() + decrypter = crypto.decrypter() + + headerLength = PacketOpcodeLength + PacketSessionIdLength + adLength = headerLength + PacketReplayIdLength + PacketReplayTimestampLength + tagLength = 32 + + currentReplayId = BidirectionalState(withResetValue: 1) + plain = PlainSerializer() + } + + func reset() { + currentReplayId.reset() + } + + func serialize(packet: ControlPacket) throws -> Data { + return try serialize(packet: packet, timestamp: UInt32(Date().timeIntervalSince1970)) + } + + func serialize(packet: ControlPacket, timestamp: UInt32) throws -> Data { + let data = try packet.serialized(with: encrypter, replayId: currentReplayId.outbound, timestamp: timestamp, adLength: adLength) + currentReplayId.outbound += 1 + return data + } + + // XXX: start/end are ignored, parses whole packet + func deserialize(data packet: Data, start: Int, end: Int?) throws -> ControlPacket { + let end = end ?? packet.count + + // data starts with (ad=(header + sessionId + replayId) + tag) + guard end >= start + adLength + tagLength else { + throw ControlChannelError("Missing AD+TAG") + } + + let encryptedCount = packet.count - adLength + var decryptedPacket = Data(count: decrypter.encryptionCapacity(withLength: encryptedCount)) + var decryptedCount = 0 + try packet.withUnsafeBytes { (src: UnsafePointer) in + var flags = CryptoFlags(iv: nil, ivLength: 0, ad: src, adLength: adLength) + try decryptedPacket.withUnsafeMutableBytes { (dest: UnsafeMutablePointer) in + try decrypter.decryptBytes(src + flags.adLength, length: encryptedCount, dest: dest + headerLength, destLength: &decryptedCount, flags: &flags) + memcpy(dest, src, headerLength) + } + } + decryptedPacket.count = headerLength + decryptedCount + + // TODO: validate replay packet id + + return try plain.deserialize(data: decryptedPacket, start: 0, end: nil) + } + } +} diff --git a/TunnelKit/Sources/Core/ControlPacket.h b/TunnelKit/Sources/Core/ControlPacket.h index 95b701a..7d0eb81 100644 --- a/TunnelKit/Sources/Core/ControlPacket.h +++ b/TunnelKit/Sources/Core/ControlPacket.h @@ -62,10 +62,14 @@ NS_ASSUME_NONNULL_BEGIN @interface ControlPacket (Authentication) -//- (NSInteger)capacityWithAuthenticator:(id)auth; -//- (BOOL)serializeTo:(uint8_t *)to authenticatingWith:(id)auth replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp error:(NSError * _Nullable __autoreleasing *)error; - (nullable NSData *)serializedWithAuthenticator:(id)auth replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp error:(NSError * _Nullable __autoreleasing *)error; @end +@interface ControlPacket (Encryption) + +- (nullable NSData *)serializedWithEncrypter:(id)encrypter replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp adLength:(NSInteger)adLength error:(NSError * _Nullable __autoreleasing *)error; + +@end + NS_ASSUME_NONNULL_END diff --git a/TunnelKit/Sources/Core/ControlPacket.m b/TunnelKit/Sources/Core/ControlPacket.m index 2b26a32..16842cd 100644 --- a/TunnelKit/Sources/Core/ControlPacket.m +++ b/TunnelKit/Sources/Core/ControlPacket.m @@ -176,3 +176,52 @@ } @end + +@implementation ControlPacket (Encryption) + +- (NSInteger)capacityWithEncrypter:(id)encrypter +{ + return PacketOpcodeLength + PacketSessionIdLength + PacketReplayIdLength + PacketReplayTimestampLength + [encrypter encryptionCapacityWithLength:self.capacity]; +} + +- (BOOL)serializeTo:(uint8_t *)to encryptingWith:(nonnull id)encrypter replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp length:(NSInteger *)length adLength:(NSInteger)adLength error:(NSError *__autoreleasing _Nullable * _Nullable)error +{ + uint8_t *ptr; + + ptr = to; + ptr += PacketHeaderSet(to, self.code, self.key, self.sessionId.bytes); + *(uint32_t *)ptr = CFSwapInt32HostToBig(replayId); + ptr += PacketReplayIdLength; + *(uint32_t *)ptr = CFSwapInt32HostToBig(timestamp); + ptr += PacketReplayTimestampLength; + + NSAssert2(ptr - to == adLength, @"Incorrect AD bytes (%ld != %ld)", ptr - to, (long)adLength); + + NSMutableData *msg = [[NSMutableData alloc] initWithLength:self.rawCapacity]; + ptr = msg.mutableBytes; + ptr += [self rawSerializeTo:ptr]; + + CryptoFlags flags; + flags.ad = to; + flags.adLength = adLength; + NSInteger encryptedMsgLength; + if (![encrypter encryptBytes:msg.bytes length:msg.length dest:(to + adLength) destLength:&encryptedMsgLength flags:&flags error:error]) { + return NO; + } + *length = adLength + encryptedMsgLength; + + return YES; +} + +- (NSData *)serializedWithEncrypter:(id)encrypter replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp adLength:(NSInteger)adLength error:(NSError *__autoreleasing _Nullable * _Nullable)error +{ + NSMutableData *data = [[NSMutableData alloc] initWithLength:[self capacityWithEncrypter:encrypter]]; + NSInteger length; + if (![self serializeTo:data.mutableBytes encryptingWith:encrypter replayId:replayId timestamp:timestamp length:&length adLength:adLength error:error]) { + return nil; + } + data.length = length; + return data; +} + +@end diff --git a/TunnelKit/Sources/Core/CryptoCTR.m b/TunnelKit/Sources/Core/CryptoCTR.m index e321fc8..d91ef42 100644 --- a/TunnelKit/Sources/Core/CryptoCTR.m +++ b/TunnelKit/Sources/Core/CryptoCTR.m @@ -33,7 +33,6 @@ #import "Allocation.h" #import "Errors.h" -const NSInteger CryptoCTRADLength = PacketOpcodeLength + PacketSessionIdLength + PacketReplayIdLength + PacketReplayTimestampLength; const NSInteger CryptoCTRTagLength = 32; @interface CryptoCTR () diff --git a/TunnelKitTests/ControlChannelTests.swift b/TunnelKitTests/ControlChannelTests.swift index 6505f44..f698528 100644 --- a/TunnelKitTests/ControlChannelTests.swift +++ b/TunnelKitTests/ControlChannelTests.swift @@ -58,7 +58,7 @@ class ControlChannelTests: XCTestCase { let data = hmac + subject print(data.toHex()) - XCTAssertNoThrow(try server.decrypter().verifyData(data, extra: nil)) + XCTAssertNoThrow(try server.decrypter().verifyData(data, flags: nil)) } // 38 // HARD_RESET From 28d9f3ee68baf0aaf655386515ae32469b59e477 Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Sun, 7 Oct 2018 16:21:22 +0200 Subject: [PATCH 4/5] Add crypt strategy --- TunnelKit/Sources/Core/SessionProxy+TLSWrap.swift | 3 +++ TunnelKit/Sources/Core/SessionProxy.swift | 3 +++ 2 files changed, 6 insertions(+) diff --git a/TunnelKit/Sources/Core/SessionProxy+TLSWrap.swift b/TunnelKit/Sources/Core/SessionProxy+TLSWrap.swift index 05feef6..5fe7d4e 100644 --- a/TunnelKit/Sources/Core/SessionProxy+TLSWrap.swift +++ b/TunnelKit/Sources/Core/SessionProxy+TLSWrap.swift @@ -35,6 +35,9 @@ extension SessionProxy { /// Authenticates payload (--tls-auth). case auth + + /// Encrypts payload (--tls-crypt). + case crypt } /// The wrapping strategy. diff --git a/TunnelKit/Sources/Core/SessionProxy.swift b/TunnelKit/Sources/Core/SessionProxy.swift index 842e025..a9b3469 100644 --- a/TunnelKit/Sources/Core/SessionProxy.swift +++ b/TunnelKit/Sources/Core/SessionProxy.swift @@ -177,6 +177,9 @@ public class SessionProxy { switch tlsWrap.strategy { case .auth: controlChannel = try ControlChannel(withAuthKey: tlsWrap.key, digest: configuration.digest) + + case .crypt: + controlChannel = try ControlChannel(withCryptKey: tlsWrap.key) } } else { controlChannel = ControlChannel() From a71d093734c5c5c9bb90cbf2a3a554861b75ffec Mon Sep 17 00:00:00 2001 From: Davide De Rosa Date: Fri, 21 Sep 2018 00:11:01 +0200 Subject: [PATCH 5/5] Update README and CHANGELOG --- CHANGELOG.md | 3 ++- README.md | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a709cbb..fe5fc94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this project _will soon adhere_ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] @@ -11,6 +11,7 @@ and this project _will soon adhere_ to [Semantic Versioning](https://semver.org/ - Handling of mixed DATA_V1/DATA_V2 packets. [#30](https://github.com/keeshux/tunnelkit/issues/30) - Support for `--tls-auth` wrapping. [#34](https://github.com/keeshux/tunnelkit/pull/34) +- Support for `--tls-crypt` wrapping. [#35](https://github.com/keeshux/tunnelkit/pull/35) ## 1.1.2 (2018-10-18) diff --git a/README.md b/README.md index 6f7a520..3cb1b52 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ The client is known to work with [OpenVPNĀ®][openvpn] 2.3+ servers. Key renegoti - Client certificate - [x] TLS wrapping - Authentication (`--tls-auth`) + - Encryption (`--tls-crypt`) - [x] Compression framing - Disabled - Compress (2.4)