From 2c04784410b730b947a91e0391668fae0aa99be3 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 13 Dec 2023 21:37:57 +0100 Subject: [PATCH 01/23] Handle 410 status code --- .../CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index f32906807..34132b59b 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -54,7 +54,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return .success(data, httpResponse?.allHeaderFields ?? [:]) case 402: return .licenseExceeded - case 403: + case 403, 410: return .accessNotGranted case 404: return .needsDeviceRegistration From 59da4c3901519d798861b4e3f11db3c73dce9794 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 13 Dec 2023 22:37:41 +0100 Subject: [PATCH 02/23] Handle 449 status code --- .../Hub/CryptomatorHubAuthenticator.swift | 4 ++++ .../Hub/HubAuthenticationCoordinator.swift | 14 ++++++++++++++ .../Hub/HubAuthenticationViewModel.swift | 5 +++++ SharedResources/en.lproj/Localizable.strings | 3 +++ 4 files changed, 26 insertions(+) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 34132b59b..fe996aabc 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -17,6 +17,7 @@ public enum HubAuthenticationFlow { case accessNotGranted case needsDeviceRegistration case licenseExceeded + case requiresAccountInitialization(at: URL) } public enum CryptomatorHubAuthenticatorError: Error { @@ -58,6 +59,9 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return .accessNotGranted case 404: return .needsDeviceRegistration + case 449: + let profileURL = baseURL.appendingPathComponent("/app/profile") + return .requiresAccountInitialization(at: profileURL) default: throw CryptomatorHubAuthenticatorError.unexpectedResponse } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationCoordinator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationCoordinator.swift index 9e700e326..008c73b76 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationCoordinator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationCoordinator.swift @@ -105,4 +105,18 @@ extension HubAuthenticationCoordinator: HubAuthenticationViewModelDelegate { public func hubAuthenticationViewModelWantsToHideLoadingIndicator() async { await hideProgressHUD() } + + public func hubAuthenticationViewModelWantsToShowNeedsAccountInitAlert(profileURL: URL) { + let alertController = UIAlertController(title: LocalizedString.getValue("hubAuthentication.requireAccountInit.alert.title"), + message: LocalizedString.getValue("hubAuthentication.requireAccountInit.alert.message"), + preferredStyle: .alert) + + alertController.addAction(UIAlertAction(title: LocalizedString.getValue("common.button.cancel"), style: .cancel)) + let goToProfileAction = UIAlertAction(title: LocalizedString.getValue("hubAuthentication.requireAccountInit.alert.actionButton"), + style: .default, + handler: { _ in UIApplication.shared.open(profileURL) }) + alertController.addAction(goToProfileAction) + + navigationController.present(alertController, animated: true) + } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift index 197f351f3..bd8cf072e 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift @@ -20,6 +20,9 @@ public protocol HubAuthenticationViewModelDelegate: AnyObject { @MainActor func hubAuthenticationViewModelWantsToHideLoadingIndicator() async + + @MainActor + func hubAuthenticationViewModelWantsToShowNeedsAccountInitAlert(profileURL: URL) } public final class HubAuthenticationViewModel: ObservableObject { @@ -101,6 +104,8 @@ public final class HubAuthenticationViewModel: ObservableObject { await setState(to: .deviceRegistration(.deviceName)) case .licenseExceeded: await setState(to: .licenseExceeded) + case let .requiresAccountInitialization(profileURL): + await delegate?.hubAuthenticationViewModelWantsToShowNeedsAccountInitAlert(profileURL: profileURL) } } diff --git a/SharedResources/en.lproj/Localizable.strings b/SharedResources/en.lproj/Localizable.strings index f98e2720a..65ea219a7 100644 --- a/SharedResources/en.lproj/Localizable.strings +++ b/SharedResources/en.lproj/Localizable.strings @@ -121,6 +121,9 @@ "hubAuthentication.deviceRegistration.deviceName.footer.title" = "This seems to be the first Hub access from this device. In order to identify it for access authorization, you need to name this device."; "hubAuthentication.deviceRegistration.needsAuthorization.alert.title" = "Register Device Successful"; "hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "To access the vault, your device needs to be authorized by the vault owner."; +"hubAuthentication.requireAccountInit.alert.title" = "Action required"; +"hubAuthentication.requireAccountInit.alert.message" = "To proceed, please complete the steps required in your Hub user profile."; +"hubAuthentication.requireAccountInit.alert.actionButton" = "Go to profile"; "intents.saveFile.missingFile" = "The provided file is not valid."; "intents.saveFile.invalidFolder" = "The provided folder is not valid."; From 69155872532281cef95379a2fd6dd9f933ee7ed7 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:48:26 +0100 Subject: [PATCH 03/23] DeviceSetup: Decrypt / Encrypt user key --- .../CryptomatorCommonCore/JWEHelper.swift | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift index 2d88abc2b..e7a6775ba 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift @@ -19,6 +19,7 @@ public enum JWEHelper { let decryptionKey = try ECPrivateKey(crv: "P-384", x: x.base64UrlEncodedString(), y: y.base64UrlEncodedString(), privateKey: k.base64UrlEncodedString()) guard let decrypter = Decrypter(keyManagementAlgorithm: .ECDH_ES, contentEncryptionAlgorithm: .A256GCM, decryptionKey: decryptionKey) else { + // TODO: Change Error throw VaultManagerError.invalidDecrypter } let payload = try jwe.decrypt(using: decrypter) @@ -29,4 +30,40 @@ public enum JWEHelper { } return Masterkey.createFromRaw(rawKey: [UInt8](masterkeyData)) } + + public static func decryptUserKey(jwe: JWE, setupCode: String) throws -> String { + guard let decrypter = Decrypter(keyManagementAlgorithm: .PBES2_HS512_A256KW, contentEncryptionAlgorithm: .A256GCM, decryptionKey: setupCode) else { + // TODO: Change Error + throw VaultManagerError.invalidDecrypter + } + let payload = try jwe.decrypt(using: decrypter) + let payloadData = payload.data() + guard let jsonObject = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String: Any], + let key = jsonObject["key"] as? String else { + // TODO: Change Error + throw VaultManagerError.invalidPayloadMasterkey + } + return key + } + + public static func encryptUserKey(userKey: String, deviceKey: P384.KeyAgreement.PublicKey) throws -> JWE { + let header = JWEHeader(keyManagementAlgorithm: .ECDH_ES, contentEncryptionAlgorithm: .A256GCM) + let x = deviceKey.x963Representation[1 ..< 49] + let y = deviceKey.x963Representation[49 ..< 97] + let encryptionKey = ECPublicKey(crv: .P384, + x: x.base64EncodedString(), + y: y.base64EncodedString()) + guard let encrypter = Encrypter(keyManagementAlgorithm: .ECDH_ES, + contentEncryptionAlgorithm: .A256GCM, + encryptionKey: encryptionKey) else { + // TODO: Change Error + throw VaultManagerError.invalidDecrypter + } + guard let userKey = userKey.data(using: .utf8) else { + // TODO: Change Error + throw VaultManagerError.invalidDecrypter + } + let payload = Payload(userKey) + return try JWE(header: header, payload: payload, encrypter: encrypter) + } } From df39a7850d4aad77c1224f6cf5928e0e50ae6a0a Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:50:49 +0100 Subject: [PATCH 04/23] Device Setup: Register Device with new requests --- .../Hub/CryptomatorHubAuthenticator.swift | 101 ++++++++++++++++-- .../Hub/HubDeviceRegisteringService.swift | 30 +++--- 2 files changed, 107 insertions(+), 24 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index fe996aabc..0ca45eadf 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -67,25 +67,84 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving } } - public func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState) async throws { - let deviceID = try getDeviceID() + public func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) async throws { + guard let apiBaseURL = hubConfig.getAPIBaseURL() else { + // TODO: More specific error + throw CryptomatorHubAuthenticatorError.invalidBaseURL + } + + let userDto = try await getUser(apiBaseURL: apiBaseURL, authState: authState) + let publicKey = try cryptomatorHubKeyProvider.getPublicKey() + + let encryptedUserKeyJWE = try getEncryptedUserKeyJWE(userDto: userDto, setupCode: setupCode, publicKey: publicKey) + + let deviceID = try getDeviceID() let derPubKey = publicKey.derRepresentation - let dto = CreateDeviceDto(id: deviceID, name: name, type: "MOBILE", publicKey: derPubKey.base64URLEncodedString()) - guard let devicesResourceURL = URL(string: hubConfig.devicesResourceUrl) else { - throw CryptomatorHubAuthenticatorError.invalidDeviceResourceURL + + let now = getCurrentDateForDeviceCreation() + + let dto = CreateDeviceDto(id: deviceID, + name: name, + type: "MOBILE", + publicKey: derPubKey.base64EncodedString(), + userPrivateKey: encryptedUserKeyJWE.compactSerializedString, + creationTime: now) + + try await createDevice(dto, apiBaseURL: apiBaseURL, authState: authState) + } + + private func getUser(apiBaseURL: URL, authState: OIDAuthState) async throws -> UserDTO { + let url = apiBaseURL.appendingPathComponent("users/me") + let (accessToken, _) = try await authState.performAction() + guard let accessToken = accessToken else { + throw CryptomatorHubAuthenticatorError.missingAccessToken + } + var request = URLRequest(url: url) + request.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] + let (data, response) = try await URLSession.shared.data(with: request) + let httpResponse = response as? HTTPURLResponse + guard httpResponse?.statusCode == 200 else { + throw CryptomatorHubAuthenticatorError.unexpectedResponse + } + return try JSONDecoder().decode(UserDTO.self, from: data) + } + + private func getEncryptedUserKeyJWE(userDto: UserDTO, setupCode: String, publicKey: P384.KeyAgreement.PublicKey) throws -> JWE { + guard let privateKey = userDto.privateKey.data(using: .utf8) else { + // TODO: Throw proper error + fatalError() } - let keyURL = devicesResourceURL.appendingPathComponent("\(deviceID)") - var request = URLRequest(url: keyURL) + let jwe = try JWE(compactSerialization: privateKey) + + let userKey = try JWEHelper.decryptUserKey(jwe: jwe, setupCode: setupCode) + + return try JWEHelper.encryptUserKey(userKey: userKey, deviceKey: publicKey) + } + + private func getCurrentDateForDeviceCreation() -> String { + let formatter = ISO8601DateFormatter() + formatter.timeZone = TimeZone(secondsFromGMT: 0) // Set to UTC + return formatter.string(from: Date()) + } + + private func createDevice(_ dto: CreateDeviceDto, apiBaseURL: URL, authState: OIDAuthState) async throws { + let deviceResourceURL = apiBaseURL.appendingPathComponent("devices") + let deviceURL = deviceResourceURL.appendingPathComponent(dto.id) + + var request = URLRequest(url: deviceURL) request.httpMethod = "PUT" request.setValue("application/json", forHTTPHeaderField: "Content-Type") request.httpBody = try JSONEncoder().encode(dto) + let (accessToken, _) = try await authState.performAction() - guard let accessToken = accessToken else { + guard let secondAccessToken = accessToken else { throw CryptomatorHubAuthenticatorError.missingAccessToken } - request.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] + request.allHTTPHeaderFields = ["Authorization": "Bearer \(secondAccessToken)"] + let (_, response) = try await URLSession.shared.data(with: request) + switch (response as? HTTPURLResponse)?.statusCode { case 201: break @@ -115,6 +174,12 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving let name: String let type: String let publicKey: String + let userPrivateKey: String + let creationTime: String + } + + private struct DeviceDto: Codable { + let userPrivateKey: String } } @@ -161,3 +226,21 @@ extension String { return String(dropFirst(prefix.count)) } } + +extension HubConfig { + func getAPIBaseURL() -> URL? { + return URL(string: apiBaseUrl) + } + + func getWebAppURL() -> URL? { + getAPIBaseURL()?.deletingLastPathComponent().appendingPathComponent("app") + } +} + +private struct UserDTO: Codable { + let id: String + let name: String + let publicKey: String + let privateKey: String + let setupCode: String +} diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegisteringService.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegisteringService.swift index b1bd034f5..bf1de0b72 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegisteringService.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegisteringService.swift @@ -13,7 +13,7 @@ import Foundation import XCTestDynamicOverlay public protocol HubDeviceRegistering { - func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState) async throws + func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) async throws } private enum HubDeviceRegisteringKey: DependencyKey { @@ -32,7 +32,7 @@ extension DependencyValues { #if DEBUG final class UnimplementedHubDeviceRegisteringService: HubDeviceRegistering { - func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState) async throws { + func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) async throws { XCTFail("\(Self.self).registerDevice is unimplemented.") } } @@ -43,24 +43,24 @@ final class UnimplementedHubDeviceRegisteringService: HubDeviceRegistering { final class HubDeviceRegisteringMock: HubDeviceRegistering { // MARK: - registerDevice - var registerDeviceWithNameHubConfigAuthStateThrowableError: Error? - var registerDeviceWithNameHubConfigAuthStateCallsCount = 0 - var registerDeviceWithNameHubConfigAuthStateCalled: Bool { - registerDeviceWithNameHubConfigAuthStateCallsCount > 0 + var registerDeviceWithNameHubConfigAuthStateSetupCodeThrowableError: Error? + var registerDeviceWithNameHubConfigAuthStateSetupCodeCallsCount = 0 + var registerDeviceWithNameHubConfigAuthStateSetupCodeCalled: Bool { + registerDeviceWithNameHubConfigAuthStateSetupCodeCallsCount > 0 } - var registerDeviceWithNameHubConfigAuthStateReceivedArguments: (name: String, hubConfig: HubConfig, authState: OIDAuthState)? - var registerDeviceWithNameHubConfigAuthStateReceivedInvocations: [(name: String, hubConfig: HubConfig, authState: OIDAuthState)] = [] - var registerDeviceWithNameHubConfigAuthStateClosure: ((String, HubConfig, OIDAuthState) throws -> Void)? + var registerDeviceWithNameHubConfigAuthStateSetupCodeReceivedArguments: (name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String)? + var registerDeviceWithNameHubConfigAuthStateSetupCodeReceivedInvocations: [(name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String)] = [] + var registerDeviceWithNameHubConfigAuthStateSetupCodeClosure: ((String, HubConfig, OIDAuthState, String) throws -> Void)? - func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState) throws { - if let error = registerDeviceWithNameHubConfigAuthStateThrowableError { + func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) throws { + if let error = registerDeviceWithNameHubConfigAuthStateSetupCodeThrowableError { throw error } - registerDeviceWithNameHubConfigAuthStateCallsCount += 1 - registerDeviceWithNameHubConfigAuthStateReceivedArguments = (name: name, hubConfig: hubConfig, authState: authState) - registerDeviceWithNameHubConfigAuthStateReceivedInvocations.append((name: name, hubConfig: hubConfig, authState: authState)) - try registerDeviceWithNameHubConfigAuthStateClosure?(name, hubConfig, authState) + registerDeviceWithNameHubConfigAuthStateSetupCodeCallsCount += 1 + registerDeviceWithNameHubConfigAuthStateSetupCodeReceivedArguments = (name: name, hubConfig: hubConfig, authState: authState, setupCode: setupCode) + registerDeviceWithNameHubConfigAuthStateSetupCodeReceivedInvocations.append((name: name, hubConfig: hubConfig, authState: authState, setupCode: setupCode)) + try registerDeviceWithNameHubConfigAuthStateSetupCodeClosure?(name, hubConfig, authState, setupCode) } } // swiftlint: enable all From 320b1c0a696ec289437bc2b17260f819a72d83fb Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:56:53 +0100 Subject: [PATCH 05/23] Update unlock flow (temp) --- .../Hub/CryptomatorHubAuthenticator.swift | 162 +++++++++++++++--- 1 file changed, 137 insertions(+), 25 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 0ca45eadf..7edd9138a 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -11,9 +11,10 @@ import CryptoKit import CryptomatorCloudAccessCore import Dependencies import Foundation +import JOSESwift public enum HubAuthenticationFlow { - case success(Data, [AnyHashable: Any]) + case success(encryptedVaultKey: String, encryptedUserKey: String) case accessNotGranted case needsDeviceRegistration case licenseExceeded @@ -28,43 +29,61 @@ public enum CryptomatorHubAuthenticatorError: Error { case invalidBaseURL case invalidDeviceResourceURL case missingAccessToken + case incompatibleHubVersion } public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving { private static let scheme = "hub+" + private static let minimumHubVersion = 1 @Dependency(\.cryptomatorHubKeyProvider) private var cryptomatorHubKeyProvider public init() {} public func receiveKey(authState: OIDAuthState, vaultConfig: UnverifiedVaultConfig) async throws -> HubAuthenticationFlow { - guard let baseURL = createBaseURL(vaultConfig: vaultConfig) else { + guard let hubConfig = vaultConfig.allegedHubConfig, let vaultBaseURL = getVaultBaseURL(from: vaultConfig) else { + // handle error + fatalError() + } + + guard let apiBaseURL = hubConfig.getAPIBaseURL(), let webAppURL = hubConfig.getWebAppURL() else { + // TODO: More specific error throw CryptomatorHubAuthenticatorError.invalidBaseURL } - let deviceID = try getDeviceID() - let url = baseURL.appendingPathComponent("/keys").appendingPathComponent("/\(deviceID)") - let (accessToken, _) = try await authState.performAction() - guard let accessToken = accessToken else { - throw CryptomatorHubAuthenticatorError.missingAccessToken + + guard try await hubInstanceHasMinimumAPILevel(of: Self.minimumHubVersion, apiBaseURL: apiBaseURL, authState: authState) else { + throw CryptomatorHubAuthenticatorError.incompatibleHubVersion } - var urlRequest = URLRequest(url: url) - urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] - let (data, response) = try await URLSession.shared.data(with: urlRequest) - let httpResponse = response as? HTTPURLResponse - switch httpResponse?.statusCode { - case 200: - return .success(data, httpResponse?.allHeaderFields ?? [:]) - case 402: - return .licenseExceeded - case 403, 410: +// let deviceID = try getDeviceID() + + let retrieveMasterkeyResponse = try await getVaultMasterKey(vaultBaseURL: vaultBaseURL, + authState: authState, + webAppURL: webAppURL) + + let encryptedVaultKey: String + switch retrieveMasterkeyResponse { + case let .success(key): + encryptedVaultKey = key + case .accessNotGranted: return .accessNotGranted - case 404: - return .needsDeviceRegistration - case 449: - let profileURL = baseURL.appendingPathComponent("/app/profile") + case .licenseExceeded: + return .licenseExceeded + case let .requiresAccountInitialization(profileURL): return .requiresAccountInitialization(at: profileURL) - default: - throw CryptomatorHubAuthenticatorError.unexpectedResponse + case .legacyHubVersion: + throw CryptomatorHubAuthenticatorError.incompatibleHubVersion } + + let retrieveUserPrivateKeyResponse = try await getUserKey(apiBaseURL: apiBaseURL, authState: authState) + + let encryptedUserKey: String + switch retrieveUserPrivateKeyResponse { + case let .unlockedSucceeded(deviceDto): + encryptedUserKey = deviceDto.userPrivateKey + case .deviceSetup: + return .needsDeviceRegistration + } + + return .success(encryptedVaultKey: encryptedVaultKey, encryptedUserKey: encryptedUserKey) } public func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) async throws { @@ -155,7 +174,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving } } - func createBaseURL(vaultConfig: UnverifiedVaultConfig) -> URL? { + private func getVaultBaseURL(from vaultConfig: UnverifiedVaultConfig) -> URL? { guard let keyId = vaultConfig.keyId, keyId.hasPrefix(CryptomatorHubAuthenticator.scheme) else { return nil } @@ -163,12 +182,81 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return URL(string: baseURLPath) } - func getDeviceID() throws -> String { + private func getDeviceID() throws -> String { let publicKey = try cryptomatorHubKeyProvider.getPublicKey() let digest = SHA256.hash(data: publicKey.derRepresentation) return digest.data.base16EncodedString } + private func hubInstanceHasMinimumAPILevel(of minimumLevel: Int, apiBaseURL: URL, authState: OIDAuthState) async throws -> Bool { + let url = apiBaseURL.appendingPathComponent("config") + let (accessToken, _) = try await authState.performAction() + guard let accessToken = accessToken else { + throw CryptomatorHubAuthenticatorError.missingAccessToken + } + var request = URLRequest(url: url) + request.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] + let (data, response) = try await URLSession.shared.data(with: request) + + guard (response as? HTTPURLResponse)?.statusCode == 200 else { + throw CryptomatorHubAuthenticatorError.unexpectedResponse + } + let config = try JSONDecoder().decode(APIConfigDto.self, from: data) + return config.apiLevel >= minimumLevel + } + + private func getVaultMasterKey(vaultBaseURL: URL, authState: OIDAuthState, webAppURL: URL) async throws -> RetrieveVaultMasterkeyEncryptedForUserResponse { + let url = vaultBaseURL.appendingPathComponent("access-token") + let (accessToken, _) = try await authState.performAction() + guard let accessToken = accessToken else { + throw CryptomatorHubAuthenticatorError.missingAccessToken + } + var urlRequest = URLRequest(url: url) + urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] + let (data, response) = try await URLSession.shared.data(with: urlRequest) + let httpResponse = response as? HTTPURLResponse + switch httpResponse?.statusCode { + case 200: + guard let body = String(data: data, encoding: .utf8) else { + throw CryptomatorHubAuthenticatorError.unexpectedResponse + } + return .success(encryptedVaultKey: body) + case 402: + return .licenseExceeded + case 403, 410: + return .accessNotGranted + case 404: + return .legacyHubVersion + case 449: + let profileURL = webAppURL.appendingPathComponent("profile") + return .requiresAccountInitialization(at: profileURL) + default: + throw CryptomatorHubAuthenticatorError.unexpectedResponse + } + } + + private func getUserKey(apiBaseURL: URL, authState: OIDAuthState) async throws -> RetrieveUserEncryptedPKResponse { + let deviceID = try getDeviceID() + let url = apiBaseURL.appendingPathComponent("devices").appendingPathComponent(deviceID) + let (accessToken, _) = try await authState.performAction() + guard let accessToken = accessToken else { + throw CryptomatorHubAuthenticatorError.missingAccessToken + } + var urlRequest = URLRequest(url: url) + urlRequest.allHTTPHeaderFields = ["Authorization": "Bearer \(accessToken)"] + let (data, response) = try await URLSession.shared.data(with: urlRequest) + let httpResponse = response as? HTTPURLResponse + + switch httpResponse?.statusCode { + case 200: + return try .unlockedSucceeded(JSONDecoder().decode(DeviceDto.self, from: data)) + case 404: + return .deviceSetup + default: + throw CryptomatorHubAuthenticatorError.unexpectedResponse + } + } + struct CreateDeviceDto: Codable { let id: String let name: String @@ -178,6 +266,30 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving let creationTime: String } + private struct APIConfigDto: Codable { + let apiLevel: Int + } + + private enum RetrieveUserEncryptedPKResponse { + // 200 + case unlockedSucceeded(DeviceDto) + // 404 + case deviceSetup + } + + private enum RetrieveVaultMasterkeyEncryptedForUserResponse { + // 200 + case success(encryptedVaultKey: String) + // 403, 410 + case accessNotGranted + // 402 + case licenseExceeded + // 449 + case requiresAccountInitialization(at: URL) + // 404 + case legacyHubVersion + } + private struct DeviceDto: Codable { let userPrivateKey: String } From 9ac1232e1fd2259fcbcb05a2d1eb631bd2f4bd99 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Sat, 23 Dec 2023 22:58:08 +0100 Subject: [PATCH 06/23] Remove unneeded method --- .../Hub/CryptomatorHubAuthenticator.swift | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 7edd9138a..699484e6f 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -101,7 +101,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving let deviceID = try getDeviceID() let derPubKey = publicKey.derRepresentation - let now = getCurrentDateForDeviceCreation() + let now = ISO8601DateFormatter().string(from: Date()) let dto = CreateDeviceDto(id: deviceID, name: name, @@ -141,12 +141,6 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return try JWEHelper.encryptUserKey(userKey: userKey, deviceKey: publicKey) } - private func getCurrentDateForDeviceCreation() -> String { - let formatter = ISO8601DateFormatter() - formatter.timeZone = TimeZone(secondsFromGMT: 0) // Set to UTC - return formatter.string(from: Date()) - } - private func createDevice(_ dto: CreateDeviceDto, apiBaseURL: URL, authState: OIDAuthState) async throws { let deviceResourceURL = apiBaseURL.appendingPathComponent("devices") let deviceURL = deviceResourceURL.appendingPathComponent(dto.id) From b35eecb8c35258ab431345039ced022d15db1e91 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Sun, 24 Dec 2023 12:21:35 +0100 Subject: [PATCH 07/23] Add Account Key input --- CryptomatorCommon/Package.swift | 3 +- .../Hub/HubAuthenticationView.swift | 1 + .../Hub/HubAuthenticationViewModel.swift | 3 +- .../Hub/HubDeviceRegistrationView.swift | 20 +++- .../SwiftUI/SwiftUI+CustomKeyboard.swift | 107 ++++++++++++++++++ 5 files changed, 130 insertions(+), 4 deletions(-) create mode 100644 CryptomatorCommon/Sources/CryptomatorCommonCore/SwiftUI/SwiftUI+CustomKeyboard.swift diff --git a/CryptomatorCommon/Package.swift b/CryptomatorCommon/Package.swift index 23e7e2842..bea40fdf4 100644 --- a/CryptomatorCommon/Package.swift +++ b/CryptomatorCommon/Package.swift @@ -45,7 +45,8 @@ let package = Package( .product(name: "CocoaLumberjackSwift", package: "CocoaLumberjack"), .product(name: "CryptomatorCloudAccessCore", package: "cloud-access-swift"), .product(name: "Dependencies", package: "simple-swift-dependencies"), - .product(name: "Introspect", package: "SwiftUI-Introspect") + .product(name: "Introspect", package: "SwiftUI-Introspect"), + .product(name: "SwiftUIIntrospect", package: "SwiftUI-Introspect") ] ), .testTarget( diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift index adc7c43ff..77e3c2815 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationView.swift @@ -16,6 +16,7 @@ public struct HubAuthenticationView: View { case .deviceRegistration: HubDeviceRegistrationView( deviceName: $viewModel.deviceName, + accountKey: $viewModel.setupCode, onRegisterTap: { Task { await viewModel.register() }} ) case .accessNotGranted: diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift index bd8cf072e..5d0a6bdab 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift @@ -44,6 +44,7 @@ public final class HubAuthenticationViewModel: ObservableObject { @Published var authenticationFlowState: State? @Published public var deviceName: String = UIDevice.current.name + @Published public var setupCode: String = "" private(set) var isLoggedIn = false private let vaultConfig: UnverifiedVaultConfig @@ -71,7 +72,7 @@ public final class HubAuthenticationViewModel: ObservableObject { } do { - try await deviceRegisteringService.registerDevice(withName: deviceName, hubConfig: hubConfig, authState: authState) + try await deviceRegisteringService.registerDevice(withName: deviceName, hubConfig: hubConfig, authState: authState, setupCode: setupCode) } catch { await setStateToErrorState(with: error) return diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift index 67260b4ed..5da7c5d0b 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift @@ -2,12 +2,14 @@ import SwiftUI struct HubDeviceRegistrationView: View { @Binding var deviceName: String + @Binding var accountKey: String var onRegisterTap: () -> Void @FocusStateLegacy private var field: Field? = .deviceName private enum Field: CaseIterable { case deviceName + case accountKey } var body: some View { @@ -16,12 +18,26 @@ struct HubDeviceRegistrationView: View { TextField( LocalizedString.getValue("hubAuthentication.deviceRegistration.deviceName.cells.name"), text: $deviceName, - onCommit: onRegisterTap + onCommit: { field = .accountKey } ) .focusedLegacy($field, equals: .deviceName) + .backportedSubmitlabel(.next) } footer: { Text(LocalizedString.getValue("hubAuthentication.deviceRegistration.deviceName.footer.title")) } + + Section { + TextField( + "Account Key", + text: $accountKey, + onCommit: onRegisterTap + ) + .focusedLegacy($field, equals: .accountKey) + .backportedSubmitlabel(.done) + } footer: { + // TODO: Add localization + Text("Your Account Key is required to login from other apps or browsers. It can be found in your profile.") + } } .setListBackgroundColor(.cryptomatorBackground) .toolbar { @@ -36,6 +52,6 @@ struct HubDeviceRegistrationView: View { struct HubDeviceRegistrationView_Previews: PreviewProvider { static var previews: some View { - HubDeviceRegistrationView(deviceName: .constant(""), onRegisterTap: {}) + HubDeviceRegistrationView(deviceName: .constant(""), accountKey: .constant(""), onRegisterTap: {}) } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/SwiftUI/SwiftUI+CustomKeyboard.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/SwiftUI/SwiftUI+CustomKeyboard.swift new file mode 100644 index 000000000..3d4425e91 --- /dev/null +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/SwiftUI/SwiftUI+CustomKeyboard.swift @@ -0,0 +1,107 @@ +// +// SwiftUI+CustomKeyboard.swift +// +// +// Created by Philipp Schmid on 23.12.23. +// + +import SwiftUI +import SwiftUIIntrospect + +public extension View { + func backportedSubmitlabel(_ submitLabel: BackportedSubmitLabel) -> some View { + modifier(BackportedSubmitLabelModifier(label: submitLabel)) + } +} + +public enum BackportedSubmitLabel { + /// Defines a submit label with text of "Done". + case done + + /// Defines a submit label with text of "Go". + case go + + /// Defines a submit label with text of "Send". + case send + + /// Defines a submit label with text of "Join". + case join + + /// Defines a submit label with text of "Route". + case route + + /// Defines a submit label with text of "Search". + case search + + /// Defines a submit label with text of "Return". + case `return` + + /// Defines a submit label with text of "Next". + case next + + /// Defines a submit label with text of "Continue". + case `continue` + + @available(iOS 15, *) + var submitLabel: SubmitLabel { + switch self { + case .done: + return .done + case .go: + return .go + case .send: + return .send + case .join: + return .join + case .route: + return .route + case .search: + return .search + case .return: + return .return + case .next: + return .next + case .continue: + return .continue + } + } + + var returnKeyType: UIReturnKeyType { + switch self { + case .done: + return .done + case .go: + return .go + case .send: + return .send + case .join: + return .join + case .route: + return .route + case .search: + return .search + case .return: + return .default + case .next: + return .next + case .continue: + return .continue + } + } +} + +struct BackportedSubmitLabelModifier: ViewModifier { + let label: BackportedSubmitLabel + + public func body(content: Content) -> some View { + if #available(iOS 15, *) { + content + .submitLabel(label.submitLabel) + } else { + content + .introspect(.textField, on: .iOS(.v13, .v14), scope: .ancestor) { textField in + textField.returnKeyType = label.returnKeyType + } + } + } +} From d3b12a4477dd318a689ac12195d8df39c6d1cd2f Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Mon, 25 Dec 2023 16:48:10 +0100 Subject: [PATCH 08/23] New unlock flow completed - support PKCS#8 key - encrypt vault key with user key --- CryptomatorCommon/Package.swift | 6 +- .../Hub/CryptomatorHubAuthenticator.swift | 15 ++++- .../Hub/HubAuthenticationViewModel.swift | 21 ++++--- .../HubXPCVaultUnlockHandler.swift | 2 +- .../CryptomatorCommonCore/JWEHelper.swift | 62 ++++++++++++++----- .../Manager/VaultDBManager.swift | 2 +- 6 files changed, 77 insertions(+), 31 deletions(-) diff --git a/CryptomatorCommon/Package.swift b/CryptomatorCommon/Package.swift index bea40fdf4..e4a11411a 100644 --- a/CryptomatorCommon/Package.swift +++ b/CryptomatorCommon/Package.swift @@ -29,7 +29,8 @@ let package = Package( .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.8.0")), .package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", .upToNextMinor(from: "3.8.0")), .package(url: "https://github.com/PhilLibs/simple-swift-dependencies", .upToNextMajor(from: "0.1.0")), - .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", .upToNextMajor(from: "0.3.0")) + .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", .upToNextMajor(from: "0.3.0")), + .package(url: "https://github.com/leif-ibsen/SwiftECC", from: "5.0.0") ], targets: [ .target( @@ -46,7 +47,8 @@ let package = Package( .product(name: "CryptomatorCloudAccessCore", package: "cloud-access-swift"), .product(name: "Dependencies", package: "simple-swift-dependencies"), .product(name: "Introspect", package: "SwiftUI-Introspect"), - .product(name: "SwiftUIIntrospect", package: "SwiftUI-Introspect") + .product(name: "SwiftUIIntrospect", package: "SwiftUI-Introspect"), + .product(name: "SwiftECC", package: "SwiftECC") ] ), .testTarget( diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 699484e6f..7d5b0bd7c 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -9,18 +9,25 @@ import AppAuthCore import CryptoKit import CryptomatorCloudAccessCore +import CryptomatorCryptoLib import Dependencies import Foundation import JOSESwift public enum HubAuthenticationFlow { - case success(encryptedVaultKey: String, encryptedUserKey: String) + case success(HubAuthenticationFlowSuccess) case accessNotGranted case needsDeviceRegistration case licenseExceeded case requiresAccountInitialization(at: URL) } +public struct HubAuthenticationFlowSuccess { + public let encryptedUserKey: JWE + public let encryptedVaultKey: JWE + public let header: [AnyHashable: Any] +} + public enum CryptomatorHubAuthenticatorError: Error { case unexpectedError case unexpectedResponse @@ -53,7 +60,6 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving guard try await hubInstanceHasMinimumAPILevel(of: Self.minimumHubVersion, apiBaseURL: apiBaseURL, authState: authState) else { throw CryptomatorHubAuthenticatorError.incompatibleHubVersion } -// let deviceID = try getDeviceID() let retrieveMasterkeyResponse = try await getVaultMasterKey(vaultBaseURL: vaultBaseURL, authState: authState, @@ -83,7 +89,10 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return .needsDeviceRegistration } - return .success(encryptedVaultKey: encryptedVaultKey, encryptedUserKey: encryptedUserKey) + let encryptedUserKeyJWE = try JWE(compactSerialization: encryptedUserKey) + let encryptedVaultKeyJWE = try JWE(compactSerialization: encryptedVaultKey) + + return .success(.init(encryptedUserKey: encryptedUserKeyJWE, encryptedVaultKey: encryptedVaultKeyJWE, header: [:])) } public func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) async throws { diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift index 5d0a6bdab..88dac11a9 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift @@ -2,6 +2,7 @@ import AppAuthCore import CocoaLumberjackSwift import CryptoKit import CryptomatorCloudAccessCore +import CryptomatorCryptoLib import Dependencies import Foundation import JOSESwift @@ -97,8 +98,8 @@ public final class HubAuthenticationViewModel: ObservableObject { await delegate?.hubAuthenticationViewModelWantsToHideLoadingIndicator() switch authFlow { - case let .success(data, header): - await receivedExistingKey(data: data, header: header) + case let .success(response): + await receivedExistingKey(response) case .accessNotGranted: await setState(to: .accessNotGranted) case .needsDeviceRegistration: @@ -110,20 +111,20 @@ public final class HubAuthenticationViewModel: ObservableObject { } } - private func receivedExistingKey(data: Data, header: [AnyHashable: Any]) async { - let privateKey: P384.KeyAgreement.PrivateKey - let jwe: JWE + private func receivedExistingKey(_ flowResponse: HubAuthenticationFlowSuccess) async { let subscriptionState: HubSubscriptionState + let userKey: P384.KeyAgreement.PrivateKey do { - privateKey = try cryptomatorHubKeyProvider.getPrivateKey() - jwe = try JWE(compactSerialization: data) - subscriptionState = try getSubscriptionState(from: header) + let deviceKey = try cryptomatorHubKeyProvider.getPrivateKey() + userKey = try JWEHelper.decryptUserKey(jwe: flowResponse.encryptedUserKey, privateKey: deviceKey) + subscriptionState = .active // try getSubscriptionState(from: flowResponse.header) // TODO: Revert this after Cryptomator Hub adds the subscription state back to the header } catch { await setStateToErrorState(with: error) return } - let response = HubUnlockResponse(jwe: jwe, - privateKey: privateKey, + + let response = HubUnlockResponse(jwe: flowResponse.encryptedVaultKey, + privateKey: userKey, subscriptionState: subscriptionState) await MainActor.run { isLoggedIn = true } await unlockHandler.didSuccessfullyRemoteUnlock(response) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/UnlockHandler/HubXPCVaultUnlockHandler.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/UnlockHandler/HubXPCVaultUnlockHandler.swift index 4e78362c7..d52575fdc 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/UnlockHandler/HubXPCVaultUnlockHandler.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/UnlockHandler/HubXPCVaultUnlockHandler.swift @@ -19,7 +19,7 @@ struct HubXPCVaultUnlockHandler: HubVaultUnlockHandler { func didSuccessfullyRemoteUnlock(_ response: HubUnlockResponse) async { let masterkey: Masterkey do { - masterkey = try JWEHelper.decrypt(jwe: response.jwe, with: response.privateKey) + masterkey = try JWEHelper.decryptVaultKey(jwe: response.jwe, with: response.privateKey) } catch { await delegate?.failedToProcessUnlockedVault(error: error) return diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift index e7a6775ba..6476e482a 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift @@ -9,9 +9,10 @@ import CryptoKit import CryptomatorCryptoLib import Foundation import JOSESwift +import SwiftECC public enum JWEHelper { - public static func decrypt(jwe: JWE, with privateKey: P384.KeyAgreement.PrivateKey) throws -> Masterkey { + public static func decryptVaultKey(jwe: JWE, with privateKey: P384.KeyAgreement.PrivateKey) throws -> Masterkey { // see https://developer.apple.com/forums/thread/680554 let x = privateKey.x963Representation[1 ..< 49] let y = privateKey.x963Representation[49 ..< 97] @@ -31,22 +32,36 @@ public enum JWEHelper { return Masterkey.createFromRaw(rawKey: [UInt8](masterkeyData)) } - public static func decryptUserKey(jwe: JWE, setupCode: String) throws -> String { - guard let decrypter = Decrypter(keyManagementAlgorithm: .PBES2_HS512_A256KW, contentEncryptionAlgorithm: .A256GCM, decryptionKey: setupCode) else { + public static func decryptUserKey(jwe: JWE, privateKey: P384.KeyAgreement.PrivateKey) throws -> P384.KeyAgreement.PrivateKey { + let x = privateKey.x963Representation[1 ..< 49] + let y = privateKey.x963Representation[49 ..< 97] + let k = privateKey.x963Representation[97 ..< 145] + let decryptionKey = try ECPrivateKey(crv: "P-384", + x: x.base64UrlEncodedString(), + y: y.base64UrlEncodedString(), + privateKey: k.base64UrlEncodedString()) + guard let decrypter = Decrypter(keyManagementAlgorithm: .ECDH_ES, + contentEncryptionAlgorithm: .A256GCM, + decryptionKey: decryptionKey) else { // TODO: Change Error throw VaultManagerError.invalidDecrypter } let payload = try jwe.decrypt(using: decrypter) - let payloadData = payload.data() - guard let jsonObject = try JSONSerialization.jsonObject(with: payloadData, options: []) as? [String: Any], - let key = jsonObject["key"] as? String else { + return try decodeUserKey(payload: payload) + } + + public static func decryptUserKey(jwe: JWE, setupCode: String) throws -> P384.KeyAgreement.PrivateKey { + guard let decrypter = Decrypter(keyManagementAlgorithm: .PBES2_HS512_A256KW, + contentEncryptionAlgorithm: .A256GCM, + decryptionKey: setupCode) else { // TODO: Change Error - throw VaultManagerError.invalidPayloadMasterkey + throw VaultManagerError.invalidDecrypter } - return key + let payload = try jwe.decrypt(using: decrypter) + return try decodeUserKey(payload: payload) } - public static func encryptUserKey(userKey: String, deviceKey: P384.KeyAgreement.PublicKey) throws -> JWE { + public static func encryptUserKey(userKey: P384.KeyAgreement.PrivateKey, deviceKey: P384.KeyAgreement.PublicKey) throws -> JWE { let header = JWEHeader(keyManagementAlgorithm: .ECDH_ES, contentEncryptionAlgorithm: .A256GCM) let x = deviceKey.x963Representation[1 ..< 49] let y = deviceKey.x963Representation[49 ..< 97] @@ -59,11 +74,30 @@ public enum JWEHelper { // TODO: Change Error throw VaultManagerError.invalidDecrypter } - guard let userKey = userKey.data(using: .utf8) else { - // TODO: Change Error - throw VaultManagerError.invalidDecrypter - } - let payload = Payload(userKey) + let payloadKey = try PayloadMasterkey(key: userKey.derPkcs8().base64EncodedString()) + let payload = try Payload(JSONEncoder().encode(payloadKey)) return try JWE(header: header, payload: payload, encrypter: encrypter) } + + private static func decodeUserKey(payload: Payload) throws -> P384.KeyAgreement.PrivateKey { + let decodedPayload = try JSONDecoder().decode(PayloadMasterkey.self, from: payload.data()) + + guard let privateKeyData = Data(base64Encoded: decodedPayload.key) else { + // TODO: Change + fatalError() + } + return try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: privateKeyData) + } +} + +public extension P384.KeyAgreement.PrivateKey { + init(pkcs8DerRepresentation: Data) throws { + let privateKey = try ECPrivateKey(der: Array(pkcs8DerRepresentation), pkcs8: true) + try self.init(pemRepresentation: privateKey.pem) + } + + func derPkcs8() throws -> Data { + let privateKey = try ECPrivateKey(pem: pemRepresentation) + return Data(privateKey.derPkcs8) + } } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift index 2b146f7e9..85ca0bbf5 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift @@ -312,7 +312,7 @@ public class VaultDBManager: VaultManager { let masterkey: Masterkey do { let jwe = try JWE(compactSerialization: jweData) - masterkey = try JWEHelper.decrypt(jwe: jwe, with: vault.privateKey) + masterkey = try JWEHelper.decryptVaultKey(jwe: jwe, with: vault.privateKey) } catch { return Promise(error) } From fcdc134b322a4a97a901ec243f9c7b2ed1c1ce7e Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Tue, 26 Dec 2023 00:39:52 +0100 Subject: [PATCH 09/23] Add decrypt user key PBES2 tests --- .../Hub/JWEHelperTests.swift | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift new file mode 100644 index 000000000..bb2c7978d --- /dev/null +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift @@ -0,0 +1,117 @@ +// +// JWEHelperTests.swift +// CryptomatorCommonCoreTests +// +// Created by Philipp Schmid on 25.12.23. +// Copyright © 2023 Skymatic GmbH. All rights reserved. + +import CryptomatorCommonCore +import JOSESwift +import SwiftECC +import XCTest + +final class JWEHelperTests: XCTestCase { + // key pairs from frontend tests (crypto.spec.ts): + private let userPrivKey = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDCi4K1Ts3DgTz/ufkLX7EGMHjGpJv+WJmFgyzLwwaDFSfLpDw0Kgf3FKK+LAsV8r+hZANiAARLOtFebIjxVYUmDV09Q1sVxz2Nm+NkR8fu6UojVSRcCW13tEZatx8XGrIY9zC7oBCEdRqDc68PMSvS5RA0Pg9cdBNc/kgMZ1iEmEv5YsqOcaNADDSs0bLlXb35pX7Kx5Y=" + private let devicePrivKey = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB2bmFCWy2p+EbAn8NWS5Om+GA7c5LHhRZb8g2pSMSf0fsd7k7dZDVrnyHFiLdd/YGhZANiAAR6bsjTEdXKWIuu1Bvj6Y8wySlIROy7YpmVZTY128ItovCD8pcR4PnFljvAIb2MshCdr1alX4g6cgDOqcTeREiObcSfucOU9Ry1pJ/GnX6KA0eSljrk6rxjSDos8aiZ6Mg=" + + // used for JWE generation in frontend: (jwe.spec.ts): + private let privKey = "ME8CAQAwEAYHKoZIzj0CAQYFK4EEACIEODA2AgEBBDEA6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ" + + override func setUpWithError() throws {} + + func testDecryptUserKeyECDHES() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrZXlfb3BzIjpbXSwiZXh0Ijp\ + 0cnVlLCJrdHkiOiJFQyIsIngiOiJoeHpiSWh6SUJza3A5ZkZFUmJSQ2RfOU1fbWYxNElqaDZhcnNoVX\ + NkcEEyWno5ejZZNUs4NHpZR2I4b2FHemNUIiwieSI6ImJrMGRaNWhpelZ0TF9hN2hNejBjTUduNjhIR\ + jZFdWlyNHdlclNkTFV5QWd2NWUzVzNYSG5sdHJ2VlRyU3pzUWYiLCJjcnYiOiJQLTM4NCJ9LCJhcHUi\ + OiIiLCJhcHYiOiIifQ..pu3Q1nR_yvgRAapG.4zW0xm0JPxbcvZ66R-Mn3k841lHelDQfaUvsZZAtWs\ + L2w4FMi6H_uu6ArAWYLtNREa_zfcPuyuJsFferYPSNRUWt4OW6aWs-l_wfo7G1ceEVxztQXzQiwD30U\ + TA8OOdPcUuFfEq2-d9217jezrcyO6m6FjyssEZIrnRArUPWKzGdghXccGkkf0LTZcGJoHeKal-RtyP8\ + PfvEAWTjSOCpBlSdUJ-1JL3tyd97uVFNaVuH3i7vvcMoUP_bdr0XW3rvRgaeC6X4daPLUvR1hK5Msut\ + QMtM2vpFghS_zZxIQRqz3B2ECxa9Bjxhmn8kLX5heZ8fq3lH-bmJp1DxzZ4V1RkWk.yVwXG9yARa5Ih\ + q2koh2NbQ + """) + + let data = Data(base64Encoded: devicePrivKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + let userKey = try JWEHelper.decryptUserKey(jwe: jwe, privateKey: privateKey) + + let x = userKey.x963Representation[1 ..< 49] + let y = userKey.x963Representation[49 ..< 97] + let k = userKey.x963Representation[97 ..< 145] + + /// PKSCS #8: MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDCi4K1Ts3DgTz/ufkLX7EGMHjGpJv+WJmFgyzLwwaDFSfLpDw0Kgf3FKK+LAsV8r+hZANiAARLOtFebIjxVYUmDV09Q1sVxz2Nm+NkR8fu6UojVSRcCW13tEZatx8XGrIY9zC7oBCEdRqDc68PMSvS5RA0Pg9cdBNc/kgMZ1iEmEv5YsqOcaNADDSs0bLlXb35pX7Kx5Y= + /// see: (crypto.spec.ts) in the Hub Frontend + XCTAssertEqual(x.base64URLEncodedString(), "SzrRXmyI8VWFJg1dPUNbFcc9jZvjZEfH7ulKI1UkXAltd7RGWrcfFxqyGPcwu6AQ") + XCTAssertEqual(y.base64URLEncodedString(), "hHUag3OvDzEr0uUQND4PXHQTXP5IDGdYhJhL-WLKjnGjQAw0rNGy5V29-aV-yseW") + XCTAssertEqual(k.base64URLEncodedString(), "wouCtU7Nw4E8_7n5C1-xBjB4xqSb_liZhYMsy8MGgxUny6Q8NCoH9xSiviwLFfK_") + } + + func testDecryptUserKeyECDHESWrongKey() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrZXlfb3BzIjpbXSwiZXh0Ijp\ + 0cnVlLCJrdHkiOiJFQyIsIngiOiJoeHpiSWh6SUJza3A5ZkZFUmJSQ2RfOU1fbWYxNElqaDZhcnNoVX\ + NkcEEyWno5ejZZNUs4NHpZR2I4b2FHemNUIiwieSI6ImJrMGRaNWhpelZ0TF9hN2hNejBjTUduNjhIR\ + jZFdWlyNHdlclNkTFV5QWd2NWUzVzNYSG5sdHJ2VlRyU3pzUWYiLCJjcnYiOiJQLTM4NCJ9LCJhcHUi\ + OiIiLCJhcHYiOiIifQ..pu3Q1nR_yvgRAapG.4zW0xm0JPxbcvZ66R-Mn3k841lHelDQfaUvsZZAtWs\ + L2w4FMi6H_uu6ArAWYLtNREa_zfcPuyuJsFferYPSNRUWt4OW6aWs-l_wfo7G1ceEVxztQXzQiwD30U\ + TA8OOdPcUuFfEq2-d9217jezrcyO6m6FjyssEZIrnRArUPWKzGdghXccGkkf0LTZcGJoHeKal-RtyP8\ + PfvEAWTjSOCpBlSdUJ-1JL3tyd97uVFNaVuH3i7vvcMoUP_bdr0XW3rvRgaeC6X4daPLUvR1hK5Msut\ + QMtM2vpFghS_zZxIQRqz3B2ECxa9Bjxhmn8kLX5heZ8fq3lH-bmJp1DxzZ4V1RkWk.yVwXG9yARa5Ih\ + q2koh2NbQ + """) + + let data = Data(base64Encoded: userPrivKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + + XCTAssertThrowsError(try JWEHelper.decryptUserKey(jwe: jwe, privateKey: privateKey)) { error in + guard case JOSESwiftError.decryptingFailed = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } + + func testDecryptUserKeyPBES2() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoiT3hMY0Q\ + xX1pCODc1c2hvUWY2Q1ZHQSIsInAyYyI6MTAwMCwiYXB1IjoiIiwiYXB2IjoiIn0.FD4fcrP4Pb\ + aKOQ9ZfXl0gpMM6Fa2rfqAvL0K5ZyYUiVeHCNV-A02Rg.urT1ShSv6qQxh8X7.gEqAiUWD98a2E\ + P7ITCPTw4DJo6-BpqrxA73D6gNIj9z4d1hN-EP99Q4mWBWLH97H8ugbG5rGsm8xsjsBqpWORQqF\ + mJZR2AhlPiwFaC7n_MDDBupSy_swDnCfj731Lal297IP5WbkFcmozKsyhmwdkctxjf_VHA.fJki\ + kDjUaxwUKqpvT7qaAQ + """) + + let userKey = try JWEHelper.decryptUserKey(jwe: jwe, setupCode: "123456") + + let x = userKey.x963Representation[1 ..< 49] + let y = userKey.x963Representation[49 ..< 97] + let k = userKey.x963Representation[97 ..< 145] + + /// PKSCS #8: ME8CAQAwEAYHKoZIzj0CAQYFK4EEACIEODA2AgEBBDEA6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ + /// see: (jwe.spec.ts) in the Hub Frontend + XCTAssertEqual(x.base64URLEncodedString(), "RxQR-NRN6Wga01370uBBzr2NHDbKIC56tPUEq2HX64RhITGhii8Zzbkb1HnRmdF0") + XCTAssertEqual(y.base64URLEncodedString(), "aq6uqmUy4jUhuxnKxsv59A6JeK7Unn-mpmm3pQAygjoGc9wrvoH4HWJSQYUlsXDu") + XCTAssertEqual(k.base64URLEncodedString(), "6QybmBitf94veD5aCLr7nlkF5EZpaXHCfq1AXm57AKQyGOjTDAF9EQB28fMywTDQ") + } + + func testDecryptUserKeyPBES2WrongKey() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJQQkVTMi1IUzUxMitBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIiwicDJzIjoiT3hMY0Q\ + xX1pCODc1c2hvUWY2Q1ZHQSIsInAyYyI6MTAwMCwiYXB1IjoiIiwiYXB2IjoiIn0.FD4fcrP4Pb\ + aKOQ9ZfXl0gpMM6Fa2rfqAvL0K5ZyYUiVeHCNV-A02Rg.urT1ShSv6qQxh8X7.gEqAiUWD98a2E\ + P7ITCPTw4DJo6-BpqrxA73D6gNIj9z4d1hN-EP99Q4mWBWLH97H8ugbG5rGsm8xsjsBqpWORQqF\ + mJZR2AhlPiwFaC7n_MDDBupSy_swDnCfj731Lal297IP5WbkFcmozKsyhmwdkctxjf_VHA.fJki\ + kDjUaxwUKqpvT7qaAQ + """) + + XCTAssertThrowsError(try JWEHelper.decryptUserKey(jwe: jwe, setupCode: "654321")) { error in + guard case JOSESwiftError.decryptingFailed = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } +} From dbfc30ac624119ea9baf8f35d42b09e6b31d34ea Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 27 Dec 2023 12:50:37 +0100 Subject: [PATCH 10/23] Add test to decrypt vault key --- .../Hub/JWEHelperTests.swift | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift index bb2c7978d..684075880 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift @@ -5,10 +5,12 @@ // Created by Philipp Schmid on 25.12.23. // Copyright © 2023 Skymatic GmbH. All rights reserved. +import CryptoKit import CryptomatorCommonCore import JOSESwift import SwiftECC import XCTest +@testable import CryptomatorCryptoLib final class JWEHelperTests: XCTestCase { // key pairs from frontend tests (crypto.spec.ts): @@ -114,4 +116,42 @@ final class JWEHelperTests: XCTestCase { } } } + + func testDecryptVaultKey() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlA\ + tMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IllUcEY3bGtTc3JvZVVUVFdCb21LNzBTN0\ + FhVTJyc0ptMURpZ1ZzbjRMY2F5eUxFNFBabldkYmFVcE9jQVV5a1ciLCJ5IjoiLU5pS3loUktjSk52N\ + m02Z0ZJUWc4cy1Xd1VXUW9uT3A5dkQ4cHpoa2tUU3U2RzFlU2FUTVlhZGltQ2Q4V0ExMSJ9LCJhcHUi\ + OiIiLCJhcHYiOiIifQ..BECWGzd9UvhHcTJC.znt4TlS-qiNEjxiu2v-du_E1QOBnyBR6LCt865SHxD\ + -kwRc1JwX_Lq9XVoFj2GnK9-9CgxhCLGurg5Jt9g38qv2brGAzWL7eSVeY1fIqdO_kUhLpGslRTN6h2\ + U0NHJi2-iE.WDVI2kOk9Dy3PWHyIg8gKA + """) + + let data = Data(base64Encoded: privKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + let masterkey = try JWEHelper.decryptVaultKey(jwe: jwe, with: privateKey) + + let expectedEncKey = [UInt8](repeating: 0x55, count: 32) + let expectedMacKey = [UInt8](repeating: 0x77, count: 32) + + XCTAssertEqual(masterkey.aesMasterKey, expectedEncKey) + XCTAssertEqual(masterkey.macMasterKey, expectedMacKey) + } + + func testDecryptInvalidVaultKey_wrongKey() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImdodGR3VnNoUU8wRGFBdjVBOXBiZ1NCTW0yYzZKWVF4dkloR3p6RVdQTncxczZZcEFYeTRQTjBXRFJUWExtQ2wiLCJ5IjoiN3Rncm1Gd016NGl0ZmVQNzBndkpLcjRSaGdjdENCMEJHZjZjWE9WZ2M0bjVXMWQ4dFgxZ1RQakdrczNVSm1zUiJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..x6JWRGSojUJUJYpp.5BRuzcaV.lLIhGH7Wz0n_iTBAubDFZA + """) + + let data = Data(base64Encoded: privKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + + XCTAssertThrowsError(try JWEHelper.decryptVaultKey(jwe: jwe, with: privateKey)) { error in + guard case JOSESwiftError.decryptingFailed = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } } From d71b6a8f33f7797eb6739c4d9ce79c08ef83ea7c Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 27 Dec 2023 14:03:01 +0100 Subject: [PATCH 11/23] Add tests for decrypting vault key --- .../Hub/JWEHelperTests.swift | 64 +++++++++++++++++++ 1 file changed, 64 insertions(+) diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift index 684075880..9c958a524 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift @@ -154,4 +154,68 @@ final class JWEHelperTests: XCTestCase { } } } + + func testDecryptInvalidVaultKey_payloadIsNotJSON() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IkM2bWhsNE5BTHhEdHMwUlFlNXlyZWxQVDQyOGhDVzJNeUNYS3EwdUI0TDFMdnpXRHhVaVk3YTdZcEhJakJXcVoiLCJ5IjoiakM2dWc1NE9tbmdpNE9jUk1hdkNrczJpcFpXQjdkUmotR3QzOFhPSDRwZ2tpQ0lybWNlUnFxTnU3Z0c3Qk1yOSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..HNJJghL-SvERFz2v.N0z8YwFg.rYw29iX4i8XujdM4P4KKWg + """) + + let data = Data(base64Encoded: privKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + + XCTAssertThrowsError(try JWEHelper.decryptVaultKey(jwe: jwe, with: privateKey)) { error in + guard case DecodingError.dataCorrupted = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } + + func testDecryptInvalidVaultKey_jsonDoesNotContainKey() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6InB3R05vcXRnY093MkJ6RDVmSnpBWDJvMzUwSWNsY3A5cFdVTHZ5VDRqRWVCRWdCc3hhTVJXQ1ZyNlJMVUVXVlMiLCJ5IjoiZ2lIVEE5MlF3VU5lbmg1OFV1bWFfb09BX3hnYmFDVWFXSlRnb3Z4WjU4R212TnN4eUlQRElLSm9WV1h5X0R6OSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..jDbzdI7d67_cUjGD.01BPnMq_tQ.aG_uFA6FYqoPS64QAJ4VBQ + """) + + let data = Data(base64Encoded: privKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + + XCTAssertThrowsError(try JWEHelper.decryptVaultKey(jwe: jwe, with: privateKey)) { error in + guard case DecodingError.keyNotFound = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } + + func testDecryptInvalidVaultKey_jsonKeyIsNotAString() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6IkJyYm9UQkl5Y0NDUEdJQlBUekU2RjBnbTRzRjRCamZPN1I0a2x0aWlCaThKZkxxcVdXNVdUSVBLN01yMXV5QVUiLCJ5IjoiNUpGVUI0WVJiYjM2RUZpN2Y0TUxMcFFyZXd2UV9Tc3dKNHRVbFd1a2c1ZU04X1ZyM2pkeml2QXI2WThRczVYbSJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..QEq4Z2m6iwBx2ioS.IBo8TbKJTS4pug.61Z-agIIXgP8bX10O_yEMA + """) + + let data = Data(base64Encoded: privKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + + XCTAssertThrowsError(try JWEHelper.decryptVaultKey(jwe: jwe, with: privateKey)) { error in + guard case DecodingError.typeMismatch = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } + + func testDecryptInvalidVaultKey_invalidBase64Data() throws { + let jwe = try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImNZdlVFZm9LYkJjenZySE5zQjUxOGpycUxPMGJDOW5lZjR4NzFFMUQ5dk95MXRqd1piZzV3cFI0OE5nU1RQdHgiLCJ5IjoiaWRJekhCWERzSzR2NTZEeU9yczJOcDZsSG1zb29fMXV0VTlzX3JNdVVkbkxuVXIzUXdLZkhYMWdaVXREM1RKayJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..0VZqu5ei9U3blGtq.eDvhU6drw7mIwvXu6Q.f05QnhI7JWG3IYHvexwdFQ + """) + + let data = Data(base64Encoded: privKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + + XCTAssertThrowsError(try JWEHelper.decryptVaultKey(jwe: jwe, with: privateKey)) { error in + guard case VaultManagerError.invalidPayloadMasterkey = error else { + XCTFail("Unexpected error: \(error)") + return + } + } + } } From 9476edbd0792134eb2750212e192b8a8f15865f0 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 27 Dec 2023 16:21:51 +0100 Subject: [PATCH 12/23] Refined errors --- .../Hub/CryptomatorHubAuthenticator.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 7d5b0bd7c..35ebc286a 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -33,6 +33,9 @@ public enum CryptomatorHubAuthenticatorError: Error { case unexpectedResponse case deviceNameAlreadyExists + case unexpectedPrivateKeyFormat + case invalidVaultConfig + case invalidHubConfig case invalidBaseURL case invalidDeviceResourceURL case missingAccessToken @@ -48,13 +51,11 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving public func receiveKey(authState: OIDAuthState, vaultConfig: UnverifiedVaultConfig) async throws -> HubAuthenticationFlow { guard let hubConfig = vaultConfig.allegedHubConfig, let vaultBaseURL = getVaultBaseURL(from: vaultConfig) else { - // handle error - fatalError() + throw CryptomatorHubAuthenticatorError.invalidVaultConfig } guard let apiBaseURL = hubConfig.getAPIBaseURL(), let webAppURL = hubConfig.getWebAppURL() else { - // TODO: More specific error - throw CryptomatorHubAuthenticatorError.invalidBaseURL + throw CryptomatorHubAuthenticatorError.invalidHubConfig } guard try await hubInstanceHasMinimumAPILevel(of: Self.minimumHubVersion, apiBaseURL: apiBaseURL, authState: authState) else { @@ -97,7 +98,6 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving public func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) async throws { guard let apiBaseURL = hubConfig.getAPIBaseURL() else { - // TODO: More specific error throw CryptomatorHubAuthenticatorError.invalidBaseURL } @@ -140,8 +140,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving private func getEncryptedUserKeyJWE(userDto: UserDTO, setupCode: String, publicKey: P384.KeyAgreement.PublicKey) throws -> JWE { guard let privateKey = userDto.privateKey.data(using: .utf8) else { - // TODO: Throw proper error - fatalError() + throw CryptomatorHubAuthenticatorError.unexpectedPrivateKeyFormat } let jwe = try JWE(compactSerialization: privateKey) From e797b2ad0da262f32c9fd732dc1dc5d82c08e9c1 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 27 Dec 2023 16:34:51 +0100 Subject: [PATCH 13/23] Added documentation to registerDevice --- .../Hub/CryptomatorHubAuthenticator.swift | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 35ebc286a..88090d620 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -96,7 +96,19 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return .success(.init(encryptedUserKey: encryptedUserKeyJWE, encryptedVaultKey: encryptedVaultKeyJWE, header: [:])) } - public func registerDevice(withName name: String, hubConfig: HubConfig, authState: OIDAuthState, setupCode: String) async throws { + /** Registers a new device. + + Registers a new mobile device at the hub instance derived from the `hubConfig` with the given `name`. + + The device registration consists of two requests: + + 1. Request the encrypted user key which can be decrypted by using the `setupCode`. + 2. Send a Create Device request to the hub instance which contains the user key encrypted with the device key pair + */ + public func registerDevice(withName name: String, + hubConfig: HubConfig, + authState: OIDAuthState, + setupCode: String) async throws { guard let apiBaseURL = hubConfig.getAPIBaseURL() else { throw CryptomatorHubAuthenticatorError.invalidBaseURL } From e523906d9dfbe26ce920e682251b014d8a97f333 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Thu, 28 Dec 2023 02:12:58 +0100 Subject: [PATCH 14/23] Update HubAuthenticationViewModelTests --- .../Hub/HubAuthenticationViewModelTests.swift | 73 ++++++++++++++++--- 1 file changed, 64 insertions(+), 9 deletions(-) diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift index 7f0266703..56fcbd80e 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift @@ -7,6 +7,7 @@ import AppAuthCore import CryptoKit +import JOSESwift import XCTest @testable import CryptomatorCloudAccessCore @testable import CryptomatorCommonCore @@ -45,7 +46,7 @@ final class HubAuthenticationViewModelTests: XCTestCase { let calledReceiveKey = XCTestExpectation() hubKeyServiceMock.receiveKeyAuthStateVaultConfigClosure = { _, _ in calledReceiveKey.fulfill() - return .success(Data(), [:]) + return try .successMock() } let calledShowLoadingIndicator = XCTestExpectation() @@ -103,8 +104,12 @@ final class HubAuthenticationViewModelTests: XCTestCase { // GIVEN // the hub key service returns success with an active Cryptomator Hub subscription state - hubKeyServiceMock.receiveKeyAuthStateVaultConfigReturnValue = .success(validHubResponseData(), ["hub-subscription-state": "ACTIVE"]) - hubKeyProviderMock.getPrivateKeyReturnValue = P384.KeyAgreement.PrivateKey(compactRepresentable: false) + hubKeyServiceMock.receiveKeyAuthStateVaultConfigReturnValue = try .successMock(header: ["hub-subscription-state": "ACTIVE"]) + + let devicePrivKey = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB2bmFCWy2p+EbAn8NWS5Om+GA7c5LHhRZb8g2pSMSf0fsd7k7dZDVrnyHFiLdd/YGhZANiAAR6bsjTEdXKWIuu1Bvj6Y8wySlIROy7YpmVZTY128ItovCD8pcR4PnFljvAIb2MshCdr1alX4g6cgDOqcTeREiObcSfucOU9Ry1pJ/GnX6KA0eSljrk6rxjSDos8aiZ6Mg=" + let data = Data(base64Encoded: devicePrivKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + hubKeyProviderMock.getPrivateKeyReturnValue = privateKey // WHEN // continue the access check @@ -117,14 +122,15 @@ final class HubAuthenticationViewModelTests: XCTestCase { XCTAssertEqual(receivedResponse?.subscriptionState, .active) } - func testContinueToAccessCheck_success_hubSubscriptionStateIsInactive() async throws { + // TODO: Don't skip test as soon as the hub instance does return a valid header and we no longer hardcode a active hub subscription state! + func skip_testContinueToAccessCheck_success_hubSubscriptionStateIsInactive() async throws { DependencyValues.mockDependency(\.hubKeyService, with: hubKeyServiceMock) let hubKeyProviderMock = CryptomatorHubKeyProviderMock() DependencyValues.mockDependency(\.cryptomatorHubKeyProvider, with: hubKeyProviderMock) // GIVEN // the hub key service returns success with an inactive Cryptomator Hub subscription state - hubKeyServiceMock.receiveKeyAuthStateVaultConfigReturnValue = .success(validHubResponseData(), ["hub-subscription-state": "INACTIVE"]) + hubKeyServiceMock.receiveKeyAuthStateVaultConfigReturnValue = try .successMock(header: ["hub-subscription-state": "INACTIVE"]) hubKeyProviderMock.getPrivateKeyReturnValue = P384.KeyAgreement.PrivateKey(compactRepresentable: false) // WHEN @@ -145,7 +151,7 @@ final class HubAuthenticationViewModelTests: XCTestCase { // GIVEN // the hub key service returns success with an unknown Cryptomator Hub subscription state - hubKeyServiceMock.receiveKeyAuthStateVaultConfigReturnValue = .success(validHubResponseData(), ["hub-subscription-state": "FOO"]) + hubKeyServiceMock.receiveKeyAuthStateVaultConfigReturnValue = try .successMock(header: ["hub-subscription-state": "foo"]) hubKeyProviderMock.getPrivateKeyReturnValue = P384.KeyAgreement.PrivateKey(compactRepresentable: false) // WHEN @@ -224,8 +230,8 @@ final class HubAuthenticationViewModelTests: XCTestCase { // THEN // the registerDevice got called on the device registering servie - let receivedArguments = deviceRegisteringMock.registerDeviceWithNameHubConfigAuthStateReceivedArguments - XCTAssertEqual(deviceRegisteringMock.registerDeviceWithNameHubConfigAuthStateCallsCount, 1) + let receivedArguments = deviceRegisteringMock.registerDeviceWithNameHubConfigAuthStateSetupCodeReceivedArguments + XCTAssertEqual(deviceRegisteringMock.registerDeviceWithNameHubConfigAuthStateSetupCodeCallsCount, 1) // with the name set by the user XCTAssertEqual(receivedArguments?.name, "My Device 123") } @@ -233,7 +239,7 @@ final class HubAuthenticationViewModelTests: XCTestCase { private struct TestError: Error {} private func validHubVaultConfig() -> Data { - "eyJraWQiOiJodWIraHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcvaHViMjkvYXBpL3ZhdWx0cy9mYjUzMDdmMC1jOWI4LTRjNWYtYjJiMi03ZDM4ODE4ZjZhNGIiLCJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiIsImh1YiI6eyJjbGllbnRJZCI6ImNyeXB0b21hdG9yIiwiYXV0aEVuZHBvaW50IjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcva2MvcmVhbG1zL2h1YjI5L3Byb3RvY29sL29wZW5pZC1jb25uZWN0L2F1dGgiLCJ0b2tlbkVuZHBvaW50IjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcva2MvcmVhbG1zL2h1YjI5L3Byb3RvY29sL29wZW5pZC1jb25uZWN0L3Rva2VuIiwiZGV2aWNlc1Jlc291cmNlVXJsIjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcvaHViMjkvYXBpL2RldmljZXMvIiwiYXV0aFN1Y2Nlc3NVcmwiOiJodHRwczovL3Rlc3RpbmcuaHViLmNyeXB0b21hdG9yLm9yZy9odWIyOS9hcHAvdW5sb2NrLXN1Y2Nlc3M_dmF1bHQ9ZmI1MzA3ZjAtYzliOC00YzVmLWIyYjItN2QzODgxOGY2YTRiIiwiYXV0aEVycm9yVXJsIjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcvaHViMjkvYXBwL3VubG9jay1lcnJvcj92YXVsdD1mYjUzMDdmMC1jOWI4LTRjNWYtYjJiMi03ZDM4ODE4ZjZhNGIifX0.eyJqdGkiOiJmYjUzMDdmMC1jOWI4LTRjNWYtYjJiMi03ZDM4ODE4ZjZhNGIiLCJmb3JtYXQiOjgsImNpcGhlckNvbWJvIjoiU0lWX0dDTSIsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMH0.2iFWE4Jj5lV6iaVTPOzGovnrNreuuAJCy_gPmK90MMU".data(using: .utf8)! + "eyJraWQiOiJodWIraHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcvaHViMzAvYXBpL3ZhdWx0cy83NWFmMjFiNy00ODQ5LTQ1NTgtYjA1Yy1kZTZkYzkwNzdhNjciLCJ0eXAiOiJqd3QiLCJhbGciOiJIUzI1NiIsImh1YiI6eyJjbGllbnRJZCI6ImNyeXB0b21hdG9yIiwiYXV0aEVuZHBvaW50IjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcva2MvcmVhbG1zL2h1YjMwL3Byb3RvY29sL29wZW5pZC1jb25uZWN0L2F1dGgiLCJ0b2tlbkVuZHBvaW50IjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcva2MvcmVhbG1zL2h1YjMwL3Byb3RvY29sL29wZW5pZC1jb25uZWN0L3Rva2VuIiwiYXV0aFN1Y2Nlc3NVcmwiOiJodHRwczovL3Rlc3RpbmcuaHViLmNyeXB0b21hdG9yLm9yZy9odWIzMC9hcHAvdW5sb2NrLXN1Y2Nlc3M_dmF1bHQ9NzVhZjIxYjctNDg0OS00NTU4LWIwNWMtZGU2ZGM5MDc3YTY3IiwiYXV0aEVycm9yVXJsIjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcvaHViMzAvYXBwL3VubG9jay1lcnJvcj92YXVsdD03NWFmMjFiNy00ODQ5LTQ1NTgtYjA1Yy1kZTZkYzkwNzdhNjciLCJhcGlCYXNlVXJsIjoiaHR0cHM6Ly90ZXN0aW5nLmh1Yi5jcnlwdG9tYXRvci5vcmcvaHViMzAvYXBpLyIsImRldmljZXNSZXNvdXJjZVVybCI6Imh0dHBzOi8vdGVzdGluZy5odWIuY3J5cHRvbWF0b3Iub3JnL2h1YjMwL2FwaS9kZXZpY2VzLyJ9fQ.eyJqdGkiOiI3NWFmMjFiNy00ODQ5LTQ1NTgtYjA1Yy1kZTZkYzkwNzdhNjciLCJmb3JtYXQiOjgsImNpcGhlckNvbWJvIjoiU0lWX0dDTSIsInNob3J0ZW5pbmdUaHJlc2hvbGQiOjIyMH0.Z0x_5D073zo3smZq5q5wgDRheewcapCrIqg_0iD5qwM".data(using: .utf8)! } private func validHubResponseData() -> Data { @@ -258,6 +264,37 @@ private extension HubAuthenticationViewModel.State { } } +private extension JWE { + static func encryptedUserKeyStub() throws -> JWE { + try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrZXlfb3BzIjpbXSwiZXh0Ijp\ + 0cnVlLCJrdHkiOiJFQyIsIngiOiJoeHpiSWh6SUJza3A5ZkZFUmJSQ2RfOU1fbWYxNElqaDZhcnNoVX\ + NkcEEyWno5ejZZNUs4NHpZR2I4b2FHemNUIiwieSI6ImJrMGRaNWhpelZ0TF9hN2hNejBjTUduNjhIR\ + jZFdWlyNHdlclNkTFV5QWd2NWUzVzNYSG5sdHJ2VlRyU3pzUWYiLCJjcnYiOiJQLTM4NCJ9LCJhcHUi\ + OiIiLCJhcHYiOiIifQ..pu3Q1nR_yvgRAapG.4zW0xm0JPxbcvZ66R-Mn3k841lHelDQfaUvsZZAtWs\ + L2w4FMi6H_uu6ArAWYLtNREa_zfcPuyuJsFferYPSNRUWt4OW6aWs-l_wfo7G1ceEVxztQXzQiwD30U\ + TA8OOdPcUuFfEq2-d9217jezrcyO6m6FjyssEZIrnRArUPWKzGdghXccGkkf0LTZcGJoHeKal-RtyP8\ + PfvEAWTjSOCpBlSdUJ-1JL3tyd97uVFNaVuH3i7vvcMoUP_bdr0XW3rvRgaeC6X4daPLUvR1hK5Msut\ + QMtM2vpFghS_zZxIQRqz3B2ECxa9Bjxhmn8kLX5heZ8fq3lH-bmJp1DxzZ4V1RkWk.yVwXG9yARa5Ih\ + q2koh2NbQ + """) + } + + static func encryptedVaultKeyStub() throws -> JWE { + try JWE(compactSerialization: """ + eyJhbGciOiJFQ0RILUVTIiwiZW5jIjoiQTI1NkdDTSIsImVwayI6eyJrdHkiOiJFQyIsImNydiI6IlAtMzg0Iiwia2V5X29wcyI6W10sImV4dCI6dHJ1ZSwieCI6ImNZdlVFZm9LYkJjenZySE5zQjUxOGpycUxPMGJDOW5lZjR4NzFFMUQ5dk95MXRqd1piZzV3cFI0OE5nU1RQdHgiLCJ5IjoiaWRJekhCWERzSzR2NTZEeU9yczJOcDZsSG1zb29fMXV0VTlzX3JNdVVkbkxuVXIzUXdLZkhYMWdaVXREM1RKayJ9LCJhcHUiOiIiLCJhcHYiOiIifQ..0VZqu5ei9U3blGtq.eDvhU6drw7mIwvXu6Q.f05QnhI7JWG3IYHvexwdFQ + """) + } +} + +private extension HubAuthenticationFlow { + static func successMock(header: [AnyHashable: Any] = [:]) throws -> HubAuthenticationFlow { + try .success(.init(encryptedUserKey: .encryptedUserKeyStub(), + encryptedVaultKey: .encryptedVaultKeyStub(), + header: header)) + } +} + // MARK: - HubAuthenticationViewModelDelegateMock - // swiftlint: disable all @@ -289,6 +326,24 @@ final class HubAuthenticationViewModelDelegateMock: HubAuthenticationViewModelDe hubAuthenticationViewModelWantsToHideLoadingIndicatorCallsCount += 1 hubAuthenticationViewModelWantsToHideLoadingIndicatorClosure?() } + + // MARK: - hubAuthenticationViewModelWantsToShowNeedsAccountInitAlert + + var hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLCallsCount = 0 + var hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLCalled: Bool { + hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLCallsCount > 0 + } + + var hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLReceivedProfileURL: URL? + var hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLReceivedInvocations: [URL] = [] + var hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLClosure: ((URL) -> Void)? + + func hubAuthenticationViewModelWantsToShowNeedsAccountInitAlert(profileURL: URL) { + hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLCallsCount += 1 + hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLReceivedProfileURL = profileURL + hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLReceivedInvocations.append(profileURL) + hubAuthenticationViewModelWantsToShowNeedsAccountInitAlertProfileURLClosure?(profileURL) + } } // swiftlint: enable all From 7b98ba37b7f5dfc3b75a8416dbe0e08529fabfb1 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Thu, 28 Dec 2023 02:32:51 +0100 Subject: [PATCH 15/23] Add handling for legacy hub --- .../Hub/CryptomatorHubAuthenticator.swift | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index 88090d620..bba103495 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -202,6 +202,10 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return digest.data.base16EncodedString } + /** Checks if the Cryptomator Hub Instance at `apiBaseURL` has at least the API level of `minimumLevel`. + + - Note: The legacy Hub which is not supported returns a 0 + */ private func hubInstanceHasMinimumAPILevel(of minimumLevel: Int, apiBaseURL: URL, authState: OIDAuthState) async throws -> Bool { let url = apiBaseURL.appendingPathComponent("config") let (accessToken, _) = try await authState.performAction() @@ -355,7 +359,13 @@ extension String { extension HubConfig { func getAPIBaseURL() -> URL? { - return URL(string: apiBaseUrl) + if let apiBaseUrl { + return URL(string: apiBaseUrl) + } + guard let deviceResourceURL = URL(string: devicesResourceUrl) else { + return nil + } + return deviceResourceURL.deletingLastPathComponent() } func getWebAppURL() -> URL? { From f0bb27a2cb4eec1ae718f5dcccbbf6602f2fe68d Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Thu, 28 Dec 2023 12:22:21 +0100 Subject: [PATCH 16/23] Update swift-tools-version from 5.7 to 5.9 needed for SwiftECC --- CryptomatorCommon/Package.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CryptomatorCommon/Package.swift b/CryptomatorCommon/Package.swift index e4a11411a..0409f44e3 100644 --- a/CryptomatorCommon/Package.swift +++ b/CryptomatorCommon/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.7 +// swift-tools-version:5.9 // // Package.swift From b1b8e0a6d4a47e997c45dc85b5a86ab92f725e75 Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Sat, 30 Dec 2023 13:57:11 +0100 Subject: [PATCH 17/23] Updated localizations --- .../Hub/HubDeviceRegistrationView.swift | 3 +-- SharedResources/en.lproj/Localizable.strings | 5 +++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift index 5da7c5d0b..92bf6ce50 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubDeviceRegistrationView.swift @@ -35,8 +35,7 @@ struct HubDeviceRegistrationView: View { .focusedLegacy($field, equals: .accountKey) .backportedSubmitlabel(.done) } footer: { - // TODO: Add localization - Text("Your Account Key is required to login from other apps or browsers. It can be found in your profile.") + Text(LocalizedString.getValue("hubAuthentication.deviceRegistration.accountKey.footer.title")) } } .setListBackgroundColor(.cryptomatorBackground) diff --git a/SharedResources/en.lproj/Localizable.strings b/SharedResources/en.lproj/Localizable.strings index 65ea219a7..5a1eb77f1 100644 --- a/SharedResources/en.lproj/Localizable.strings +++ b/SharedResources/en.lproj/Localizable.strings @@ -119,11 +119,12 @@ "hubAuthentication.licenseExceeded" = "Your Cryptomator Hub instance has an invalid license. Please inform a Hub administrator to upgrade or renew the license."; "hubAuthentication.deviceRegistration.deviceName.cells.name" = "Device Name"; "hubAuthentication.deviceRegistration.deviceName.footer.title" = "This seems to be the first Hub access from this device. In order to identify it for access authorization, you need to name this device."; +"hubAuthentication.deviceRegistration.accountKey.footer.title" = "Your Account Key is required to login from new apps or browsers. It can be found in your profile."; "hubAuthentication.deviceRegistration.needsAuthorization.alert.title" = "Register Device Successful"; "hubAuthentication.deviceRegistration.needsAuthorization.alert.message" = "To access the vault, your device needs to be authorized by the vault owner."; -"hubAuthentication.requireAccountInit.alert.title" = "Action required"; +"hubAuthentication.requireAccountInit.alert.title" = "Action Required"; "hubAuthentication.requireAccountInit.alert.message" = "To proceed, please complete the steps required in your Hub user profile."; -"hubAuthentication.requireAccountInit.alert.actionButton" = "Go to profile"; +"hubAuthentication.requireAccountInit.alert.actionButton" = "Go to Profile"; "intents.saveFile.missingFile" = "The provided file is not valid."; "intents.saveFile.invalidFolder" = "The provided folder is not valid."; From 6f92d49826bb23bb9655b85eb5c67684ec73be2e Mon Sep 17 00:00:00 2001 From: Tobias Hagemann Date: Sat, 30 Dec 2023 15:17:02 +0100 Subject: [PATCH 18/23] Minor refactorings and reformattings --- .../Hub/CryptomatorHubAuthenticator.swift | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift index bba103495..0420202f9 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/CryptomatorHubAuthenticator.swift @@ -96,14 +96,15 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return .success(.init(encryptedUserKey: encryptedUserKeyJWE, encryptedVaultKey: encryptedVaultKeyJWE, header: [:])) } - /** Registers a new device. + /** + Registers a new device. - Registers a new mobile device at the hub instance derived from the `hubConfig` with the given `name`. + Registers a new mobile device at the Hub instance derived from the `hubConfig` with the given `name`. - The device registration consists of two requests: + The device registration consists of two requests: - 1. Request the encrypted user key which can be decrypted by using the `setupCode`. - 2. Send a Create Device request to the hub instance which contains the user key encrypted with the device key pair + 1. Request the encrypted user key which can be decrypted by using the `setupCode`. + 2. Send a Create Device request to the Hub instance which contains the user key encrypted with the device key pair. */ public func registerDevice(withName name: String, hubConfig: HubConfig, @@ -134,7 +135,7 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving try await createDevice(dto, apiBaseURL: apiBaseURL, authState: authState) } - private func getUser(apiBaseURL: URL, authState: OIDAuthState) async throws -> UserDTO { + private func getUser(apiBaseURL: URL, authState: OIDAuthState) async throws -> UserDto { let url = apiBaseURL.appendingPathComponent("users/me") let (accessToken, _) = try await authState.performAction() guard let accessToken = accessToken else { @@ -147,10 +148,10 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving guard httpResponse?.statusCode == 200 else { throw CryptomatorHubAuthenticatorError.unexpectedResponse } - return try JSONDecoder().decode(UserDTO.self, from: data) + return try JSONDecoder().decode(UserDto.self, from: data) } - private func getEncryptedUserKeyJWE(userDto: UserDTO, setupCode: String, publicKey: P384.KeyAgreement.PublicKey) throws -> JWE { + private func getEncryptedUserKeyJWE(userDto: UserDto, setupCode: String, publicKey: P384.KeyAgreement.PublicKey) throws -> JWE { guard let privateKey = userDto.privateKey.data(using: .utf8) else { throw CryptomatorHubAuthenticatorError.unexpectedPrivateKeyFormat } @@ -202,9 +203,10 @@ public class CryptomatorHubAuthenticator: HubDeviceRegistering, HubKeyReceiving return digest.data.base16EncodedString } - /** Checks if the Cryptomator Hub Instance at `apiBaseURL` has at least the API level of `minimumLevel`. + /** + Checks if the Hub instance at `apiBaseURL` has at least the API level of `minimumLevel`. - - Note: The legacy Hub which is not supported returns a 0 + - Note: The legacy Hub which is not supported returns a 0. */ private func hubInstanceHasMinimumAPILevel(of minimumLevel: Int, apiBaseURL: URL, authState: OIDAuthState) async throws -> Bool { let url = apiBaseURL.appendingPathComponent("config") @@ -373,7 +375,7 @@ extension HubConfig { } } -private struct UserDTO: Codable { +private struct UserDto: Codable { let id: String let name: String let publicKey: String From cace28b083109a656560450e08891a5771b14ceb Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Sat, 30 Dec 2023 21:29:36 +0100 Subject: [PATCH 19/23] Use Xcode 15.1 --- .github/workflows/build.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7225ba82d..e2cfc5839 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -30,6 +30,8 @@ jobs: run: | cd fastlane ./scripts/create-cloud-access-secrets.sh + - name: Select Xcode 15.1 + run: sudo xcode-select -s /Applications/Xcode_15.1.app - name: Configuration for freemium if: ${{ matrix.config == 'freemium' }} run: | From 3c98ece2b96e97a7dd5c69d7729981060c673eff Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Sun, 31 Dec 2023 18:33:19 +0100 Subject: [PATCH 20/23] Use latest cloud-access-swift version --- .../xcshareddata/swiftpm/Package.resolved | 52 ++++++++++++++++--- CryptomatorCommon/Package.swift | 2 +- 2 files changed, 45 insertions(+), 9 deletions(-) diff --git a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index d69681916..b87dbea32 100644 --- a/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Cryptomator.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,6 +9,15 @@ "version" : "1.6.2" } }, + { + "identity" : "asn1", + "kind" : "remoteSourceControl", + "location" : "https://github.com/leif-ibsen/ASN1", + "state" : { + "revision" : "5bb6eca2e4b250995f189c3d04ec53b6cd8257c5", + "version" : "2.2.0" + } + }, { "identity" : "asn1swift", "kind" : "remoteSourceControl", @@ -23,8 +32,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/aws-amplify/aws-sdk-ios-spm.git", "state" : { - "revision" : "ca31418963a90bac80538e13f6b7af87ea14d279", - "version" : "2.33.4" + "revision" : "59fdc9ca7ff3f5d38e07af27526a527c199b8de6", + "version" : "2.33.7" } }, { @@ -36,13 +45,22 @@ "version" : "0.9.0" } }, + { + "identity" : "bigint", + "kind" : "remoteSourceControl", + "location" : "https://github.com/leif-ibsen/BigInt", + "state" : { + "revision" : "3fe07ec38afa732e86d4f3e867cb43b05d004941", + "version" : "1.14.0" + } + }, { "identity" : "cloud-access-swift", "kind" : "remoteSourceControl", "location" : "https://github.com/cryptomator/cloud-access-swift.git", "state" : { - "revision" : "1fe06a85f9ea38d9b22a84fb7dbd8de127c65f82", - "version" : "1.8.1" + "revision" : "63fd1cfee9e4d1c0a8d585dd0c7008eb37d2f037", + "version" : "1.9.0" } }, { @@ -63,6 +81,15 @@ "version" : "1.1.0" } }, + { + "identity" : "digest", + "kind" : "remoteSourceControl", + "location" : "https://github.com/leif-ibsen/Digest", + "state" : { + "revision" : "fd501645c5f14c17207c4ada4281a1e6b7cb03df", + "version" : "1.1.0" + } + }, { "identity" : "dropbox-sdk-obj-c-spm", "kind" : "remoteSourceControl", @@ -113,8 +140,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/tobihagemann/JOSESwift.git", "state" : { - "revision" : "11442e7f1f803ef42281909c68f386b38afc5096", - "version" : "2.4.0-cryptomator" + "revision" : "3544f8117908ef12ea13b1c0927e0e3c0d30ee01", + "version" : "2.4.1-cryptomator" } }, { @@ -122,8 +149,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/AzureAD/microsoft-authentication-library-for-objc.git", "state" : { - "revision" : "35846731c0971694f162b28fe8494c03b615ae74", - "version" : "1.2.16" + "revision" : "e9ef281b2f281c3ba2d32608138b1431cba5e4df", + "version" : "1.2.20" } }, { @@ -180,6 +207,15 @@ "version" : "1.5.3" } }, + { + "identity" : "swiftecc", + "kind" : "remoteSourceControl", + "location" : "https://github.com/leif-ibsen/SwiftECC", + "state" : { + "revision" : "18c0e462882d0a4fa910472a0a6cc13ef97bbc21", + "version" : "5.0.0" + } + }, { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", diff --git a/CryptomatorCommon/Package.swift b/CryptomatorCommon/Package.swift index 0409f44e3..ecb713a4b 100644 --- a/CryptomatorCommon/Package.swift +++ b/CryptomatorCommon/Package.swift @@ -26,7 +26,7 @@ let package = Package( ) ], dependencies: [ - .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.8.0")), + .package(url: "https://github.com/cryptomator/cloud-access-swift.git", .upToNextMinor(from: "1.9.0")), .package(url: "https://github.com/CocoaLumberjack/CocoaLumberjack.git", .upToNextMinor(from: "3.8.0")), .package(url: "https://github.com/PhilLibs/simple-swift-dependencies", .upToNextMajor(from: "0.1.0")), .package(url: "https://github.com/siteline/SwiftUI-Introspect.git", .upToNextMajor(from: "0.3.0")), From 1b6089c773f25446e81a649fc5d8c9b2593718e5 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:22:00 +0100 Subject: [PATCH 21/23] Resolve error handling todos --- .../CryptomatorCommonCore/JWEHelper.swift | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift index 6476e482a..213a63676 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift @@ -11,6 +11,11 @@ import Foundation import JOSESwift import SwiftECC +public enum JWEHelperError: Error { + case invalidDecrypter + case invalidMasterkeyPayload +} + public enum JWEHelper { public static func decryptVaultKey(jwe: JWE, with privateKey: P384.KeyAgreement.PrivateKey) throws -> Masterkey { // see https://developer.apple.com/forums/thread/680554 @@ -20,8 +25,7 @@ public enum JWEHelper { let decryptionKey = try ECPrivateKey(crv: "P-384", x: x.base64UrlEncodedString(), y: y.base64UrlEncodedString(), privateKey: k.base64UrlEncodedString()) guard let decrypter = Decrypter(keyManagementAlgorithm: .ECDH_ES, contentEncryptionAlgorithm: .A256GCM, decryptionKey: decryptionKey) else { - // TODO: Change Error - throw VaultManagerError.invalidDecrypter + throw JWEHelperError.invalidDecrypter } let payload = try jwe.decrypt(using: decrypter) let payloadMasterkey = try JSONDecoder().decode(PayloadMasterkey.self, from: payload.data()) @@ -43,8 +47,7 @@ public enum JWEHelper { guard let decrypter = Decrypter(keyManagementAlgorithm: .ECDH_ES, contentEncryptionAlgorithm: .A256GCM, decryptionKey: decryptionKey) else { - // TODO: Change Error - throw VaultManagerError.invalidDecrypter + throw JWEHelperError.invalidDecrypter } let payload = try jwe.decrypt(using: decrypter) return try decodeUserKey(payload: payload) @@ -54,8 +57,7 @@ public enum JWEHelper { guard let decrypter = Decrypter(keyManagementAlgorithm: .PBES2_HS512_A256KW, contentEncryptionAlgorithm: .A256GCM, decryptionKey: setupCode) else { - // TODO: Change Error - throw VaultManagerError.invalidDecrypter + throw JWEHelperError.invalidDecrypter } let payload = try jwe.decrypt(using: decrypter) return try decodeUserKey(payload: payload) @@ -71,8 +73,7 @@ public enum JWEHelper { guard let encrypter = Encrypter(keyManagementAlgorithm: .ECDH_ES, contentEncryptionAlgorithm: .A256GCM, encryptionKey: encryptionKey) else { - // TODO: Change Error - throw VaultManagerError.invalidDecrypter + throw JWEHelperError.invalidDecrypter } let payloadKey = try PayloadMasterkey(key: userKey.derPkcs8().base64EncodedString()) let payload = try Payload(JSONEncoder().encode(payloadKey)) @@ -83,8 +84,7 @@ public enum JWEHelper { let decodedPayload = try JSONDecoder().decode(PayloadMasterkey.self, from: payload.data()) guard let privateKeyData = Data(base64Encoded: decodedPayload.key) else { - // TODO: Change - fatalError() + throw JWEHelperError.invalidMasterkeyPayload } return try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: privateKeyData) } From ebde6294e89228d5e560e6fd4f70cb5a7fdcd103 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:22:38 +0100 Subject: [PATCH 22/23] Derive Hub subscription state again from the header --- .../Hub/HubAuthenticationViewModel.swift | 2 +- .../Hub/HubAuthenticationViewModelTests.swift | 11 +++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift index 88dac11a9..ee81e7354 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Hub/HubAuthenticationViewModel.swift @@ -117,7 +117,7 @@ public final class HubAuthenticationViewModel: ObservableObject { do { let deviceKey = try cryptomatorHubKeyProvider.getPrivateKey() userKey = try JWEHelper.decryptUserKey(jwe: flowResponse.encryptedUserKey, privateKey: deviceKey) - subscriptionState = .active // try getSubscriptionState(from: flowResponse.header) // TODO: Revert this after Cryptomator Hub adds the subscription state back to the header + subscriptionState = try getSubscriptionState(from: flowResponse.header) } catch { await setStateToErrorState(with: error) return diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift index 56fcbd80e..a0710a866 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/HubAuthenticationViewModelTests.swift @@ -122,16 +122,19 @@ final class HubAuthenticationViewModelTests: XCTestCase { XCTAssertEqual(receivedResponse?.subscriptionState, .active) } - // TODO: Don't skip test as soon as the hub instance does return a valid header and we no longer hardcode a active hub subscription state! - func skip_testContinueToAccessCheck_success_hubSubscriptionStateIsInactive() async throws { + func testContinueToAccessCheck_success_hubSubscriptionStateIsInactive() async throws { DependencyValues.mockDependency(\.hubKeyService, with: hubKeyServiceMock) let hubKeyProviderMock = CryptomatorHubKeyProviderMock() DependencyValues.mockDependency(\.cryptomatorHubKeyProvider, with: hubKeyProviderMock) // GIVEN - // the hub key service returns success with an inactive Cryptomator Hub subscription state + // the hub key service returns success with an active Cryptomator Hub subscription state hubKeyServiceMock.receiveKeyAuthStateVaultConfigReturnValue = try .successMock(header: ["hub-subscription-state": "INACTIVE"]) - hubKeyProviderMock.getPrivateKeyReturnValue = P384.KeyAgreement.PrivateKey(compactRepresentable: false) + + let devicePrivKey = "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDB2bmFCWy2p+EbAn8NWS5Om+GA7c5LHhRZb8g2pSMSf0fsd7k7dZDVrnyHFiLdd/YGhZANiAAR6bsjTEdXKWIuu1Bvj6Y8wySlIROy7YpmVZTY128ItovCD8pcR4PnFljvAIb2MshCdr1alX4g6cgDOqcTeREiObcSfucOU9Ry1pJ/GnX6KA0eSljrk6rxjSDos8aiZ6Mg=" + let data = Data(base64Encoded: devicePrivKey)! + let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) + hubKeyProviderMock.getPrivateKeyReturnValue = privateKey // WHEN // continue the access check From 8ebb9b58a9c0bc460df765afba40c71ff4b47070 Mon Sep 17 00:00:00 2001 From: Philipp Schmid <25935690+phil1995@users.noreply.github.com> Date: Wed, 3 Jan 2024 11:26:14 +0100 Subject: [PATCH 23/23] Error cleanup --- CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift | 2 +- .../Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift | 2 -- .../Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift index 213a63676..62b8a1037 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/JWEHelper.swift @@ -31,7 +31,7 @@ public enum JWEHelper { let payloadMasterkey = try JSONDecoder().decode(PayloadMasterkey.self, from: payload.data()) guard let masterkeyData = Data(base64Encoded: payloadMasterkey.key) else { - throw VaultManagerError.invalidPayloadMasterkey + throw JWEHelperError.invalidMasterkeyPayload } return Masterkey.createFromRaw(rawKey: [UInt8](masterkeyData)) } diff --git a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift index 85ca0bbf5..da860ef53 100644 --- a/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift +++ b/CryptomatorCommon/Sources/CryptomatorCommonCore/Manager/VaultDBManager.swift @@ -21,8 +21,6 @@ public enum VaultManagerError: Error { case vaultVersionNotSupported case fileProviderDomainNotFound case moveVaultInsideItself - case invalidDecrypter - case invalidPayloadMasterkey case missingVaultConfigToken } diff --git a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift index 9c958a524..311bb051c 100644 --- a/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift +++ b/CryptomatorCommon/Tests/CryptomatorCommonCoreTests/Hub/JWEHelperTests.swift @@ -212,7 +212,7 @@ final class JWEHelperTests: XCTestCase { let privateKey = try P384.KeyAgreement.PrivateKey(pkcs8DerRepresentation: data) XCTAssertThrowsError(try JWEHelper.decryptVaultKey(jwe: jwe, with: privateKey)) { error in - guard case VaultManagerError.invalidPayloadMasterkey = error else { + guard case JWEHelperError.invalidMasterkeyPayload = error else { XCTFail("Unexpected error: \(error)") return }