Skip to content

Commit

Permalink
Merge pull request #286 from CleverTap/develop
Browse files Browse the repository at this point in the history
Release v5.2.0
  • Loading branch information
nishant-clevertap authored Aug 16, 2023
2 parents cd70e19 + 0d15136 commit 465d0ce
Show file tree
Hide file tree
Showing 71 changed files with 2,347 additions and 8 deletions.
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
# Change Log
All notable changes to this project will be documented in this file.

### [Version 5.2.0](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/5.2.0) (August 16, 2023)

#### Added
- Adds support for encryption of PII data wiz. Email, Identity, Name and Phone.
Please refer to [Encryption.md](/docs/Encryption.md) file to read more on how to
enable/disable encryption.
- Adds support for custom KV pairs common to all inbox messages in AppInbox.
- Adds sample SwiftUIStarter app to support CleverTap iOS SDK for SwiftUI, added steps to track screen views in SwiftUI. Refer to [SwiftUI doc](/docs/SwiftUI.md) for more details.

### [Version 5.1.2](https://github.com/CleverTap/clevertap-ios-sdk/releases/tag/5.1.2) (July 28, 2023)

#### Fixed
Expand Down
8 changes: 8 additions & 0 deletions CleverTapSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@
32394C2729FA278C00956058 /* CTUriHelperTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32394C2629FA278C00956058 /* CTUriHelperTest.m */; };
32790957299CC099001FE140 /* CTUtilsTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32790956299CC099001FE140 /* CTUtilsTest.m */; };
32790959299F4B29001FE140 /* CTDeviceInfoTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32790958299F4B29001FE140 /* CTDeviceInfoTest.m */; };
4803951B2A7ABAD200C4D254 /* CTAES.m in Sources */ = {isa = PBXBuildFile; fileRef = 480395192A7ABAD200C4D254 /* CTAES.m */; };
4803951C2A7ABAD200C4D254 /* CTAES.h in Headers */ = {isa = PBXBuildFile; fileRef = 4803951A2A7ABAD200C4D254 /* CTAES.h */; };
4808030E292EB4FB00C06E2F /* CleverTap+PushPermission.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */; };
48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */ = {isa = PBXBuildFile; fileRef = 4808030F292EB50D00C06E2F /* CTLocalInApp.h */; settings = {ATTRIBUTES = (Public, ); }; };
48080312292EB50D00C06E2F /* CTLocalInApp.m in Sources */ = {isa = PBXBuildFile; fileRef = 48080310292EB50D00C06E2F /* CTLocalInApp.m */; };
Expand Down Expand Up @@ -613,6 +615,8 @@
32790956299CC099001FE140 /* CTUtilsTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTUtilsTest.m; sourceTree = "<group>"; };
32790958299F4B29001FE140 /* CTDeviceInfoTest.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = CTDeviceInfoTest.m; sourceTree = "<group>"; };
37D3A7E5589257CFA37243C3 /* libPods-shared-CleverTapSDKUITests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-shared-CleverTapSDKUITests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
480395192A7ABAD200C4D254 /* CTAES.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTAES.m; sourceTree = "<group>"; };
4803951A2A7ABAD200C4D254 /* CTAES.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTAES.h; sourceTree = "<group>"; };
4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "CleverTap+PushPermission.h"; sourceTree = "<group>"; };
4808030F292EB50D00C06E2F /* CTLocalInApp.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CTLocalInApp.h; sourceTree = "<group>"; };
48080310292EB50D00C06E2F /* CTLocalInApp.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CTLocalInApp.m; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1222,6 +1226,8 @@
D0C7BBBF207D82C0001345EF /* CleverTapSDK */ = {
isa = PBXGroup;
children = (
4803951A2A7ABAD200C4D254 /* CTAES.h */,
480395192A7ABAD200C4D254 /* CTAES.m */,
4808030D292EB4FB00C06E2F /* CleverTap+PushPermission.h */,
4E838C4429A0C94B00ED0875 /* CleverTap+CTVar.h */,
4E64855A287440BA00C2F409 /* AmazonRootCA1.cer */,
Expand Down Expand Up @@ -1518,6 +1524,7 @@
07B94550219EA39000D4C542 /* CleverTap+Inbox.h in Headers */,
071EB4F9217F6427008F0FAB /* CTNotificationButton.h in Headers */,
48080311292EB50D00C06E2F /* CTLocalInApp.h in Headers */,
4803951C2A7ABAD200C4D254 /* CTAES.h in Headers */,
D0213D4D207D905800FE5740 /* CleverTapTrackedViewController.h in Headers */,
4E25E3C5278887A70008C888 /* CTFlexibleIdentityRepo.h in Headers */,
071EB512217F6427008F0FAB /* CTInAppHTMLViewController.h in Headers */,
Expand Down Expand Up @@ -2019,6 +2026,7 @@
07B9454D219EA34300D4C542 /* Inbox.xcdatamodeld in Sources */,
D0213D51207D905800FE5740 /* CleverTapUTMDetail.m in Sources */,
D0596EFB208A6A9000A80753 /* CTSwizzle.m in Sources */,
4803951B2A7ABAD200C4D254 /* CTAES.m in Sources */,
D032F3A92093EC9700F98D74 /* CTValidationResult.m in Sources */,
071EB501217F6427008F0FAB /* CTFooterViewController.m in Sources */,
D0A84ADD209136D500191B1F /* CTLogger.m in Sources */,
Expand Down
18 changes: 18 additions & 0 deletions CleverTapSDK/CTAES.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#import <Foundation/Foundation.h>
#import "CleverTap.h"

@interface CTAES : NSObject

/**
* Returns AES128 encrypted string using the crypto framework.
*/
- (NSString *)getEncryptedString:(NSString *)identifier;

/**
* Returns AES128 decrypted string using the crypto framework.
*/
- (NSString *)getDecryptedString:(NSString *)identifier;

- (instancetype)initWithAccountID:(NSString *)accountID encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel isDefaultInstance:(BOOL)isDefaultInstance;

@end
178 changes: 178 additions & 0 deletions CleverTapSDK/CTAES.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
#import <CommonCrypto/CommonCryptor.h>
#import "CTAES.h"
#import "CTConstants.h"
#import "CTPreferences.h"
#import "CTUtils.h"

NSString *const kENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY";
NSString *const kCRYPT_KEY_PREFIX = @"Lq3fz";
NSString *const kCRYPT_KEY_SUFFIX = @"bLti2";
NSString *const kCacheGUIDS = @"CachedGUIDS";

@interface CTAES () {}
@property (nonatomic, strong) NSString *accountID;
@property (nonatomic, assign) CleverTapEncryptionLevel encryptionLevel;
@property (nonatomic, assign) BOOL isDefaultInstance;
@end

@implementation CTAES

- (instancetype)initWithAccountID:(NSString *)accountID
encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel
isDefaultInstance:(BOOL)isDefaultInstance {
if (self = [super init]) {
_accountID = accountID;
_isDefaultInstance = isDefaultInstance;
[self updateEncryptionLevel:encryptionLevel];
}
return self;
}

- (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel {
_encryptionLevel = encryptionLevel;
long lastEncryptionLevel = [CTPreferences getIntForKey:[CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID] withResetValue:0];
if (lastEncryptionLevel != _encryptionLevel) {
CleverTapLogStaticInternal(@"CleverTap Encryption level changed for account: %@ to: %d", _accountID, _encryptionLevel);
[self updatePreferencesValues];
if (!_isDefaultInstance) {
// For Default instance, we are updating this after updating Local DB values on App Launch.
[CTPreferences putInt:_encryptionLevel forKey:[CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID]];
}
}
}

- (void)updatePreferencesValues {
NSDictionary *cachedGUIDS = [CTPreferences getObjectForKey:[CTUtils getKeyWithSuffix:kCacheGUIDS accountID:_accountID]];
if (cachedGUIDS) {
NSMutableDictionary *newCache = [NSMutableDictionary new];
if (_encryptionLevel == CleverTapEncryptionNone) {
[cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) {
NSString *key = [self getCachedKey:cachedKey];
NSString *identifier = [self getCachedIdentifier:cachedKey];
NSString *decryptedString = [self getDecryptedString:identifier];
NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, decryptedString];
newCache[cacheKey] = value;
}];
} else if (_encryptionLevel == CleverTapEncryptionMedium) {
[cachedGUIDS enumerateKeysAndObjectsUsingBlock:^(NSString* _Nonnull cachedKey, NSString* _Nonnull value, BOOL * _Nonnull stopp) {
NSString *key = [self getCachedKey:cachedKey];
NSString *identifier = [self getCachedIdentifier:cachedKey];
NSString *encryptedString = [self getEncryptedString:identifier];
NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedString];
newCache[cacheKey] = value;
}];
}
[CTPreferences putObject:newCache forKey:[CTUtils getKeyWithSuffix:kCacheGUIDS accountID:_accountID]];
}
}

