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

Commit

Permalink
resolved requested comments
Browse files Browse the repository at this point in the history
  • Loading branch information
Antonwy committed Jan 28, 2023
1 parent 1e672da commit 70fc34e
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 156 deletions.
22 changes: 12 additions & 10 deletions Campus-iOS.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -265,13 +265,14 @@
995831912936336900F25E11 /* GRPC in Frameworks */ = {isa = PBXBuildFile; productRef = 995831902936336900F25E11 /* GRPC */; };
995831932936336900F25E11 /* protoc-gen-grpc-swift in Frameworks */ = {isa = PBXBuildFile; productRef = 995831922936336900F25E11 /* protoc-gen-grpc-swift */; };
995831952936469100F25E11 /* CampusBackend.swift in Sources */ = {isa = PBXBuildFile; fileRef = 995831942936469000F25E11 /* CampusBackend.swift */; };
99706870298569E10028D235 /* CrashlyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9970686F298569E10028D235 /* CrashlyticsService.swift */; };
99706872298572A50028D235 /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99706871298572A50028D235 /* KeychainService.swift */; };
997068732985752E0028D235 /* KeychainService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 99706871298572A50028D235 /* KeychainService.swift */; };
9973857C29490EFE00B1AE34 /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9973857B29490EFE00B1AE34 /* NotificationService.swift */; };
9973858029490EFE00B1AE34 /* NotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 9973857929490EFE00B1AE34 /* NotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
997385862949324F00B1AE34 /* CryptoExportImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997385852949324F00B1AE34 /* CryptoExportImportManager.swift */; };
997385872949324F00B1AE34 /* CryptoExportImportManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 997385852949324F00B1AE34 /* CryptoExportImportManager.swift */; };
99706870298569E10028D235 /* CrashlyticsService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9970686F298569E10028D235 /* CrashlyticsService.swift */; };
999E324B29859DDA0099755D /* Credentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36203E8A2761C6EC00C24658 /* Credentials.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -555,12 +556,12 @@
97270F5927AB2A4900BB25E4 /* Array+Rearrange.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array+Rearrange.swift"; sourceTree = "<group>"; };
974D5B9927E5E9CB00FD7B11 /* GlowBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GlowBorder.swift; sourceTree = "<group>"; };
97F8A79227E641570099EE83 /* AcademicDegree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AcademicDegree.swift; sourceTree = "<group>"; };
9970686F298569E10028D235 /* CrashlyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsService.swift; sourceTree = "<group>"; };
99438EFF2950770400766197 /* CampusService.pb.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CampusService.pb.swift; sourceTree = "<group>"; };
99438F002950770400766197 /* CampusService.grpc.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CampusService.grpc.swift; sourceTree = "<group>"; };
9958317F29361F6F00F25E11 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
99583182293621A200F25E11 /* PushNotifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PushNotifications.swift; sourceTree = "<group>"; };
995831942936469000F25E11 /* CampusBackend.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CampusBackend.swift; sourceTree = "<group>"; };
9970686F298569E10028D235 /* CrashlyticsService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CrashlyticsService.swift; sourceTree = "<group>"; };
99706871298572A50028D235 /* KeychainService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeychainService.swift; sourceTree = "<group>"; };
9973857929490EFE00B1AE34 /* NotificationService.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = NotificationService.appex; sourceTree = BUILT_PRODUCTS_DIR; };
9973857B29490EFE00B1AE34 /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -1584,14 +1585,6 @@
path = Icons;
sourceTree = "<group>";
};
9970686E298569CE0028D235 /* Crashlytics */ = {
isa = PBXGroup;
children = (
9970686F298569E10028D235 /* CrashlyticsService.swift */,
);
path = Crashlytics;
sourceTree = "<group>";
};
995831812936215D00F25E11 /* PushNotifications */ = {
isa = PBXGroup;
children = (
Expand All @@ -1602,6 +1595,14 @@
path = PushNotifications;
sourceTree = "<group>";
};
9970686E298569CE0028D235 /* Crashlytics */ = {
isa = PBXGroup;
children = (
9970686F298569E10028D235 /* CrashlyticsService.swift */,
);
path = Crashlytics;
sourceTree = "<group>";
};
9973857A29490EFE00B1AE34 /* NotificationService */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -2070,6 +2071,7 @@
files = (
9973857C29490EFE00B1AE34 /* NotificationService.swift in Sources */,
997385872949324F00B1AE34 /* CryptoExportImportManager.swift in Sources */,
999E324B29859DDA0099755D /* Credentials.swift in Sources */,
997068732985752E0028D235 /* KeychainService.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
Expand Down
5 changes: 2 additions & 3 deletions Campus-iOS/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,13 @@ class AppDelegate: NSObject, UIApplicationDelegate {
}

func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {
print("Failed to register for remote notifications")
print(error.localizedDescription)
CrashlyticsService.log("Failed to register for remote notifications with error message: \(error.localizedDescription)")
}

func registerForPushNotifications() {
UNUserNotificationCenter.current()
.requestAuthorization(options: [.alert, .sound, .badge]) { granted, _ in
print("Permission granted: \(granted)")
print("Notification Permission granted: \(granted)")
}
}

Expand Down
2 changes: 1 addition & 1 deletion Campus-iOS/Crashlytics/CrashlyticsService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class CrashlyticsService {
#endif
}

static func log(_ value: String) -> Void {
static func log(_ error: String) -> Void {
#if !DEBUG
CrashlyticsService.crashlytics.log(value)
#endif
Expand Down
132 changes: 85 additions & 47 deletions Campus-iOS/PushNotifications/KeychainService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,11 @@ import KeychainAccess
typealias RSAKeyPair = (privateKey: String, publicKey: String)

enum RSAKeyPairError: Error {
case failedGeneratingPrivateKey
case failedGeneratingPrivateKey(_ error: String)
case failedObtainingKeyPairFromKeyChain
case failedObtainingPublicKeyFromPrivateKey
case failedObtainingStringRepresentationOfPrivateKey
case failedObtainingStringRepresentationOfPublicKey
case failedToExportPublicKey
case failedObtainingExternalRepresentationOfKey(_ error: String)
case failedToExportPublicKey(_ error: String)
}

class KeychainService {
Expand Down Expand Up @@ -49,23 +48,16 @@ class KeychainService {

- Returns: A tuple containing the RSA public and private key
*/
func getPublicPrivateKeys() throws -> RSAKeyPair {
if checkIfPrivateKeyAlreadyExists() {
return try obtainPublicPrivateKeyFromKeyChain()
func obtainOrGeneratePublicPrivateKeys() throws -> RSAKeyPair {
guard let privateKey = optionalOptainPrivateKey() else {
try generatePrivateKey()

return try obtainPublicPrivateKey()
}

try generatePrivateKey()
let publicKey = try obtainPublicKey(privateKey)

return try obtainPublicPrivateKeyFromKeyChain()
}

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

return exportManager.exportRSAPublicKeyToPEM(publicKey, keyType: keyType, keySize: keySize)
return try publicPrivateKeyToValidFormat(publicKey: publicKey, privateKey: privateKey)
}

/**
Expand All @@ -84,75 +76,121 @@ class KeychainService {

var error: Unmanaged<CFError>?
guard SecKeyCreateRandomKey(attributes as CFDictionary, &error) != nil else {
throw RSAKeyPairError.failedGeneratingPrivateKey
throw RSAKeyPairError.failedGeneratingPrivateKey(error.debugDescription)
}
}


private func checkIfPrivateKeyAlreadyExists() -> Bool {
func optionalOptainPrivateKey() -> SecKey? {
do {
let _ = try obtainPrivateKeyFromKeyChain()
return try obtainPrivateKey()
} catch {
return false
return nil
}

return true
}

/**
Tries to query for the private key using the `privateKeyKeychainQuery`
*/
func obtainPrivateKeyFromKeyChain() throws -> SecKey {
func obtainPrivateKey() throws -> SecKey {
var item: CFTypeRef?
let status = SecItemCopyMatching(privateKeyKeychainQuery as CFDictionary, &item)
let status = SecItemCopyMatching(privateKeyQuery as CFDictionary, &item)

guard status == errSecSuccess && item != nil else {
guard status == errSecSuccess && item != nil && CFGetTypeID(item) == SecKeyGetTypeID() else {
throw RSAKeyPairError.failedObtainingKeyPairFromKeyChain
}

return item as! SecKey
}

func obtainPrivateKeyFromKeyChain() throws -> String {
let privateKey: SecKey = try obtainPrivateKeyFromKeyChain()
func obtainPrivateKey() throws -> String {
let privateKey: SecKey = try obtainPrivateKey()

let privateData = try getExternalRepresenation(privateKey)

return exportPrivateKeyAsValidString(privateData)
}

/**
Obtains the private key from the keychain, creates a public key from the private key and finally returns an external representation for the keys.
*/
private func obtainPublicPrivateKey() throws -> RSAKeyPair {
let privateKey: SecKey = try obtainPrivateKey()

let publicKey = try obtainPublicKey(privateKey)

return try publicPrivateKeyToValidFormat(publicKey: publicKey, privateKey: privateKey)
}


/**
Creates an external representation for both the `publicKey` and the `privateKey`
*/
private func publicPrivateKeyToValidFormat(publicKey: SecKey, privateKey: SecKey) throws -> RSAKeyPair {
let privateData = try getExternalRepresenation(privateKey)

let publicData = try getExternalRepresenation(publicKey)

return (exportPrivateKeyAsValidString(privateData), exportPublicKeyAsValidPEM(publicData))
}

private func getExternalRepresenation(_ key: SecKey) throws -> Data {
var error: Unmanaged<CFError>?
guard let privateERData = SecKeyCopyExternalRepresentation(privateKey, &error) else {
throw RSAKeyPairError.failedObtainingStringRepresentationOfPrivateKey
guard let keyERData = SecKeyCopyExternalRepresentation(key, &error) else {
throw RSAKeyPairError.failedObtainingExternalRepresentationOfKey(error.debugDescription)
}

let privateData: Data = privateERData as Data
return keyERData as Data
}

private func exportPrivateKeyAsValidString(_ privateKey: Data) -> String {
return privateKey.base64EncodedString()
}

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

return privateData.base64EncodedString()
return exportManager.exportRSAPublicKeyToPEM(publicKey, keyType: keyType, keySize: keySize)
}

/**
Obtains the private key from the keychain, creates a public key from the private key and finally returns an external representation for the keys.
Generates a public key, from a `privateKey`
*/
private func obtainPublicPrivateKeyFromKeyChain() throws -> RSAKeyPair {
let privateKey: SecKey = try obtainPrivateKeyFromKeyChain()

private func obtainPublicKey(_ privateKey: SecKey) throws -> SecKey {
guard let publicKey = SecKeyCopyPublicKey(privateKey) else {
throw RSAKeyPairError.failedObtainingPublicKeyFromPrivateKey
}

var error: Unmanaged<CFError>?
guard let privateERData = SecKeyCopyExternalRepresentation(privateKey, &error) else {
throw RSAKeyPairError.failedObtainingStringRepresentationOfPrivateKey
return publicKey
}

/**
Decrypts a given `cipherText` using the RSA `privateKey`

- Parameters:
- cipherText: Encrypted text that should be decrypted
- privateKey: RSA PrivateKey from the keychain

*/
func decrypt(cipherText: String, privateKey: SecKey) -> String? {
guard let cipherData = Data(base64Encoded: cipherText) else {
return nil
}

let privateData: Data = privateERData as Data
var error: Unmanaged<CFError>?
let plaintext = SecKeyCreateDecryptedData(privateKey, .rsaEncryptionOAEPSHA256, cipherData as CFData, &error)

guard let publicERData = SecKeyCopyExternalRepresentation(publicKey, &error) else {
throw RSAKeyPairError.failedObtainingStringRepresentationOfPublicKey
guard let data: NSData = plaintext else {
return nil
}

let publicData: Data = publicERData as Data

return (privateData.base64EncodedString(), exportPublicKeyAsValidPEM(publicData))
return String(data: data as Data, encoding: .utf8)
}

private var privateKeyKeychainQuery: [String: Any] {
private var privateKeyQuery: [String: Any] {
return [
kSecClass as String: kSecClassKey,
kSecAttrKeyClass as String: kSecAttrKeyClassPrivate,
Expand Down
26 changes: 15 additions & 11 deletions Campus-iOS/PushNotifications/PushNotifications.swift
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,27 @@ class PushNotifications {
*/
func registerDeviceToken(_ deviceToken: String) async -> Void {
do {
let keyPair = try keychain.getPublicPrivateKeys()
let keyPair = try keychain.obtainOrGeneratePublicPrivateKeys()

let device: Api_RegisterDeviceRequest = .with({
$0.deviceID = deviceToken
$0.publicKey = keyPair.publicKey
$0.deviceType = .ios
})

print(keyPair.publicKey)

let _ = try await CampusBackend.shared.registerDevice(device)
} catch RSAKeyPairError.failedGeneratingPrivateKey {
print("Something went wrong while generating the rsa private key")
} catch RSAKeyPairError.failedGeneratingPrivateKey(let error) {
CrashlyticsService.log("Something went wrong while generating the rsa private key with error message: \(error)")
} catch RSAKeyPairError.failedObtainingKeyPairFromKeyChain {
print("Something went wrong while obtaining the rsa private key")
CrashlyticsService.log("Something went wrong while obtaining the rsa private key")
} catch RSAKeyPairError.failedObtainingPublicKeyFromPrivateKey {
CrashlyticsService.log("Something went wrong while obtaining the public key from the private key")
} catch RSAKeyPairError.failedObtainingExternalRepresentationOfKey(let error) {
CrashlyticsService.log("Something went wrong while obtaining the external string representation of a key with error message: \(error)")
} catch RSAKeyPairError.failedToExportPublicKey(let error) {
CrashlyticsService.log("Something went wrong while exporting the public key with error message: \(error)")
} catch {
print("Failed registering ios device token! \(error)")
CrashlyticsService.log("Failed registering ios device token! \(error)")
}

}
Expand All @@ -69,20 +73,20 @@ class PushNotifications {
*/
func handleBackgroundNotification(data: [AnyHashable : Any]) async throws {
guard let requestId = data["request_id"] as? String else {
print("Failed responding to push device request because no 'request_id' was defined")
CrashlyticsService.log("Failed responding to push device request because no 'request_id' was defined")
throw HandlePushDeviceRequestError.noRequestId
}

guard let notificationType = data["notification_type"] as? String else {
print("Failed responding to push device request because no 'notification_type' was defined")
CrashlyticsService.log("Failed responding to push device request because no 'notification_type' was defined")
throw HandlePushDeviceRequestError.noNotificationType
}

switch notificationType {
case BackgroundNotificationType.campusTokenRequest.rawValue:
return try await handleCampusTokenRequest(requestId)
default:
print("Failed responding to push device request because 'notification_type' was invalid")
CrashlyticsService.log("Failed responding to push device request because 'notification_type' was invalid")
throw HandlePushDeviceRequestError.invalidNotificationType
}
}
Expand All @@ -96,7 +100,7 @@ class PushNotifications {
*/
private func handleCampusTokenRequest(_ requestId: String) async throws {
guard let campusToken = keychain.campusToken else {
print("Failed responding to push device request because no campus token was available")
CrashlyticsService.log("Failed responding to push device request because no campus token was available")
throw HandlePushDeviceRequestError.noCampusToken
}

Expand Down
Loading

0 comments on commit 70fc34e

Please sign in to comment.