Skip to content
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CleverTapSDK/CTLocalDataStore.m
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,9 @@ - (instancetype)initWithConfig:(CleverTapInstanceConfig *)config profileValues:(
self.dispatchQueueManager = dispatchQueueManager;
self.userEventLogs = [NSMutableSet set];
self.dbHelper = [CTEventDatabase sharedInstanceWithDispatchQueueManager:dispatchQueueManager];
// encrypt if level has been changed to 2/high


localProfileUpdateExpiryStore = [NSMutableDictionary new];
_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);
Expand Down Expand Up @@ -1019,6 +1022,41 @@ - (NSMutableDictionary *)decryptPIIDataIfEncrypted:(NSMutableDictionary *)profil

return updatedProfile;
}
else if (lastEncryptionLevel == CleverTapEncryptionHigh && self.config.cryptManager) {
// Always store the local profile data in decrypted values.
NSMutableDictionary *updatedProfile = [NSMutableDictionary new];

for (NSString *key in profile) {
@try {
// Validate the value before attempting to decrypt
id value = profile[key];
if (!value || ![value isKindOfClass:[NSString class]]) {
CleverTapLogDebug(self.config.logLevel, @"%@: Invalid value for key: %@, skipping decryption", self, key);
updatedProfile[key] = value ?: [NSNull null];
continue;
}

NSString *stringValue = [NSString stringWithFormat:@"%@", value];
NSString *decryptedString = [self.config.cryptManager decryptString:stringValue];

// Validate decryption result
if (!decryptedString) {
CleverTapLogDebug(self.config.logLevel, @"%@: Failed to decrypt data for key: %@", self, key);
// Return original value if decryption fails
updatedProfile[key] = stringValue;
} else {
updatedProfile[key] = decryptedString;
}
} @catch (NSException *e) {
CleverTapLogDebug(self.config.logLevel, @"%@: Exception during decryption for key %@: %@", self, key, e);
// Add original value to avoid data loss
updatedProfile[key] = profile[key];
}

}

return updatedProfile;
}

return profile;
}
Expand All @@ -1036,6 +1074,14 @@ - (NSMutableDictionary *)cryptValuesIfNeeded:(NSMutableDictionary *)profile {
}
return updatedProfile;
}
else if (self.config.encryptionLevel == CleverTapEncryptionHigh && self.config.cryptManager) {
NSMutableDictionary *updatedProfile = [NSMutableDictionary new];
for (NSString *key in profile) {
NSString *value = [NSString stringWithFormat:@"%@",profile[key]];
updatedProfile[key] = [self.config.cryptManager encryptString:value];
}
return updatedProfile;
}

return profile;
}
Expand Down
8 changes: 6 additions & 2 deletions CleverTapSDK/CTPlistInfo.m
Original file line number Diff line number Diff line change
Expand Up @@ -137,9 +137,13 @@ - (void)setEncryption:(NSString *)encryptionLevel {
_encryptionLevel = CleverTapEncryptionNone;
} else if (encryptionLevel && [encryptionLevel isEqualToString:@"1"]) {
_encryptionLevel = CleverTapEncryptionMedium;
} else {
}
else if (encryptionLevel && [encryptionLevel isEqualToString:@"2"]) {
_encryptionLevel = CleverTapEncryptionHigh;
}
else {
_encryptionLevel = CleverTapEncryptionNone;
CleverTapLogStaticInternal(@"Supported encryption levels are only 0 and 1. Setting it to 0 by default");
CleverTapLogStaticInternal(@"Supported encryption levels are only 0, 1 and 2. Setting it to 0 by default");
}
}

Expand Down
6 changes: 4 additions & 2 deletions CleverTapSDK/CleverTap.h
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
#import <CoreLocation/CoreLocation.h>

#if !TARGET_OS_TV
#import <WatchConnectivity/WatchConnectivity.h>
#endif
Expand Down Expand Up @@ -63,7 +64,8 @@ typedef NS_ENUM(int, CTSignedCallEvent) {

typedef NS_ENUM(int, CleverTapEncryptionLevel) {
CleverTapEncryptionNone = 0,
CleverTapEncryptionMedium = 1
CleverTapEncryptionMedium = 1,
CleverTapEncryptionHigh = 2,
};

typedef void (^CleverTapFetchInAppsBlock)(BOOL success);
Expand Down Expand Up @@ -1388,7 +1390,7 @@ extern NSString * _Nonnull const CleverTapProfileDidInitializeNotification;
*/
- (void)recordGeofenceExitedEvent:(NSDictionary *_Nonnull)geofenceDetails;

#if !TARGET_OS_TV
#if defined(CLEVERTAP_HOST_WATCHOS)
/** HostWatchOS
*/
- (BOOL)handleMessage:(NSDictionary<NSString *, id> *_Nonnull)message forWatchSession:(WCSession *_Nonnull)session API_AVAILABLE(ios(9.0));
Expand Down
67 changes: 58 additions & 9 deletions CleverTapSDK/CleverTap.m
Original file line number Diff line number Diff line change
Expand Up @@ -2033,21 +2033,60 @@ - (void)inflateQueuesAsync {
}

- (void)inflateEventsQueue {
self.eventsQueue = (NSMutableArray *)[CTPreferences unarchiveFromFile:[self eventsFileName] ofType:[NSMutableArray class] removeFile:YES];
if (!self.eventsQueue || [self isMuted]) {
// If the previous encryption level was 2/high, decrypt the object
BOOL wasEncrypted = (self.config.cryptManager.previousEncryptionLevel == CleverTapEncryptionHigh);

if (wasEncrypted) {
// File was encrypted, so decrypt when reading
self.eventsQueue = (NSMutableArray *)[self.config.cryptManager decryptObject:
[CTPreferences unarchiveFromFile:[self eventsFileName]
ofType:[NSMutableArray class]
removeFile:YES]];
} else {
// File was stored raw
self.eventsQueue = (NSMutableArray *)[CTPreferences unarchiveFromFile:
[self eventsFileName] ofType:[NSMutableArray class] removeFile:YES];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Queue Decryption Fails Due to Incorrect Type Handling

The inflateEventsQueue, inflateProfileQueue, and inflateNotificationsQueue methods are passing an NSMutableArray to decryptObject. When data is encrypted with CleverTapEncryptionHigh, it's persisted as an encrypted string. The current logic attempts to unarchiveFromFile as an NSMutableArray before decryption, which results in decryptObject receiving the wrong type (or nil), causing decryption to fail and queues to be empty.

Additional Locations (2)

Fix in Cursor Fix in Web


// fallback incase decryption fails
if (!self.eventsQueue || ![self.eventsQueue isKindOfClass:[NSMutableArray class]] || [self isMuted]) {
self.eventsQueue = [NSMutableArray array];
}
}

- (void)inflateProfileQueue {
self.profileQueue = (NSMutableArray *)[CTPreferences unarchiveFromFile:[self profileEventsFileName] ofType:[NSMutableArray class] removeFile:YES];
// If the previous encryption level was 2/high, decrypt the object
BOOL wasEncrypted = (self.config.cryptManager.previousEncryptionLevel == CleverTapEncryptionHigh);

if (wasEncrypted) {
// File was encrypted, so decrypt when reading
self.profileQueue = (NSMutableArray *)[self.config.cryptManager decryptObject:
[CTPreferences unarchiveFromFile:[self profileEventsFileName]
ofType:[NSMutableArray class]
removeFile:YES]];
} else {
// File was stored raw
self.profileQueue = (NSMutableArray *)[CTPreferences unarchiveFromFile:[self profileEventsFileName] ofType:[NSMutableArray class] removeFile:YES];
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Queue Initialization Fails Without Type Check

The inflateProfileQueue and inflateNotificationsQueue methods are missing a type check for NSMutableArray after unarchiving/decryption, unlike inflateEventsQueue. If the unarchived object isn't an array, these queues won't be properly initialized, which could lead to crashes.

Additional Locations (1)

Fix in Cursor Fix in Web

if (!self.profileQueue || [self isMuted]) {
self.profileQueue = [NSMutableArray array];
}
}

- (void)inflateNotificationsQueue {
self.notificationsQueue = (NSMutableArray *)[CTPreferences unarchiveFromFile:[self notificationsFileName] ofType:[NSMutableArray class] removeFile:YES];
// If the previous encryption level was 2/high, decrypt the object
BOOL wasEncrypted = (self.config.cryptManager.previousEncryptionLevel == CleverTapEncryptionHigh);

if (wasEncrypted) {
// File was encrypted, so decrypt when reading
self.notificationsQueue = (NSMutableArray *)[self.config.cryptManager decryptObject:
[CTPreferences unarchiveFromFile:[self notificationsFileName]
ofType:[NSMutableArray class]
removeFile:YES]];
} else {
// File was stored raw
self.notificationsQueue = (NSMutableArray *)[CTPreferences unarchiveFromFile:[self notificationsFileName] ofType:[NSMutableArray class] removeFile:YES];
}
if (!self.notificationsQueue || [self isMuted]) {
self.notificationsQueue = [NSMutableArray array];
}
Expand Down Expand Up @@ -2078,6 +2117,7 @@ - (void)persistOrClearQueues {
if ([self isMuted]) {
[self clearQueues];
} else {
// encrypt if level has been changed to 2/high
[self persistProfileQueue];
[self persistEventsQueue];
[self persistNotificationsQueue];
Expand All @@ -2086,27 +2126,36 @@ - (void)persistOrClearQueues {

- (void)persistEventsQueue {
NSString *fileName = [self eventsFileName];
NSMutableArray *eventsCopy;
id eventsCopy;
@synchronized (self) {
eventsCopy = [NSMutableArray arrayWithArray:[self.eventsQueue copy]];
if (self.config.encryptionLevel == CleverTapEncryptionHigh) {
eventsCopy = [self.config.cryptManager encryptObject:eventsCopy];
}
}
[CTPreferences archiveObject:eventsCopy forFileName:fileName config:_config];
}

- (void)persistProfileQueue {
NSString *fileName = [self profileEventsFileName];
NSMutableArray *profileEventsCopy;
id profileEventsCopy;
@synchronized (self) {
profileEventsCopy = [NSMutableArray arrayWithArray:[self.profileQueue copy]];
if (self.config.encryptionLevel == CleverTapEncryptionHigh) {
profileEventsCopy = [self.config.cryptManager encryptObject:profileEventsCopy];
}
}
[CTPreferences archiveObject:profileEventsCopy forFileName:fileName config:_config];
}

- (void)persistNotificationsQueue {
NSString *fileName = [self notificationsFileName];
NSMutableArray *notificationsCopy;
id notificationsCopy;
@synchronized (self) {
notificationsCopy = [NSMutableArray arrayWithArray:[self.notificationsQueue copy]];
if (self.config.encryptionLevel == CleverTapEncryptionHigh) {
notificationsCopy = [self.config.cryptManager encryptObject:notificationsCopy];
}
}
[CTPreferences archiveObject:notificationsCopy forFileName:fileName config:_config];
}
Expand Down Expand Up @@ -3652,7 +3701,7 @@ - (void)initializeInboxWithCallback:(CleverTapInboxSuccessBlock)callback {
return;
}
if (self.deviceInfo.deviceId) {
self.inboxController = [[CTInboxController alloc] initWithAccountId: [self.config.accountId copy] guid: [self.deviceInfo.deviceId copy]];
self.inboxController = [[CTInboxController alloc] initWithAccountId: [self.config.accountId copy] guid: [self.deviceInfo.deviceId copy] encryptionLevel:self.config.encryptionLevel previousEncryptionLevel:self.config.cryptManager.previousEncryptionLevel encryptionManager:self.config.cryptManager];
self.inboxController.delegate = self;
[CTUtils runSyncMainQueue: ^{
callback(self.inboxController.isInitialized);
Expand Down Expand Up @@ -3807,7 +3856,7 @@ - (void)dismissAppInbox {

- (void)_resetInbox {
if (self.inboxController && self.inboxController.isInitialized && self.deviceInfo.deviceId) {
self.inboxController = [[CTInboxController alloc] initWithAccountId: [self.config.accountId copy] guid: [self.deviceInfo.deviceId copy]];
self.inboxController = [[CTInboxController alloc] initWithAccountId: [self.config.accountId copy] guid: [self.deviceInfo.deviceId copy] encryptionLevel:self.config.encryptionLevel previousEncryptionLevel:self.config.cryptManager.previousEncryptionLevel encryptionManager:self.config.cryptManager];
self.inboxController.delegate = self;
}
}
Expand Down
2 changes: 2 additions & 0 deletions CleverTapSDK/Encryption/CTEncryptionManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ NS_ASSUME_NONNULL_BEGIN
*/
@interface CTEncryptionManager : NSObject <NSSecureCoding>

@property (nonatomic, assign) CleverTapEncryptionLevel previousEncryptionLevel;

/**
* Initializes the encryption manager with an account ID.
*
Expand Down
4 changes: 3 additions & 1 deletion CleverTapSDK/Encryption/CTEncryptionManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ - (void)updateEncryptionLevel:(CleverTapEncryptionLevel)encryptionLevel {
_encryptionLevel = encryptionLevel;
NSString *encryptionKey = [CTUtils getKeyWithSuffix:kENCRYPTION_KEY accountID:_accountID];
long lastEncryptionLevel = [CTPreferences getIntForKey:encryptionKey withResetValue:0];
self.previousEncryptionLevel = (CleverTapEncryptionLevel)lastEncryptionLevel;

if (lastEncryptionLevel != _encryptionLevel) {
CleverTapLogStaticInternal(@"CleverTap Encryption level changed for account: %@ to: %d", _accountID, _encryptionLevel);
[self updateCachedGUIDS];
Expand Down Expand Up @@ -341,7 +343,7 @@ - (void)updateCachedGUIDS {

NSString *key = components[0];
NSString *identifier = components[1];
NSString *processedIdentifier = self->_encryptionLevel == CleverTapEncryptionMedium ?
NSString *processedIdentifier = (self->_encryptionLevel == CleverTapEncryptionMedium || self->_encryptionLevel == CleverTapEncryptionHigh) ?
[self encryptString:identifier] : [self decryptString:identifier];

if (processedIdentifier) {
Expand Down
7 changes: 6 additions & 1 deletion CleverTapSDK/Inbox/controllers/CTInboxController.h
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
#import <Foundation/Foundation.h>
#import "CleverTap.h"
#import "CTEncryptionManager.h"

@protocol CTInboxDelegate <NSObject>
@required
Expand All @@ -22,7 +24,10 @@ NS_ASSUME_NONNULL_BEGIN

// blocking, call off main thread
- (instancetype _Nullable)initWithAccountId:(NSString *)accountId
guid:(NSString *)guid;
guid:(NSString *)guid
encryptionLevel:(CleverTapEncryptionLevel)encryptionLevel
previousEncryptionLevel:(CleverTapEncryptionLevel)previousEncryptionLevel
encryptionManager:(CTEncryptionManager*)encryptionManager;

- (void)updateMessages:(NSArray<NSDictionary*> *)messages;
- (NSDictionary * _Nullable )messageForId:(NSString *)messageId;
Expand Down
Loading