diff --git a/native/expo-modules/comm-expo-package/ios/AESCryptoModule.swift b/native/expo-modules/comm-expo-package/ios/AESCryptoModule.swift index 99fd86ced..e8ffa736f 100644 --- a/native/expo-modules/comm-expo-package/ios/AESCryptoModule.swift +++ b/native/expo-modules/comm-expo-package/ios/AESCryptoModule.swift @@ -1,164 +1,202 @@ import ExpoModulesCore import CryptoKit private let KEY_SIZE = 32 // bytes private let IV_LENGTH = 12 // bytes, IV - unique Initialization Vector (nonce) private let TAG_LENGTH = 16 // bytes - GCM auth tag // Expo module called from the RN app JS code public class AESCryptoModule: Module { public func definition() -> ModuleDefinition { Name("AESCrypto") Function("generateKey") { (destination: Uint8Array) throws in try! generateKey(destinationPtr: destination.rawBufferPtr(), byteLength: destination.byteLength) } Function("encrypt") { (rawKey: Uint8Array, plaintext: Uint8Array, destination: Uint8Array) throws in try! encrypt(rawKey: rawKey.data(), plaintext: plaintext.data(), plaintextLength: plaintext.byteLength, destinationPtr: destination.rawBufferPtr(), destinationLength: destination.byteLength) } Function("decrypt") { (rawKey: Uint8Array, sealedData: Uint8Array, destination: Uint8Array) throws in try! decrypt(rawKey: rawKey.data(), sealedData: sealedData.data(), sealedDataLength: sealedData.byteLength, destinationPtr: destination.rawBufferPtr(), destinationLength: destination.byteLength) } } } -// ObjC-compatible module, used from Objective-C code in NSE +// ObjC-compatible module, used from Objective-C code in NSE and from Rust @objc(AESCryptoModuleObjCCompat) public class AESCryptoModuleObjCCompat: NSObject { @objc(generateKey:withError:) public func generateKeyCompat(destination: NSMutableData) throws { let destinationPtr = UnsafeMutableRawBufferPointer(start: destination.mutableBytes, count: KEY_SIZE) try! generateKey(destinationPtr: destinationPtr, byteLength: destination.length) } + + @objc(generateKey:destinationLength:withError:) + public static func generateKeyCompat(destinationPtr: UnsafeMutableRawPointer, + destinationLength: Int) throws { + let destinationBufferPtr = UnsafeMutableRawBufferPointer(start: destinationPtr, + count: destinationLength) + try generateKey(destinationPtr: destinationBufferPtr, + byteLength: destinationLength) + } @objc(encryptedLength:) public func encryptedLengthCompat(plaintext: Data) -> NSInteger { return plaintext.count + IV_LENGTH + TAG_LENGTH } @objc(encryptWithKey:plaintext:destination:withError:) public func encryptCompat(rawKey: Data, plaintext: Data, destination: NSMutableData) throws { let destinationPtr = UnsafeMutableRawBufferPointer(start: destination.mutableBytes, count: destination.length) try! encrypt(rawKey: rawKey, plaintext: plaintext, plaintextLength: plaintext.count, destinationPtr: destinationPtr, destinationLength: destination.length) } + @objc(encryptWithKey:plaintext:destinationPtr:destinationLength:withError:) + public static func encryptCompat(rawKey: Data, + plaintext: Data, + destinationPtr: UnsafeMutableRawPointer, + destinationLength: Int) throws { + let destinationBufferPtr = UnsafeMutableRawBufferPointer(start: destinationPtr, + count: destinationLength) + + try encrypt(rawKey: rawKey, + plaintext: plaintext, + plaintextLength: plaintext.count, + destinationPtr: destinationBufferPtr, + destinationLength: destinationLength) + } + @objc(decryptedLength:) public func decryptedLengthCompat(sealedData: Data) -> NSInteger { return sealedData.count - IV_LENGTH - TAG_LENGTH } @objc(decryptWithKey:sealedData:destination:withError:) public func decryptCompat(rawKey: Data, sealedData: Data, destination: NSMutableData) throws { let destinationPtr = UnsafeMutableRawBufferPointer(start: destination.mutableBytes, count: destination.length) try! decrypt(rawKey: rawKey, sealedData: sealedData, sealedDataLength: sealedData.count, destinationPtr: destinationPtr, destinationLength: destination.length) } + @objc(decryptWithKey:sealedData:destinationPtr:destinationLength:withError:) + public static func decryptCompat(rawKey: Data, + sealedData: Data, + destinationPtr: UnsafeMutableRawPointer, + destinationLength: Int) throws { + let destinationBufferPtr = UnsafeMutableRawBufferPointer(start: destinationPtr, + count: destinationLength) + try decrypt(rawKey: rawKey, + sealedData: sealedData, + sealedDataLength: sealedData.count, + destinationPtr: destinationBufferPtr, + destinationLength: destinationLength) + } + } // MARK: - Function implementations private func generateKey(destinationPtr: UnsafeMutableRawBufferPointer, byteLength: Int) throws { guard byteLength == KEY_SIZE else { throw InvalidKeyLengthException() } let key = SymmetricKey(size: .bits256) key.withUnsafeBytes { bytes in let _ = bytes.copyBytes(to: destinationPtr) } } private func encrypt(rawKey: Data, plaintext: Data, plaintextLength: Int, destinationPtr: UnsafeMutableRawBufferPointer, destinationLength: Int) throws { guard destinationLength == plaintextLength + IV_LENGTH + TAG_LENGTH else { throw InvalidDataLengthException() } let key = SymmetricKey(data: rawKey) let iv = AES.GCM.Nonce() let encryptionResult = try AES.GCM.seal(plaintext, using: key, nonce: iv) // 'combined' returns concatenated: iv || ciphertext || tag guard let sealedData = encryptionResult.combined else { // this happens only if Nonce/IV != 12 bytes long throw EncryptionFailedException("Incorrect AES configuration") } guard sealedData.count == destinationLength else { throw EncryptionFailedException("Encrypted data has unexpected length") } sealedData.copyBytes(to: destinationPtr) } private func decrypt(rawKey: Data, sealedData: Data, sealedDataLength: Int, destinationPtr: UnsafeMutableRawBufferPointer, destinationLength: Int) throws { guard destinationLength == sealedDataLength - IV_LENGTH - TAG_LENGTH else { throw InvalidDataLengthException() } let key = SymmetricKey(data: rawKey) let sealedBox = try AES.GCM.SealedBox(combined: sealedData) let plaintext = try AES.GCM.open(sealedBox, using: key) plaintext.copyBytes(to: destinationPtr) } // MARK: - Exception definitions private class InvalidKeyLengthException: Exception { override var reason: String { "The AES key has invalid length" } } private class InvalidDataLengthException: Exception { override var reason: String { "Source or destination array has invalid length" } } private class EncryptionFailedException: GenericException { override var reason: String { "Failed to encrypt data: \(param)" } } diff --git a/native/ios/Comm/AESCrypto.mm b/native/ios/Comm/AESCrypto.mm index 36db958ca..7e8d8aa83 100644 --- a/native/ios/Comm/AESCrypto.mm +++ b/native/ios/Comm/AESCrypto.mm @@ -1,20 +1,62 @@ #import "AESCrypto.h" +#import "AESCryptoModuleObjCCompat.h" +#import namespace comm { void AESCrypto::generateKey(rust::Slice buffer) { + NSError *keyGenerationError = nil; + [AESCryptoModuleObjCCompat generateKey:buffer.data() + destinationLength:buffer.size() + withError:&keyGenerationError]; + if (keyGenerationError) { + throw std::runtime_error( + [[keyGenerationError localizedDescription] UTF8String]); + } } void AESCrypto::encrypt( rust::Slice key, rust::Slice plaintext, rust::Slice sealedData) { + NSData *keyBuffer = [NSData dataWithBytesNoCopy:key.data() + length:key.size() + freeWhenDone:NO]; + NSData *plaintextBuffer = [NSData dataWithBytesNoCopy:plaintext.data() + length:plaintext.size() + freeWhenDone:NO]; + NSError *encryptError = nil; + [AESCryptoModuleObjCCompat encryptWithKey:keyBuffer + plaintext:plaintextBuffer + destinationPtr:sealedData.data() + destinationLength:sealedData.size() + withError:&encryptError]; + + if (encryptError) { + throw std::runtime_error([[encryptError localizedDescription] UTF8String]); + } } void AESCrypto::decrypt( rust::Slice key, rust::Slice sealedData, rust::Slice plaintext) { + NSData *keyBuffer = [NSData dataWithBytesNoCopy:key.data() + length:key.size() + freeWhenDone:NO]; + NSData *sealedDataBuffer = [NSData dataWithBytesNoCopy:sealedData.data() + length:sealedData.size() + freeWhenDone:NO]; + NSError *decryptError = nil; + [AESCryptoModuleObjCCompat decryptWithKey:keyBuffer + sealedData:sealedDataBuffer + destinationPtr:plaintext.data() + destinationLength:plaintext.size() + withError:&decryptError]; + + if (decryptError) { + throw std::runtime_error([[decryptError localizedDescription] UTF8String]); + } } } // namespace comm diff --git a/native/ios/Comm/CommAESCryptoUtils/AESCryptoModuleObjCCompat.h b/native/ios/Comm/CommAESCryptoUtils/AESCryptoModuleObjCCompat.h index db6f6a077..909715905 100644 --- a/native/ios/Comm/CommAESCryptoUtils/AESCryptoModuleObjCCompat.h +++ b/native/ios/Comm/CommAESCryptoUtils/AESCryptoModuleObjCCompat.h @@ -1,17 +1,30 @@ #import @interface AESCryptoModuleObjCCompat : NSObject - (BOOL)generateKey:(NSMutableData *_Nonnull)destination withError:(NSError *_Nullable *_Nullable)error; ++ (BOOL)generateKey:(void *_Nonnull)destinationPtr + destinationLength:(NSInteger)destinationLength + withError:(NSError *_Nullable *_Nullable)error; - (NSInteger)encryptedLength:(NSData *_Nonnull)plaintext; - (BOOL)encryptWithKey:(NSData *_Nonnull)rawKey plaintext:(NSData *_Nonnull)plaintext destination:(NSMutableData *_Nonnull)destination withError:(NSError *_Nullable *_Nullable)error; ++ (BOOL)encryptWithKey:(NSData *_Nonnull)rawKey + plaintext:(NSData *_Nonnull)plaintext + destinationPtr:(void *_Nonnull)destinationPtr + destinationLength:(NSInteger)destinationLength + withError:(NSError *_Nullable *_Nullable)error; - (NSInteger)decryptedLength:(NSData *_Nonnull)sealedData; - (BOOL)decryptWithKey:(NSData *_Nonnull)rawKey sealedData:(NSData *_Nonnull)sealedData destination:(NSMutableData *_Nonnull)destination withError:(NSError *_Nullable *_Nullable)error; ++ (BOOL)decryptWithKey:(NSData *_Nonnull)rawKey + sealedData:(NSData *_Nonnull)sealedData + destinationPtr:(void *_Nonnull)destinationPtr + destinationLength:(NSInteger)destinationLength + withError:(NSError *_Nullable *_Nullable)error; - (nonnull instancetype)init; @end