commit
fcd25608af
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -44,8 +44,13 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
@ -218,7 +223,10 @@
|
||||
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 = "<group>"; };
|
||||
0E39BCE7214B2AB60035E9DE /* ControlPacket.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = ControlPacket.m; sourceTree = "<group>"; };
|
||||
0E3B15C52152B05E00984B17 /* CryptoCTR.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CryptoCTR.h; sourceTree = "<group>"; };
|
||||
0E3B15C62152B05E00984B17 /* CryptoCTR.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CryptoCTR.m; sourceTree = "<group>"; };
|
||||
0E3E0F202108A8CC00B371C1 /* SessionProxy+PushReply.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SessionProxy+PushReply.swift"; sourceTree = "<group>"; };
|
||||
0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ControlChannelTests.swift; sourceTree = "<group>"; };
|
||||
0E58F12F2138AC2F00A49F27 /* DNSTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DNSTests.swift; sourceTree = "<group>"; };
|
||||
0E6479DD212EAC96008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
0E6479E0212EACD6008E6888 /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
@ -339,6 +347,7 @@
|
||||
children = (
|
||||
0E11089E1F77B9E800A92462 /* Info.plist */,
|
||||
0E85A259202CC5AE0059E9F9 /* AppExtensionTests.swift */,
|
||||
0E50D57421634E0A00FC87A8 /* ControlChannelTests.swift */,
|
||||
0EB2B45420F0BB53004233D7 /* DataManipulationTests.swift */,
|
||||
0EE7A7A020F664AB00B42E6A /* DataPathEncryptionTests.swift */,
|
||||
0EB2B46020F0C0A4004233D7 /* DataPathPerformanceTests.swift */,
|
||||
@ -459,6 +468,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 +570,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 +593,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 */,
|
||||
@ -849,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 */,
|
||||
@ -890,6 +904,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 +963,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 */,
|
||||
|
@ -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
|
||||
|
@ -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<UInt32>
|
||||
|
||||
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<UInt8>) in
|
||||
var flags = CryptoFlags(iv: nil, ivLength: 0, ad: src, adLength: adLength)
|
||||
try decryptedPacket.withUnsafeMutableBytes { (dest: UnsafeMutablePointer<UInt8>) 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -62,10 +62,14 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ControlPacket (Authentication)
|
||||
|
||||
//- (NSInteger)capacityWithAuthenticator:(id<Encrypter>)auth;
|
||||
//- (BOOL)serializeTo:(uint8_t *)to authenticatingWith:(id<Encrypter>)auth replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp error:(NSError * _Nullable __autoreleasing *)error;
|
||||
- (nullable NSData *)serializedWithAuthenticator:(id<Encrypter>)auth replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
@interface ControlPacket (Encryption)
|
||||
|
||||
- (nullable NSData *)serializedWithEncrypter:(id<Encrypter>)encrypter replayId:(uint32_t)replayId timestamp:(uint32_t)timestamp adLength:(NSInteger)adLength error:(NSError * _Nullable __autoreleasing *)error;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@ -176,3 +176,52 @@
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ControlPacket (Encryption)
|
||||
|
||||
- (NSInteger)capacityWithEncrypter:(id<Encrypter>)encrypter
|
||||
{
|
||||
return PacketOpcodeLength + PacketSessionIdLength + PacketReplayIdLength + PacketReplayTimestampLength + [encrypter encryptionCapacityWithLength:self.capacity];
|
||||
}
|
||||
|
||||
- (BOOL)serializeTo:(uint8_t *)to encryptingWith:(nonnull id<Encrypter>)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>)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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
41
TunnelKit/Sources/Core/CryptoCTR.h
Normal file
41
TunnelKit/Sources/Core/CryptoCTR.h
Normal file
@ -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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Crypto.h"
|
||||
#import "DataPathCrypto.h"
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
extern const NSInteger CryptoCTRADLength;
|
||||
|
||||
@interface CryptoCTR : NSObject <Encrypter, Decrypter>
|
||||
|
||||
- (instancetype)initWithCipherName:(nullable NSString *)cipherName digestName:(NSString *)digestName;
|
||||
|
||||
@end
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
210
TunnelKit/Sources/Core/CryptoCTR.m
Normal file
210
TunnelKit/Sources/Core/CryptoCTR.m
Normal file
@ -0,0 +1,210 @@
|
||||
//
|
||||
// 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 <http://www.gnu.org/licenses/>.
|
||||
//
|
||||
|
||||
#import <openssl/evp.h>
|
||||
#import <openssl/hmac.h>
|
||||
#import <openssl/rand.h>
|
||||
|
||||
#import "CryptoCTR.h"
|
||||
#import "CryptoMacros.h"
|
||||
#import "PacketMacros.h"
|
||||
#import "Allocation.h"
|
||||
#import "Errors.h"
|
||||
|
||||
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>)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>)dataPathDecrypter
|
||||
{
|
||||
[NSException raise:NSInvalidArgumentException format:@"DataPathEncryption not supported"];
|
||||
return nil;
|
||||
}
|
||||
|
||||
@end
|
@ -35,6 +35,9 @@ extension SessionProxy {
|
||||
|
||||
/// Authenticates payload (--tls-auth).
|
||||
case auth
|
||||
|
||||
/// Encrypts payload (--tls-crypt).
|
||||
case crypt
|
||||
}
|
||||
|
||||
/// The wrapping strategy.
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user