Skip to content
This repository was archived by the owner on Jan 10, 2024. It is now read-only.

Commit 958344c

Browse files
committed
Added comments
1 parent bcb7061 commit 958344c

2 files changed

Lines changed: 81 additions & 19 deletions

File tree

Campus-iOS/PushNotifications/PushNotifications.swift

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,19 @@ enum HandlePushDeviceRequestError: Error {
2626
case invalidNotificationType
2727
}
2828

29+
enum BackgroundNotificationType: String {
30+
case campusTokenRequest = "CAMPUS_TOKEN_REQUEST"
31+
}
32+
33+
34+
/**
35+
Integrates keychain management, including generating and storing of public and private RSA keys.
36+
Handles registering the device id in the backend, responding to background notifications and reading the Campus API Token from the keychain.
37+
38+
The `PushNotification` class can be used as singleton by accessing the static `shared` property.
39+
*/
2940
class PushNotifications {
30-
31-
private static let privateKeyApplicationTag = "de.tum.tca.keys.push_public_key"
41+
private static let privateKeyApplicationTag = "de.tum.tca.keys.push_rsa_key"
3242
private static let keychainAccessGroupName = "2J3C6P6X3N.de.tum.tca.notificationextension"
3343
private static let keyType = kSecAttrKeyTypeRSA as String
3444
private static let keySize = 2048
@@ -38,6 +48,11 @@ class PushNotifications {
3848

3949
static let shared = PushNotifications()
4050

51+
/**
52+
Registers the `deviceToken` in the backend to receive push notifications.
53+
54+
- Parameter deviceToken: the current device token
55+
*/
4156
func registerDeviceToken(_ deviceToken: String) async -> Void {
4257
do {
4358
let keyPair = try getPublicPrivateKeys()
@@ -48,7 +63,9 @@ class PushNotifications {
4863
$0.deviceType = .ios
4964
})
5065

51-
let response = try await CampusBackend.shared.registerDevice(device)
66+
print(keyPair.publicKey)
67+
68+
let _ = try await CampusBackend.shared.registerDevice(device)
5269
} catch RSAKeyPairError.failedGeneratingPrivateKey {
5370
print("Something went wrong while generating the rsa private key")
5471
} catch RSAKeyPairError.failedObtainingKeyPairFromKeyChain {
@@ -59,6 +76,16 @@ class PushNotifications {
5976

6077
}
6178

79+
/**
80+
Handles incoming background notification requests from the backend.
81+
82+
- Throws:
83+
- `HandlePushDeviceRequestError.noRequestId`: if the push notification body does not contain the `request_id` parameter
84+
- `HandlePushDeviceRequestError.noNotificationType`: if if the push notification body does not contain the `notification_type` parameter
85+
- `HandlePushDeviceRequestError.invalidNotificationType`: if the `notification_type` is other then `BackgroundNotificationType`
86+
87+
- Parameter data: the background notification body
88+
*/
6289
func handleBackgroundNotification(data: [AnyHashable : Any]) async throws {
6390
guard let requestId = data["request_id"] as? String else {
6491
print("Failed responding to push device request because no 'request_id' was defined")
@@ -71,14 +98,21 @@ class PushNotifications {
7198
}
7299

73100
switch notificationType {
74-
case "CAMPUS_TOKEN_REQUEST":
101+
case BackgroundNotificationType.campusTokenRequest.rawValue:
75102
return try await handleCampusTokenRequest(requestId)
76103
default:
77104
print("Failed responding to push device request because 'notification_type' was invalid")
78105
throw HandlePushDeviceRequestError.invalidNotificationType
79106
}
80107
}
81108

109+
/**
110+
Handles a `BackgroundNotificationType.campusTokenRequest`.
111+
Reads the `campusToken` from the keychain and sends it to the backend including the `requestId`.
112+
113+
- Parameter requestId: identifies the background notification request in the backend and needs to be transmitted with the campus token
114+
- Throws: `HandlePushDeviceRequestError.noCampusToken` if the campus token cannot be read from the keychain
115+
*/
82116
private func handleCampusTokenRequest(_ requestId: String) async throws {
83117
guard let campusToken = self.campusToken else {
84118
print("Failed responding to push device request because no campus token was available")
@@ -90,7 +124,7 @@ class PushNotifications {
90124
$0.requestID = requestId
91125
})
92126

93-
let res = try await CampusBackend.shared.iOSDeviceRequestResponse(response)
127+
let _ = try await CampusBackend.shared.iOSDeviceRequestResponse(response)
94128
}
95129

96130
private var campusToken: String? {
@@ -109,26 +143,38 @@ class PushNotifications {
109143
return try? PropertyListDecoder().decode(Credentials.self, from: data)
110144
}
111145

146+
/**
147+
Checks if the there are already public and private keys stored in the keychain. If yes, it just returns them, otherwise it generates new ones.
148+
149+
- Returns: A tuple containing the RSA public and private key
150+
*/
112151
private func getPublicPrivateKeys() throws -> RSAKeyPair {
113-
if checkIfPrivateKeyAlreadyExists() {
152+
/* if checkIfPrivateKeyAlreadyExists() {
153+
print("Obtaining private key from keychain")
114154
return try obtainPublicPrivateKeyFromKeyChain()
115-
}
155+
}*/
116156

117-
try generatePrivateKeys()
157+
try generatePrivateKey()
118158

119159
return try obtainPublicPrivateKeyFromKeyChain()
120160
}
121161

162+
/**
163+
Uses `CryptoExportImportManager` to export the public key in a format (PEM) that can be read by the backend
164+
*/
122165
private func exportPublicKeyAsValidPEM(_ publicKey: Data) -> String {
123166
let exportManager = CryptoExportImportManager()
124167

125168
return exportManager.exportRSAPublicKeyToPEM(publicKey, keyType: PushNotifications.keyType, keySize: PushNotifications.keySize)
126169
}
127170

128-
private func generatePrivateKeys() throws {
171+
/**
172+
Generates a private inside the Keychain can then be queried afterwards
173+
*/
174+
private func generatePrivateKey() throws {
129175
let attributes: [String: Any] = [
130-
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
131-
kSecAttrKeySizeInBits as String: 2048,
176+
kSecAttrKeyType as String: PushNotifications.keyType,
177+
kSecAttrKeySizeInBits as String: PushNotifications.keySize,
132178
kSecPrivateKeyAttrs as String: [
133179
kSecAttrIsPermanent as String: true,
134180
kSecAttrApplicationTag as String: PushNotifications.privateKeyApplicationTag
@@ -142,6 +188,7 @@ class PushNotifications {
142188
}
143189
}
144190

191+
145192
private func checkIfPrivateKeyAlreadyExists() -> Bool {
146193
do {
147194
let _ = try obtainPrivateKeyFromKeyChain()
@@ -152,6 +199,9 @@ class PushNotifications {
152199
return true
153200
}
154201

202+
/**
203+
Tries to query for the private key using the `privateKeyKeychainQuery`
204+
*/
155205
private func obtainPrivateKeyFromKeyChain() throws -> SecKey {
156206
var item: CFTypeRef?
157207
let status = SecItemCopyMatching(privateKeyKeychainQuery as CFDictionary, &item)
@@ -163,7 +213,9 @@ class PushNotifications {
163213
return item as! SecKey
164214
}
165215

166-
216+
/**
217+
Obtains the private key from the keychain, creates a public key from the private key and finally returns an external representation for the keys.
218+
*/
167219
private func obtainPublicPrivateKeyFromKeyChain() throws -> RSAKeyPair {
168220
let privateKey = try obtainPrivateKeyFromKeyChain()
169221

NotificationService/NotificationService.swift

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ class NotificationService: UNNotificationServiceExtension {
1818
var contentHandler: ((UNNotificationContent) -> Void)?
1919
var bestAttemptContent: UNMutableNotificationContent?
2020

21-
private static let privateKeyApplicationTag = "de.tum.tca.keys.push_public_key"
21+
private static let privateKeyApplicationTag = "de.tum.tca.keys.push_rsa_key"
2222
private static let keychainAccessGroupName = "2J3C6P6X3N.de.tum.tca.notificationextension"
2323

2424
private var privateKeyKeychainQuery: [String: Any] = [
@@ -31,11 +31,13 @@ class NotificationService: UNNotificationServiceExtension {
3131
kSecAttrAccessGroup as String: NotificationService.keychainAccessGroupName
3232
]
3333

34+
/**
35+
Called before the push notification is displayed to the user
36+
*/
3437
override func didReceive(_ request: UNNotificationRequest, withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
3538
self.contentHandler = contentHandler
3639
bestAttemptContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
3740

38-
3941
if let bestAttemptContent = bestAttemptContent {
4042

4143
let exists = checkIfPrivateKeyAlreadyExists()
@@ -56,19 +58,19 @@ class NotificationService: UNNotificationServiceExtension {
5658
let privateKey = try obtainRawPrivateKeyFromKeyChain()
5759

5860
if bestAttemptContent.title != "" {
59-
let plainText = decrypt(cypherText: bestAttemptContent.title, privateKey: privateKey)
61+
let plainText = decrypt(cipherText: bestAttemptContent.title, privateKey: privateKey)
6062

6163
bestAttemptContent.title = plainText ?? "Decryption Error"
6264
}
6365

6466
if bestAttemptContent.subtitle != "" {
65-
let plainText = decrypt(cypherText: bestAttemptContent.subtitle, privateKey: privateKey)
67+
let plainText = decrypt(cipherText: bestAttemptContent.subtitle, privateKey: privateKey)
6668

6769
bestAttemptContent.subtitle = plainText ?? "Decryption Error"
6870
}
6971

7072
if bestAttemptContent.body != "" {
71-
let plainText = decrypt(cypherText: bestAttemptContent.body, privateKey: privateKey)
73+
let plainText = decrypt(cipherText: bestAttemptContent.body, privateKey: privateKey)
7274

7375
bestAttemptContent.body = plainText ?? "Decryption Error"
7476
}
@@ -88,8 +90,16 @@ class NotificationService: UNNotificationServiceExtension {
8890
}
8991
}
9092

91-
private func decrypt(cypherText: String, privateKey: SecKey) -> String? {
92-
let cipherData = Data(base64Encoded: cypherText)! as CFData
93+
/**
94+
Decrypts a given `cipherText` using the RSA `privateKey`
95+
96+
- Parameters:
97+
- cipherText: Encrypted text that should be decrypted
98+
- privateKey: RSA PrivateKey from the keychain
99+
100+
*/
101+
private func decrypt(cipherText: String, privateKey: SecKey) -> String? {
102+
let cipherData = Data(base64Encoded: cipherText)! as CFData
93103

94104
var error: Unmanaged<CFError>?
95105
let plaintext = SecKeyCreateDecryptedData(privateKey, .rsaEncryptionOAEPSHA256, cipherData, &error)

0 commit comments

Comments
 (0)