Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Encryption Algorithm upgrade to AES-GCM #401

Open
wants to merge 9 commits into
base: feature/swift-support
Choose a base branch
from

Conversation

kushCT
Copy link
Contributor

@kushCT kushCT commented Feb 3, 2025

https://wizrocket.atlassian.net/browse/SDK-4206

[TAN][SDK] Encryption Algorithm Upgrade in SDK [W.I.P]

  • New Features
    • Enhanced encryption capabilities with new AES-GCM encryption methods
    • Introduced advanced cryptographic key management with Keychain

Summary by CodeRabbit

  • New Features
    • Now supports Swift, expanding compatibility with modern apps.
    • Introduced interactive components for display units, messaging inbox, and in-app notifications.
    • Added a new encryption manager for enhanced data protection.
    • Introduced new constants and utilities for cryptographic operations.
    • Added support for AES-GCM encryption and decryption.
  • Refactor
    • Consolidated cryptographic operations to deliver streamlined, robust security.

- Added CTAES-GCM encryption algorithm
- Added CTKeychainManager for generating and storing keys for encryption
- Updated podspec to give support for introducing swift files in the SDK and added support version for Swift
Copy link
Contributor

coderabbitai bot commented Feb 3, 2025

Walkthrough

This pull request updates the CleverTap iOS SDK specification and project configuration by adding Swift file support and defining a required Swift version. Multiple SDK files and tests have transitioned from using an AES-based encryption handler (CTAES) to a new handler (CTCryptHandler). New files and classes have been introduced to support AES-GCM encryption, keychain management, cryptographic models, and migration of cryptographic data. Additional files for display units, inbox features, and in-app notifications have been added.

Changes

File(s) Change Summary
CleverTap-iOS-SDK.podspec Updated iOS source files to include Swift (from s.ios.source_files = 'CleverTapSDK/**/*.{h,m}' to s.ios.source_files = 'CleverTapSDK/**/*.{h,m,swift}') and added Swift version property (s.swift_version = '5.0').
CleverTapSDK.xcodeproj/project.pbxproj Updated project configuration to include new files for display unit, inbox, in-app notifications, and utility classes.
CleverTapSDK/CTConstants.h Added AES constants: AES_GCM_PREFIX and AES_GCM_SUFFIX.
CleverTapSDK/{CTLocalDataStore.m, CTLoginInfoProvider.m, CleverTap.m, CleverTapInstanceConfig.{h,m}, InApps/CTInAppStore.m} Replaced deprecated AES encryption handler (CTAES) with the new cryptographic handler (CTCryptHandler): updated imports, property declarations, and method calls for encryption/decryption.
CleverTapSDK/Encryption/{CTAESGCMCrypt.swift, CTCryptHandler.{h,m}, CTCryptModel.swift, CTEncryptionManager.swift, CTKeychainManager.swift, CTAESCrypt.{h,m}, CTCryptMigrator.{h,m}} Introduced new encryption-related files and classes: AES-GCM encryption (CTAESGCMCrypt), cryptographic models (CTCryptModel), an encryption manager (CTEncryptionManager), a keychain manager (CTKeychainManager), AES-128 crypt utilities (CTAESCrypt), and a migrator for crypt data (CTCryptMigrator); also renamed CTAES to CTCryptHandler with updated encryption logic.

Suggested reviewers

  • nzagorchev

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR. (Beta)
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@kushCT
Copy link
Contributor Author

kushCT commented Feb 3, 2025

@coderabbitai review

Copy link
Contributor

coderabbitai bot commented Feb 3, 2025

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🔭 Outside diff range comments (8)
CleverTapSDKTests/CTEncryptionTests.m (3)

18-28: Enhance test coverage for encryption functionality.

The current test only covers the happy path. Consider adding test cases for:

  • Error scenarios (invalid input, encryption/decryption failures)
  • Different encryption levels (none, medium)
  • Edge cases (empty data, large data)
  • Key management scenarios

Example test cases to add:

- (void)testInvalidInput {
    CTCryptHandler *ctCrypt = [[CTCryptHandler alloc] initWithAccountID:@"test"];
    XCTAssertThrows([ctCrypt getEncryptedBase64String:nil]);
}

- (void)testLargeDataEncryption {
    // Test with data larger than 1MB
    NSMutableArray *largeArray = [NSMutableArray array];
    for (int i = 0; i < 100000; i++) {
        [largeArray addObject:@{@"key": @"value"}];
    }
    CTCryptHandler *ctCrypt = [[CTCryptHandler alloc] initWithAccountID:@"test"];
    NSString *encryptedString = [ctCrypt getEncryptedBase64String:largeArray];
    NSArray *decryptedObject = [ctCrypt getDecryptedObject:encryptedString];
    XCTAssertEqualObjects(largeArray, decryptedObject);
}

18-28: Enhance test coverage for the encryption upgrade.

The test suite needs expansion to cover:

  • Key management and storage
  • Error handling (invalid inputs, encryption/decryption failures)
  • Different data types (strings, numbers, nested objects)
  • Edge cases (empty/null values, large payloads)

Would you like me to generate additional test cases to ensure comprehensive coverage of the new encryption functionality?


18-28: Enhance test coverage for encryption functionality.

The test suite should be expanded to include:

  • Edge cases (empty/null data)
  • Error scenarios
  • AES-GCM specific features (authentication tags, nonces)
  • Key rotation scenarios
 - (void)testInAppEncryption {
+    // Test basic encryption/decryption
     NSBundle *bundle = [NSBundle bundleForClass:[self class]];
     NSData *inAppData = [NSData dataWithContentsOfFile:[bundle pathForResource:@"inapp_interstitial" ofType:@"json"]];
     NSError *error;
     NSArray *objectToEncrypt = [NSJSONSerialization JSONObjectWithData:inAppData options:kNilOptions error:&error];
     CTCryptHandler *ctCrypt = [[CTCryptHandler alloc] initWithAccountID:@"test"];
     
     NSString *encryptedString = [ctCrypt getEncryptedBase64String:objectToEncrypt];
     NSArray *decryptedObject = [ctCrypt getDecryptedObject:encryptedString];
     XCTAssertEqualObjects(objectToEncrypt, decryptedObject);
+}
+
+- (void)testEncryptionEdgeCases {
+    CTCryptHandler *ctCrypt = [[CTCryptHandler alloc] initWithAccountID:@"test"];
+    
+    // Test empty data
+    XCTAssertNotNil([ctCrypt getEncryptedBase64String:@[]]);
+    
+    // Test null input
+    XCTAssertNil([ctCrypt getEncryptedBase64String:nil]);
+}
+
+- (void)testEncryptionErrors {
+    CTCryptHandler *ctCrypt = [[CTCryptHandler alloc] initWithAccountID:@"test"];
+    
+    // Test invalid input type
+    NSData *invalidInput = [@"invalid" dataUsingEncoding:NSUTF8StringEncoding];
+    XCTAssertThrows([ctCrypt getEncryptedBase64String:invalidInput]);
 }
CleverTapSDKTests/CleverTapInstanceConfigTests.m (1)

35-36: Add encryption-specific configuration tests.

While the NSCoding tests cover basic serialization, consider adding specific tests for:

  • Encryption level configuration persistence
  • Encryption key management
  • Migration from old encryption to new encryption

Also applies to: 39-40

CleverTapSDK/InApps/CTInAppStore.m (4)

54-68: Improve migration error handling and data validation.

The migration logic lacks proper error handling and validation of the encrypted data.

Apply this diff to enhance the migration logic:

 - (void)migrateInAppQueueKeys {
     @synchronized(self) {
         NSString *storageKey = [CTPreferences storageKeyWithSuffix:CLTAP_PREFS_INAPP_KEY config: self.config];
         id data = [CTPreferences getObjectForKey:storageKey];
         if (data) {
             if ([data isKindOfClass:[NSArray class]]) {
                 _inAppsQueue = data;
-                NSString *encryptedString = [self.ctCrypt getEncryptedBase64String:data];
-                NSString *newStorageKey = [self storageKeyWithSuffix:CLTAP_PREFS_INAPP_KEY];
-                [CTPreferences putString:encryptedString forKey:newStorageKey];
+                @try {
+                    NSString *encryptedString = [self.ctCrypt getEncryptedBase64String:data];
+                    if (encryptedString) {
+                        // Verify encryption by attempting decryption
+                        id decryptedData = [self.ctCrypt getDecryptedObject:encryptedString];
+                        if ([decryptedData isKindOfClass:[NSArray class]]) {
+                            NSString *newStorageKey = [self storageKeyWithSuffix:CLTAP_PREFS_INAPP_KEY];
+                            [CTPreferences putString:encryptedString forKey:newStorageKey];
+                        } else {
+                            CleverTapLogDebug(self.config.logLevel, @"Migration failed: Decryption verification failed");
+                        }
+                    } else {
+                        CleverTapLogDebug(self.config.logLevel, @"Migration failed: Encryption failed");
+                    }
+                } @catch (NSException *e) {
+                    CleverTapLogDebug(self.config.logLevel, @"Migration failed with exception: %@", e);
+                }
             }
             [CTPreferences removeObjectForKey:storageKey];
         }
     }
 }

246-257: Add data integrity validation for decrypted content.

The decryption utility method lacks validation of the decrypted content's integrity.

