diff --git a/Darwin/Entitlements.plist b/Darwin/Entitlements.plist index 70084b4..1b0cdf5 100644 --- a/Darwin/Entitlements.plist +++ b/Darwin/Entitlements.plist @@ -2,7 +2,11 @@ + com.apple.developer.devicecheck.app-attest-opt-in + + CDhash + com.apple.developer.devicecheck.appattest-environment - development + $(APP_ATTEST_ENVIRONMENT) diff --git a/Darwin/PediFoods.xcodeproj/project.pbxproj b/Darwin/PediFoods.xcodeproj/project.pbxproj index 9a9e402..5fdba32 100644 --- a/Darwin/PediFoods.xcodeproj/project.pbxproj +++ b/Darwin/PediFoods.xcodeproj/project.pbxproj @@ -178,6 +178,7 @@ isa = XCBuildConfiguration; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */; buildSettings = { + APP_ATTEST_ENVIRONMENT = development; DEVELOPMENT_TEAM = K4E5BZMM4V; ENABLE_PREVIEWS = YES; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; @@ -196,8 +197,9 @@ isa = XCBuildConfiguration; baseConfigurationReference = 496EB72F2A6AE4DE00C1253B /* PediFoods.xcconfig */; buildSettings = { - CODE_SIGN_IDENTITY = "Apple Distribution"; - CODE_SIGN_STYLE = Manual; + APP_ATTEST_ENVIRONMENT = production; + CODE_SIGN_IDENTITY = "Apple Development"; + CODE_SIGN_STYLE = Automatic; DEVELOPMENT_TEAM = K4E5BZMM4V; ENABLE_PREVIEWS = YES; INFOPLIST_KEY_CFBundleDisplayName = "Pedi Foods"; @@ -206,7 +208,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; - PROVISIONING_PROFILE_SPECIFIER = "com.br.pedifoods.app AppStore"; + PROVISIONING_PROFILE_SPECIFIER = ""; SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; SUPPORTS_MACCATALYST = NO; TARGETED_DEVICE_FAMILY = 1; diff --git a/Sources/PediFoods/Services/GuestSessionService.swift b/Sources/PediFoods/Services/GuestSessionService.swift index 869c3ed..411bace 100644 --- a/Sources/PediFoods/Services/GuestSessionService.swift +++ b/Sources/PediFoods/Services/GuestSessionService.swift @@ -93,6 +93,7 @@ actor GuestSessionService { #if os(iOS) private func refreshTokenWithAppAttest() async throws -> String { + print("[GuestSessionService] DCAppAttestService.isSupported = \(DCAppAttestService.shared.isSupported), existingKeyId = \(store.appAttestKeyId ?? "nil")") guard DCAppAttestService.shared.isSupported else { // Simulator can never support App Attest (hardware limitation, // not environment-specific) — server has its own documented @@ -112,6 +113,15 @@ actor GuestSessionService { // other error (network blip, timeout, decode issue) must // NOT wipe a perfectly valid registered key. store.appAttestKeyId = nil + } catch let error as DCError { + // DeviceCheck itself rejects the key locally (e.g. the app + // was reinstalled and the Secure Enclave key backing this + // keyId no longer exists) - distinct from the server + // rejecting it, but equally unrecoverable without a fresh + // key. Without this, generateAssertion fails the same way + // forever since appAttestKeyId is never cleared. + print("[GuestSessionService] existing key rejected locally, re-attesting with a fresh key: \(error)") + store.appAttestKeyId = nil } } @@ -141,9 +151,22 @@ actor GuestSessionService { } private func handshakeWithFreshAttestation(challenge: String) async throws -> String { - let keyId = try await DCAppAttestService.shared.generateKey() + let keyId: String + do { + keyId = try await DCAppAttestService.shared.generateKey() + } catch { + print("[GuestSessionService] generateKey failed: \(error)") + throw error + } + let clientDataHash = Data(SHA256.hash(data: Data(challenge.utf8))) - let attestationObject = try await DCAppAttestService.shared.attestKey(keyId, clientDataHash: clientDataHash) + let attestationObject: Data + do { + attestationObject = try await DCAppAttestService.shared.attestKey(keyId, clientDataHash: clientDataHash) + } catch { + print("[GuestSessionService] attestKey failed: \(error)") + throw error + } let payload = GuestSessionAttestPayload( platform: "ios", @@ -175,7 +198,13 @@ actor GuestSessionService { private func handshakeWithAssertion(keyId: String, challenge: String) async throws -> String { let clientDataHash = Data(SHA256.hash(data: Data(challenge.utf8))) - let assertionObject = try await DCAppAttestService.shared.generateAssertion(keyId, clientDataHash: clientDataHash) + let assertionObject: Data + do { + assertionObject = try await DCAppAttestService.shared.generateAssertion(keyId, clientDataHash: clientDataHash) + } catch { + print("[GuestSessionService] generateAssertion failed: \(error)") + throw error + } let payload = GuestSessionAttestPayload( platform: "ios", diff --git a/Sources/PediFoods/Views/Main/PublicLocationPickerView.swift b/Sources/PediFoods/Views/Main/PublicLocationPickerView.swift index 085b911..f5263e1 100644 --- a/Sources/PediFoods/Views/Main/PublicLocationPickerView.swift +++ b/Sources/PediFoods/Views/Main/PublicLocationPickerView.swift @@ -161,6 +161,7 @@ struct PublicLocationPickerView: View { errorMessage = "Nenhum estado disponível no momento." } } catch { + print("[PublicLocationPickerView] loadLocations failed: \(error)") errorMessage = "Não foi possível carregar. Tente novamente." } isLoading = false