diff --git a/ios/NotificationService/NotificationService.entitlements b/ios/NotificationService/NotificationService.entitlements
new file mode 100644
index 0000000..78e4d59
--- /dev/null
+++ b/ios/NotificationService/NotificationService.entitlements
@@ -0,0 +1,10 @@
+
+
+
+
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)eu.twonly.shared
+
+
+
diff --git a/ios/NotificationService/NotificationService.swift b/ios/NotificationService/NotificationService.swift
index e515fb4..83cfd55 100644
--- a/ios/NotificationService/NotificationService.swift
+++ b/ios/NotificationService/NotificationService.swift
@@ -18,64 +18,20 @@ class NotificationService: UNNotificationServiceExtension {
self.contentHandler = contentHandler
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
-
-
if let bestAttemptContent = bestAttemptContent {
-
-
- // Extract the ciphertext and nonce from the notification's userInfo
+
guard let _userInfo = bestAttemptContent.userInfo as? [String: Any],
- let ciphertextString = bestAttemptContent.userInfo["ciphertext"] as? String,
- let nonceString = bestAttemptContent.userInfo["nonce"] as? String else {
- return
+ let push_data = bestAttemptContent.userInfo["push_data"] as? String else {
+ return contentHandler(bestAttemptContent);
}
- // Convert the base64 encoded strings to Data
- guard let ciphertextData = Data(base64Encoded: ciphertextString),
- let nonceData = Data(base64Encoded: nonceString) else {
- return
- }
+ let data = getPushNotificationData(pushDataJson: push_data)
- // Create the key (32 bytes of "A")
- let keyString = String(repeating: "A", count: 32)
- guard let keyData = keyString.data(using: .utf8) else {
- return
- }
-
- // Ensure the key is 32 bytes
- guard keyData.count == 32 else {
- return
- }
-
- // Ensure the ciphertext is more than 16 Bytes
- guard ciphertextData.count >= 16 else {
- return
- }
-
- // Split the ciphertextData into the actual ciphertext and the tag
- let tagLength = 16
- let ciphertext = ciphertextData.prefix(ciphertextData.count - tagLength)
- let tag = ciphertextData.suffix(tagLength)
-
- // Create a SymmetricKey from the key data
- let key = SymmetricKey(data: keyData)
-
- // Decrypt the ciphertext using ChaCha20
- do {
- let nonce = try ChaChaPoly.Nonce(data: nonceData)
- let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: Data(tag))
- let decryptedData = try ChaChaPoly.open(sealedBox, using: key)
-
- // Convert decrypted data to a string
- if let decryptedMessage = String(data: decryptedData, encoding: .utf8) {
- NSLog("Decrypted message: \(decryptedMessage)")
-
- bestAttemptContent.title = "\(bestAttemptContent.title)"
- bestAttemptContent.body = decryptedMessage
-
- }
- } catch {
- NSLog("Decryption failed: \(error)")
+ if data != nil {
+ bestAttemptContent.title = data!.title;
+ bestAttemptContent.body = data!.body;
+ } else {
+ bestAttemptContent.title = "\(bestAttemptContent.title) [failed to decrypt]"
}
contentHandler(bestAttemptContent)
@@ -91,3 +47,288 @@ class NotificationService: UNNotificationServiceExtension {
}
}
+
+
+enum PushKind: String, Codable {
+ case text
+ case twonly
+ case video
+ case image
+ case contactRequest
+ case acceptRequest
+ case storedMediaFile
+ case reaction
+ case testNotification
+}
+
+import CryptoKit
+import Foundation
+import Security
+
+func getPushNotificationData(pushDataJson: String) -> (title: String, body: String)? {
+ // Decode the pushDataJson
+ guard let pushData = decodePushData(pushDataJson) else {
+ NSLog("Failed to decode push data")
+ return nil
+ }
+
+ var pushKind: PushKind?
+ var displayName: String?
+
+ // Check the keyId
+ if pushData.keyId == 0 {
+ let key = "InsecureOnlyUsedForAddingContact".data(using: .utf8)!.map { Int($0) }
+ pushKind = tryDecryptMessage(key: key, pushData: pushData)
+ } else {
+ let pushKeys = getPushKey()
+ if pushKeys != nil {
+ for (userId, userKeys) in pushKeys! {
+ for key in userKeys.keys {
+ if key.id == pushData.keyId {
+ pushKind = tryDecryptMessage(key: key.key, pushData: pushData)
+ if pushKind != nil {
+ displayName = userKeys.displayName
+ break
+ }
+ }
+ }
+ // Found correct key and user
+ if displayName != nil { break }
+ }
+ } else {
+ NSLog("pushKeys are empty")
+ }
+ }
+
+ // Handle the push notification based on the pushKind
+ if let pushKind = pushKind {
+
+ let bestAttemptContent = UNMutableNotificationContent()
+
+ if pushKind == .testNotification {
+ return ("Test Notification", "This is a test notification.")
+ } else if displayName != nil {
+ return (displayName!, getPushNotificationText(pushKind: pushKind))
+ }
+
+ } else {
+ NSLog("Failed to decrypt message or pushKind is nil")
+ }
+ return nil
+}
+
+func tryDecryptMessage(key: [Int], pushData: PushNotification) -> PushKind? {
+ // Convert the key from [Int] to Data
+ let keyData = Data(key.map { UInt8($0) }) // Convert Int to UInt8
+
+ guard let nonceData = Data(base64Encoded: pushData.nonce),
+ let cipherTextData = Data(base64Encoded: pushData.cipherText),
+ let macData = Data(base64Encoded: pushData.mac) else {
+ NSLog("Failed to decode base64 strings")
+ return nil
+ }
+
+ do {
+ // Create a nonce for ChaChaPoly
+ let nonce = try ChaChaPoly.Nonce(data: nonceData)
+
+ // Create a sealed box for ChaChaPoly
+ let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: cipherTextData, tag: macData)
+
+ // Decrypt the data using the key
+ let decryptedData = try ChaChaPoly.open(sealedBox, using: SymmetricKey(data: keyData))
+
+ // Convert decrypted data to a string
+ if let decryptedMessage = String(data: decryptedData, encoding: .utf8) {
+ NSLog("Decrypted message: \(decryptedMessage)")
+
+ // Here you can determine the PushKind based on the decrypted message
+ return determinePushKind(from: decryptedMessage)
+ }
+ } catch {
+ NSLog("Decryption failed: \(error)")
+ }
+
+ return nil
+}
+
+// Placeholder function to determine PushKind from the decrypted message
+func determinePushKind(from message: String) -> PushKind? {
+ // Implement your logic to determine the PushKind based on the message content
+ // For example, you might check for specific keywords or formats in the message
+ // This is just a placeholder implementation
+ if message.contains("text") {
+ return .text
+ } else if message.contains("video") {
+ return .video
+ } else if message.contains("image") {
+ return .image
+ } else if message.contains("twonly") {
+ return .twonly
+ } else if message.contains("contactRequest") {
+ return .contactRequest
+ } else if message.contains("acceptRequest") {
+ return .acceptRequest
+ } else if message.contains("storedMediaFile") {
+ return .storedMediaFile
+ } else if message.contains("reaction") {
+ return .reaction
+ } else if message.contains("testNotification") {
+ return .testNotification
+ } else {
+ return nil // Unknown PushKind
+ }
+}
+
+
+func decodePushData(_ json: String) -> PushNotification? {
+ // First, decode the base64 string
+ guard let base64Data = Data(base64Encoded: json) else {
+ NSLog("Failed to decode base64 string")
+ return nil
+ }
+
+ // Convert the base64 decoded data to a JSON string
+ guard let jsonString = String(data: base64Data, encoding: .utf8) else {
+ NSLog("Failed to convert base64 data to JSON string")
+ return nil
+ }
+
+ // Convert the JSON string to Data
+ guard let jsonData = jsonString.data(using: .utf8) else {
+ NSLog("Failed to convert JSON string to Data")
+ return nil
+ }
+
+ do {
+ // Use JSONDecoder to decode the JSON data into a PushNotification instance
+ let decoder = JSONDecoder()
+ let pushNotification = try decoder.decode(PushNotification.self, from: jsonData)
+ return pushNotification
+ } catch {
+ NSLog("Error decoding JSON: \(error)")
+ return nil
+ }
+}
+
+struct PushNotification: Codable {
+ let keyId: Int
+ let nonce: String
+ let cipherText: String
+ let mac: String
+
+ // You can add custom coding keys if the JSON keys differ from the property names
+ enum CodingKeys: String, CodingKey {
+ case keyId
+ case nonce
+ case cipherText
+ case mac
+ }
+}
+
+struct PushKeyMeta: Codable {
+ let id: Int
+ let key: [Int]
+ let createdAt: Date
+
+ enum CodingKeys: String, CodingKey {
+ case id
+ case key
+ case createdAt
+ }
+}
+
+struct PushUser: Codable {
+ let displayName: String
+ let keys: [PushKeyMeta]
+
+ enum CodingKeys: String, CodingKey {
+ case displayName
+ case keys
+ }
+}
+
+func getPushKey() -> [Int: PushUser]? {
+ // Retrieve the data from secure storage (Keychain)
+ guard let data = readFromKeychain(key: "receivingPushKeys") else {
+ print("No data found for key: receivingPushKeys")
+ return nil
+ }
+
+ do {
+ // Decode the JSON data into a dictionary
+ let jsonData = data.data(using: .utf8)!
+ let jsonMap = try JSONSerialization.jsonObject(with: jsonData, options: []) as? [String: Any]
+
+ var pushKeys: [Int: PushUser] = [:]
+
+ // Iterate through the JSON map and decode each PushUser
+ for (key, value) in jsonMap ?? [:] {
+ if let userData = try? JSONSerialization.data(withJSONObject: value, options: []),
+ let pushUser = try? JSONDecoder().decode(PushUser.self, from: userData) {
+ pushKeys[Int(key)!] = pushUser
+ }
+ }
+
+ return pushKeys
+ } catch {
+ print("Error decoding JSON: \(error)")
+ return nil
+ }
+}
+
+// Helper function to read from Keychain
+func readFromKeychain(key: String) -> String? {
+ let query: [String: Any] = [
+ kSecClass as String: kSecClassGenericPassword,
+ kSecAttrAccount as String: key,
+ kSecReturnData as String: kCFBooleanTrue!,
+ kSecMatchLimit as String: kSecMatchLimitOne,
+ kSecAttrAccessGroup as String: "CN332ZUGRP.eu.twonly.shared" // Use your access group
+ ]
+
+ var dataTypeRef: AnyObject? = nil
+ let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
+
+ if status == errSecSuccess {
+ if let data = dataTypeRef as? Data {
+ return String(data: data, encoding: .utf8)
+ }
+ }
+
+ return nil
+}
+
+func getPushNotificationText(pushKind: PushKind) -> String {
+ let systemLanguage = Locale.current.languageCode ?? "en" // Get the current system language
+
+ var pushNotificationText: [PushKind: String] = [:]
+
+ // Define the messages based on the system language
+ if systemLanguage.contains("de") { // German
+ pushNotificationText = [
+ .text: "hat dir eine Nachricht gesendet.",
+ .twonly: "hat dir ein twonly gesendet.",
+ .video: "hat dir ein Video gesendet.",
+ .image: "hat dir ein Bild gesendet.",
+ .contactRequest: "möchte sich mit dir vernetzen.",
+ .acceptRequest: "ist jetzt mit dir vernetzt.",
+ .storedMediaFile: "hat dein Bild gespeichert.",
+ .reaction: "hat auf dein Bild reagiert."
+ ]
+ } else { // Default to English
+ pushNotificationText = [
+ .text: "has sent you a message.",
+ .twonly: "has sent you a twonly.",
+ .video: "has sent you a video.",
+ .image: "has sent you an image.",
+ .contactRequest: "wants to connect with you.",
+ .acceptRequest: "is now connected with you.",
+ .storedMediaFile: "has stored your image.",
+ .reaction: "has reacted to your image."
+ ]
+ }
+
+ // Return the corresponding message or an empty string if not found
+ return pushNotificationText[pushKind] ?? ""
+}
diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj
index d336b8e..933446c 100644
--- a/ios/Runner.xcodeproj/project.pbxproj
+++ b/ios/Runner.xcodeproj/project.pbxproj
@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
- objectVersion = 54;
+ objectVersion = 77;
objects = {
/* Begin PBXBuildFile section */
@@ -878,6 +878,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = CN332ZUGRP;
@@ -918,6 +919,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = CN332ZUGRP;
@@ -955,6 +957,7 @@
CLANG_ENABLE_OBJC_WEAK = YES;
CLANG_WARN_DOCUMENTATION_COMMENTS = YES;
CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE;
+ CODE_SIGN_ENTITLEMENTS = NotificationService/NotificationService.entitlements;
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = CN332ZUGRP;
diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift
index 4aa4d70..1b12b3f 100644
--- a/ios/Runner/AppDelegate.swift
+++ b/ios/Runner/AppDelegate.swift
@@ -33,86 +33,24 @@ import Foundation
override func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
NSLog("userNotificationCenter:willPresent")
+
+ /*
+ debugging NotificationService
+ let pushKeys = getPushKey();
+ print(pushKeys)
+
+ let bestAttemptContent = notification.request.content
+
+ guard let _userInfo = bestAttemptContent.userInfo as? [String: Any],
+ let push_data = bestAttemptContent.userInfo["push_data"] as? String else {
+ return completionHandler([.alert, .sound])
+ }
+
+ let data = getPushNotificationData(pushDataJson: push_data)
+ print(data)
+ */
+
completionHandler([.alert, .sound])
}
-
-}
-
-
-import Security
-import CommonCrypto
-import Foundation
-import CryptoKit
-
-class KeychainHelper {
- static func save(key: String, data: Data) -> OSStatus {
- let query: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrAccount as String: key,
- kSecValueData as String: data
- ]
- SecItemDelete(query as CFDictionary) // Delete any existing item
- return SecItemAdd(query as CFDictionary, nil)
- }
-
- static func load(key: String) -> Data? {
- let query: [String: Any] = [
- kSecClass as String: kSecClassGenericPassword,
- kSecAttrAccount as String: key,
- kSecReturnData as String: kCFBooleanTrue!,
- kSecMatchLimit as String: kSecMatchLimitOne
- ]
-
- var dataTypeRef: AnyObject? = nil
- let status: OSStatus = SecItemCopyMatching(query as CFDictionary, &dataTypeRef)
-
- if status == errSecSuccess {
- return dataTypeRef as? Data
- } else {
- return nil
- }
- }
-}
-
-// Function to decrypt data
-func decrypt(data: Data, key: SymmetricKey) -> Data? {
- do {
- // Extract nonce, ciphertext, and tag
- let nonceData = data.prefix(12) // ChaCha20-Poly1305 nonce is 12 bytes
- let ciphertext = data.dropFirst(12).dropLast(16) // Exclude nonce and tag
- let tag = data.suffix(16) // Last 16 bytes are the tag
-
- // Create a nonce from the extracted nonce data
- let nonce = try ChaChaPoly.Nonce(data: nonceData)
-
- // Create a sealed box with the ciphertext and tag
- let sealedBox = try ChaChaPoly.SealedBox(nonce: nonce, ciphertext: ciphertext, tag: tag)
-
- // Decrypt the data
- let decryptedData = try ChaChaPoly.open(sealedBox, using: key)
- return decryptedData
- } catch {
- print("Decryption error: \(error)")
- return nil
- }
-}
-
-
-func handleReceivedMessage(encryptedMessage: Data, keyID: String) {
- // Load the AES key from Keychain
- guard let keyData = KeychainHelper.load(key: keyID) else {
- print("Key not found")
- return
- }
-
- let key = SymmetricKey(data: keyData);
-
- // Decrypt the message
- if let decryptedData = decrypt(data: encryptedMessage, key: key) {
- let decryptedMessage = String(data: decryptedData, encoding: .utf8)
- print("Decrypted message: \(decryptedMessage ?? "nil")")
- } else {
- print("Decryption failed")
- }
}
diff --git a/ios/Runner/Runner.entitlements b/ios/Runner/Runner.entitlements
index 903def2..d2149cc 100644
--- a/ios/Runner/Runner.entitlements
+++ b/ios/Runner/Runner.entitlements
@@ -4,5 +4,9 @@
aps-environment
development
+ keychain-access-groups
+
+ $(AppIdentifierPrefix)eu.twonly.shared
+
diff --git a/lib/src/components/message_send_state_icon.dart b/lib/src/components/message_send_state_icon.dart
index 98a0c76..f1a1e62 100644
--- a/lib/src/components/message_send_state_icon.dart
+++ b/lib/src/components/message_send_state_icon.dart
@@ -169,9 +169,13 @@ class _MessageSendStateIconState extends State {
children: [
// First icon (bottom icon)
icons[0],
- Positioned(
- top: 5.0,
- left: 5.0,
+
+ Transform(
+ transform: Matrix4.identity()
+ ..scale(0.7) // Scale to half
+ ..translate(3.0, 5.0),
+ // Move down by 10 pixels (adjust as needed)
+ alignment: Alignment.center,
child: icons[1],
),
// Second icon (top icon, slightly offset)
diff --git a/lib/src/services/notification_service.dart b/lib/src/services/notification_service.dart
index a05a86f..1631961 100644
--- a/lib/src/services/notification_service.dart
+++ b/lib/src/services/notification_service.dart
@@ -6,6 +6,7 @@ import 'dart:ui' as ui;
import 'package:cryptography_plus/cryptography_plus.dart';
import 'package:flutter/services.dart';
import 'package:flutter_local_notifications/flutter_local_notifications.dart';
+import 'package:flutter_secure_storage/flutter_secure_storage.dart';
import 'package:flutter_svg/svg.dart';
import 'package:logging/logging.dart';
import 'package:path_provider/path_provider.dart';
@@ -321,7 +322,13 @@ Future handlePushData(String pushDataJson) async {
Future