-
Notifications
You must be signed in to change notification settings - Fork 58
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
base: feature/swift-support
Are you sure you want to change the base?
Conversation
- 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
… AES and AESGCM for iOS 13+
WalkthroughThis 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
Suggested reviewers
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? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
There was a problem hiding this 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 namedencryptString
, it accepts and returnsData
. If you plan to encrypt arbitrary binary data, consider renaming it toencryptData
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 inconvertData
.
WhenencryptString:data error:
returns a non-nilNSError
, the current code simply returnsencryptedData
which may also benil
. 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:
- Moving encryption outside synchronized blocks
- 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
📒 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 functionalityThe following Swift files have been added to implement encryption capabilities:
CTAESCrypt.swift
: Implements AES encryption/decryptionCTAESGCMCrypt.swift
: Implements AES-GCM mode encryption/decryptionCTKeychainManager.swift
: Handles keychain operationsCTCryptModel.swift
: Defines encryption-related modelsCTEncryptionManager.swift
: Manages encryption operationsThe 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 handlerAdded
CTCryptHandler.h
andCTCryptHandler.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 supportThe 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 modulesThe 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 optimizationAdded build settings:
SWIFT_OPTIMIZATION_LEVEL = "-Onone"
for debug buildsSWIFT_VERSION = 6.0
to specify Swift language versionAppropriate Swift version and optimization settings for the project.
3302-3303
: Consistent Swift settings across configurationsThe 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 phasesThe 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 groupCreated 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.
ImportingCTCryptHandler.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 fromCTAES
toCTCryptHandler
consistently conveys its broader encryption responsibilities.
121-121
: Decrypt method usage.
Replacing the old approach withconvertData2
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) ifdecryptedData
is nil.CleverTapSDK/Encryption/CTCryptHandler.h (1)
4-4
: Renamed class toCTCryptHandler
.
This rename more accurately represents the broader cryptographic functionality (beyond just AES) you’re implementing in this refactoring. RetainingNSSecureCoding
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:
- Implementing class validation before unarchiving
- 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:
andsupportsSecureCoding
. 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 objcLength of output: 298
Script:
#!/bin/bash # Check if CTCryptHandler implements secure coding in its implementation file. rg "initWithCoder" CleverTapSDK/Encryption/CTCryptHandler.mLength 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:
- Check for medium encryption level
- Use the new
CTCryptHandler
for decryption- Handle PII data appropriately
988-994
: LGTM! Updated encryption logic with encryption level check.The encryption logic has been updated to:
- Check for medium encryption level
- Use the new
CTCryptHandler
for encryption- 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
CleverTap-iOS-SDK.podspec
Outdated
@@ -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' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
s.swift_version = '6.0' | |
s.swift_version = '5.0' |
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) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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:
- CBC mode doesn't provide authentication
- No integrity verification of ciphertext
- Vulnerable to padding oracle attacks
Consider:
- Using CryptoKit's
AES.GCM
for modern authenticated encryption - Adding authentication tag handling
- 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)
}
}
// 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)" | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enhance key management security.
The current key management implementation has several security concerns:
- No secure key derivation
- Hardcoded key prefix/suffix
- Keys not stored in Keychain
Consider:
- Using PBKDF2 or Argon2 for key derivation
- Implementing key rotation
- Storing keys in Keychain
- 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
// 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 | ||
} | ||
} |
There was a problem hiding this comment.
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:
- Creating custom error types
- Using proper logging framework
- Adding input validation
- 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)
}
}
import Foundation | ||
import CommonCrypto | ||
|
||
class CryptoUtils { |
There was a problem hiding this comment.
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 {
// 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 | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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) | ||
} |
There was a problem hiding this comment.
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.
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 | |
} | |
} |
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 | ||
) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Add input validation and improve nonce generation.
The encryption method needs additional security measures:
- Fixed nonce size could lead to nonce reuse under high load
- 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.
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 | |
) | |
} |
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 | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Improve error handling and remove force unwrapping.
The saveKey method needs improvements:
- Force unwrapping of keychainTag.data should be handled safely
- 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.
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 | |
} | |
} |
NSString *encryptedIdentifier = identifier; | ||
if (self.config.aesCrypt) { | ||
encryptedIdentifier = [self.config.aesCrypt getEncryptedString:identifier]; | ||
if (self.config.cryptHandler) { | ||
encryptedIdentifier = [self.config.cryptHandler getEncryptedString:identifier]; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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; | |
} | |
} |
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
There was a problem hiding this 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 issueCritical: 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
📒 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:
- Keychain access control is properly configured
- Document the security implications of different encryption methods
- 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 ofkSecAttrApplicationTag
), 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.
- (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; | ||
} |
There was a problem hiding this comment.
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.
- Use consistent logging mechanism
- Reduce code duplication in error handling
- Document encryption differences between iOS versions
- 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";
@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 | ||
} |
There was a problem hiding this comment.
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
let cryptStatus = CCCrypt(operation, | ||
CCAlgorithm(kCCAlgorithmAES), | ||
CCOptions(kCCOptionPKCS7Padding), | ||
keyBuffer, | ||
key.count, | ||
ivBuffer, | ||
dataBuffer, | ||
dataLength_, | ||
buffer, | ||
bufferSize, | ||
&numBytesProcessed) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
CleverTapSDK/Encryption/CTAES.swift
Outdated
// Copyright © 2025 CleverTap. All rights reserved. | ||
// | ||
|
||
import Foundation |
There was a problem hiding this comment.
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]; |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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
There was a problem hiding this 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 supportThe following build settings have been added/modified to support Swift:
CLANG_ENABLE_MODULES = YES
- Enables modules for better Swift/Objective-C interopSWIFT_OPTIMIZATION_LEVEL = "-Onone"
- Sets Swift optimization level for debug buildsSWIFT_VERSION = 6.0
- Sets Swift language version to 6.0Consider 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.hCleverTapSDK/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 issueSeveral 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 codeAlso, add these constants at the top of the file:
static NSString *const kEncryptionKeyTag = @"com.clevertap.encryption.key";
136-171
: 🛠️ Refactor suggestionSeveral 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 issueSeveral improvements needed in the encryption implementation.
The implementation needs improvements in logging consistency, error handling, and documentation.
Previous review comment still applies. Additionally:
- The
return nil
statement on line 170 is unreachable- 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:
- Memory cleanup in dealloc
- Secure memory wiping for sensitive data
- 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
📒 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 suggestionEnhance key generation security.
The current key generation method using simple string concatenation might not provide sufficient security. Consider:
- Using a cryptographically secure key derivation function (KDF)
- Adding salt and iteration count
- 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 addedThe 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 structureA 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 orderThe Swift files are compiled in the following order:
- CTCryptModel.swift
- CTKeychainManager.swift
- CTAESGCMCrypt.swift
- CTEncryptionManager.swift
This order ensures proper dependency resolution between the Swift files.
2787-2788
: Encryption implementation files compilationThe 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 migrationThe 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 integrationThe 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 keychainLength 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 keychainLength of output: 629
CleverTapSDK/Encryption/CTAESCrypt.m (1)
55-59
:⚠️ Potential issueImplement 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.
- (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; | ||
} |
There was a problem hiding this comment.
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.
- (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; | |
} |
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 */; }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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
@interface CTCryptMigrator : NSObject | ||
|
||
- (instancetype)initWithAccountID:(NSString *)accountID isDefaultInstance:(BOOL)isDefaultInstance; | ||
|
||
- (instancetype)initWithAccountID:(NSString *)accountID; | ||
|
||
- (NSString *)generateKeyPassword; | ||
|
||
@end |
There was a problem hiding this comment.
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.
@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.
@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 |
CleverTapSDK/Encryption/CTAESCrypt.m
Outdated
- (NSString *)generateKeyPassword { | ||
// Add your implementation here | ||
// Return the key password string | ||
return @"key"; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
- (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]; | |
+} |
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.
- (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]; | |
} |
CleverTapSDK/Encryption/CTAESCrypt.m
Outdated
- (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]; | ||
} |
There was a problem hiding this comment.
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.
- (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.
- (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]; | |
} |
Add input validation and secure memory handling.
The AES128 implementation needs several security improvements:
- Validate input parameters before processing
- Use secure memory wiping for sensitive data
- 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.
- (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; |
There was a problem hiding this comment.
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.
- (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)) |
There was a problem hiding this comment.
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.
@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. |
There was a problem hiding this 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 issueCritical: Remove hardcoded cryptographic key components.
Using static strings (
kCRYPT_KEY_PREFIX
andkCRYPT_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 issueAdd 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 issueImprove memory management and security.
The current implementation has several memory-related security issues:
- No check for memory allocation failure
- No secure memory wiping
- 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 issueCritical: 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 suggestionSeveral improvements needed in the encryption implementation.
- Use consistent logging mechanism
- Use constants for keychain tags
- 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
📒 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:
- Adding input validation for the accountID and data parameters
- Implementing secure key derivation (e.g., using PBKDF2)
- Adding proper error handling and logging mechanisms
- Including security-related assertions in debug builds
Would you like me to provide example implementations for these security measures?
15-24
: undefinedSwiftStarter/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:
- Moving these values to a secure configuration management system
- Using environment variables or a secure key-value store
- 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 suggestionRename 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 usingNSError
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.
NSString *iv = CLTAP_ENCRYPTION_IV; | ||
[iv getCString:identifierCString | ||
maxLength:sizeof(identifierCString) | ||
encoding:NSUTF8StringEncoding]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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; |
There was a problem hiding this comment.
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.
return nil; | |
// (The redundant return statement has been removed) |
https://wizrocket.atlassian.net/browse/SDK-4206
[TAN][SDK] Encryption Algorithm Upgrade in SDK [W.I.P]
Summary by CodeRabbit