- (NSString *)getEncryptedString:(NSString *)identifier {
NSString *encryptedString = identifier;
if (_encryptionLevel == CleverTapEncryptionMedium) {
@try {
NSData *dataValue = [identifier dataUsingEncoding:NSUTF8StringEncoding];
NSData *encryptedData = [self convertData:dataValue withOperation:kCCEncrypt];
if (encryptedData) {
encryptedString = [encryptedData base64EncodedStringWithOptions:kNilOptions];
}
} @catch (NSException *e) {
CleverTapLogStaticInternal(@"Error: %@ while encrypting the string: %@", e.debugDescription, identifier);
return identifier;
}
}
return encryptedString;
}

- (NSString *)getDecryptedString:(NSString *)identifier {
NSString *decryptedString = identifier;
@try {
NSData *dataValue = [[NSData alloc] initWithBase64EncodedString:identifier options:kNilOptions];
NSData *decryptedData = [self convertData:dataValue withOperation:kCCDecrypt];
if (decryptedData && decryptedData.length > 0) {
NSString *utf8EncodedString = [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding];
if (utf8EncodedString) {
decryptedString = utf8EncodedString;
}
}
} @catch (NSException *e) {
CleverTapLogStaticInternal(@"Error: %@ while decrypting the string: %@", e.debugDescription, identifier);
return identifier;
}
return decryptedString;
}

- (NSData *)convertData:(NSData *)data
withOperation:(CCOperation)operation {
NSData *outputData = [self AES128WithOperation:operation
key:[self generateKeyPassword]
identifier:CLTAP_ENCRYPTION_IV
data:data];
return outputData;
}

- (NSData *)AES128WithOperation:(CCOperation)operation
key:(NSString *)key
identifier:(NSString *)identifier
data:(NSData *)data {
// Note: The key will be 0's but we intentionally are keeping it this way to maintain
// compatibility. The correct code is:
// char keyPtr[[key length] + 1];
char keyCString[kCCKeySizeAES128 + 1];
memset(keyCString, 0, sizeof(keyCString));
[key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding];

char identifierCString[kCCBlockSizeAES128 + 1];
memset(identifierCString, 0, sizeof(identifierCString));
[identifier getCString:identifierCString
maxLength:sizeof(identifierCString)
encoding:NSUTF8StringEncoding];

size_t outputAvailableSize = [data length] + kCCBlockSizeAES128;
void *output = malloc(outputAvailableSize);

size_t outputMovedSize = 0;
CCCryptorStatus cryptStatus = CCCrypt(operation,
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
keyCString,
kCCBlockSizeAES128,
identifierCString,
[data bytes],
[data length],
output,
outputAvailableSize,
&outputMovedSize);

if (cryptStatus != kCCSuccess) {
CleverTapLogStaticInternal(@"Failed to encode/deocde the string with error code: %d", cryptStatus);
free(output);
return nil;
}

return [NSData dataWithBytesNoCopy:output length:outputMovedSize];
}

- (NSString *)getCachedKey:(NSString *)value {
if ([value rangeOfString:@"_"].length > 0) {
NSUInteger index = [value rangeOfString:@"_"].location;
return [value substringToIndex:index];
} else {
return nil;
}
}

- (NSString *)getCachedIdentifier:(NSString *)value {
if ([value rangeOfString:@"_"].length > 0) {
NSUInteger index = [value rangeOfString:@"_"].location;
return [value substringFromIndex:index+1];
} else {
return nil;
}
}

- (NSString *)generateKeyPassword {
NSString *keyPassword = [NSString stringWithFormat:@"%@%@%@",kCRYPT_KEY_PREFIX, _accountID, kCRYPT_KEY_SUFFIX];
return keyPassword;
}

@end
4 changes: 4 additions & 0 deletions CleverTapSDK/CTConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -152,4 +152,8 @@ extern NSString *CLTAP_PROFILE_IDENTITY_KEY;

#define CLTAP_DEFINE_VARS_URL @"/defineVars"

#define CLTAP_ENCRYPTION_LEVEL @"CleverTapEncryptionLevel"
#define CLTAP_ENCRYPTION_IV @"__CL3>3Rt#P__1V_"
#define CLTAP_ENCRYPTION_PII_DATA (@[@"Identity", @"userEmail", @"userPhone", @"userName"]);


50 changes: 48 additions & 2 deletions CleverTapSDK/CTLocalDataStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,16 @@
#import "CleverTapInstanceConfig.h"
#import "CleverTapInstanceConfigPrivate.h"
#import "CTLoginInfoProvider.h"
#import "CTAES.h"
#import "CTPreferences.h"
#import "CTUtils.h"

static const void *const kProfileBackgroundQueueKey = &kProfileBackgroundQueueKey;
static const double kProfilePersistenceIntervalSeconds = 30.f;
NSString* const kWR_KEY_EVENTS = @"local_events_cache";
NSString* const kLocalCacheLastSync = @"local_cache_last_sync";
NSString* const kLocalCacheExpiry = @"local_cache_expiry";
NSString *const CT_ENCRYPTION_KEY = @"CLTAP_ENCRYPTION_KEY";

@interface CTLocalDataStore() {
NSMutableDictionary *localProfileUpdateExpiryStore;
Expand All @@ -23,6 +27,7 @@ @interface CTLocalDataStore() {

@property (nonatomic, strong) CleverTapInstanceConfig *config;
@property (nonatomic, strong) CTDeviceInfo *deviceInfo;
@property (nonatomic, strong) NSArray *piiKeys;

@end

Expand All @@ -36,6 +41,7 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(
_backgroundQueue = dispatch_queue_create([[NSString stringWithFormat:@"com.clevertap.profileBackgroundQueue:%@", _config.accountId] UTF8String], DISPATCH_QUEUE_SERIAL);
dispatch_queue_set_specific(_backgroundQueue, kProfileBackgroundQueueKey, (__bridge void *)self, NULL);
lastProfilePersistenceTime = 0;
_piiKeys = CLTAP_ENCRYPTION_PII_DATA;
[self runOnBackgroundQueue:^{
@synchronized (self->localProfileForSession) {
self->localProfileForSession = [self _inflateLocalProfile];
Expand Down Expand Up @@ -624,7 +630,8 @@ - (NSMutableDictionary *)_inflateLocalProfile {
if (!_profile) {
_profile = [NSMutableDictionary dictionary];
}
return _profile;
NSMutableDictionary *updatedProfile = [self decryptPIIDataIfEncrypted:_profile];
return updatedProfile;
}

- (void)persistLocalProfileIfRequired {
Expand Down Expand Up @@ -656,7 +663,8 @@ - (void)_persistLocalProfileAsync {
self->lastProfilePersistenceTime = @([[[NSDate alloc] init] timeIntervalSince1970]);
}

[CTPreferences archiveObject:_profile forFileName:[self profileFileName]];
NSMutableDictionary *updatedProfile = [self cryptValuesIfNeeded:_profile];
[CTPreferences archiveObject:updatedProfile forFileName:[self profileFileName]];
}];
}

Expand Down Expand Up @@ -807,4 +815,42 @@ - (NSDictionary*)generateBaseProfile {
return profile;
}

- (NSMutableDictionary *)decryptPIIDataIfEncrypted:(NSMutableDictionary *)profile {
long lastEncryptionLevel = [CTPreferences getIntForKey:[CTUtils getKeyWithSuffix:CT_ENCRYPTION_KEY accountID:self.config.accountId] withResetValue:0];
[CTPreferences putInt:self.config.encryptionLevel forKey:[CTUtils getKeyWithSuffix:CT_ENCRYPTION_KEY accountID:self.config.accountId]];
if (lastEncryptionLevel == CleverTapEncryptionMedium && self.config.aesCrypt) {
// Always store the local profile data in decrypted values.
NSMutableDictionary *updatedProfile = [NSMutableDictionary new];
for (NSString *key in profile) {
if ([_piiKeys containsObject:key]) {
NSString *value = [NSString stringWithFormat:@"%@",profile[key]];
NSString *decryptedString = [self.config.aesCrypt getDecryptedString:value];
updatedProfile[key] = decryptedString;
} else {
updatedProfile[key] = profile[key];
}
}
return updatedProfile;
}

return profile;
}

- (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile {
if (self.config.encryptionLevel == CleverTapEncryptionMedium && self.config.aesCrypt) {
NSMutableDictionary *updatedProfile = [NSMutableDictionary new];
for (NSString *key in profile) {
if ([_piiKeys containsObject:key]) {
NSString *value = [NSString stringWithFormat:@"%@",profile[key]];
updatedProfile[key] = [self.config.aesCrypt getEncryptedString:value];
} else {
updatedProfile[key] = profile[key];
}
}
return updatedProfile;
}

return profile;
}

@end
14 changes: 12 additions & 2 deletions CleverTapSDK/CTLoginInfoProvider.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#import "CTPreferences.h"
#import "CTConstants.h"
#import "CleverTapInstanceConfigPrivate.h"
#import "CTAES.h"

NSString *const kCachedGUIDS = @"CachedGUIDS";
NSString *const kCachedIdentities = @"CachedIdentities";
Expand All @@ -36,7 +37,12 @@ - (void)cacheGUID:(NSString *)guid forKey:(NSString *)key andIdentifier:(NSStrin
NSDictionary *cache = [self getCachedGUIDs];
if (!cache) cache = @{};
NSMutableDictionary *newCache = [NSMutableDictionary dictionaryWithDictionary:cache];
NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, identifier];

NSString *encryptedIdentifier = identifier;
if (self.config.aesCrypt) {
encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier];
}
NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier];
newCache[cacheKey] = guid;
[self setCachedGUIDs:newCache];
}
Expand Down Expand Up @@ -71,7 +77,11 @@ - (NSString *)getGUIDforKey:(NSString *)key andIdentifier:(NSString *)identifier
if (!key || !identifier) return nil;

NSDictionary *cache = [self getCachedGUIDs];
NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, identifier];
NSString *encryptedIdentifier = identifier;
if (self.config.aesCrypt) {
encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier];
}
NSString *cacheKey = [NSString stringWithFormat:@"%@_%@", key, encryptedIdentifier];
if (!cache) return nil;
else return cache[cacheKey];
}
Expand Down
Loading

0 comments on commit 465d0ce

Please sign in to comment.