// // CryptoCBC.m // PIATunnel // // Created by Davide De Rosa on 06/07/2018. // Copyright © 2018 London Trust Media. All rights reserved. // #import #import #import #import "CryptoCBC.h" #import "CryptoMacros.h" #import "PacketMacros.h" #import "Allocation.h" #import "Errors.h" const NSInteger CryptoCBCMaxHMACLength = 100; @interface CryptoCBC () @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 digestLength; @property (nonatomic, assign) int overheadLength; @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 CryptoCBC - (instancetype)initWithCipherName:(NSString *)cipherName digestName:(NSString *)digestName { NSParameterAssert([[cipherName uppercaseString] hasSuffix:@"CBC"]); 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); self.digestLength = EVP_MD_size(self.digest); self.overheadLength = self.cipherIVLength + self.digestLength; 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(CryptoCBCMaxHMACLength); } 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, CryptoCBCMaxHMACLength); free(self.bufferDecHMAC); self.cipher = NULL; self.digest = NULL; } - (int)extraLength { return 0; } #pragma mark Encrypter - (void)configureEncryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey { 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.digestLength, self.digest, NULL); } - (NSData *)encryptData:(NSData *)data offset:(NSInteger)offset extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error { NSParameterAssert(data); const uint8_t *bytes = data.bytes + offset; const int length = (int)(data.length - offset); const int maxOutputSize = (int)safe_crypto_capacity(data.length, self.overheadLength); NSMutableData *dest = [[NSMutableData alloc] initWithLength:maxOutputSize]; NSInteger encryptedLength = INT_MAX; if (![self encryptBytes:bytes length:length dest:dest.mutableBytes destLength:&encryptedLength extra:extra error:error]) { return nil; } dest.length = encryptedLength; return dest; } - (BOOL)encryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength extra:(nonnull const uint8_t *)extra error:(NSError *__autoreleasing *)error { uint8_t *outIV = dest + self.digestLength; uint8_t *outEncrypted = dest + self.digestLength + self.cipherIVLength; int l1 = 0, l2 = 0; unsigned int l3 = 0; int code = 1; if (RAND_bytes(outIV, self.cipherIVLength) != 1) { if (error) { *error = PIATunnelErrorWithCode(PIATunnelErrorCodeCryptoBoxRandomGenerator); } return NO; } PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxEnc, NULL, NULL, outIV, -1); PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxEnc, outEncrypted, &l1, bytes, (int)length); PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxEnc, outEncrypted + l1, &l2); PIA_CRYPTO_TRACK_STATUS(code) HMAC_Init_ex(self.hmacCtxEnc, NULL, 0, NULL, NULL); PIA_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxEnc, outIV, l1 + l2 + self.cipherIVLength); PIA_CRYPTO_TRACK_STATUS(code) HMAC_Final(self.hmacCtxEnc, dest, &l3); *destLength = l1 + l2 + self.cipherIVLength + self.digestLength; PIA_CRYPTO_RETURN_STATUS(code) } - (id)dataPathEncrypter { return [[DataPathCryptoCBC alloc] initWithCrypto:self]; } #pragma mark Decrypter - (void)configureDecryptionWithCipherKey:(ZeroingData *)cipherKey hmacKey:(ZeroingData *)hmacKey { 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.digestLength, self.digest, NULL); } - (NSData *)decryptData:(NSData *)data offset:(NSInteger)offset extra:(const uint8_t *)extra error:(NSError *__autoreleasing *)error { NSParameterAssert(data); const uint8_t *bytes = data.bytes + offset; const int length = (int)(data.length - offset); const int maxOutputSize = (int)safe_crypto_capacity(data.length, self.overheadLength); NSMutableData *dest = [[NSMutableData alloc] initWithLength:maxOutputSize]; NSInteger decryptedLength; if (![self decryptBytes:bytes length:length dest:dest.mutableBytes destLength:&decryptedLength extra:extra error:error]) { return nil; } dest.length = decryptedLength; return dest; } - (BOOL)decryptBytes:(const uint8_t *)bytes length:(NSInteger)length dest:(uint8_t *)dest destLength:(NSInteger *)destLength extra:(const uint8_t *)extra error:(NSError *__autoreleasing *)error { const uint8_t *iv = bytes + self.digestLength; const uint8_t *encrypted = bytes + self.digestLength + self.cipherIVLength; int l1 = 0, l2 = 0; int code = 1; PIA_CRYPTO_TRACK_STATUS(code) HMAC_Init_ex(self.hmacCtxDec, NULL, 0, NULL, NULL); PIA_CRYPTO_TRACK_STATUS(code) HMAC_Update(self.hmacCtxDec, bytes + self.digestLength, length - self.digestLength); PIA_CRYPTO_TRACK_STATUS(code) HMAC_Final(self.hmacCtxDec, self.bufferDecHMAC, (unsigned *)&l1); if (PIA_CRYPTO_SUCCESS(code) && CRYPTO_memcmp(self.bufferDecHMAC, bytes, self.digestLength) != 0) { if (error) { *error = PIATunnelErrorWithCode(PIATunnelErrorCodeCryptoBoxHMAC); } return NO; } PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherInit(self.cipherCtxDec, NULL, NULL, iv, -1); PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherUpdate(self.cipherCtxDec, dest, &l1, encrypted, (int)length - self.digestLength - self.cipherIVLength); PIA_CRYPTO_TRACK_STATUS(code) EVP_CipherFinal(self.cipherCtxDec, dest + l1, &l2); *destLength = l1 + l2; PIA_CRYPTO_RETURN_STATUS(code) } - (id)dataPathDecrypter { return [[DataPathCryptoCBC alloc] initWithCrypto:self]; } @end #pragma mark - @interface DataPathCryptoCBC () @property (nonatomic, strong) CryptoCBC *crypto; @property (nonatomic, assign) int headerLength; @property (nonatomic, copy) void (^setDataHeader)(uint8_t *, uint8_t); @property (nonatomic, copy) BOOL (^checkPeerId)(const uint8_t *); @end @implementation DataPathCryptoCBC - (instancetype)initWithCrypto:(CryptoCBC *)crypto { if ((self = [super init])) { self.crypto = crypto; self.peerId = PacketPeerIdDisabled; } return self; } - (int)overheadLength { return self.crypto.overheadLength; } - (void)setPeerId:(uint32_t)peerId { _peerId = peerId & 0xffffff; if (_peerId == PacketPeerIdDisabled) { self.headerLength = 1; self.setDataHeader = ^(uint8_t *to, uint8_t key) { PacketHeaderSet(to, PacketCodeDataV1, key); }; } else { self.headerLength = 4; self.setDataHeader = ^(uint8_t *to, uint8_t key) { PacketHeaderSetDataV2(to, key, peerId); }; self.checkPeerId = ^BOOL(const uint8_t *ptr) { return (PacketHeaderGetDataV2PeerId(ptr) == self.peerId); }; } } #pragma mark DataPathEncrypter - (void)assembleDataPacketWithPacketId:(uint32_t)packetId compression:(uint8_t)compression payload:(NSData *)payload into:(uint8_t *)dest length:(NSInteger *)length { uint8_t *ptr = dest; *(uint32_t *)ptr = htonl(packetId); ptr += sizeof(uint32_t); *ptr = compression; ptr += sizeof(uint8_t); memcpy(ptr, payload.bytes, payload.length); *length = (int)(ptr - dest + payload.length); } - (NSData *)encryptedDataPacketWithKey:(uint8_t)key packetId:(uint32_t)packetId payload:(const uint8_t *)payload payloadLength:(NSInteger)payloadLength error:(NSError *__autoreleasing *)error { const int capacity = self.headerLength + (int)safe_crypto_capacity(payloadLength, self.crypto.overheadLength); NSMutableData *encryptedPacket = [[NSMutableData alloc] initWithLength:capacity]; uint8_t *ptr = encryptedPacket.mutableBytes; NSInteger encryptedPayloadLength = INT_MAX; const BOOL success = [self.crypto encryptBytes:payload length:payloadLength dest:(ptr + self.headerLength) // skip header byte destLength:&encryptedPayloadLength extra:NULL error:error]; NSAssert(encryptedPayloadLength <= capacity, @"Did not allocate enough bytes for payload"); if (!success) { return nil; } self.setDataHeader(ptr, key); encryptedPacket.length = self.headerLength + encryptedPayloadLength; return encryptedPacket; } #pragma mark DataPathDecrypter - (BOOL)decryptDataPacket:(NSData *)packet into:(uint8_t *)dest length:(NSInteger *)length packetId:(nonnull uint32_t *)packetId error:(NSError *__autoreleasing *)error { // skip header = (code, key) const BOOL success = [self.crypto decryptBytes:(packet.bytes + self.headerLength) length:(int)(packet.length - self.headerLength) dest:dest destLength:length extra:NULL error:error]; if (!success) { return NO; } if (self.checkPeerId && !self.checkPeerId(packet.bytes)) { if (error) { *error = PIATunnelErrorWithCode(PIATunnelErrorCodeDataPathPeerIdMismatch); } return NO; } *packetId = ntohl(*(uint32_t *)dest); return YES; } - (const uint8_t *)parsePayloadWithDataPacket:(const uint8_t *)packet packetLength:(NSInteger)packetLength length:(NSInteger *)length compression:(uint8_t *)compression { const uint8_t *ptr = packet; ptr += sizeof(uint32_t); // packet id *compression = *ptr; ptr += sizeof(uint8_t); // compression byte *length = packetLength - (int)(ptr - packet); return ptr; } @end