Apply this diff to add validation:

 - (NSArray *)decryptInAppsWithKeySuffix:(NSString *)keySuffix {
     NSString *key = [self storageKeyWithSuffix:keySuffix];
     NSString *encryptedString = [CTPreferences getObjectForKey:key];
     if (encryptedString) {
-        NSArray *arr = [self.ctCrypt getDecryptedObject:encryptedString];
-        if (arr) {
-            return arr;
+        @try {
+            id decryptedData = [self.ctCrypt getDecryptedObject:encryptedString];
+            if ([decryptedData isKindOfClass:[NSArray class]]) {
+                // Additional validation of array contents could be added here
+                return decryptedData;
+            } else {
+                CleverTapLogDebug(self.config.logLevel, @"Decrypted data is not an array");
+            }
+        } @catch (NSException *e) {
+            CleverTapLogDebug(self.config.logLevel, @"Decryption failed with exception: %@", e);
         }
     }
     return [NSArray new];
 }

79-89: Add encryption failure recovery mechanism.

The storage method needs a recovery mechanism for encryption failures to prevent data loss.

Apply this diff to add recovery logic:

 - (void)storeInApps:(NSArray *)inApps {
     if (!inApps) return;
     
     @synchronized (self) {
         _inAppsQueue = inApps;
         
-        NSString *encryptedString = [self.ctCrypt getEncryptedBase64String:inApps];
-        NSString *storageKey = [self storageKeyWithSuffix:CLTAP_PREFS_INAPP_KEY];
-        [CTPreferences putString:encryptedString forKey:storageKey];
+        @try {
+            NSString *encryptedString = [self.ctCrypt getEncryptedBase64String:inApps];
+            if (encryptedString) {
+                NSString *storageKey = [self storageKeyWithSuffix:CLTAP_PREFS_INAPP_KEY];
+                [CTPreferences putString:encryptedString forKey:storageKey];
+            } else {
+                CleverTapLogDebug(self.config.logLevel, @"Failed to encrypt inApps data");
+                // Store unencrypted data as fallback with a marker
+                NSString *storageKey = [self storageKeyWithSuffix:@"UNENCRYPTED_FALLBACK_INAPP_KEY"];
+                [CTPreferences putObject:inApps forKey:storageKey];
+            }
+        } @catch (NSException *e) {
+            CleverTapLogDebug(self.config.logLevel, @"Exception during encryption: %@", e);
+            // Store unencrypted data as fallback with a marker
+            NSString *storageKey = [self storageKeyWithSuffix:@"UNENCRYPTED_FALLBACK_INAPP_KEY"];
+            [CTPreferences putObject:inApps forKey:storageKey];
+        }
     }
 }

246-257: Add error handling for decryption failures.

The decryption method should handle and log encryption/decryption errors appropriately.

 - (NSArray *)decryptInAppsWithKeySuffix:(NSString *)keySuffix {
     NSString *key = [self storageKeyWithSuffix:keySuffix];
     NSString *encryptedString = [CTPreferences getObjectForKey:key];
     if (encryptedString) {
+        @try {
             NSArray *arr = [self.ctCrypt getDecryptedObject:encryptedString];
             if (arr) {
                 return arr;
             }
+        } @catch (NSException *e) {
+            CleverTapLogInternal(self.config.logLevel, @"%@: Failed to decrypt in-app data: %@", self, e);
+        }
     }
     return [NSArray new];
 }
🧹 Nitpick comments (13)
CleverTapSDK/Encryption/CTCryptModel.swift (3)

12-12: Consider moving the availability attribute to individual types.

The @available attribute on a struct level might be too restrictive. Consider moving it to specific properties or methods that actually require iOS 13.0+, allowing the rest of the struct to be used on earlier versions if needed.


19-23: Enhance error handling with associated values.

The error enums could be more informative by including associated values to provide additional context about the failure.

Here's a suggested improvement:

 enum KeychainError: Error {
-    case unableToStoreKey
-    case unableToRetrieveKey
-    case unableToDeleteKey
+    case unableToStoreKey(reason: String)
+    case unableToRetrieveKey(reason: String)
+    case unableToDeleteKey(reason: String)
 }

 enum EncryptionError: Error {
-    case invalidInput
-    case encryptionFailed
-    case decryptionFailed
+    case invalidInput(description: String)
+    case encryptionFailed(reason: String)
+    case decryptionFailed(reason: String)
+    case algorithmNotSupported(algorithm: EncryptionAlgorithm)
 }

Also applies to: 25-29


36-39: Follow Swift naming conventions for enum cases.

The AES_GCM case uses underscore notation which isn't idiomatic Swift. Consider using camelCase instead.

Apply this diff to follow Swift naming conventions:

 enum EncryptionAlgorithm {
     case AES
-    case AES_GCM
+    case aesGcm
 }
CleverTapSDK/Encryption/CTEncryptionManager.swift (3)

19-22: Consider parameterizing the keychain tag if usage contexts differ.
Right now, the keychain tag is passed to the manager's initializer, which is good for flexibility. If multiple encryption contexts or different security domains are needed in the future, providing additional configuration parameters or a mechanism to segregate keys for different use cases may be desirable.


24-33: Validate key existence during concurrency.
getOrCreateKey() attempts to fetch a key from the keychain, creating one if it doesn't exist. If multiple threads call this concurrently, both threads might end up creating a new key at nearly the same time. While keychain operations are generally thread-safe, consider adding a small concurrency safeguard here (e.g., a dispatch queue or lock) to avoid potential edge cases.


35-47: Method name mismatch between data type and usage.
Although this method is named encryptString, it accepts and returns Data. If you plan to encrypt arbitrary binary data, consider renaming it to encryptData to better reflect its functionality. This is purely a naming concern; otherwise, the encryption approach is correct and secure with AES-GCM.

CleverTapSDK/Encryption/CTCryptHandler.m (3)

17-17: Marked extension with empty braces.
This private extension with {} can be removed if not used for storing private properties or methods, as it currently appears empty.


137-145: Error handling in convertData.
When encryptString:data error: returns a non-nil NSError, the current code simply returns encryptedData which may also be nil. Consider at least logging or exposing the error to the caller.

 NSError *error = nil;
 NSData *encryptedData = [encryptionManager encryptString:data error:&error];
+if (error) {
+    CleverTapLogStaticInternal(@"Encryption error: %@", error.localizedDescription);
+}
 return encryptedData;

147-162: Consider removing commented old AES code.
Lines 154–159 remain commented out, referencing the legacy AES128 approach. For cleaner maintainability, consider removing unused blocks if you’re certain the old logic is not needed.

CleverTapSDK/Encryption/CTKeychainManager.swift (1)

41-57: Add retry mechanism for transient keychain errors.

The retrieveKey method should handle transient errors with retries.

Apply this diff:

 func retrieveKey() throws -> Data {
+    let maxRetries = 3
+    var currentTry = 0
+    
+    while currentTry < maxRetries {
+        do {
+            return try retrieveKeyOnce()
+        } catch KeychainError.unableToRetrieveKey where currentTry < maxRetries - 1 {
+            currentTry += 1
+            Thread.sleep(forTimeInterval: 0.1 * Double(currentTry))
+            continue
+        }
+    }
+    throw KeychainError.unableToRetrieveKey
+}
+
+private func retrieveKeyOnce() throws -> Data {
     let query: [String: Any] = [
         kSecClass as String: kSecClassKey,
         kSecAttrApplicationTag as String: keychainTag.data(using: .utf8)!,
         kSecReturnData as String: true
     ]
     
     var result: AnyObject?
     let status = SecItemCopyMatching(query as CFDictionary, &result)
     
     guard status == errSecSuccess,
           let keyData = result as? Data else {
         throw KeychainError.unableToRetrieveKey
     }
     
     return keyData
 }
CleverTapSDK/CleverTapInstanceConfig.h (1)

24-24: Add documentation for cryptHandler property.

Add documentation to explain the purpose and usage of the cryptHandler property.

+/*!
+ @property
+ 
+ @abstract
+ The encryption handler for this instance
+ 
+ @discussion
+ Handles encryption and decryption of sensitive data using AES-GCM.
+ The encryption level can be configured using setEncryptionLevel:.
+ */
 @property (nonatomic, strong, nullable) CTCryptHandler *cryptHandler;
CleverTapSDK/CTConstants.h (1)

280-283: LGTM! The encryption format identifiers are well-defined.

The new constants clearly distinguish between legacy AES and AES-GCM encrypted data formats, which is crucial for backward compatibility and proper data handling.

Consider documenting the encryption format identifiers in a comment block to help future maintainers understand their purpose and usage. For example:

// Encryption format identifiers
// Legacy AES: [encrypted_data]
// AES-GCM: <ct<encrypted_data>ct>
CleverTapSDK/InApps/CTInAppStore.m (1)

79-89: Optimize encryption performance in synchronized blocks.

Multiple encryption operations within synchronized blocks could impact performance. Consider:

  1. Moving encryption outside synchronized blocks
  2. Implementing batch encryption for arrays
 - (void)storeInApps:(NSArray *)inApps {
     if (!inApps) return;
     
+    // Encrypt outside synchronized block
+    NSString *encryptedString = [self.ctCrypt getEncryptedBase64String:inApps];
+    
     @synchronized (self) {
         _inAppsQueue = inApps;
-        
-        NSString *encryptedString = [self.ctCrypt getEncryptedBase64String:inApps];
         NSString *storageKey = [self storageKeyWithSuffix:CLTAP_PREFS_INAPP_KEY];
         [CTPreferences putString:encryptedString forKey:storageKey];
     }
 }
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8cbe61 and d522a99.

📒 Files selected for processing (20)
  • CleverTap-iOS-SDK.podspec (1 hunks)
  • CleverTapSDK.xcodeproj/project.pbxproj (21 hunks)
  • CleverTapSDK/CTConstants.h (1 hunks)
  • CleverTapSDK/CTLocalDataStore.m (3 hunks)
  • CleverTapSDK/CTLoginInfoProvider.m (3 hunks)
  • CleverTapSDK/CleverTap.m (2 hunks)
  • CleverTapSDK/CleverTapInstanceConfig.h (2 hunks)
  • CleverTapSDK/CleverTapInstanceConfig.m (6 hunks)
  • CleverTapSDK/Encryption/CTAES.swift (1 hunks)
  • CleverTapSDK/Encryption/CTAESCrypt.swift (1 hunks)
  • CleverTapSDK/Encryption/CTAESGCMCrypt.swift (1 hunks)
  • CleverTapSDK/Encryption/CTCryptHandler.h (1 hunks)
  • CleverTapSDK/Encryption/CTCryptHandler.m (3 hunks)
  • CleverTapSDK/Encryption/CTCryptModel.swift (1 hunks)
  • CleverTapSDK/Encryption/CTEncryptionManager.swift (1 hunks)
  • CleverTapSDK/Encryption/CTKeychainManager.swift (1 hunks)
  • CleverTapSDK/InApps/CTInAppStore.m (9 hunks)
  • CleverTapSDKTests/CTEncryptionTests.m (2 hunks)
  • CleverTapSDKTests/CleverTapInstanceConfigTests.m (3 hunks)
  • CleverTapSDKTests/InApps/CTInAppStoreTest.m (4 hunks)
✅ Files skipped from review due to trivial changes (2)
  • CleverTapSDK/Encryption/CTAES.swift
  • CleverTapSDK/CleverTap.m
👮 Files not reviewed due to content moderation or server errors (4)
  • CleverTapSDKTests/CTEncryptionTests.m
  • CleverTapSDKTests/CleverTapInstanceConfigTests.m
  • CleverTapSDK/CTLoginInfoProvider.m
  • CleverTapSDK/InApps/CTInAppStore.m
🔇 Additional comments (38)
CleverTapSDK/Encryption/CTCryptModel.swift (2)

13-17: LGTM! Well-structured encrypted data model.

The EncryptedData struct is well-designed with immutable properties and proper type usage from CryptoKit.


31-34: LGTM! Clear and concise operation type.

The CryptoOperation enum effectively represents the basic cryptographic operations.

CleverTapSDK.xcodeproj/project.pbxproj (8)

782-786: New Swift files added for encryption functionality

The following Swift files have been added to implement encryption capabilities:

  • CTAESCrypt.swift: Implements AES encryption/decryption
  • CTAESGCMCrypt.swift: Implements AES-GCM mode encryption/decryption
  • CTKeychainManager.swift: Handles keychain operations
  • CTCryptModel.swift: Defines encryption-related models
  • CTEncryptionManager.swift: Manages encryption operations

The new Swift files follow a clear separation of concerns and encapsulate different aspects of the encryption functionality.


798-799: New Objective-C files added for encryption handler

Added CTCryptHandler.h and CTCryptHandler.m files to provide an Objective-C interface for the encryption functionality.

Good practice to have an Objective-C wrapper around the Swift encryption implementation to maintain compatibility.


2327-2328: Updated project settings for Swift support

The project's LastSwiftMigration setting has been updated to version 1610 to support the new Swift files.

Appropriate Swift version migration setting to support the new Swift code.


3252-3254: Updated build settings for Swift modules

The following build settings have been modified:

  • Enabled Swift modules with CLANG_ENABLE_MODULES = YES
  • Added Swift optimization level for debug builds

Necessary build setting changes to support mixed Swift and Objective-C code.


3292-3293: Set Swift version and optimization

Added build settings:

  • SWIFT_OPTIMIZATION_LEVEL = "-Onone" for debug builds
  • SWIFT_VERSION = 6.0 to specify Swift language version

Appropriate Swift version and optimization settings for the project.


3302-3303: Consistent Swift settings across configurations

The same Swift module and version settings are applied consistently in both Debug and Release configurations.

Good practice to maintain consistent Swift settings across build configurations.

Also applies to: 3332-3333


2571-2582: Added Swift source files to build phases

The Swift files are properly added to the source build phase:

0B75722D2D50AB6B004C3B2B /* CTAESCrypt.swift in Sources */,
0B7572362D50E237004C3B2B /* CTCryptModel.swift in Sources */,
0B7572332D50AB87004C3B2B /* CTKeychainManager.swift in Sources */,
0B7572302D50AB79004C3B2B /* CTAESGCMCrypt.swift in Sources */,
0B7572382D50E303004C3B2B /* CTEncryptionManager.swift in Sources */,

All Swift source files are properly included in the build process.


1348-1361: Organized encryption files in dedicated group

Created a new "Encryption" group in the project structure to organize the encryption-related files:

0B75722A2D50A875004C3B2B /* Encryption */ = {
    isa = PBXGroup;
    children = (
        4803951A2A7ABAD200C4D254 /* CTCryptHandler.h */,
        480395192A7ABAD200C4D254 /* CTCryptHandler.m */,
        0B75722B2D50AB6B004C3B2B /* CTAESCrypt.swift */,
        0B75722E2D50AB79004C3B2B /* CTAESGCMCrypt.swift */,
        0B7572312D50AB87004C3B2B /* CTKeychainManager.swift */,
        0B7572342D50E237004C3B2B /* CTCryptModel.swift */,
        0B7572372D50E303004C3B2B /* CTEncryptionManager.swift */,
    );
    path = Encryption;
    sourceTree = "<group>";
};

Good organization of encryption-related files in a dedicated group for better code organization and maintainability.

CleverTap-iOS-SDK.podspec (2)

16-16: LGTM! Source files updated to include Swift.

The source files pattern has been correctly updated to include Swift files, which is necessary for the new encryption implementation.


13-13: Verify cryptographic dependencies for AES-GCM implementation.

Given that this PR introduces AES-GCM encryption, consider if any additional dependencies are required for the cryptographic implementation.

Run the following script to check for common cryptographic dependencies in the codebase:

✅ Verification successful

No additional cryptographic dependency is needed for the AES-GCM implementation.
The AES-GCM code in the SDK relies on Apple's native frameworks (CryptoKit in Swift and CommonCrypto in Objective-C), which are already being imported in the relevant source files. This confirms that the implementation uses system-provided cryptographic libraries, so no extra dependency is required.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for cryptographic implementations and dependencies

# Search for common crypto imports
echo "Searching for crypto-related imports..."
rg -i "import.*Crypto|import.*Common" -g "*.{swift,h,m}"

# Search for CommonCrypto usage
echo "Searching for CommonCrypto usage..."
rg -i "CC[A-Z][a-zA-Z]+" -g "*.{swift,h,m}"

# Search for Security framework usage
echo "Searching for Security framework usage..."
rg -i "import.*Security|#import.*Security" -g "*.{swift,h,m}"

Length of output: 70537

CleverTapSDK/Encryption/CTEncryptionManager.swift (2)

9-11: Use of CryptoKit and Foundation is appropriate.
These imports are sufficient for AES-GCM and basic Swift functionalities. Good job leveraging built-in cryptographic APIs for reliability and simplicity.


50-69: Safely handling empty ciphertext scenario.
The method checks the combined data length for a 12-byte nonce plus a 16-byte tag. This is correct for most AES-GCM cases, but consider whether the application might occasionally encounter zero-length ciphertext (plaintext). If so, you might want to permit a combined data size of exactly 28 for that corner case or handle explicit zero-length plaintext more gracefully. Otherwise, this looks clean and secure for typical usage.

CleverTapSDK/Encryption/CTCryptHandler.m (4)

1-1: Header import looks good.
Importing CTCryptHandler.h clarifies the class’s public interface. No concerns here.


6-10: Swift bridging header usage.
The conditional import of <CleverTapSDK/CleverTapSDK-Swift.h> vs. "CleverTapSDK-Swift.h" is a standard approach. Confirm that the bridging header is properly exposed when using dynamic frameworks or Swift Package Manager to avoid import issues across build systems.


23-23: Class renaming ensures clarity.
Renaming from CTAES to CTCryptHandler consistently conveys its broader encryption responsibilities.


121-121: Decrypt method usage.
Replacing the old approach with convertData2 is a step in the right direction for your new AES-GCM encryption scheme. Ensure you handle error propagation properly (for example, logging or fallback) if decryptedData is nil.

CleverTapSDK/Encryption/CTCryptHandler.h (1)

4-4: Renamed class to CTCryptHandler.
This rename more accurately represents the broader cryptographic functionality (beyond just AES) you’re implementing in this refactoring. Retaining NSSecureCoding conformance is a great practice for security.

CleverTapSDKTests/CleverTapInstanceConfigTests.m (2)

14-14: LGTM! Test coverage properly updated for encryption changes.

The test file has been correctly updated to use CTCryptHandler instead of CTAES in the allowed classes for unarchiving. The changes maintain proper test coverage for the encryption upgrade.

Also applies to: 39-39, 63-63


39-40: Review security implications of unarchiving CTCryptHandler.

Allowing CTCryptHandler class for unarchiving could pose security risks. Consider:

  1. Implementing class validation before unarchiving
  2. Adding integrity checks for the cryptographic handler

Also applies to: 63-64

✅ Verification successful

Security verification for unarchiving CTCryptHandler

Our investigation confirms that CTCryptHandler implements secure coding as evidenced by its implementation of both initWithCoder: and supportsSecureCoding. This indicates that the class is designed to be safely unarchived via the NSSecureCoding mechanism. However, while this ensures that only expected (whitelisted) classes are decoded, you might still want to consider adding runtime integrity validations or additional checks for cryptographic data if untrusted sources are involved.

  • CTCryptHandler implements initWithCoder: (see CleverTapSDK/Encryption/CTCryptHandler.m).
  • CTCryptHandler implements supportsSecureCoding, ensuring it adheres to NSSecureCoding.
  • Consider evaluating if additional integrity validation is necessary when handling sensitive or external data.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check for secure unarchiving implementations in the codebase
rg -l "NSSecureCoding|NSCoding.*initWithCoder|encodeWithCoder" --type objc

Length of output: 298


Script:

#!/bin/bash
# Check if CTCryptHandler implements secure coding in its implementation file.
rg "initWithCoder" CleverTapSDK/Encryption/CTCryptHandler.m

Length of output: 125


Script:

#!/bin/bash
# Check if CTCryptHandler implements supportsSecureCoding in its header or implementation files
rg "supportsSecureCoding" CleverTapSDK/Encryption/CTCryptHandler.*

Length of output: 179

CleverTapSDK/CTLoginInfoProvider.m (1)

41-44: LGTM! Proper encryption handling for sensitive identifiers.

The encryption of identifiers is correctly implemented using the new CTCryptHandler. Thread safety is maintained through synchronized blocks, and proper null checks are in place.

Also applies to: 81-83

CleverTapSDK/InApps/CTInAppStore.m (3)

25-25: LGTM! Proper initialization of encryption handler.

The CTCryptHandler is correctly initialized with the account ID, ensuring proper encryption context.

Also applies to: 45-45


55-66: LGTM! Secure migration of existing encrypted data.

The migration logic properly handles the transition of existing encrypted data to the new encryption format while maintaining data security.


85-85: LGTM! Consistent encryption across all storage operations.

The encryption is consistently applied across all data storage operations (inApps, clientSideInApps, serverSideInApps) using the new CTCryptHandler.

Also applies to: 201-201, 231-231, 251-251

CleverTapSDK/CleverTapInstanceConfig.m (7)

235-235: LGTM! Secure initialization of encryption handler.

The CTCryptHandler is properly initialized with account ID, encryption level, and instance type for default instances.


250-257: LGTM! Secure handling of encryption level changes.

The encryption level changes are properly handled with appropriate restrictions for default instances and proper reinitialization of the encryption handler.


30-31: LGTM! Proper persistence of encryption handler.

The encryption handler is correctly encoded and decoded during instance serialization, ensuring encryption settings persist across app launches.

Also applies to: 56-57, 207-208


5-5: LGTM! Import and property changes align with encryption upgrade.

The changes correctly update the encryption-related imports and properties to use the new CTCryptHandler class.

Also applies to: 30-30


235-235: LGTM! Proper initialization of CTCryptHandler.

The initialization includes all required parameters (accountID, encryptionLevel, isDefaultInstance) for secure encryption setup.


250-257: LGTM! Proper handling of encryption level changes.

The method correctly reinitializes the cryptHandler when encryption level changes, maintaining security configuration integrity.


30-30: LGTM! Proper implementation of NSCoding for encryption handler.

The NSCoding implementation correctly handles serialization and deserialization of the new encryption handler.

Also applies to: 56-56

CleverTapSDK/CTLocalDataStore.m (3)

10-10: LGTM! Updated import for new encryption handler.

The import statement has been updated to use the new CTCryptHandler.h header.


969-976: LGTM! Updated decryption logic with encryption level check.

The decryption logic has been updated to:

  1. Check for medium encryption level
  2. Use the new CTCryptHandler for decryption
  3. Handle PII data appropriately

988-994: LGTM! Updated encryption logic with encryption level check.

The encryption logic has been updated to:

  1. Check for medium encryption level
  2. Use the new CTCryptHandler for encryption
  3. Handle PII data appropriately
CleverTapSDKTests/InApps/CTInAppStoreTest.m (3)

17-17: LGTM! Test setup properly adapted for new encryption implementation.

The test class correctly updates imports, properties, and initialization to use the new CTCryptHandler.

Also applies to: 24-24, 40-40


106-106: LGTM! Proper usage of new encryption handler for decryption.

The helper method correctly uses CTCryptHandler for decryption operations while maintaining error handling.


205-205: LGTM! Comprehensive test coverage for encryption functionality.

The test methods properly verify the encryption operations using the new CTCryptHandler.

Also applies to: 212-212

@@ -13,11 +13,12 @@ s.resource_bundles = {'CleverTapSDK' => ['CleverTapSDK/*.{xcprivacy}']}
s.ios.dependency 'SDWebImage', '~> 5.11'
s.ios.resource_bundle = {'CleverTapSDK' => ['CleverTapSDK/**/*.{png,xib,html}', 'CleverTapSDK/**/*.xcdatamodeld']}
s.ios.deployment_target = '9.0'
s.ios.source_files = 'CleverTapSDK/**/*.{h,m}'
s.ios.source_files = 'CleverTapSDK/**/*.{h,m,swift}'
s.swift_version = '6.0'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Update Swift version to a stable release.

The specified Swift version 6.0 is not yet released and could cause compatibility issues. The latest stable version is 5.9.

Apply this diff to update to the current stable version:

-s.swift_version             = '6.0'
+s.swift_version             = '5.0'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
s.swift_version = '6.0'
s.swift_version = '5.0'

Comment on lines 6 to 47
func aes128(operation: CCOperation,
key: String,
identifier: String,
data: Data) -> Data? {
// Prepare key
var keyCString = [CChar](repeating: 0, count: kCCKeySizeAES128 + 1)
key.getCString(&keyCString, maxLength: keyCString.count, encoding: .utf8)

// Prepare identifier
var identifierCString = [CChar](repeating: 0, count: kCCBlockSizeAES128 + 1)
identifier.getCString(&identifierCString, maxLength: identifierCString.count, encoding: .utf8)

// Prepare output buffer
let outputAvailableSize = data.count + kCCBlockSizeAES128
let outputBuffer = UnsafeMutableRawPointer.allocate(byteCount: outputAvailableSize, alignment: 1)
defer { outputBuffer.deallocate() }

var outputMovedSize: size_t = 0

let cryptStatus = data.withUnsafeBytes { dataBytes in
CCCrypt(
operation,
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
keyCString,
kCCBlockSizeAES128,
identifierCString,
dataBytes.baseAddress,
data.count,
outputBuffer,
outputAvailableSize,
&outputMovedSize
)
}

guard cryptStatus == kCCSuccess else {
print("Failed to encode/decode with error code: \(cryptStatus)")
return nil
}

return Data(bytes: outputBuffer, count: outputMovedSize)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Implementation uses AES-CBC instead of AES-GCM.

The current implementation uses AES in CBC mode with PKCS7 padding, which doesn't provide authenticated encryption. According to the PR objectives, this should be using AES-GCM mode to provide both confidentiality and authenticity.

Key issues:

  1. CBC mode doesn't provide authentication
  2. No integrity verification of ciphertext
  3. Vulnerable to padding oracle attacks

Consider:

  1. Using CryptoKit's AES.GCM for modern authenticated encryption
  2. Adding authentication tag handling
  3. Implementing proper nonce management

Example implementation using CryptoKit:

import CryptoKit

func aesGCM(operation: CryptoOperation,
            key: SymmetricKey,
            nonce: AES.GCM.Nonce,
            data: Data) throws -> Data {
    switch operation {
    case .encrypt:
        let sealedBox = try AES.GCM.seal(data, using: key, nonce: nonce)
        return sealedBox.combined!
    case .decrypt:
        let sealedBox = try AES.GCM.SealedBox(combined: data)
        return try AES.GCM.open(sealedBox, using: key)
    }
}

Comment on lines 49 to 64
// Get cached key
func getCachedKey(from value: String) -> String? {
guard let index = value.firstIndex(of: "_") else { return nil }
return String(value[..<index])
}

// Get cached identifier
func getCachedIdentifier(from value: String) -> String? {
guard let index = value.firstIndex(of: "_") else { return nil }
return String(value[value.index(after: index)...])
}

// Generate key password (assuming accountID is a property or passed in)
func generateKeyPassword(accountID: String) -> String {
return "\(kCRYPT_KEY_PREFIX)\(accountID)\(kCRYPT_KEY_SUFFIX)"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Enhance key management security.

The current key management implementation has several security concerns:

  1. No secure key derivation
  2. Hardcoded key prefix/suffix
  3. Keys not stored in Keychain

Consider:

  1. Using PBKDF2 or Argon2 for key derivation
  2. Implementing key rotation
  3. Storing keys in Keychain
  4. Using secure random values instead of string concatenation

Example implementation:

import Security

class KeyManager {
    func deriveKey(from password: String, salt: Data) throws -> SymmetricKey {
        let rounds = 100_000
        var key = Data(count: 32)
        let status = key.withUnsafeMutableBytes { keyPtr in
            password.withCString { passPtr in
                salt.withUnsafeBytes { saltPtr in
                    CCKeyDerivationPBKDF(
                        CCPBKDFAlgorithm(kCCPBKDF2),
                        passPtr, password.count,
                        saltPtr.baseAddress, salt.count,
                        CCPseudoRandomAlgorithm(kCCPRFHmacAlgSHA256),
                        UInt32(rounds),
                        keyPtr.baseAddress, key.count
                    )
                }
            }
        }
        guard status == kCCSuccess else {
            throw CryptoError.keyDerivationFailed
        }
        return SymmetricKey(data: key)
    }
}

Also applies to: 122-124

Comment on lines 66 to 98
// Encrypt object to base64 string
func getEncryptedBase64String<T: Codable>(objectToEncrypt: T) -> String? {
do {
let encoder = JSONEncoder()
let dataValue = try encoder.encode(objectToEncrypt)

guard let encryptedData = convertData(dataValue, withOperation: .encrypt) else {
return nil
}

return encryptedData.base64EncodedString()
} catch {
print("Error encrypting object: \(error)")
return nil
}
}

// Decrypt object from base64 string
func getDecryptedObject<T: Codable>(encryptedString: String) -> T? {
guard let dataValue = Data(base64Encoded: encryptedString) else { return nil }

do {
guard let decryptedData = convertData(dataValue, withOperation: .decrypt) else {
return nil
}

let decoder = JSONDecoder()
return try decoder.decode(T.self, from: decryptedData)
} catch {
print("Error decrypting object: \(error)")
return nil
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve error handling and logging.

The current implementation uses print statements for error logging and has basic error handling.

Consider:

  1. Creating custom error types
  2. Using proper logging framework
  3. Adding input validation
  4. Implementing proper error propagation

Example implementation:

enum CryptoError: Error {
    case encodingFailed(Error)
    case encryptionFailed
    case decodingFailed(Error)
    case invalidBase64
}

func getEncryptedBase64String<T: Codable>(objectToEncrypt: T) throws -> String {
    do {
        let dataValue = try JSONEncoder().encode(objectToEncrypt)
        guard let encryptedData = try convertData(dataValue, withOperation: .encrypt) else {
            throw CryptoError.encryptionFailed
        }
        return encryptedData.base64EncodedString()
    } catch let error as CryptoError {
        Logger.error("Encryption failed: \(error)")
        throw error
    } catch {
        Logger.error("JSON encoding failed: \(error)")
        throw CryptoError.encodingFailed(error)
    }
}

Comment on lines 1 to 4
import Foundation
import CommonCrypto

class CryptoUtils {
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add proper access control and align naming convention.

The class name should match the file name for consistency. Also, the class needs explicit access control.

Apply this diff:

 import Foundation
 import CommonCrypto
 
-class CryptoUtils {
+public class CTAESCrypt {

Comment on lines 100 to 113
// Helper method for encryption/decryption
private func convertData(_ data: Data, withOperation operation: CryptoOperation) -> Data? {
// You'll need to implement the key and identifier generation logic here
// This is a placeholder and should be replaced with your actual key generation method
let key = "YourDefaultKey"
let identifier = "YourDefaultIdentifier"

return aes128(
operation: operation == .encrypt ? CCOperation(kCCEncrypt) : CCOperation(kCCDecrypt),
key: key,
identifier: identifier,
data: data
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security Risk: Remove hardcoded cryptographic material.

The convertData method uses hardcoded default values for key and identifier, which is a significant security risk.

Remove the hardcoded values and implement proper key management:

-        let key = "YourDefaultKey"
-        let identifier = "YourDefaultIdentifier"
+        guard let key = try? keyManager.getKey(),
+              let identifier = try? keyManager.getIdentifier() else {
+            throw CryptoError.keyManagementError
+        }

Committable suggestion skipped: line range outside the PR's diff.

Comment on lines +31 to +39
func decrypt(_ encryptedData: EncryptedData, using key: SymmetricKey) throws -> Data {
let sealedBox = try AES.GCM.SealedBox(
nonce: encryptedData.nonce,
ciphertext: encryptedData.ciphertext,
tag: encryptedData.tag
)

return try AES.GCM.open(sealedBox, using: key)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for decryption failures.

The decrypt method should handle authentication failures explicitly.

Apply this diff to improve error handling:

 func decrypt(_ encryptedData: EncryptedData, using key: SymmetricKey) throws -> Data {
+    guard encryptedData.ciphertext.count > 0 else {
+        throw CryptError.invalidInput
+    }
     let sealedBox = try AES.GCM.SealedBox(
         nonce: encryptedData.nonce,
         ciphertext: encryptedData.ciphertext,
         tag: encryptedData.tag
     )
     
-    return try AES.GCM.open(sealedBox, using: key)
+    do {
+        return try AES.GCM.open(sealedBox, using: key)
+    } catch {
+        throw CryptError.authenticationFailed
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func decrypt(_ encryptedData: EncryptedData, using key: SymmetricKey) throws -> Data {
let sealedBox = try AES.GCM.SealedBox(
nonce: encryptedData.nonce,
ciphertext: encryptedData.ciphertext,
tag: encryptedData.tag
)
return try AES.GCM.open(sealedBox, using: key)
}
func decrypt(_ encryptedData: EncryptedData, using key: SymmetricKey) throws -> Data {
guard encryptedData.ciphertext.count > 0 else {
throw CryptError.invalidInput
}
let sealedBox = try AES.GCM.SealedBox(
nonce: encryptedData.nonce,
ciphertext: encryptedData.ciphertext,
tag: encryptedData.tag
)
do {
return try AES.GCM.open(sealedBox, using: key)
} catch {
throw CryptError.authenticationFailed
}
}

Comment on lines +20 to +29
func encrypt(_ data: Data, using key: SymmetricKey) throws -> EncryptedData {
let nonce = try AES.GCM.Nonce(data: Data(count: 12))
let sealedBox = try AES.GCM.seal(data, using: key, nonce: nonce)

return EncryptedData(
ciphertext: sealedBox.ciphertext,
nonce: sealedBox.nonce,
tag: sealedBox.tag
)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add input validation and improve nonce generation.

The encryption method needs additional security measures:

  1. Fixed nonce size could lead to nonce reuse under high load
  2. Missing input validation for data size

Apply this diff to improve security:

 func encrypt(_ data: Data, using key: SymmetricKey) throws -> EncryptedData {
+    guard data.count <= 64_000_000 else { // 64MB limit
+        throw CryptError.dataTooLarge
+    }
-    let nonce = try AES.GCM.Nonce(data: Data(count: 12))
+    let nonce = AES.GCM.Nonce() // Use random nonce generation
     let sealedBox = try AES.GCM.seal(data, using: key, nonce: nonce)
     
     return EncryptedData(
         ciphertext: sealedBox.ciphertext,
         nonce: sealedBox.nonce,
         tag: sealedBox.tag
     )
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func encrypt(_ data: Data, using key: SymmetricKey) throws -> EncryptedData {
let nonce = try AES.GCM.Nonce(data: Data(count: 12))
let sealedBox = try AES.GCM.seal(data, using: key, nonce: nonce)
return EncryptedData(
ciphertext: sealedBox.ciphertext,
nonce: sealedBox.nonce,
tag: sealedBox.tag
)
}
func encrypt(_ data: Data, using key: SymmetricKey) throws -> EncryptedData {
guard data.count <= 64_000_000 else { // 64MB limit
throw CryptError.dataTooLarge
}
let nonce = AES.GCM.Nonce() // Use random nonce generation
let sealedBox = try AES.GCM.seal(data, using: key, nonce: nonce)
return EncryptedData(
ciphertext: sealedBox.ciphertext,
nonce: sealedBox.nonce,
tag: sealedBox.tag
)
}

Comment on lines 11 to 39
func saveKey(_ key: Data) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: keychainTag.data(using: .utf8)!,
kSecValueData as String: key,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]

let status = SecItemAdd(query as CFDictionary, nil)

if status == errSecDuplicateItem {
// Update existing key
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: keychainTag.data(using: .utf8)!
]

let attributesToUpdate: [String: Any] = [
kSecValueData as String: key
]

let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributesToUpdate as CFDictionary)
guard updateStatus == errSecSuccess else {
throw KeychainError.unableToStoreKey
}
} else if status != errSecSuccess {
throw KeychainError.unableToStoreKey
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve error handling and remove force unwrapping.

The saveKey method needs improvements:

  1. Force unwrapping of keychainTag.data should be handled safely
  2. Specific error codes should be handled explicitly

Apply this diff:

 func saveKey(_ key: Data) throws {
+    guard let tagData = keychainTag.data(using: .utf8) else {
+        throw KeychainError.invalidInput
+    }
     let query: [String: Any] = [
         kSecClass as String: kSecClassKey,
-        kSecAttrApplicationTag as String: keychainTag.data(using: .utf8)!,
+        kSecAttrApplicationTag as String: tagData,
         kSecValueData as String: key,
         kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
     ]
     
     let status = SecItemAdd(query as CFDictionary, nil)
     
     if status == errSecDuplicateItem {
         let updateQuery: [String: Any] = [
             kSecClass as String: kSecClassKey,
-            kSecAttrApplicationTag as String: keychainTag.data(using: .utf8)!
+            kSecAttrApplicationTag as String: tagData
         ]
         
         let attributesToUpdate: [String: Any] = [
             kSecValueData as String: key
         ]
         
         let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributesToUpdate as CFDictionary)
-        guard updateStatus == errSecSuccess else {
-            throw KeychainError.unableToStoreKey
+        switch updateStatus {
+        case errSecSuccess:
+            return
+        case errSecDecode:
+            throw KeychainError.invalidKeyFormat
+        default:
+            throw KeychainError.unableToStoreKey
         }
     }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
func saveKey(_ key: Data) throws {
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: keychainTag.data(using: .utf8)!,
kSecValueData as String: key,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecDuplicateItem {
// Update existing key
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: keychainTag.data(using: .utf8)!
]
let attributesToUpdate: [String: Any] = [
kSecValueData as String: key
]
let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributesToUpdate as CFDictionary)
guard updateStatus == errSecSuccess else {
throw KeychainError.unableToStoreKey
}
} else if status != errSecSuccess {
throw KeychainError.unableToStoreKey
}
}
func saveKey(_ key: Data) throws {
guard let tagData = keychainTag.data(using: .utf8) else {
throw KeychainError.invalidInput
}
let query: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tagData,
kSecValueData as String: key,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
]
let status = SecItemAdd(query as CFDictionary, nil)
if status == errSecDuplicateItem {
// Update existing key
let updateQuery: [String: Any] = [
kSecClass as String: kSecClassKey,
kSecAttrApplicationTag as String: tagData
]
let attributesToUpdate: [String: Any] = [
kSecValueData as String: key
]
let updateStatus = SecItemUpdate(updateQuery as CFDictionary, attributesToUpdate as CFDictionary)
switch updateStatus {
case errSecSuccess:
return
case errSecDecode:
throw KeychainError.invalidKeyFormat
default:
throw KeychainError.unableToStoreKey
}
} else if status != errSecSuccess {
throw KeychainError.unableToStoreKey
}
}

Comment on lines 41 to 44
NSString *encryptedIdentifier = identifier;
if (self.config.aesCrypt) {
encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier];
if (self.config.cryptHandler) {
encryptedIdentifier = [self.config.cryptHandler getEncryptedString:identifier];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add error handling for encryption operations.

The encryption of identifiers lacks error handling. Encryption failures could lead to security issues or data corruption.

Apply this diff to add error handling:

 NSString *encryptedIdentifier = identifier;
 if (self.config.cryptHandler) {
-    encryptedIdentifier = [self.config.cryptHandler getEncryptedString:identifier];
+    @try {
+        encryptedIdentifier = [self.config.cryptHandler getEncryptedString:identifier];
+        if (!encryptedIdentifier) {
+            // Log error and use unencrypted identifier as fallback
+            CleverTapLogDebug(self.config.logLevel, @"Encryption failed for identifier");
+            encryptedIdentifier = identifier;
+        }
+    } @catch (NSException *e) {
+        CleverTapLogDebug(self.config.logLevel, @"Exception during encryption: %@", e);
+        encryptedIdentifier = identifier;
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NSString *encryptedIdentifier = identifier;
if (self.config.aesCrypt) {
encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier];
if (self.config.cryptHandler) {
encryptedIdentifier = [self.config.cryptHandler getEncryptedString:identifier];
}
NSString *encryptedIdentifier = identifier;
if (self.config.cryptHandler) {
@try {
encryptedIdentifier = [self.config.cryptHandler getEncryptedString:identifier];
if (!encryptedIdentifier) {
// Log error and use unencrypted identifier as fallback
CleverTapLogDebug(self.config.logLevel, @"Encryption failed for identifier");
encryptedIdentifier = identifier;
}
} @catch (NSException *e) {
CleverTapLogDebug(self.config.logLevel, @"Exception during encryption: %@", e);
encryptedIdentifier = identifier;
}
}

⚠️ Potential issue

Add migration handling for previously encrypted data.

The transition from CTAES to CTCryptHandler needs to handle previously encrypted data. Consider adding migration logic to handle data encrypted with the old mechanism.

 NSString *encryptedIdentifier = identifier;
 if (self.config.cryptHandler) {
+    // Try decrypting with old mechanism first
+    if ([self isLegacyEncryptedData:identifier]) {
+        NSString *decryptedIdentifier = [self decryptLegacyData:identifier];
+        encryptedIdentifier = [self.config.cryptHandler getEncryptedString:decryptedIdentifier];
+    } else {
         encryptedIdentifier = [self.config.cryptHandler getEncryptedString:identifier];
+    }
 }

Also applies to: 80-83

…th a randomized key and storing it in keychain
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

♻️ Duplicate comments (1)
CleverTapSDK/Encryption/CTAESCrypt.swift (1)

12-19: ⚠️ Potential issue

Critical: Implementation uses AES-CBC instead of AES-GCM.

The current implementation uses AES in CBC mode with PKCS7 padding, which doesn't provide authenticated encryption. According to the PR objectives, this should be using AES-GCM mode.

Consider using the modern CTEncryptionManager class instead, which already implements AES-GCM using CryptoKit.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d522a99 and add1d8e.

📒 Files selected for processing (3)
  • CleverTapSDK/Encryption/CTAESCrypt.swift (1 hunks)
  • CleverTapSDK/Encryption/CTCryptHandler.m (2 hunks)
  • CleverTapSDK/Encryption/CTEncryptionManager.swift (1 hunks)
🧰 Additional context used
🪛 Gitleaks (8.21.2)
CleverTapSDK/Encryption/CTAESCrypt.swift

58-58: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🔇 Additional comments (5)
CleverTapSDK/Encryption/CTCryptHandler.m (4)

1-1: LGTM! Good practice with Swift header imports.

The conditional import of Swift headers ensures compatibility across different build configurations.

Also applies to: 6-10


17-17: LGTM! Appropriate class renaming.

The rename from CTAES to CTCryptHandler better reflects the class's broader cryptographic responsibilities.

Also applies to: 23-23


100-133: LGTM! Well-implemented helper methods.

The encryption/decryption helper methods demonstrate:

  • Proper error handling with try-catch blocks
  • Correct memory management
  • Consistent logging using CleverTapLogStaticInternal

Also applies to: 252-279


137-162: Verify keychain security and document encryption differences.

The implementation uses different encryption methods based on iOS version. Please ensure:

  1. Keychain access control is properly configured
  2. Document the security implications of different encryption methods
  3. Consider adding migration path for existing encrypted data

Let's verify the keychain configuration:

✅ Verification successful

Secure keychain configuration verified; please document encryption differences and migration path.

  • The keychain configuration in CTKeychainManager.swift uses secure settings (e.g., kSecAttrAccessibleWhenUnlockedThisDeviceOnly and proper use of kSecAttrApplicationTag), confirming that key material is stored securely.
  • The implementation uses different keychain tags for encryption on iOS 13+ (EncryptionKey) versus earlier versions (com.clevertap.aesencryption), so please add documentation outlining the security implications and any considerations for migrating existing encrypted data.
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Search for keychain configuration
ast-grep --pattern 'kSecAttr$$$' 
rg -A 5 'kSecAttr|SecItemAdd|SecItemUpdate'

Length of output: 3315

CleverTapSDK/Encryption/CTEncryptionManager.swift (1)

35-47: LGTM! Secure implementation of AES-GCM encryption.

The implementation correctly uses CryptoKit's AES-GCM for authenticated encryption, with proper key management and nonce handling.

Comment on lines 135 to 185
- (NSData *)convertData:(NSData *)data
withOperation:(CCOperation)operation {
NSData *outputData = [self AES128WithOperation:operation
key:[self generateKeyPassword]
identifier:CLTAP_ENCRYPTION_IV
data:data];
return outputData;
if (@available(iOS 13.0, *)) {
CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:@"EncryptionKey"];
NSError *error = nil;
NSData *processedData = nil;

switch (operation) {
case kCCEncrypt:
processedData = [encryptionManager encryptData:data error:&error];
break;
case kCCDecrypt:
processedData = [encryptionManager decryptData:data error:&error];
break;
default:
NSLog(@"Unsupported operation");
break;
}

if (error) {
NSLog(@"Encryption/Decryption error: %@", error);
return nil;
}

return processedData;
} else {
CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:@"com.clevertap.aesencryption"];
NSError *error = nil;
NSData *processedData = nil;

switch (operation) {
case kCCEncrypt:
processedData = [encryptionManager encryptDataWithAES:data error:&error];
break;
case kCCDecrypt:
processedData = [encryptionManager decryptDataWithAES:data error:&error];
break;
default:
NSLog(@"Unsupported operation");
break;
}

if (error) {
NSLog(@"Encryption/Decryption error: %@", error);
return nil;
}

return processedData;
}
return nil;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Several improvements needed in the encryption implementation.

  1. Use consistent logging mechanism
  2. Reduce code duplication in error handling
  3. Document encryption differences between iOS versions
  4. Consider keychain tag migration strategy

Consider applying these improvements:

 - (NSData *)convertData:(NSData *)data
           withOperation:(CCOperation)operation {
     if (@available(iOS 13.0, *)) {
-        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:@"EncryptionKey"];
+        // Use AES-GCM encryption for iOS 13+ for enhanced security
+        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:kEncryptionKeyTag];
         NSError *error = nil;
         NSData *processedData = nil;
         
         switch (operation) {
             case kCCEncrypt:
                 processedData = [encryptionManager encryptData:data error:&error];
                 break;
             case kCCDecrypt:
                 processedData = [encryptionManager decryptData:data error:&error];
                 break;
             default:
-                NSLog(@"Unsupported operation");
+                CleverTapLogStaticInternal(@"Unsupported cryptographic operation");
                 break;
         }
         
         if (error) {
-            NSLog(@"Encryption/Decryption error: %@", error);
+            CleverTapLogStaticInternal(@"Encryption/Decryption error: %@", error.localizedDescription);
             return nil;
         }
         
         return processedData;
     } else {
-        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:@"com.clevertap.aesencryption"];
+        // Use legacy AES encryption for iOS < 13
+        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:kLegacyEncryptionKeyTag];
         NSError *error = nil;
         NSData *processedData = nil;
         
         switch (operation) {
             case kCCEncrypt:
                 processedData = [encryptionManager encryptDataWithAES:data error:&error];
                 break;
             case kCCDecrypt:
                 processedData = [encryptionManager decryptDataWithAES:data error:&error];
                 break;
             default:
-                NSLog(@"Unsupported operation");
+                CleverTapLogStaticInternal(@"Unsupported cryptographic operation");
                 break;
         }
         
         if (error) {
-            NSLog(@"Encryption/Decryption error: %@", error);
+            CleverTapLogStaticInternal(@"Encryption/Decryption error: %@", error.localizedDescription);
             return nil;
         }
         
         return processedData;
     }
-    return nil;
 }

Also, add these constants at the top of the file:

static NSString *const kEncryptionKeyTag = @"EncryptionKey";
static NSString *const kLegacyEncryptionKeyTag = @"com.clevertap.aesencryption";

Comment on lines 71 to 83
@objc
func encryptDataWithAES(_ data: Data) throws -> Data {
let key = try getOrCreateKey()
let encrypted = try aesGCM.encrypt(data, using: key)

// Combine nonce, ciphertext, and tag
var combinedData = Data()
combinedData.append(encrypted.nonce.withUnsafeBytes { Data($0) }) // Fixed: proper nonce data conversion
combinedData.append(encrypted.ciphertext)
combinedData.append(encrypted.tag)

return combinedData
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove duplicate code by consolidating encryption methods.

The encryptDataWithAES and decryptDataWithAES methods are identical to encryptData and decryptData respectively. This violates the DRY principle.

Apply this diff to remove the duplication:

-    @objc
-    func encryptDataWithAES(_ data: Data) throws -> Data {
-        let key = try getOrCreateKey()
-        let encrypted = try aesGCM.encrypt(data, using: key)
-        
-        // Combine nonce, ciphertext, and tag
-        var combinedData = Data()
-        combinedData.append(encrypted.nonce.withUnsafeBytes { Data($0) })
-        combinedData.append(encrypted.ciphertext)
-        combinedData.append(encrypted.tag)
-        
-        return combinedData
-    }
-    
-    @objc
-    func decryptDataWithAES(_ combinedData: Data) throws -> Data {
-        guard combinedData.count >= 28 else { // 12 (nonce) + 16 (tag) minimum
-            throw EncryptionError.invalidInput
-        }
-        
-        let nonceData = combinedData.prefix(12)
-        let tagData = combinedData.suffix(16)
-        let ciphertextData = combinedData.dropFirst(12).dropLast(16)
-        
-        let nonce = try AES.GCM.Nonce(data: nonceData)
-        let encryptedData = EncryptedData(
-            ciphertext: ciphertextData,
-            nonce: nonce,
-            tag: tagData
-        )
-        
-        let key = try getOrCreateKey()
-        let decryptedData = try aesGCM.decrypt(encryptedData, using: key)
-        return decryptedData
-    }

If these methods exist for backward compatibility, consider marking them as deprecated:

@available(*, deprecated, message: "Use encryptData instead")
@objc
func encryptDataWithAES(_ data: Data) throws -> Data {
    return try encryptData(data)
}

Also applies to: 85-105

Comment on lines 75 to 85
let cryptStatus = CCCrypt(operation,
CCAlgorithm(kCCAlgorithmAES),
CCOptions(kCCOptionPKCS7Padding),
keyBuffer,
key.count,
ivBuffer,
dataBuffer,
dataLength_,
buffer,
bufferSize,
&numBytesProcessed)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Add integrity verification for encrypted data.

The current implementation doesn't verify the integrity of the ciphertext. This is a critical security concern when using CBC mode.

Consider adding HMAC for integrity verification or switching to the CTEncryptionManager class that uses authenticated encryption with AES-GCM.

Comment on lines 69 to 71
let ivBuffer = (iv as NSData).bytes.assumingMemoryBound(to: UInt8.self)
let keyBuffer = (key as NSData).bytes.assumingMemoryBound(to: UInt8.self)
let dataBuffer = (data as NSData).bytes.assumingMemoryBound(to: UInt8.self)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Security: Avoid force unwrapping in cryptographic operations.

Using assumingMemoryBound with force-unwrapped pointers in cryptographic operations can lead to crashes if memory layout assumptions are incorrect.

Apply this diff to add proper error handling:

-        let ivBuffer = (iv as NSData).bytes.assumingMemoryBound(to: UInt8.self)
-        let keyBuffer = (key as NSData).bytes.assumingMemoryBound(to: UInt8.self)
-        let dataBuffer = (data as NSData).bytes.assumingMemoryBound(to: UInt8.self)
+        var cryptSuccess = false
+        let result = iv.withUnsafeBytes { ivPtr -> Data? in
+            guard let ivBuffer = ivPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
+                return nil
+            }
+            return key.withUnsafeBytes { keyPtr -> Data? in
+                guard let keyBuffer = keyPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
+                    return nil
+                }
+                return data.withUnsafeBytes { dataPtr -> Data? in
+                    guard let dataBuffer = dataPtr.baseAddress?.assumingMemoryBound(to: UInt8.self) else {
+                        return nil
+                    }
+                    // Continue with CCCrypt...
+                    cryptSuccess = true
+                    return processedData
+                }
+            }
+        }
+        guard cryptSuccess, let processedData = result else {
+            throw CTCryptoError.encryptionFailed
+        }

Committable suggestion skipped: line range outside the PR's diff.

// Copyright © 2025 CleverTap. All rights reserved.
//

import Foundation
Copy link
Collaborator

Choose a reason for hiding this comment

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

This file is empty.


switch (operation) {
case kCCEncrypt:
processedData = [encryptionManager encryptData:data error:&error];
Copy link
Collaborator

Choose a reason for hiding this comment

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

can you rename the methods to mention if they are using legacy AES or the newer one? Also add comments to mention the same

}

@objc
func encryptDataWithAES(_ data: Data) throws -> Data {
Copy link
Collaborator

Choose a reason for hiding this comment

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

this method looks to be the same as encryptData

return keyData
}

func deleteKey() throws {
Copy link
Collaborator

Choose a reason for hiding this comment

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

will this be called at some point?

… from legacy encryption to new encryption algorithm
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 10

🔭 Outside diff range comments (3)
CleverTapSDK.xcodeproj/project.pbxproj (1)

3270-3312: Build settings updated for Swift support

The following build settings have been added/modified to support Swift:

  • CLANG_ENABLE_MODULES = YES - Enables modules for better Swift/Objective-C interop
  • SWIFT_OPTIMIZATION_LEVEL = "-Onone" - Sets Swift optimization level for debug builds
  • SWIFT_VERSION = 6.0 - Sets Swift language version to 6.0

Consider adding the following build settings for better Swift integration:

+ DEFINES_MODULE = YES
+ SWIFT_INSTALL_OBJC_HEADER = YES
+ SWIFT_OBJC_INTERFACE_HEADER_NAME = $(PRODUCT_MODULE_NAME)-Swift.h
CleverTapSDK/Encryption/CTCryptHandler.m (2)

191-194: Improve key password generation security.

The current key generation method concatenates static strings with the account ID, which could be predictable.

Consider using a more secure key derivation:

 - (NSString *)generateKeyPassword {
-    NSString *keyPassword = [NSString stringWithFormat:@"%@%@%@",kCRYPT_KEY_PREFIX, _accountID, kCRYPT_KEY_SUFFIX];
-    return keyPassword;
+    // Use PBKDF2 for key derivation
+    NSData *salt = [self getOrGenerateSalt];
+    NSData *keyData = [self deriveKeyFromAccountID:_accountID withSalt:salt];
+    return [keyData base64EncodedStringWithOptions:0];
 }
+
+- (NSData *)deriveKeyFromAccountID:(NSString *)accountID withSalt:(NSData *)salt {
+    NSData *passwordData = [accountID dataUsingEncoding:NSUTF8StringEncoding];
+    NSMutableData *derivedKey = [NSMutableData dataWithLength:kCCKeySizeAES256];
+    
+    CCKeyDerivationPBKDF(kCCPBKDF2,
+                         passwordData.bytes,
+                         passwordData.length,
+                         salt.bytes,
+                         salt.length,
+                         kCCPRFHmacAlgSHA256,
+                         10000,  // iterations
+                         derivedKey.mutableBytes,
+                         derivedKey.length);
+    
+    return derivedKey;
+}

196-208: Add error handling for NSKeyedArchiver operations.

The getEncryptedBase64String: method should handle potential archiving failures.

 - (NSString *)getEncryptedBase64String:(id)objectToEncrypt {
     @try {
-        NSData *dataValue = [NSKeyedArchiver archivedDataWithRootObject:objectToEncrypt];
+        NSError *error = nil;
+        NSData *dataValue = [NSKeyedArchiver archivedDataWithRootObject:objectToEncrypt 
+                                                requiringSecureCoding:YES 
+                                                              error:&error];
+        if (error) {
+            CleverTapLogStaticInternal(@"Failed to archive object: %@", error);
+            return nil;
+        }
         NSData *encryptedData = [self convertData:dataValue withOperation:kCCEncrypt];
♻️ Duplicate comments (3)
CleverTapSDK/Encryption/CTCryptHandler.m (3)

138-171: ⚠️ Potential issue

Several improvements needed in the encryption implementation.

The implementation needs improvements in logging, error handling, and documentation.

Apply these improvements:

 if (@available(iOS 13.0, *)) {
-    CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:@"EncryptionKey"];
+    // Use AES-GCM encryption for iOS 13+ for enhanced security
+    CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:kEncryptionKeyTag];
     NSError *error = nil;
     NSData *processedData = nil;
     
     switch (operation) {
         case kCCEncrypt:
             processedData = [encryptionManager encryptData:data error:&error];
             break;
         case kCCDecrypt:
             processedData = [encryptionManager decryptData:data error:&error];
             break;
         default:
-            NSLog(@"Unsupported operation");
+            CleverTapLogStaticInternal(@"Unsupported cryptographic operation");
             break;
     }
     
     if (error) {
-        NSLog(@"Encryption/Decryption error: %@", error);
+        CleverTapLogStaticInternal(@"Encryption/Decryption error: %@", error.localizedDescription);
         return nil;
     }
     
     return processedData;
 } else {
-    CTAESCrypt *aesCrypt = [[CTAESCrypt alloc] init];
+    // Use legacy AES encryption for iOS < 13
+    static CTAESCrypt *aesCrypt = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        aesCrypt = [[CTAESCrypt alloc] init];
+    });
     NSData *outputData = [aesCrypt
                         AES128WithOperation:operation
                         key:[aesCrypt generateKeyPassword]
                         identifier:CLTAP_ENCRYPTION_IV
                         data:data];
     return outputData;
 }
-return nil; // Unreachable code

Also, add these constants at the top of the file:

static NSString *const kEncryptionKeyTag = @"com.clevertap.encryption.key";

136-171: 🛠️ Refactor suggestion

Several improvements needed in the encryption implementation.

The implementation needs improvements in logging consistency, error handling, and constants management.

Apply the improvements as suggested in the previous review:

 - (NSData *)convertData:(NSData *)data
           withOperation:(CCOperation)operation {
     if (@available(iOS 13.0, *)) {
-        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:@"EncryptionKey"];
+        // Use AES-GCM encryption for iOS 13+ for enhanced security
+        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:kEncryptionKeyTag];
         NSError *error = nil;
         NSData *processedData = nil;
         
         switch (operation) {
             case kCCEncrypt:
                 processedData = [encryptionManager encryptData:data error:&error];
                 break;
             case kCCDecrypt:
                 processedData = [encryptionManager decryptData:data error:&error];
                 break;
             default:
-                NSLog(@"Unsupported operation");
+                CleverTapLogStaticInternal(@"Unsupported cryptographic operation");
                 break;
         }
         
         if (error) {
-            NSLog(@"Encryption/Decryption error: %@", error);
+            CleverTapLogStaticInternal(@"Encryption/Decryption error: %@", error.localizedDescription);
             return nil;
         }
         
         return processedData;
     } else {
-        CTAESCrypt *aesCrypt = [[CTAESCrypt alloc] init];
+        // Use legacy AES encryption for iOS < 13
+        CTAESCrypt *aesCrypt = [[CTAESCrypt alloc] init];
         NSData *outputData = [aesCrypt
                             AES128WithOperation:operation
                             key:[aesCrypt generateKeyPassword]
                             identifier:CLTAP_ENCRYPTION_IV
                             data:data];
         return outputData;
     }
-    return nil;
 }

138-171: ⚠️ Potential issue

Several improvements needed in the encryption implementation.

The implementation needs improvements in logging consistency, error handling, and documentation.

Previous review comment still applies. Additionally:

  1. The return nil statement on line 170 is unreachable
  2. Consider adding migration logic for existing encrypted data when switching between iOS versions
🧹 Nitpick comments (8)
CleverTapSDK/Encryption/CTCryptMigrator.m (3)

8-12: Remove unused imports.

The following imports appear to be unused in this file:

  • CTPreferences.h
  • CTUtils.h
#import "CTCryptMigrator.h"
#import "CTConstants.h"
-#import "CTPreferences.h"
-#import "CTUtils.h"
#import "CTAESCrypt.h"

17-21: Remove empty protocol implementation block.

The empty curly braces after the interface declaration are unnecessary.

-@interface CTCryptMigrator () {}
+@interface CTCryptMigrator ()

23-65: Consider implementing secure memory cleanup.

Since this class handles sensitive cryptographic material, consider implementing:

  1. Memory cleanup in dealloc
  2. Secure memory wiping for sensitive data
  3. Conformance to NSSecureCoding (already done)

Example implementation:

- (void)dealloc {
    if (_accountID) {
        // Securely clear the string by overwriting with zeros
        char *ptr = (char *)[_accountID UTF8String];
        size_t len = strlen(ptr);
        memset_s(ptr, len, 0, len);
        _accountID = nil;
    }
}
CleverTapSDK/Encryption/CTAESCrypt.h (2)

15-27: Enhance security documentation and parameter types.

While the documentation is good, it should include security considerations and use more specific parameter types.

Consider these improvements:

 /**
  * Performs AES-128 encryption/decryption operation
+ * 
+ * @warning This implementation is for backward compatibility only.
+ * For iOS 13.0 and later, use CTEncryptionManager which provides
+ * AES-GCM encryption with better security guarantees.
  *
  * @param operation The operation to perform (encryption or decryption)
  * @param key The encryption/decryption key
  * @param identifier The initialization vector
  * @param data The data to encrypt/decrypt
+ * @throws NSInvalidArgumentException if key or identifier length is invalid
  * @return The processed data, or nil if operation fails
  */
- - (nullable NSData *)AES128WithOperation:(CCOperation)operation
-                                   key:(NSString *)key
-                            identifier:(NSString *)identifier
-                                  data:(NSData *)data;
+ - (nullable NSData *)AES128WithOperation:(CCOperation)operation
+                                    key:(NSString * _Nonnull)key
+                             identifier:(NSString * _Nonnull)identifier
+                                  data:(NSData * _Nonnull)data
+                    NS_SWIFT_NAME(aes128(operation:key:identifier:data:));

29-29: Add nullability annotation and documentation for generateKeyPassword.

The method lacks both nullability annotation and documentation block.

+/**
+ * Generates a key password for AES encryption.
+ * @return The generated key password string
+ */
-  - (NSString *)generateKeyPassword;
+  - (nullable NSString *)generateKeyPassword;
CleverTapSDK/Encryption/CTCryptHandler.m (2)

13-17: Move encryption-related constants to a dedicated header.

Constants related to encryption should be moved to a dedicated header file for better organization and reusability.

Create a new header file CTEncryptionConstants.h:

// CTEncryptionConstants.h
extern NSString *const kENCRYPTION_KEY;
extern NSString *const kCRYPT_KEY_PREFIX;
extern NSString *const kCRYPT_KEY_SUFFIX;
extern NSString *const kCacheGUIDS;
extern NSString *const kEncryptionKeyTag;

136-137: Rename method to indicate encryption version.

As suggested in past comments, rename the method to clearly indicate which encryption version is being used.

-- (NSData *)convertData:(NSData *)data
-          withOperation:(CCOperation)operation {
+- (NSData *)convertData:(NSData *)data
+          withOperation:(CCOperation)operation
+     usingEncryption:(CTEncryptionVersion)version {
CleverTapSDK/Encryption/CTCryptMigrator.h (1)

13-20: Add documentation for the migration interface.

The interface methods lack documentation explaining their purpose and usage. Consider adding documentation blocks to clarify:

  • The migration strategy being implemented
  • The purpose of each initialization method
  • The role of generateKeyPassword in the migration process
 @interface CTCryptMigrator : NSObject
 
+/**
+ * Initializes the migrator with an account ID and instance flag.
+ * @param accountID The account identifier for the migration
+ * @param isDefaultInstance Flag indicating if this is the default instance
+ * @return An initialized migrator instance
+ */
 - (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance;
 
+/**
+ * Initializes the migrator with an account ID.
+ * @param accountID The account identifier for the migration
+ * @return An initialized migrator instance
+ */
 - (instancetype)initWithAccountID:(NSString *)accountID;
 
+/**
+ * Generates a key password for the migration process.
+ * @return The generated key password string
+ */
 - (NSString *)generateKeyPassword;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between add1d8e and c69007b.

📒 Files selected for processing (6)
  • CleverTapSDK.xcodeproj/project.pbxproj (26 hunks)
  • CleverTapSDK/Encryption/CTAESCrypt.h (1 hunks)
  • CleverTapSDK/Encryption/CTAESCrypt.m (1 hunks)
  • CleverTapSDK/Encryption/CTCryptHandler.m (2 hunks)
  • CleverTapSDK/Encryption/CTCryptMigrator.h (1 hunks)
  • CleverTapSDK/Encryption/CTCryptMigrator.m (1 hunks)
👮 Files not reviewed due to content moderation or server errors (4)
  • CleverTapSDK/Encryption/CTCryptMigrator.h
  • CleverTapSDK/Encryption/CTAESCrypt.h
  • CleverTapSDK/Encryption/CTAESCrypt.m
  • CleverTapSDK/Encryption/CTCryptHandler.m
🔇 Additional comments (9)
CleverTapSDK/Encryption/CTCryptMigrator.m (2)

14-15: Consider obfuscating key derivation constants.

Hardcoded string constants used for key derivation could be vulnerable to reverse engineering. Consider implementing additional security measures such as:

  • Obfuscating these values
  • Using more secure key derivation functions (KDF)
  • Storing sensitive parts in the keychain

60-63: 🛠️ Refactor suggestion

Enhance key generation security.

The current key generation method using simple string concatenation might not provide sufficient security. Consider:

  1. Using a cryptographically secure key derivation function (KDF)
  2. Adding salt and iteration count
  3. Validating accountID before use
✅ Verification successful

Enhance Key Generation Security

The current implementation in CleverTapSDK/Encryption/CTCryptMigrator.m uses basic string concatenation, which is not suitable for cryptographic key generation. Based on best practices for iOS using CommonCrypto, consider the following improvements:

  • Use a Cryptographically Secure KDF: Replace the simple concatenation with a key derivation function like PBKDF2 (via CCKeyDerivationPBKDF) or HKDF.
  • Generate a Random Salt: Create a unique, random salt (e.g., using SecRandomCopyBytes) for every key derivation.
  • Use a High Iteration Count: Configure PBKDF2 with a sufficiently high number of iterations (e.g., ≥100,000) to mitigate brute-force attacks.
  • Validate Inputs: Validate the accountID to ensure it meets the expected format and length before using it in key derivation.
  • Secure Key Storage: Once derived, ensure keys are stored securely (for example, using the iOS Keychain).

Implementing these changes will help protect against vulnerabilities such as brute-force attacks and ensure that the derived keys are robust and secure.

🏁 Scripts executed

The following scripts were executed for the analysis:

Web query:

What are the best practices for key derivation in iOS using CommonCrypto?

Length of output: 4491

CleverTapSDK.xcodeproj/project.pbxproj (6)

788-795: New encryption-related header files added

The following new encryption-related header files have been added to the project:

  • CTAESCrypt.h - For AES encryption functionality
  • CTCryptMigrator.h - For handling encryption migration
  • CTAESGCMCrypt.swift - For AES-GCM encryption implementation
  • CTKeychainManager.swift - For keychain management
  • CTCryptModel.swift - For encryption data models
  • CTEncryptionManager.swift - For centralized encryption management
  • CTCryptHandler.h - For unified encryption handling

This indicates a major refactoring of the encryption system with new Swift files for modern encryption capabilities.


1357-1373: New Encryption group added to project structure

A new "Encryption" group has been created to organize all encryption-related files. This improves code organization by grouping related files together.


2588-2599: Swift files compilation order

The Swift files are compiled in the following order:

  1. CTCryptModel.swift
  2. CTKeychainManager.swift
  3. CTAESGCMCrypt.swift
  4. CTEncryptionManager.swift

This order ensures proper dependency resolution between the Swift files.


2787-2788: Encryption implementation files compilation

The encryption implementation files are compiled in both targets:

4803951B2A7ABAD200C4D254 /* CTCryptHandler.m in Sources */,
0B6AB3042D51E339009EE1BA /* CTAESCrypt.m in Sources */,

The encryption files are properly included in both the main framework and TVOS targets.

Also applies to: 2813-2814


2343-2344: Swift version migration

The project has been migrated to Swift 6.0 as indicated by:

LastSwiftMigration = 1610;

Since Swift 6.0 is still in development, verify compatibility:

✅ Verification successful

Swift 6.0 is confirmed stable

Based on the latest information as of February 2025, Swift 6.0 (with subsequent minor patches) is the most recent stable release, not a development version. Therefore, the migration indicated by LastSwiftMigration = 1610; is correct and aligns with the stable Swift version.

🏁 Scripts executed

The following scripts were executed for the analysis:

Web query:

What is the latest stable version of Swift?

Length of output: 2401


2824-2826: Keychain integration

The keychain manager is properly integrated:

0B7572322D50AB87004C3B2B /* CTKeychainManager.swift in Sources */,

Run this script to check keychain entitlements:

✅ Verification successful

I'm not seeing any output from the initial command, so let's dig a bit deeper. I'd like to list and decode all .entitlements files to verify if any keychain-related settings are present. Please run the script below:


Keychain Integration Verified

Based on our investigation:

  • The PBX project correctly includes CTKeychainManager.swift (as seen at line 2824-2826 in the project file).
  • The entitlements files were examined, and no explicit "keychain" or "keychain-access-groups" settings were found, which is expected in cases where keychain sharing isn’t configured.

Overall, the integration appears correct. If no keychain-sharing features are needed, the absence of explicit entitlements is normal.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Check keychain access entitlements
security cms -D -i "*.entitlements" 2>/dev/null | grep -i keychain

Length of output: 56


Script:

#!/bin/bash
echo "Listing all .entitlements files in the repository:"
find . -type f -name '*.entitlements'
echo ""
echo "Decoding each entitlements file and searching for keychain entries:"
find . -type f -name '*.entitlements' -exec security cms -D -i {} \; 2>/dev/null | grep -i keychain

Length of output: 629

CleverTapSDK/Encryption/CTAESCrypt.m (1)

55-59: ⚠️ Potential issue

Implement secure key generation instead of using a hard-coded value.

The current implementation returns a hard-coded string "key", which is insecure. Consider implementing proper key derivation.

Comment on lines +25 to +39
- (instancetype)initWithAccountID:(NSString *)accountID
isDefaultInstance:(BOOL)isDefaultInstance {
if (self = [super init]) {
_accountID = accountID;
_isDefaultInstance = isDefaultInstance;
}
return self;
}

- (instancetype)initWithAccountID:(NSString *)accountID {
if (self = [super init]) {
_accountID = accountID;
}
return self;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add input validation for accountID.

The initializers should validate that accountID is not nil or empty to prevent potential issues.

 - (instancetype)initWithAccountID:(NSString *)accountID
                 isDefaultInstance:(BOOL)isDefaultInstance {
+    if (!accountID.length) {
+        return nil;
+    }
     if (self = [super init]) {
         _accountID = accountID;
         _isDefaultInstance = isDefaultInstance;
     }
     return self;
 }

 - (instancetype)initWithAccountID:(NSString *)accountID {
+    if (!accountID.length) {
+        return nil;
+    }
     if (self = [super init]) {
         _accountID = accountID;
     }
     return self;
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (instancetype)initWithAccountID:(NSString *)accountID
isDefaultInstance:(BOOL)isDefaultInstance {
if (self = [super init]) {
_accountID = accountID;
_isDefaultInstance = isDefaultInstance;
}
return self;
}
- (instancetype)initWithAccountID:(NSString *)accountID {
if (self = [super init]) {
_accountID = accountID;
}
return self;
}
- (instancetype)initWithAccountID:(NSString *)accountID
isDefaultInstance:(BOOL)isDefaultInstance {
if (!accountID.length) {
return nil;
}
if (self = [super init]) {
_accountID = accountID;
_isDefaultInstance = isDefaultInstance;
}
return self;
}
- (instancetype)initWithAccountID:(NSString *)accountID {
if (!accountID.length) {
return nil;
}
if (self = [super init]) {
_accountID = accountID;
}
return self;
}

Comment on lines 154 to 157
0B6AB3002D51E332009EE1BA /* CTAESCrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B6AB2FF2D51E32A009EE1BA /* CTAESCrypt.h */; };
0B6AB3012D51E332009EE1BA /* CTAESCrypt.h in Headers */ = {isa = PBXBuildFile; fileRef = 0B6AB2FF2D51E32A009EE1BA /* CTAESCrypt.h */; };
0B6AB3032D51E339009EE1BA /* CTAESCrypt.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B6AB3022D51E336009EE1BA /* CTAESCrypt.m */; };
0B6AB3042D51E339009EE1BA /* CTAESCrypt.m in Sources */ = {isa = PBXBuildFile; fileRef = 0B6AB3022D51E336009EE1BA /* CTAESCrypt.m */; };
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

CTCryptHandler header duplication

The CTCryptHandler.h file is being added twice to the Headers build phase:

0B6AB3002D51E332009EE1BA /* CTAESCrypt.h in Headers */,
0B6AB3012D51E332009EE1BA /* CTAESCrypt.h in Headers */,

Remove the duplicate header inclusion:

- 0B6AB3012D51E332009EE1BA /* CTAESCrypt.h in Headers */,

Also applies to: 179-180

Comment on lines 13 to 21
@interface CTCryptMigrator : NSObject

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

- (instancetype)initWithAccountID:(NSString *)accountID;

- (NSString *)generateKeyPassword;

@end
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add documentation for the migration interface.

The interface lacks documentation explaining its purpose, migration strategy, and the significance of isDefaultInstance. Consider adding class-level documentation and method-level comments.

Add documentation like this:

+/**
+ * CTCryptMigrator handles the migration of encryption keys and data between
+ * legacy AES encryption and the new AES-GCM implementation.
+ */
 @interface CTCryptMigrator : NSObject
 
+/**
+ * Initializes the migrator with an account ID and instance type.
+ * @param accountID The account identifier used for encryption
+ * @param isDefaultInstance Whether this is the default CleverTap instance
+ */
 - (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance;
 
+/**
+ * Initializes the migrator with just an account ID.
+ * @param accountID The account identifier used for encryption
+ */
 - (instancetype)initWithAccountID:(NSString *)accountID;
 
+/**
+ * Generates a key password for encryption operations.
+ * @return The generated key password string
+ */
 - (NSString *)generateKeyPassword;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@interface CTCryptMigrator : NSObject
- (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance;
- (instancetype)initWithAccountID:(NSString *)accountID;
- (NSString *)generateKeyPassword;
@end
/**
* CTCryptMigrator handles the migration of encryption keys and data between
* legacy AES encryption and the new AES-GCM implementation.
*/
@interface CTCryptMigrator : NSObject
/**
* Initializes the migrator with an account ID and instance type.
* @param accountID The account identifier used for encryption
* @param isDefaultInstance Whether this is the default CleverTap instance
*/
- (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance;
/**
* Initializes the migrator with just an account ID.
* @param accountID The account identifier used for encryption
*/
- (instancetype)initWithAccountID:(NSString *)accountID;
/**
* Generates a key password for encryption operations.
* @return The generated key password string
*/
- (NSString *)generateKeyPassword;
@end

🛠️ Refactor suggestion

Add class and method documentation.

The class and its public methods lack documentation explaining their purpose and usage. This is particularly important for a security-related migration class.

Add documentation blocks:

+/**
+ * Handles migration of encrypted data between different encryption implementations.
+ * Used during the transition from legacy AES to AES-GCM encryption.
+ */
 @interface CTCryptMigrator : NSObject
 
+/**
+ * Initializes the migrator with an account ID and instance type.
+ * @param accountID The account identifier used for encryption
+ * @param isDefaultInstance Whether this is the default CleverTap instance
+ * @return An initialized CTCryptMigrator instance
+ */
 - (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance;
 
+/**
+ * Initializes the migrator with an account ID.
+ * @param accountID The account identifier used for encryption
+ * @return An initialized CTCryptMigrator instance
+ */
 - (instancetype)initWithAccountID:(NSString *)accountID;
 
+/**
+ * Generates a key password for encryption/decryption operations.
+ * @return The generated key password string
+ */
 - (NSString *)generateKeyPassword;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@interface CTCryptMigrator : NSObject
- (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance;
- (instancetype)initWithAccountID:(NSString *)accountID;
- (NSString *)generateKeyPassword;
@end
/**
* Handles migration of encrypted data between different encryption implementations.
* Used during the transition from legacy AES to AES-GCM encryption.
*/
@interface CTCryptMigrator : NSObject
/**
* Initializes the migrator with an account ID and instance type.
* @param accountID The account identifier used for encryption
* @param isDefaultInstance Whether this is the default CleverTap instance
* @return An initialized CTCryptMigrator instance
*/
- (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance;
/**
* Initializes the migrator with an account ID.
* @param accountID The account identifier used for encryption
* @return An initialized CTCryptMigrator instance
*/
- (instancetype)initWithAccountID:(NSString *)accountID;
/**
* Generates a key password for encryption/decryption operations.
* @return The generated key password string
*/
- (NSString *)generateKeyPassword;
@end

Comment on lines 55 to 59
- (NSString *)generateKeyPassword {
// Add your implementation here
// Return the key password string
return @"key";
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve key generation and add security measures.

The current implementation returns a static key "key", which is insecure. Additionally, the key generation should use secure random generation.

Implement secure key generation:

 - (NSString *)generateKeyPassword {
-    // Add your implementation here
-    // Return the key password string
-    return @"key";
+    NSMutableData *keyData = [NSMutableData dataWithLength:kCCKeySizeAES128];
+    int result = SecRandomCopyBytes(kSecRandomDefault, kCCKeySizeAES128, keyData.mutableBytes);
+    if (result != errSecSuccess) {
+        CleverTapLogStaticInternal(@"Failed to generate secure random key");
+        return nil;
+    }
+    return [keyData base64EncodedStringWithOptions:0];
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (NSString *)generateKeyPassword {
// Add your implementation here
// Return the key password string
return @"key";
}
- (NSString *)generateKeyPassword {
- // Add your implementation here
- // Return the key password string
- return @"key";
-}
+ (NSString *)generateKeyPassword {
+ NSMutableData *keyData = [NSMutableData dataWithLength:kCCKeySizeAES128];
+ int result = SecRandomCopyBytes(kSecRandomDefault, kCCKeySizeAES128, keyData.mutableBytes);
+ if (result != errSecSuccess) {
+ CleverTapLogStaticInternal(@"Failed to generate secure random key");
+ return nil;
+ }
+ return [keyData base64EncodedStringWithOptions:0];
+}

⚠️ Potential issue

Critical: Implement secure key generation.

The current implementation returns a hardcoded string "key" which severely compromises security. This needs to be replaced with a secure key generation mechanism.

Consider using a secure key derivation function:

 - (NSString *)generateKeyPassword {
-    // Add your implementation here
-    // Return the key password string
-    return @"key";
+    // Use a secure key derivation function (PBKDF2)
+    NSMutableData *salt = [NSMutableData dataWithLength:32];
+    SecRandomCopyBytes(kSecRandomDefault, 32, salt.mutableBytes);
+    
+    NSMutableData *derivedKey = [NSMutableData dataWithLength:32];
+    int result = CCKeyDerivationPBKDF(kCCPBKDF2,
+                                     [self.accountID UTF8String],
+                                     [self.accountID length],
+                                     salt.bytes,
+                                     salt.length,
+                                     kCCPRFHmacAlgSHA256,
+                                     10000,
+                                     derivedKey.mutableBytes,
+                                     derivedKey.length);
+    
+    if (result != kCCSuccess) {
+        return nil;
+    }
+    
+    return [derivedKey base64EncodedStringWithOptions:0];
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (NSString *)generateKeyPassword {
// Add your implementation here
// Return the key password string
return @"key";
}
- (NSString *)generateKeyPassword {
// Use a secure key derivation function (PBKDF2)
NSMutableData *salt = [NSMutableData dataWithLength:32];
SecRandomCopyBytes(kSecRandomDefault, 32, salt.mutableBytes);
NSMutableData *derivedKey = [NSMutableData dataWithLength:32];
int result = CCKeyDerivationPBKDF(kCCPBKDF2,
[self.accountID UTF8String],
[self.accountID length],
salt.bytes,
salt.length,
kCCPRFHmacAlgSHA256,
10000,
derivedKey.mutableBytes,
derivedKey.length);
if (result != kCCSuccess) {
return nil;
}
return [derivedKey base64EncodedStringWithOptions:0];
}

Comment on lines 13 to 53
- (nullable 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];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add memory zeroing for sensitive data.

The implementation should securely clear sensitive data from memory after use.

Add memory zeroing:

 - (nullable NSData *)AES128WithOperation:(CCOperation)operation
                                    key:(NSString *)key
                             identifier:(NSString *)identifier
                                   data:(NSData *)data {
     char keyCString[kCCKeySizeAES128 + 1];
     memset(keyCString, 0, sizeof(keyCString));
     [key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding];
+    @try {
         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];
+    } @finally {
+        // Zero out sensitive data
+        memset(keyCString, 0, sizeof(keyCString));
+        memset(identifierCString, 0, sizeof(identifierCString));
+    }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (nullable 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];
}
- (nullable NSData *)AES128WithOperation:(CCOperation)operation
key:(NSString *)key
identifier:(NSString *)identifier
data:(NSData *)data {
char keyCString[kCCKeySizeAES128 + 1];
memset(keyCString, 0, sizeof(keyCString));
[key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding];
@try {
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];
} @finally {
// Zero out sensitive data
memset(keyCString, 0, sizeof(keyCString));
memset(identifierCString, 0, sizeof(identifierCString));
}
}

🛠️ Refactor suggestion

Ensure proper memory management in AES128WithOperation.

The method allocates memory but doesn't properly handle all error cases.

Add memory cleanup in all error paths:

 - (nullable NSData *)AES128WithOperation:(CCOperation)operation
                                    key:(NSString *)key
                             identifier:(NSString *)identifier
                                   data:(NSData *)data {
     char keyCString[kCCKeySizeAES128 + 1];
     memset(keyCString, 0, sizeof(keyCString));
-    [key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding];
+    if (![key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding]) {
+        return nil;
+    }
     
     char identifierCString[kCCBlockSizeAES128 + 1];
     memset(identifierCString, 0, sizeof(identifierCString));
-    [identifier getCString:identifierCString
+    if (![identifier getCString:identifierCString
                  maxLength:sizeof(identifierCString)
-                   encoding:NSUTF8StringEncoding];
+                   encoding:NSUTF8StringEncoding]) {
+        return nil;
+    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (nullable 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];
}
- (nullable 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));
if (![key getCString:keyCString maxLength:sizeof(keyCString) encoding:NSUTF8StringEncoding]) {
return nil;
}
char identifierCString[kCCBlockSizeAES128 + 1];
memset(identifierCString, 0, sizeof(identifierCString));
if (![identifier getCString:identifierCString maxLength:sizeof(identifierCString) encoding:NSUTF8StringEncoding]) {
return nil;
}
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];
}

⚠️ Potential issue

Add input validation and secure memory handling.

The AES128 implementation needs several security improvements:

  1. Validate input parameters before processing
  2. Use secure memory wiping for sensitive data
  3. Handle memory allocation failures
 - (nullable NSData *)AES128WithOperation:(CCOperation)operation
                                    key:(NSString *)key
                             identifier:(NSString *)identifier
                                   data:(NSData *)data {
+    if (!key || !identifier || !data) {
+        CleverTapLogStaticInternal(@"Invalid input parameters");
+        return nil;
+    }
+
     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);
+    if (!output) {
+        CleverTapLogStaticInternal(@"Failed to allocate memory for encryption");
+        return nil;
+    }
     
     // ... rest of the code ...
     
     if (cryptStatus != kCCSuccess) {
         CleverTapLogStaticInternal(@"Failed to encode/deocde the string with error code: %d", cryptStatus);
+        memset(output, 0, outputAvailableSize); // Secure memory wiping
         free(output);
         return nil;
     }
     
-    return [NSData dataWithBytesNoCopy:output length:outputMovedSize];
+    NSData *result = [NSData dataWithBytes:output length:outputMovedSize];
+    memset(output, 0, outputAvailableSize); // Secure memory wiping
+    free(output);
+    return result;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (nullable 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];
}
- (nullable NSData *)AES128WithOperation:(CCOperation)operation
key:(NSString *)key
identifier:(NSString *)identifier
data:(NSData *)data {
+ if (!key || !identifier || !data) {
+ CleverTapLogStaticInternal(@"Invalid input parameters");
+ return nil;
+ }
+
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);
+ if (!output) {
+ CleverTapLogStaticInternal(@"Failed to allocate memory for encryption");
+ return nil;
+ }
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);
+ memset(output, 0, outputAvailableSize); // Secure memory wiping
free(output);
return nil;
}
- return [NSData dataWithBytesNoCopy:output length:outputMovedSize];
+ NSData *result = [NSData dataWithBytes:output length:outputMovedSize];
+ memset(output, 0, outputAvailableSize); // Secure memory wiping
+ free(output);
+ return result;
}

identifier:(NSString *)identifier
data:(NSData *)data;

- (NSString *)generateKeyPassword;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add documentation for generateKeyPassword method.

The method lacks documentation explaining its purpose and return value.

Add documentation block:

+/**
+ * Generates a key password for AES-128 encryption/decryption operations.
+ * @return The generated key password string
+ */
 - (NSString *)generateKeyPassword;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- (NSString *)generateKeyPassword;
/**
* Generates a key password for AES-128 encryption/decryption operations.
* @return The generated key password string
*/
- (NSString *)generateKeyPassword;

}

func encrypt(_ data: Data, using key: SymmetricKey) throws -> EncryptedData {
let nonce = try AES.GCM.Nonce(data: Data(count: 12))
Copy link
Collaborator

Choose a reason for hiding this comment

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

@kushCT from the documentation of the Nonce init method,

"/// Creates a new random nonce.
///
/// The default nonce is a 12-byte random nonce.
public init()

    /// Creates a nonce from the given data.
    ///
    /// Unless your use case calls for a nonce with a specific value, use the
    /// ``init()`` method to instead create a random nonce.
    ///
    /// - Parameters:
    ///   - data: A data representation of the nonce.
    ///     The initializer throws an error if the data has a length smaller than 12 bytes.
    public init<D>(data: D) throws where D : DataProtocol"

The default is 12 already. I think you can do let nonce = AES.GCM.Nonce() already.

@akashvercetti
Copy link
Collaborator

@kushCT most of the coderabbit comments involve implementing a do try catch block instead if just try blocks. Try to implement the catch blocks for better debugging/logging purposes.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🔭 Outside diff range comments (2)
CleverTapSDK/Encryption/CTCryptHandler.m (2)

197-201: Add error handling for NSKeyedArchiver.

The archivedDataWithRootObject: method can throw exceptions. Consider using the newer error-based API.

-        NSData *dataValue = [NSKeyedArchiver archivedDataWithRootObject:objectToEncrypt];
+        NSError *archiveError = nil;
+        NSData *dataValue = [NSKeyedArchiver archivedDataWithRootObject:objectToEncrypt requiringSecureCoding:YES error:&archiveError];
+        if (archiveError) {
+            CleverTapLogStaticInternal(@"Error archiving object: %@", archiveError.localizedDescription);
+            return nil;
+        }

214-216: Use secure coding for unarchiving objects.

The unarchiveObjectWithData: method is deprecated. Use the newer secure coding API.

-            return [NSKeyedUnarchiver unarchiveObjectWithData:decryptedData];
+            NSError *unarchiveError = nil;
+            id object = [NSKeyedUnarchiver unarchivedObjectOfClasses:[NSSet allWithObjects:[NSString class], [NSNumber class], [NSArray class], [NSDictionary class], nil]
+                                                          fromData:decryptedData
+                                                            error:&unarchiveError];
+            if (unarchiveError) {
+                CleverTapLogStaticInternal(@"Error unarchiving data: %@", unarchiveError.localizedDescription);
+                return nil;
+            }
+            return object;
♻️ Duplicate comments (5)
CleverTapSDK/Encryption/CTAESCrypt.m (4)

12-13: ⚠️ Potential issue

Critical: Remove hardcoded cryptographic key components.

Using static strings (kCRYPT_KEY_PREFIX and kCRYPT_KEY_SUFFIX) as key components severely compromises security as they are visible in the source code and remain constant across installations.

Consider using secure key generation as suggested in previous reviews, utilizing SecRandomCopyBytes or PBKDF2 for key derivation.


21-23: ⚠️ Potential issue

Add input parameter validation.

The method should validate input parameters before processing.

Add parameter validation:

 - (nullable NSData *)AES128WithOperation:(CCOperation)operation
                               accountID:(NSString *)accountID
                                    data:(NSData *)data {
+    if (!accountID || !data) {
+        CleverTapLogStaticInternal(@"Invalid input parameters");
+        return nil;
+    }

41-64: ⚠️ Potential issue

Improve memory management and security.

The current implementation has several memory-related security issues:

  1. No check for memory allocation failure
  2. No secure memory wiping
  3. Potential memory leak using dataWithBytesNoCopy

Apply these security improvements:

     size_t outputAvailableSize = [data length] + kCCBlockSizeAES128;
     void *output = malloc(outputAvailableSize);
+    if (!output) {
+        CleverTapLogStaticInternal(@"Failed to allocate memory");
+        return nil;
+    }
     
     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);
+        memset(output, 0, outputAvailableSize);
         free(output);
         return nil;
     }
     
-    return [NSData dataWithBytesNoCopy:output length:outputMovedSize];
+    NSData *result = [NSData dataWithBytes:output length:outputMovedSize];
+    memset(output, 0, outputAvailableSize);
+    free(output);
+    return result;

66-69: ⚠️ Potential issue

Critical: Replace weak key generation with cryptographic key derivation.

The current key generation method using string concatenation is cryptographically weak and predictable.

Implement a secure key derivation function (PBKDF2):

 - (NSString *)generateKeyPassword {
-    NSString *keyPassword = [NSString stringWithFormat:@"%@%@%@",kCRYPT_KEY_PREFIX, _accountID, kCRYPT_KEY_SUFFIX];
-    return keyPassword;
+    NSMutableData *salt = [NSMutableData dataWithLength:32];
+    SecRandomCopyBytes(kSecRandomDefault, 32, salt.mutableBytes);
+    
+    NSMutableData *derivedKey = [NSMutableData dataWithLength:32];
+    int result = CCKeyDerivationPBKDF(kCCPBKDF2,
+                                     [self.accountID UTF8String],
+                                     [self.accountID length],
+                                     salt.bytes,
+                                     salt.length,
+                                     kCCPRFHmacAlgSHA256,
+                                     10000,
+                                     derivedKey.mutableBytes,
+                                     derivedKey.length);
+    
+    if (result != kCCSuccess) {
+        return nil;
+    }
+    
+    return [derivedKey base64EncodedStringWithOptions:0];
 }
CleverTapSDK/Encryption/CTCryptHandler.m (1)

143-166: 🛠️ Refactor suggestion

Several improvements needed in the encryption implementation.

  1. Use consistent logging mechanism
  2. Use constants for keychain tags
  3. Add error handling documentation
    if (@available(iOS 13.0, *)) {
-        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:@"EncryptionKey"];
+        static NSString *const kEncryptionKeyTag = @"EncryptionKey";
+        CTEncryptionManager *encryptionManager = [[CTEncryptionManager alloc] initWithKeychainTag:kEncryptionKeyTag];
        NSError *error = nil;
        NSData *processedData = nil;
        
        switch (operation) {
            case kCCEncrypt:
                processedData = [encryptionManager encryptData:data error:&error];
                break;
            case kCCDecrypt:
                processedData = [encryptionManager decryptData:data error:&error];
                break;
            default:
-                NSLog(@"Unsupported operation");
+                CleverTapLogStaticInternal(@"Unsupported cryptographic operation");
                break;
        }
        
        if (error) {
-            NSLog(@"Encryption/Decryption error: %@", error);
+            CleverTapLogStaticInternal(@"Encryption/Decryption error: %@", error.localizedDescription);
            return nil;
        }
🧹 Nitpick comments (4)
CleverTapSDK/Encryption/CTAESCrypt.m (1)

1-71: Architectural Misalignment: Use AES-GCM instead of AES-128 CBC.

According to the PR objectives, the encryption should be upgraded to AES-GCM. However, this implementation uses AES-128 in CBC mode, which doesn't provide authenticated encryption. AES-GCM is recommended as it provides both confidentiality and authenticity.

Consider implementing AES-GCM using the CCCryptorCreateWithMode API which supports GCM mode. This would align with the PR objectives and provide better security through authenticated encryption.

CleverTapSDK/Encryption/CTCryptHandler.m (1)

13-18: Consider using module imports for better encapsulation.

The Swift header import could be simplified using modules.

-#if __has_include(<CleverTapSDK/CleverTapSDK-Swift.h>)
-#import <CleverTapSDK/CleverTapSDK-Swift.h>
-#else
-#import "CleverTapSDK-Swift.h"
-#endif
+@import CleverTapSDK;
SwiftStarter/SwiftStarter.xcodeproj/project.pbxproj (1)

207-207: Check PBXFrameworksBuildPhase additions.
You added multiple Pods frameworks (Pods_SwiftStarter.framework, Pods_SwiftStarterUITests.framework, etc.) to the Frameworks build phase. Confirm that each target requires these frameworks to avoid linking overhead for targets that do not need them.

Also applies to: 215-215, 223-223, 231-231, 239-239, 268-268

SwiftStarter/SwiftStarter/AppDelegate.swift (1)

33-35: Evaluate logging of the UserDefaults path.
These lines log the folder path for UserDefaults. This is often helpful in debugging but might not be needed in production logs. Confirm whether you want this log active in release builds to avoid unnecessary exposure of local paths.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ee8bb29 and ffa4158.

📒 Files selected for processing (10)
  • CleverTapSDK/CTConstants.h (1 hunks)
  • CleverTapSDK/Encryption/CTAESCrypt.h (1 hunks)
  • CleverTapSDK/Encryption/CTAESCrypt.m (1 hunks)
  • CleverTapSDK/Encryption/CTCryptHandler.m (2 hunks)
  • CleverTapSDK/Encryption/CTCryptMigrator.h (1 hunks)
  • CleverTapSDK/Encryption/CTCryptMigrator.m (1 hunks)
  • CleverTapSDK/Encryption/CTEncryptionManager.swift (1 hunks)
  • SwiftStarter/SwiftStarter.xcodeproj/project.pbxproj (28 hunks)
  • SwiftStarter/SwiftStarter/AppDelegate.swift (1 hunks)
  • SwiftStarter/SwiftStarter/Supporting Files/Info.plist (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • CleverTapSDK/Encryption/CTCryptMigrator.h
  • CleverTapSDK/CTConstants.h
  • CleverTapSDK/Encryption/CTCryptMigrator.m
  • CleverTapSDK/Encryption/CTEncryptionManager.swift
🔇 Additional comments (11)
CleverTapSDK/Encryption/CTAESCrypt.h (3)

26-26: Add documentation for generateKeyPassword method.

The method lacks documentation explaining its purpose and return value.


13-28: Consider additional security measures.

Since this is a security-critical component, consider:

  1. Adding input validation for the accountID and data parameters
  2. Implementing secure key derivation (e.g., using PBKDF2)
  3. Adding proper error handling and logging mechanisms
  4. Including security-related assertions in debug builds

Would you like me to provide example implementations for these security measures?


15-24: undefined

SwiftStarter/SwiftStarter/Supporting Files/Info.plist (2)

25-26: LGTM: Encryption level configuration added.

The addition of CleverTapEncryptionLevel key aligns with the PR's objective of upgrading to AES-GCM encryption.


21-24: Review storage of sensitive information.

The file contains sensitive information (CleverTapAccountID and Token) in plain text. Consider:

  1. Moving these values to a secure configuration management system
  2. Using environment variables or a secure key-value store
  3. Adding this file to .gitignore and providing a template instead

Let's check if this is a common practice in iOS projects:

✅ Verification successful

🌐 Web query:

Should CleverTap Account ID and Token be stored in Info.plist?

💡 Result:

Yes, CleverTap Account ID and Token should be stored in the `Info.plist` file for iOS integrations[2][4][6][8]. This applies across multiple frameworks including native iOS, Flutter, and React Native implementations.  

**Key implementation details**:  
- The credentials are added as entries under `CleverTapAccountID` (Account ID) and `CleverTapToken` (Account Token)[2][4][6]  
- Example XML structure:  
  ```xml
  <key>CleverTapAccountID</key>
  <string>YOUR_ACCOUNT_ID</string>
  <key>CleverTapToken</key>
  <string>YOUR_ACCOUNT_TOKEN</string>
  ```  
- For proxy domain configurations, additional keys like `CleverTapProxyDomain` and `CleverTapSpikyProxyDomain` may be required[2]  

This approach ensures proper initialization of the CleverTap SDK while maintaining security through platform-specific configuration files[2][6]. For Android implementations, equivalent credentials are stored in `AndroidManifest.xml` instead[2].

Citations:

- 1: https://segment.com/docs/connections/destinations/catalog/clevertap/
- 2: https://github.com/CleverTap/clevertap-flutter/blob/master/doc/Usage.md
- 3: https://developer.clevertap.com/docs/authentication
- 4: https://tudip.com/blog-post/clevertap-integration-in-react-native/
- 5: https://developer.clevertap.com/docs/api-quickstart-guide
- 6: https://developer.clevertap.com/docs/ios-quickstart-guide
- 7: https://developer.clevertap.com/docs/concepts-user-profiles
- 8: https://developer.clevertap.com/docs/react-native-quick-start-guide
- 9: https://developer.clevertap.com/docs/set-clevertap-id
- 10: https://github.com/CleverTap/clevertap-react-native/issues/42

Verified: CleverTap Credentials in Info.plist Are Standard Practice

The credentials for CleverTap (Account ID and Token) are expected to be stored in the Info.plist for iOS integrations based on multiple credible sources. This approach is aligned with the official documentation and common practices for integrating CleverTap in iOS projects. No changes are necessary regarding their placement.

CleverTapSDK/Encryption/CTCryptHandler.m (2)

167-173: 🛠️ Refactor suggestion

Rename methods to indicate encryption type.

Methods should be renamed to clearly indicate whether they use legacy AES or the newer AES-GCM encryption.

-        CTAESCrypt *aesCrypt = [[CTAESCrypt alloc] init];
+        // Use legacy AES encryption for iOS < 13
+        CTAESCrypt *legacyAESCrypt = [[CTAESCrypt alloc] init];
         NSData *outputData = [aesCrypt
                             AES128WithOperation:operation
                             accountID:_accountID
                             data:data];

Likely invalid or redundant comment.


109-119: Implement proper error handling using NSError.

Replace @try/@catch blocks with proper error handling using NSError objects for better error propagation and handling.

Also applies to: 125-138

✅ Verification successful

🌐 Web query:

What is the recommended error handling pattern in Objective-C for iOS development?

💡 Result:

In Objective-C iOS development, the recommended error handling pattern emphasizes **NSError** for recoverable user-facing issues while reserving **exceptions** for unrecoverable programming errors during development. Here's the structured approach:

---

### Primary Error Handling with NSError
1. **Error Propagation via Indirect Pointers**  
   Methods that may fail accept an `NSError **` parameter to populate error details on failure:
   ```objective-c
   - (BOOL)loadFile:(NSString *)path error:(NSError **)error {
       if (![self fileExists:path]) {
           *error = [NSError errorWithDomain:NSCocoaErrorDomain 
                                        code:NSFileReadNoSuchFileError 
                                    userInfo:nil];
           return NO;
       }
       return YES;
   }
   ```
   - Check the method’s return value (`BOOL`/`nil`) first before accessing the error object[1][4][5].

2. **User-Friendly Error Presentation**  
   Conform to `LocalizedError` to provide human-readable messages:
   ```objective-c
   NSError *error;
   if (![self loadFile:@"missing.txt" error:&error]) {
       UIAlertController *alert = [UIAlertController 
           alertControllerWithTitle:@"Error" 
                            message:error.localizedDescription 
                     preferredStyle:UIAlertControllerStyleAlert];
       [self presentViewController:alert animated:YES completion:nil];
   }
   ```
   - Use `localizedDescription` or custom messages via `NSLocalizedString`[2][4].

3. **Built-In Error Domains**  
   Leverage standard domains (e.g., `NSCocoaErrorDomain`, `NSURLErrorDomain`) and codes for common issues like file I/O or network failures[1][5].

---

### Exceptions: For Development-Phase Bugs Only
- **Use Cases**: Array bounds violations, invalid method arguments, or other unrecoverable logic errors[1][4].
- **Syntax** (rarely used in production):
  ```objective-c
  @try {
      NSString *value = array[3]; // Potential out-of-bounds exception
  } @catch (NSException *exception) {
      NSLog(@"Caught exception: %@", exception.reason);
      // Promote to NSError if needed
  } @finally {
      // Cleanup code
  }
  ```
  - Exceptions are resource-intensive; prefer `if (index < array.count)` checks instead[1][3].

---

### Best Practices
- **Avoid `try?`**: Use explicit error handling instead of discarding errors with `try?` unless truly optional[2].
- **Wrap Third-Party Errors**: Preserve original errors when mapping to custom types:
  ```objective-c
  NSError *underlyingError = ...; // From a framework
  NSError *customError = [NSError errorWithDomain:MyAppErrorDomain 
                                             code:MyAppNetworkError 
                                         userInfo:@{
        NSLocalizedDescriptionKey: @"Request failed",
        NSUnderlyingErrorKey: underlyingError
    }];
  ```
- **Debugging**: Log technical details (e.g., HTTP status codes) while showing user-friendly messages[2][5].

---

### Key Differences Between Errors and Exceptions
| Aspect              | Errors (`NSError`)                          | Exceptions (`NSException`)             |
|---------------------|---------------------------------------------|----------------------------------------|
| **Purpose**         | Expected failures (user input, network)    | Programmer mistakes (bugs)             |
| **Production Use**  | ✅ Handle gracefully                        | ❌ Fix during development              |
| **Performance**     | Optimized for flow control                 | Slow; avoid in runtime code[1][5]      |
| **Propagation**     | Manual via method parameters               | Automatic unwind of call stack[3]      |

---

By prioritizing `NSError` for recoverable scenarios and limiting exceptions to debugging, you ensure robust apps that handle failures gracefully while maintaining performance[1][4][5].

Citations:

- 1: https://fullonrager.github.io/rys-objective-c-tutorial-archive/exceptions.html
- 2: https://infinum.com/handbook/ios/miscellaneous/best-practices-for-error-handling-and-crash-reporting
- 3: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Exceptions/Tasks/HandlingExceptions.html
- 4: https://livebook.manning.com/book/objective-c-fundamentals/chapter-10/
- 5: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/ErrorHandling/ErrorHandling.html
- 6: https://www.youtube.com/watch?v=BwSpucO-JXE

Subject: Replace Try/Catch with NSError-Based Error Handling

Based on the updated guidelines for error handling in iOS development, the use of @try/@catch here is not ideal. Instead, you should refactor the encryption logic to use NSError-based error propagation. Specifically:

  • Modify the encryption method (e.g., convertData:withOperation:) to accept an NSError ** parameter and return nil if an error occurs.
  • Remove the try/catch block and instead check the return value of the encryption method. If the result is nil, inspect the NSError object, log it appropriately, and handle the error (for example, by returning the original identifier or propagating the error further).
  • Apply this refactoring to both the block around lines 109–119 and the similar code between lines 125–138 to ensure consistent error handling throughout.

These changes will align the code with the recommended Objective-C error handling pattern as confirmed by the web query results.

SwiftStarter/SwiftStarter.xcodeproj/project.pbxproj (4)

33-33: Validate newly added PBXBuildFile references.
These entries point to the Pods-based frameworks (NotificationService, Tests, TvOS, etc.). Ensure each target truly depends on these frameworks and that no duplicate or unused references exist.

Would you like a script to check for direct usage of these frameworks in the Xcode targets?

Also applies to: 38-42


174-196: Confirm PBXFileReference consistency.
These lines register references for frameworks and xcconfig files (e.g., Pods_SwiftTvOS.framework, Pods-SwiftStarter.release.xcconfig, etc.). Verify the file paths are correct, particularly if you have reorganized the Pods directory structure or changed build settings.


421-432: Review entries in the “Pods” PBXGroup.
These lines list the newly added xcconfig files for SwiftStarter, WatchOS Extension, TvOS, Tests, etc. Temporarily broken or missing references may cause build errors. Ensure the file paths match the actual folder layout in your repo.


779-961: Audit Shell Script Build Phases for CocoaPods.
A series of [CP] Check Pods Manifest.lock and [CP] Embed Pods Frameworks phases were introduced or modified for iOS, watchOS, tvOS, and the NotificationService extension. Verify that:
• The specific environment variables (PODS_ROOT, etc.) remain defined across all build configurations.
• The “${CONFIGURATION}” placeholders match your intended release/debug naming.
• Each script actually exists at the specified path.

If needed, you can run a script to ensure these references point to valid files and that the manifest lock is in sync.

Comment on lines +36 to +39
NSString *iv = CLTAP_ENCRYPTION_IV;
[iv getCString:identifierCString
maxLength:sizeof(identifierCString)
encoding:NSUTF8StringEncoding];
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Critical: Using static initialization vector (IV).

Using a static IV (CLTAP_ENCRYPTION_IV) for AES encryption severely compromises security. Each encryption operation should use a unique IV.

Generate a unique IV for each encryption:

-    NSString *iv = CLTAP_ENCRYPTION_IV;
-    [iv getCString:identifierCString
-                 maxLength:sizeof(identifierCString)
-                  encoding:NSUTF8StringEncoding];
+    NSMutableData *iv = [NSMutableData dataWithLength:kCCBlockSizeAES128];
+    int result = SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, iv.mutableBytes);
+    if (result != errSecSuccess) {
+        CleverTapLogStaticInternal(@"Failed to generate IV");
+        return nil;
+    }
+    memcpy(identifierCString, iv.bytes, kCCBlockSizeAES128);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
NSString *iv = CLTAP_ENCRYPTION_IV;
[iv getCString:identifierCString
maxLength:sizeof(identifierCString)
encoding:NSUTF8StringEncoding];
NSMutableData *iv = [NSMutableData dataWithLength:kCCBlockSizeAES128];
int result = SecRandomCopyBytes(kSecRandomDefault, kCCBlockSizeAES128, iv.mutableBytes);
if (result != errSecSuccess) {
CleverTapLogStaticInternal(@"Failed to generate IV");
return nil;
}
memcpy(identifierCString, iv.bytes, kCCBlockSizeAES128);

}

return [NSData dataWithBytesNoCopy:output length:outputMovedSize];
return nil;
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Remove redundant return statement.

The return nil statement is unreachable as all code paths in the if-else block already return.

-    return nil;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
return nil;
// (The redundant return statement has been removed)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants