From cf9f715199f23a058ef1bdbefe0d3f2ac5eff9b3 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Thu, 18 Jun 2026 17:27:42 +0300 Subject: [PATCH 1/7] Clear entered PIN code on background and after use --- .../Utilities/SecureDataTests.swift | 100 ++++++++++++++++++ .../Domain/NFC/OperationChangePin.swift | 5 + RIADigiDoc/Domain/NFC/OperationDecrypt.swift | 2 + .../Domain/NFC/OperationReadCertAndSign.swift | 2 + .../Domain/NFC/OperationUnblockPin.swift | 5 + .../Container/Signing/NFC/NFCView.swift | 20 ++-- .../Component/My eID/MyEidPinChangeView.swift | 6 ++ .../Shared/FloatingLabelTextField.swift | 1 + .../MyEid/MyEidPinChangeViewModel.swift | 5 + .../MyEidPinChangeViewModelProtocol.swift | 1 + 10 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift diff --git a/Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift b/Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift new file mode 100644 index 00000000..8343f850 --- /dev/null +++ b/Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift @@ -0,0 +1,100 @@ +/* + * Copyright 2017 - 2026 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation +import Testing + +@testable import IdCardLib + +struct SecureDataTests { + + private func bytes(of secureData: SecureData) -> [UInt8] { + secureData.withUnsafeBytes { Array($0) } + } + + @Test + func init_fromByteArray_exposesSameBytes() { + let input: [UInt8] = [1, 2, 3, 4, 5, 6] + let secureData = SecureData(input) + + #expect(bytes(of: secureData) == input) + } + + @Test + func init_fromData_exposesSameBytes() { + let input = Data([0xAA, 0xBB, 0xCC]) + let secureData = SecureData(input) + + #expect(bytes(of: secureData) == Array(input)) + } + + @Test + func init_empty_exposesNoBytes() { + let secureData = SecureData([]) + + #expect(bytes(of: secureData).isEmpty) + } + + @Test + func init_preservesNullAndAllByteValues() { + let input: [UInt8] = [0x00, 0x30, 0xFF, 0x00, 0x01] + let secureData = SecureData(input) + + #expect(bytes(of: secureData) == input) + } + + @Test + func secureZero_clearsTheBuffer() { + let secureData = SecureData([1, 2, 3, 4]) + + secureData.secureZero() + + #expect(bytes(of: secureData).isEmpty) + } + + @Test + func secureZero_staysEmptyWhenCalledTwice() { + let secureData = SecureData([9, 9, 9]) + + secureData.secureZero() + secureData.secureZero() + + #expect(bytes(of: secureData).isEmpty) + } + + @Test + func secureZero_staysEmptyWhenAlreadyEmpty() { + let secureData = SecureData([]) + + secureData.secureZero() + + #expect(bytes(of: secureData).isEmpty) + } + + @Test + func withUnsafeBytes_returnsValueFromClosure() { + let secureData = SecureData([2, 4, 6, 8]) + + let sum = secureData.withUnsafeBytes { raw in + raw.reduce(0) { $0 + Int($1) } + } + + #expect(sum == 20) + } +} diff --git a/RIADigiDoc/Domain/NFC/OperationChangePin.swift b/RIADigiDoc/Domain/NFC/OperationChangePin.swift index 75f51cbf..470a1dcc 100644 --- a/RIADigiDoc/Domain/NFC/OperationChangePin.swift +++ b/RIADigiDoc/Domain/NFC/OperationChangePin.swift @@ -76,6 +76,11 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { } do { + defer { + currentPin.secureZero() + newPin.secureZero() + } + updateAlertMessage(step: 1) OperationChangePin.logger().info("NFC: Setting up NFC connection for PIN change...") let tag = try await self.connection.setup(session, tags: tags) diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift index 418c812c..ef14ea55 100644 --- a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift +++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift @@ -101,6 +101,8 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { OperationDecrypt.logger().info("NFC: Checks complete starting decryption") do { + defer { pin1Number.secureZero() } + updateAlertMessage(step: 1) let tag = try await connection.setup(session, tags: tags) updateAlertMessage(step: 2) diff --git a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift index 00a9fe86..194c4d6c 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift @@ -116,6 +116,8 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig OperationReadCertAndSign.logger().info("NFC: Checks complete starting signing") do { + defer { pin2Number.secureZero() } + updateAlertMessage(step: 1) let tag = try await connection.setup(session, tags: tags) diff --git a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift index 64f4f766..9538057a 100644 --- a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift +++ b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift @@ -77,6 +77,11 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol return } do { + defer { + puk.secureZero() + newPin.secureZero() + } + updateAlertMessage(step: 1) let tag = try await self.connection.setup(session, tags: tags) diff --git a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift index 22dc6389..748fba9a 100644 --- a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift +++ b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift @@ -28,6 +28,7 @@ struct NFCView: View { @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled @Environment(\.dismiss) private var dismiss @Environment(\.openURL) private var openURL + @Environment(\.scenePhase) private var scenePhase @Environment(LanguageSettings.self) private var languageSettings @Environment(NavigationPathManager.self) private var pathManager @@ -267,6 +268,17 @@ struct NFCView: View { .onDisappear { cancelSigning() } + .onChange(of: scenePhase) { _, newPhase in + if newPhase == .background { + resetPinState() + } + } + } + + private func resetPinState() { + pinNumber.removeAll() + isActionEnabled = viewModel + .isActionEnabled(canNumber: canNumber, pinNumber: pinNumber, pinType: pinType) } func saveInputData() { @@ -317,17 +329,13 @@ struct NFCView: View { } private func cancelDecrypt() { - pinNumber.isEmpty ? () : (pinNumber.removeAll()) - isActionEnabled = viewModel - .isActionEnabled(canNumber: canNumber, pinNumber: pinNumber, pinType: pinType) + resetPinState() taskDecrypt?.cancel() taskDecrypt = nil } private func cancelSigning() { - pinNumber.isEmpty ? () : (pinNumber.removeAll()) - isActionEnabled = viewModel - .isActionEnabled(canNumber: canNumber, pinNumber: pinNumber, pinType: pinType) + resetPinState() taskSign?.cancel() taskSign = nil } diff --git a/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift b/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift index 2e8f1128..f2670010 100644 --- a/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift +++ b/RIADigiDoc/UI/Component/My eID/MyEidPinChangeView.swift @@ -25,6 +25,7 @@ import CommonsLib struct MyEidPinChangeView: View { @Environment(\.accessibilityVoiceOverEnabled) private var voiceOverEnabled @Environment(\.dismiss) private var dismiss + @Environment(\.scenePhase) private var scenePhase @Environment(LanguageSettings.self) private var languageSettings @AccessibilityFocusState private var flowTitleFocused: Bool @@ -290,6 +291,11 @@ struct MyEidPinChangeView: View { stepTitleFocused = true } } + .onChange(of: scenePhase) { _, newPhase in + if newPhase == .background { + viewModel.clearSensitiveDataOnBackground() + } + } } ) } diff --git a/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift b/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift index bd2cee94..aa6e1b38 100644 --- a/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift +++ b/RIADigiDoc/UI/Component/Shared/FloatingLabelTextField.swift @@ -341,6 +341,7 @@ struct FloatingLabelTextField: View { onDone() } ) + .privacySensitive(isSecure) .toolbar { keyboardToolbar } .accessibilitySortPriority(sortPriority) .accessibilityLabel(Text(verbatim: title)) diff --git a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift index 15cda380..4989cbde 100644 --- a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift +++ b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift @@ -134,6 +134,11 @@ final class MyEidPinChangeViewModel: MyEidPinChangeViewModelProtocol, Loggable { isSuccess = false } + func clearSensitiveDataOnBackground() { + clearPinCodes() + resetErrors() + } + func handleConfirmStepError() { if step == .confirm && !verifyRepeatedCode() && !isSuccess { inputErrorMessage = "PIN repeat error" diff --git a/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift b/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift index db28e38a..00191bb9 100644 --- a/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift +++ b/RIADigiDoc/ViewModel/Protocols/MyEid/MyEidPinChangeViewModelProtocol.swift @@ -32,6 +32,7 @@ protocol MyEidPinChangeViewModelProtocol: Sendable { func submit(nfcStringsUtil: NFCSessionStringsUtil) async func resetErrors() + func clearSensitiveDataOnBackground() func verifyNewCode() func verifyRepeatedCode() -> Bool From c37030205e889e16f0095cf7e57f7faf7d37d528 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Tue, 30 Jun 2026 11:02:54 +0300 Subject: [PATCH 2/7] Clear PIN on all NFC operation exit paths --- RIADigiDoc.xcodeproj/project.pbxproj | 28 +++++++++---------- .../Domain/NFC/OperationChangePin.swift | 10 +++---- RIADigiDoc/Domain/NFC/OperationDecrypt.swift | 4 +-- .../Domain/NFC/OperationReadCertAndSign.swift | 4 +-- .../Domain/NFC/OperationUnblockPin.swift | 10 +++---- 5 files changed, 28 insertions(+), 28 deletions(-) diff --git a/RIADigiDoc.xcodeproj/project.pbxproj b/RIADigiDoc.xcodeproj/project.pbxproj index d741f1f0..9a85f071 100644 --- a/RIADigiDoc.xcodeproj/project.pbxproj +++ b/RIADigiDoc.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ DF4E43CB2D0BA38600967997 /* FileImportShareExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DF4E43C12D0BA38600967997 /* FileImportShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DF4E6D822D11DE7B00F6111A /* CommonsLib in Frameworks */ = {isa = PBXBuildFile; productRef = DF4E6D812D11DE7B00F6111A /* CommonsLib */; }; DF4E6D842D120CFC00F6111A /* UtilsLib in Frameworks */ = {isa = PBXBuildFile; productRef = DF4E6D832D120CFC00F6111A /* UtilsLib */; }; + DF54F82F2D431BD50021D05A /* X509 in Frameworks */ = {isa = PBXBuildFile; productRef = DF54F82E2D431BD50021D05A /* X509 */; }; DF5903762ECCC41F00D1A278 /* MobileIdLibMocks in Frameworks */ = {isa = PBXBuildFile; productRef = DF5903752ECCC41F00D1A278 /* MobileIdLibMocks */; }; DF5903782ECCC42500D1A278 /* SmartIdLibMocks in Frameworks */ = {isa = PBXBuildFile; productRef = DF5903772ECCC42500D1A278 /* SmartIdLibMocks */; }; DF5C54272E82F07A006E2251 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = DFA3EE5C2D14C9B7001D951B /* Alamofire */; }; @@ -53,7 +54,6 @@ DFB663A22F16917E00804545 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DFB663A12F16917E00804545 /* SwiftUI.framework */; }; DFB663AF2F16918100804545 /* WidgetExtensionExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = DFB6639E2F16917D00804545 /* WidgetExtensionExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; DFBD09F62CE3FBDC006AF9C2 /* LibdigidocLib in Frameworks */ = {isa = PBXBuildFile; productRef = DFBD09F52CE3FBDC006AF9C2 /* LibdigidocLib */; }; - DF54F82F2D431BD50021D05A /* X509 in Frameworks */ = {isa = PBXBuildFile; productRef = DF54F82E2D431BD50021D05A /* X509 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -1704,6 +1704,14 @@ version = 12.12.1; }; }; + DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/apple/swift-certificates.git"; + requirement = { + kind = exactVersion; + version = 1.19.0; + }; + }; DF9AFE902E00D30A0062C64D /* XCRemoteSwiftPackageReference "Factory" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/hmlongco/Factory"; @@ -1728,14 +1736,6 @@ version = 0.63.2; }; }; - DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-certificates.git"; - requirement = { - kind = exactVersion; - version = 1.19.0; - }; - }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1818,6 +1818,11 @@ isa = XCSwiftPackageProductDependency; productName = UtilsLib; }; + DF54F82E2D431BD50021D05A /* X509 */ = { + isa = XCSwiftPackageProductDependency; + package = DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */; + productName = X509; + }; DF5903752ECCC41F00D1A278 /* MobileIdLibMocks */ = { isa = XCSwiftPackageProductDependency; productName = MobileIdLibMocks; @@ -1917,11 +1922,6 @@ isa = XCSwiftPackageProductDependency; productName = LibdigidocLib; }; - DF54F82E2D431BD50021D05A /* X509 */ = { - isa = XCSwiftPackageProductDependency; - package = DF54F82D2D431BD50021D05A /* XCRemoteSwiftPackageReference "swift-certificates" */; - productName = X509; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = DFDB14732CC97B0E00153876 /* Project object */; diff --git a/RIADigiDoc/Domain/NFC/OperationChangePin.swift b/RIADigiDoc/Domain/NFC/OperationChangePin.swift index 470a1dcc..85f9672b 100644 --- a/RIADigiDoc/Domain/NFC/OperationChangePin.swift +++ b/RIADigiDoc/Domain/NFC/OperationChangePin.swift @@ -64,6 +64,11 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { self.session = nil } + defer { + self.currentPin?.secureZero() + self.newPin?.secureZero() + } + guard let codeType = self.codeType, let currentPin = self.currentPin, let newPin = self.newPin else { @@ -76,11 +81,6 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { } do { - defer { - currentPin.secureZero() - newPin.secureZero() - } - updateAlertMessage(step: 1) OperationChangePin.logger().info("NFC: Setting up NFC connection for PIN change...") let tag = try await self.connection.setup(session, tags: tags) diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift index ef14ea55..7a8517e2 100644 --- a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift +++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift @@ -71,6 +71,8 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { self.session = nil } + defer { pin1Number.secureZero() } + guard let containerFile else { let error = DecryptError.containerFileInvalid OperationDecrypt.logger().error("NFC: \(error.localizedDescription)") @@ -101,8 +103,6 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { OperationDecrypt.logger().info("NFC: Checks complete starting decryption") do { - defer { pin1Number.secureZero() } - updateAlertMessage(step: 1) let tag = try await connection.setup(session, tags: tags) updateAlertMessage(step: 2) diff --git a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift index 194c4d6c..8a123801 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift @@ -80,6 +80,8 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig self.session = nil } + defer { pin2Number.secureZero() } + guard let signedContainer else { let error = ReadCertAndSignError.signedContainerNil OperationReadCertAndSign.logger().error("NFC: \(error.localizedDescription)") @@ -116,8 +118,6 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig OperationReadCertAndSign.logger().info("NFC: Checks complete starting signing") do { - defer { pin2Number.secureZero() } - updateAlertMessage(step: 1) let tag = try await connection.setup(session, tags: tags) diff --git a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift index 9538057a..0f907dcd 100644 --- a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift +++ b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift @@ -66,6 +66,11 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol self.session = nil } + defer { + self.puk?.secureZero() + self.newPin?.secureZero() + } + guard let codeType = self.codeType, let puk = self.puk, let newPin = self.newPin else { @@ -77,11 +82,6 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol return } do { - defer { - puk.secureZero() - newPin.secureZero() - } - updateAlertMessage(step: 1) let tag = try await self.connection.setup(session, tags: tags) From 6c592e568ff6a417d43564ecf652d213fdb1fe27 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Tue, 30 Jun 2026 12:11:10 +0300 Subject: [PATCH 3/7] Clear PIN codes on error --- RIADigiDoc/Domain/NFC/OperationChangePin.swift | 12 ++++++++---- RIADigiDoc/Domain/NFC/OperationDecrypt.swift | 8 +++++++- RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift | 8 +++++++- RIADigiDoc/Domain/NFC/OperationUnblockPin.swift | 12 ++++++++---- .../ViewModel/MyEid/MyEidPinChangeViewModel.swift | 1 + 5 files changed, 31 insertions(+), 10 deletions(-) diff --git a/RIADigiDoc/Domain/NFC/OperationChangePin.swift b/RIADigiDoc/Domain/NFC/OperationChangePin.swift index 85f9672b..05e1fae5 100644 --- a/RIADigiDoc/Domain/NFC/OperationChangePin.swift +++ b/RIADigiDoc/Domain/NFC/OperationChangePin.swift @@ -46,6 +46,7 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { self.continuation = continuation guard NFCTagReaderSession.readingAvailable else { + clearSensitiveData() continuation.resume(throwing: IdCardInternalError.nfcNotSupported) return } @@ -56,6 +57,11 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { } } + private func clearSensitiveData() { + currentPin?.secureZero() + newPin?.secureZero() + } + // MARK: - NFCTagReaderSessionDelegate public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { @@ -64,10 +70,7 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { self.session = nil } - defer { - self.currentPin?.secureZero() - self.newPin?.secureZero() - } + defer { clearSensitiveData() } guard let codeType = self.codeType, let currentPin = self.currentPin, @@ -126,6 +129,7 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { Self.logger().info("NFC: Reader session finished with error: \(error)") self.session = nil + clearSensitiveData() guard let continuationToResume = self.continuation else { return } self.continuation = nil diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift index 7a8517e2..f4426483 100644 --- a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift +++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift @@ -53,6 +53,7 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { self.continuation = continuation guard NFCTagReaderSession.readingAvailable else { + clearSensitiveData() continuation.resume(throwing: IdCardInternalError.nfcNotSupported) return } @@ -63,6 +64,10 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { } } + private func clearSensitiveData() { + pin1Number.secureZero() + } + // MARK: - NFCTagReaderSessionDelegate public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { @@ -71,7 +76,7 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { self.session = nil } - defer { pin1Number.secureZero() } + defer { clearSensitiveData() } guard let containerFile else { let error = DecryptError.containerFileInvalid @@ -158,6 +163,7 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { Self.logger().info("NFC: Reader session finished with error: \(error)") self.session = nil + clearSensitiveData() guard let continuationToResume = self.continuation else { return } self.continuation = nil diff --git a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift index 8a123801..684a3c7f 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift @@ -61,6 +61,7 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig self.continuation = continuation guard NFCTagReaderSession.readingAvailable else { + clearSensitiveData() continuation.resume(throwing: IdCardInternalError.nfcNotSupported) return } @@ -71,6 +72,10 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig } } + private func clearSensitiveData() { + pin2Number.secureZero() + } + // MARK: - NFCTagReaderSessionDelegate // swiftlint:disable:next cyclomatic_complexity @@ -80,7 +85,7 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig self.session = nil } - defer { pin2Number.secureZero() } + defer { clearSensitiveData() } guard let signedContainer else { let error = ReadCertAndSignError.signedContainerNil @@ -189,6 +194,7 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { Self.logger().info("NFC: Reader session finished with error: \(error)") self.session = nil + clearSensitiveData() guard let continuationToResume = self.continuation else { return } self.continuation = nil diff --git a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift index 0f907dcd..486b9bb0 100644 --- a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift +++ b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift @@ -48,6 +48,7 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol self.continuation = continuation guard NFCTagReaderSession.readingAvailable else { + clearSensitiveData() continuation.resume(throwing: IdCardInternalError.nfcNotSupported) return } @@ -58,6 +59,11 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol } } + private func clearSensitiveData() { + puk?.secureZero() + newPin?.secureZero() + } + // MARK: - NFCTagReaderSessionDelegate public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { @@ -66,10 +72,7 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol self.session = nil } - defer { - self.puk?.secureZero() - self.newPin?.secureZero() - } + defer { clearSensitiveData() } guard let codeType = self.codeType, let puk = self.puk, @@ -124,6 +127,7 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { Self.logger().info("NFC: Reader session finished with error: \(error)") self.session = nil + clearSensitiveData() guard let continuationToResume = self.continuation else { return } self.continuation = nil diff --git a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift index 4989cbde..2590945d 100644 --- a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift +++ b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift @@ -104,6 +104,7 @@ final class MyEidPinChangeViewModel: MyEidPinChangeViewModelProtocol, Loggable { guard verifyRepeatedCode() else { handleConfirmStepError() clearPinCodes() + input = "" return } guard let currentPinCode = currentCode, let newPinCode = newCode else { From 220c5a778f764006306a749f097984b701919bf0 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Tue, 30 Jun 2026 13:12:30 +0300 Subject: [PATCH 4/7] Refactor tagReaderSession usage --- RIADigiDoc/Domain/NFC/NFCOperationBase.swift | 40 +++++++++++- .../Domain/NFC/OperationChangePin.swift | 59 ++++-------------- RIADigiDoc/Domain/NFC/OperationDecrypt.swift | 61 +++++-------------- .../Domain/NFC/OperationReadCertAndSign.swift | 61 +++++-------------- .../Domain/NFC/OperationUnblockPin.swift | 59 ++++-------------- 5 files changed, 89 insertions(+), 191 deletions(-) diff --git a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift index 262e3455..1626d9e5 100644 --- a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift +++ b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift @@ -149,11 +149,42 @@ public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessio session: NFCTagReaderSession ) { Self.logger().error("NFC: Unknown error type: \(type(of: error))") - Self.logger().error("NFC: Error details: \(error.localizedDescription)") + Self.logger().error("NFC: Error details: \(String(describing: error))") operationError = error session.invalidate(errorMessage: strings?.sessionErrorMessage ?? "") } + func handleNFCError(_ error: Error, session: NFCTagReaderSession) -> Bool { + if (error as NSError).localizedDescription == "Failed to find lock for cert" { + handleNoCertLockError(error: error, session: session) + return true + } + if let idCardInternalError = error as? IdCardInternalError { + handleIdCardInternalError(idCardInternalError, session: session) + return true + } + if let nfcIdCardError = error as? nfclib.IdCardInternalError { + handleIdCardInternalError(nfcIdCardError, session: session) + return true + } + return false + } + + func handleSessionError(_ error: Error) -> Error { + if let operationError { + return operationError + } + if let nfcError = error as? NFCReaderError, + nfcError.code == .readerSessionInvalidationErrorUserCanceled { + return IdCardInternalError.cancelledByUser + } + return error + } + + func clearSensitiveData() { } + + func resumeContinuation(resolvedError: Error?) { } + // MARK: - NFCTagReaderSessionDelegate public func tagReaderSessionDidBecomeActive(_: NFCTagReaderSession) { } @@ -162,7 +193,10 @@ public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessio // Override in subclasses } - public func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError _: Error) { - // Override in subclasses + public func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { + Self.logger().info("NFC: Reader session finished with error: \(String(describing: error))") + session = nil + clearSensitiveData() + resumeContinuation(resolvedError: didCompleteSuccessfully ? nil : handleSessionError(error)) } } diff --git a/RIADigiDoc/Domain/NFC/OperationChangePin.swift b/RIADigiDoc/Domain/NFC/OperationChangePin.swift index 05e1fae5..d247e340 100644 --- a/RIADigiDoc/Domain/NFC/OperationChangePin.swift +++ b/RIADigiDoc/Domain/NFC/OperationChangePin.swift @@ -57,11 +57,21 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { } } - private func clearSensitiveData() { + override func clearSensitiveData() { currentPin?.secureZero() newPin?.secureZero() } + override func resumeContinuation(resolvedError: Error?) { + guard let continuationToResume = continuation else { return } + continuation = nil + if let resolvedError { + continuationToResume.resume(throwing: resolvedError) + } else { + continuationToResume.resume(returning: ()) + } + } + // MARK: - NFCTagReaderSessionDelegate public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { @@ -70,8 +80,6 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { self.session = nil } - defer { clearSensitiveData() } - guard let codeType = self.codeType, let currentPin = self.currentPin, let newPin = self.newPin else { @@ -98,18 +106,7 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { success() } catch { - if (error as NSError).localizedDescription == "Failed to find lock for cert" { - handleNoCertLockError(error: error, session: session) - return - } - - if let idCardInternalError = error as? IdCardInternalError { - handleIdCardInternalError(idCardInternalError, session: session) - return - } - - if let nfcIdCardError = error as? nfclib.IdCardInternalError { - handleIdCardInternalError(nfcIdCardError, session: session) + if handleNFCError(error, session: session) { return } @@ -125,36 +122,4 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { } } } - - public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { - Self.logger().info("NFC: Reader session finished with error: \(error)") - self.session = nil - clearSensitiveData() - - guard let continuationToResume = self.continuation else { return } - self.continuation = nil - - if didCompleteSuccessfully { - continuationToResume.resume(with: .success(())) - return - } - - if let storedError = self.operationError { - continuationToResume.resume(throwing: storedError) - return - } - - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return - - default: - break - } - } - - continuationToResume.resume(throwing: error) - } } diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift index f4426483..6b1f223d 100644 --- a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift +++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift @@ -64,10 +64,22 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { } } - private func clearSensitiveData() { + override func clearSensitiveData() { pin1Number.secureZero() } + override func resumeContinuation(resolvedError: Error?) { + guard let continuationToResume = continuation else { return } + continuation = nil + if let resolvedError { + continuationToResume.resume(throwing: resolvedError) + } else if let returnData { + continuationToResume.resume(returning: returnData) + } else { + continuationToResume.resume(throwing: DecryptError.containerFileInvalid) + } + } + // MARK: - NFCTagReaderSessionDelegate public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { @@ -76,8 +88,6 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { self.session = nil } - defer { clearSensitiveData() } - guard let containerFile else { let error = DecryptError.containerFileInvalid OperationDecrypt.logger().error("NFC: \(error.localizedDescription)") @@ -132,18 +142,7 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { success() } catch { - if (error as NSError).localizedDescription == "Failed to find lock for cert" { - handleNoCertLockError(error: error, session: session) - return - } - - if let idCardInternalError = error as? IdCardInternalError { - handleIdCardInternalError(idCardInternalError, session: session) - return - } - - if let nfcIdCardError = error as? nfclib.IdCardInternalError { - handleIdCardInternalError(nfcIdCardError, session: session) + if handleNFCError(error, session: session) { return } @@ -159,36 +158,4 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { } } } - - public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { - Self.logger().info("NFC: Reader session finished with error: \(error)") - self.session = nil - clearSensitiveData() - - guard let continuationToResume = self.continuation else { return } - self.continuation = nil - - if let returnData, didCompleteSuccessfully { - continuationToResume.resume(with: .success(returnData)) - return - } - - if let storedError = self.operationError { - continuationToResume.resume(throwing: storedError) - return - } - - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return - - default: - break - } - } - - continuationToResume.resume(throwing: error) - } } diff --git a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift index 684a3c7f..1571c7e9 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift @@ -72,10 +72,22 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig } } - private func clearSensitiveData() { + override func clearSensitiveData() { pin2Number.secureZero() } + override func resumeContinuation(resolvedError: Error?) { + guard let continuationToResume = continuation else { return } + continuation = nil + if let resolvedError { + continuationToResume.resume(throwing: resolvedError) + } else if let returnData { + continuationToResume.resume(returning: returnData) + } else { + continuationToResume.resume(throwing: ReadCertAndSignError.signedContainerNil) + } + } + // MARK: - NFCTagReaderSessionDelegate // swiftlint:disable:next cyclomatic_complexity @@ -85,8 +97,6 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig self.session = nil } - defer { clearSensitiveData() } - guard let signedContainer else { let error = ReadCertAndSignError.signedContainerNil OperationReadCertAndSign.logger().error("NFC: \(error.localizedDescription)") @@ -158,18 +168,7 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig success() } catch { - if (error as NSError).localizedDescription == "Failed to find lock for cert" { - handleNoCertLockError(error: error, session: session) - return - } - - if let idCardInternalError = error as? IdCardInternalError { - handleIdCardInternalError(idCardInternalError, session: session) - return - } - - if let nfcIdCardError = error as? nfclib.IdCardInternalError { - handleIdCardInternalError(nfcIdCardError, session: session) + if handleNFCError(error, session: session) { return } @@ -190,36 +189,4 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig } } } - - public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { - Self.logger().info("NFC: Reader session finished with error: \(error)") - self.session = nil - clearSensitiveData() - - guard let continuationToResume = self.continuation else { return } - self.continuation = nil - - if let returnData, didCompleteSuccessfully { - continuationToResume.resume(with: .success(returnData)) - return - } - - if let storedError = self.operationError { - continuationToResume.resume(throwing: storedError) - return - } - - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return - - default: - break - } - } - - continuationToResume.resume(throwing: error) - } } diff --git a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift index 486b9bb0..485282ce 100644 --- a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift +++ b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift @@ -59,11 +59,21 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol } } - private func clearSensitiveData() { + override func clearSensitiveData() { puk?.secureZero() newPin?.secureZero() } + override func resumeContinuation(resolvedError: Error?) { + guard let continuationToResume = continuation else { return } + continuation = nil + if let resolvedError { + continuationToResume.resume(throwing: resolvedError) + } else { + continuationToResume.resume(returning: ()) + } + } + // MARK: - NFCTagReaderSessionDelegate public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { @@ -72,8 +82,6 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol self.session = nil } - defer { clearSensitiveData() } - guard let codeType = self.codeType, let puk = self.puk, let newPin = self.newPin else { @@ -96,18 +104,7 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol success() } catch { - if (error as NSError).localizedDescription == "Failed to find lock for cert" { - handleNoCertLockError(error: error, session: session) - return - } - - if let idCardInternalError = error as? IdCardInternalError { - handleIdCardInternalError(idCardInternalError, session: session) - return - } - - if let nfcIdCardError = error as? nfclib.IdCardInternalError { - handleIdCardInternalError(nfcIdCardError, session: session) + if handleNFCError(error, session: session) { return } @@ -123,36 +120,4 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol } } } - - public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { - Self.logger().info("NFC: Reader session finished with error: \(error)") - self.session = nil - clearSensitiveData() - - guard let continuationToResume = self.continuation else { return } - self.continuation = nil - - if didCompleteSuccessfully { - continuationToResume.resume(with: .success(())) - return - } - - if let storedError = self.operationError { - continuationToResume.resume(throwing: storedError) - return - } - - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return - - default: - break - } - } - - continuationToResume.resume(throwing: error) - } } From 869252da76091e652294bd09ffe82c12f2db96a4 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Wed, 1 Jul 2026 16:07:45 +0300 Subject: [PATCH 5/7] Refactor NFC actions --- RIADigiDoc/Domain/NFC/NFCOperationBase.swift | 73 ++++++-- .../Domain/NFC/OperationChangePin.swift | 93 +--------- RIADigiDoc/Domain/NFC/OperationDecrypt.swift | 137 +++----------- .../Domain/NFC/OperationReadCertAndSign.swift | 168 +++--------------- .../Domain/NFC/OperationUnblockPin.swift | 89 +--------- 5 files changed, 124 insertions(+), 436 deletions(-) diff --git a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift index 1626d9e5..082a6ca9 100644 --- a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift +++ b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift @@ -26,7 +26,6 @@ import UtilsLib @MainActor public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessionDelegate { var session: NFCTagReaderSession? - var isFinished = false var canNumber: String = "" var nfcError: String = "" var strings: NFCSessionStrings? @@ -170,33 +169,85 @@ public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessio return false } - func handleSessionError(_ error: Error) -> Error { - if let operationError { - return operationError - } + func handleSessionError(_ error: Error, session: NFCTagReaderSession) -> Error { if let nfcError = error as? NFCReaderError, nfcError.code == .readerSessionInvalidationErrorUserCanceled { return IdCardInternalError.cancelledByUser } + if handleNFCError(error, session: session) { + return operationError ?? error + } + if let digiDocError = error as? DigiDocError { + handleDigiDocError(digiDocError, session: session) + return digiDocError + } + handleUnknownError(error, session: session) return error } - func clearSensitiveData() { } + private var tagContinuation: CheckedContinuation<[NFCTag], Error>? + + func waitForTagConnected() async throws -> any NFCISO7816Tag { + let tags = try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[NFCTag], Error>) in + guard NFCTagReaderSession.readingAvailable else { + continuation.resume(throwing: IdCardInternalError.nfcNotSupported) + return + } + tagContinuation = continuation + session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) + updateAlertMessage(step: 0) + session?.begin() + } - func resumeContinuation(resolvedError: Error?) { } + guard let session else { + throw IdCardInternalError.nfcNotSupported + } + updateAlertMessage(step: 1) + return try await connection.setup(session, tags: tags) + } + + func withCardCommands( + canNumber: String, + strings: NFCSessionStrings, + _ body: (any CardCommands) async throws -> T + ) async throws -> T { + self.strings = strings + let tag = try await waitForTagConnected() + guard let session else { + Self.logger().error("Unable to get session") + throw IdCardInternalError.nfcNotSupported + } + do { + updateAlertMessage(step: 2) + let cardCommands = try await connection.getCardCommands(session, tag: tag, CAN: canNumber) + let result = try await body(cardCommands) + success() + return result + } catch { + Self.logger().error("Unable to get card commands: \(String(describing: error))") + throw handleSessionError(error, session: session) + } + } // MARK: - NFCTagReaderSessionDelegate public func tagReaderSessionDidBecomeActive(_: NFCTagReaderSession) { } - public func tagReaderSession(_: NFCTagReaderSession, didDetect _: [NFCTag]) { - // Override in subclasses + public func tagReaderSession(_: NFCTagReaderSession, didDetect tags: [NFCTag]) { + tagContinuation?.resume(returning: tags) + tagContinuation = nil } public func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { Self.logger().info("NFC: Reader session finished with error: \(String(describing: error))") session = nil - clearSensitiveData() - resumeContinuation(resolvedError: didCompleteSuccessfully ? nil : handleSessionError(error)) + guard let tagContinuation else { return } + self.tagContinuation = nil + if let nfcError = error as? NFCReaderError, + nfcError.code == .readerSessionInvalidationErrorUserCanceled { + tagContinuation.resume(throwing: IdCardInternalError.cancelledByUser) + } else { + tagContinuation.resume(throwing: error) + } } } diff --git a/RIADigiDoc/Domain/NFC/OperationChangePin.swift b/RIADigiDoc/Domain/NFC/OperationChangePin.swift index d247e340..d7cc8c17 100644 --- a/RIADigiDoc/Domain/NFC/OperationChangePin.swift +++ b/RIADigiDoc/Domain/NFC/OperationChangePin.swift @@ -24,11 +24,6 @@ import nfclib @MainActor public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { - private var codeType: CodeType? - private var currentPin: SecureData? - private var newPin: SecureData? - private var continuation: CheckedContinuation? - public func startChanging( canNumber: String, codeType: CodeType, @@ -36,90 +31,14 @@ public class OperationChangePin: NFCOperationBase, OperationChangePinProtocol { newPin: SecureData, strings: NFCSessionStrings, ) async throws { - self.canNumber = canNumber - self.codeType = codeType - self.currentPin = currentPin - self.newPin = newPin - self.strings = strings - - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation - - guard NFCTagReaderSession.readingAvailable else { - clearSensitiveData() - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() - } - } - - override func clearSensitiveData() { - currentPin?.secureZero() - newPin?.secureZero() - } - - override func resumeContinuation(resolvedError: Error?) { - guard let continuationToResume = continuation else { return } - continuation = nil - if let resolvedError { - continuationToResume.resume(throwing: resolvedError) - } else { - continuationToResume.resume(returning: ()) + defer { + currentPin.secureZero() + newPin.secureZero() } - } - - // MARK: - NFCTagReaderSessionDelegate - - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { @MainActor in - defer { - self.session = nil - } - - guard let codeType = self.codeType, - let currentPin = self.currentPin, - let newPin = self.newPin else { - let error = ChangePinError.missingRequiredParameter - operationError = error - OperationChangePin.logger().error("NFC: \(error.localizedDescription)") - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Missing required parameters") - return - } - - do { - updateAlertMessage(step: 1) - OperationChangePin.logger().info("NFC: Setting up NFC connection for PIN change...") - let tag = try await self.connection.setup(session, tags: tags) - - updateAlertMessage(step: 2) - let cardCommands = try await self.connection.getCardCommands(session, tag: tag, CAN: self.canNumber) - - updateAlertMessage(step: 3) - OperationChangePin.logger().info("NFC: Changing \(codeType.name)...") - try await cardCommands.changeCode(codeType, to: newPin, verifyCode: currentPin) - OperationChangePin.logger().info("NFC: \(codeType.name) changed successfully") - - success() - } catch { - if handleNFCError(error, session: session) { - return - } - - if let changePinError = error as? ChangePinError { - operationError = changePinError - OperationChangePin.logger() - .error("NFC: changePinError: \(changePinError.localizedDescription)") - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? "") - return - } - handleUnknownError(error, session: session) - } + try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + updateAlertMessage(step: 3) + try await cardCommands.changeCode(codeType, to: newPin, verifyCode: currentPin) } } } diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift index 6b1f223d..4f7790ab 100644 --- a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift +++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift @@ -29,12 +29,6 @@ import UtilsLib @MainActor public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { - private var containerFile: URL? - private var recipients: [Addressee] = [] - private var pin1Number: SecureData = SecureData([0x00]) - private var continuation: CheckedContinuation? - private var returnData: CryptoContainerProtocol? - public func processDecrypt( canNumber: String, pin1Number: SecureData, @@ -42,120 +36,35 @@ public class OperationDecrypt: NFCOperationBase, OperationDecryptProtocol { recipients: [Addressee], strings: NFCSessionStrings, ) async throws -> CryptoContainerProtocol { - - self.canNumber = canNumber - self.pin1Number = pin1Number - self.containerFile = containerFile - self.recipients = recipients - self.strings = strings - - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation - - guard NFCTagReaderSession.readingAvailable else { - clearSensitiveData() - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + defer { + pin1Number.secureZero() } - } - override func clearSensitiveData() { - pin1Number.secureZero() - } - - override func resumeContinuation(resolvedError: Error?) { - guard let continuationToResume = continuation else { return } - continuation = nil - if let resolvedError { - continuationToResume.resume(throwing: resolvedError) - } else if let returnData { - continuationToResume.resume(returning: returnData) - } else { - continuationToResume.resume(throwing: DecryptError.containerFileInvalid) + guard !containerFile.path.isEmpty else { + OperationDecrypt.logger().error("NFC: Container file path is empty") + throw DecryptError.containerFileInvalid + } + guard !recipients.isEmpty else { + OperationDecrypt.logger().error("NFC: \(DecryptError.recipientsEmpty.localizedDescription)") + throw DecryptError.recipientsEmpty } - } - - // MARK: - NFCTagReaderSessionDelegate - - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { @MainActor in - defer { - self.session = nil - } - - guard let containerFile else { - let error = DecryptError.containerFileInvalid - OperationDecrypt.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to read container file") - return - } - - if containerFile.path.isEmpty { - let error = DecryptError.containerFileInvalid - OperationDecrypt.logger().error("NFC: Container file path is empty") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to read container file") - return - } - if recipients.isEmpty { - let error = DecryptError.recipientsEmpty - OperationDecrypt.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Person or company does not own a valid certificate") - return + return try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + updateAlertMessage(step: 3) + let (retryCount, _) = try await cardCommands.readCodeTryCounterRecord(.pin1) + if retryCount == 0 { + throw IdCardInternalError.remainingPinRetryCount(Int(retryCount)) } - OperationDecrypt.logger().info("NFC: Checks complete starting decryption") - - do { - updateAlertMessage(step: 1) - let tag = try await connection.setup(session, tags: tags) - updateAlertMessage(step: 2) - let cardCommands = try await connection.getCardCommands(session, tag: tag, CAN: canNumber) - updateAlertMessage(step: 3) - - let (retryCount, _) = try await cardCommands.readCodeTryCounterRecord(.pin1) - - if retryCount == 0 { - throw IdCardInternalError.remainingPinRetryCount(Int(retryCount)) - } - - let cert = try await cardCommands.readAuthenticationCertificate() - updateAlertMessage(step: 4) - returnData = try await CryptoContainer.decrypt( - containerFile: containerFile, - recipients: recipients, - cert: cert, - cardCommands: cardCommands, - pin: pin1Number, - ) - - success() - } catch { - if handleNFCError(error, session: session) { - return - } - - if let decryptError = error as? DecryptError { - OperationDecrypt.logger() - .error("NFC: DecryptError: \(decryptError.localizedDescription)") - operationError = decryptError - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? "") - return - } - - handleUnknownError(error, session: session) - } + let cert = try await cardCommands.readAuthenticationCertificate() + updateAlertMessage(step: 4) + return try await CryptoContainer.decrypt( + containerFile: containerFile, + recipients: recipients, + cert: cert, + cardCommands: cardCommands, + pin: pin1Number, + ) } } } diff --git a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift index 1571c7e9..65edb751 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift @@ -29,15 +29,6 @@ import UtilsLib @MainActor public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSignProtocol { - private var pin2Number: SecureData = SecureData([0x00]) - private var signedContainer: SignedContainerProtocol? - private var containerPath: URL? - private var roleData: RoleData? - private var userAgent: String = "" - private var returnData: SignedContainerProtocol? - - private var continuation: CheckedContinuation? - // swiftlint:disable:next function_parameter_count public func startOperation( canNumber: String, @@ -48,145 +39,40 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig userAgent: String, strings: NFCSessionStrings ) async throws -> SignedContainerProtocol { - - self.canNumber = canNumber - self.pin2Number = pin2Number - self.signedContainer = signedContainer - self.containerPath = containerPath - self.roleData = roleData - self.userAgent = userAgent - self.strings = strings - - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation - - guard NFCTagReaderSession.readingAvailable else { - clearSensitiveData() - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + defer { + pin2Number.secureZero() } - } - override func clearSensitiveData() { - pin2Number.secureZero() - } - - override func resumeContinuation(resolvedError: Error?) { - guard let continuationToResume = continuation else { return } - continuation = nil - if let resolvedError { - continuationToResume.resume(throwing: resolvedError) - } else if let returnData { - continuationToResume.resume(returning: returnData) - } else { - continuationToResume.resume(throwing: ReadCertAndSignError.signedContainerNil) + guard !userAgent.isEmpty else { + OperationReadCertAndSign.logger().error("NFC: \(ReadCertAndSignError.userAgentEmpty.localizedDescription)") + throw ReadCertAndSignError.userAgentEmpty } - } - - // MARK: - NFCTagReaderSessionDelegate - // swiftlint:disable:next cyclomatic_complexity - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { @MainActor in - defer { - self.session = nil - } - - guard let signedContainer else { - let error = ReadCertAndSignError.signedContainerNil - OperationReadCertAndSign.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to read container data") - return - } - guard let roleData else { - let error = ReadCertAndSignError.roleDataNil - OperationReadCertAndSign.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to read role data") - return + return try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + updateAlertMessage(step: 3) + let (retryCount, pinActive) = try await cardCommands.readCodeTryCounterRecord(.pin2) + if retryCount == 0 { + throw IdCardInternalError.remainingPinRetryCount(Int(retryCount)) } - guard let containerPath else { - let error = ReadCertAndSignError.containerPathNil - OperationReadCertAndSign.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to read container path") - return + if !pinActive { + throw IdCardInternalError.pinLocked } - if userAgent.isEmpty { - let error = ReadCertAndSignError.userAgentEmpty - OperationReadCertAndSign.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to initialize user agent") - return - } - - OperationReadCertAndSign.logger().info("NFC: Checks complete starting signing") - - do { - updateAlertMessage(step: 1) - let tag = try await connection.setup(session, tags: tags) - - updateAlertMessage(step: 2) - let cardCommands = try await connection.getCardCommands(session, tag: tag, CAN: canNumber) - - updateAlertMessage(step: 3) - - let (retryCount, pinActive) = try await cardCommands.readCodeTryCounterRecord(.pin2) - - if retryCount == 0 { - throw IdCardInternalError.remainingPinRetryCount(Int(retryCount)) - } - if !pinActive { - throw IdCardInternalError.pinLocked - } - let cert = try await cardCommands.readSignatureCertificate() - let hashToSign = try await signedContainer.prepareSignature( - cert: cert, - containerPath: containerPath, - roleData: roleData, - userAgent: userAgent - ) - - let signatureValue = try await cardCommands.calculateSignature(for: hashToSign, withPin2: pin2Number) - - updateAlertMessage(step: 4) - returnData = try await signedContainer.addSignature( - signature: signatureValue, - containerFile: containerPath - ) - - success() - } catch { - if handleNFCError(error, session: session) { - return - } - - if let readCertSignError = error as? ReadCertAndSignError { - OperationReadCertAndSign.logger() - .error("NFC: ReadCertAndSignError: \(readCertSignError.localizedDescription)") - operationError = readCertSignError - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? "") - return - } - - if let digiDocError = error as? DigiDocError { - handleDigiDocError(digiDocError, session: session) - return - } - - handleUnknownError(error, session: session) - } + let cert = try await cardCommands.readSignatureCertificate() + let hashToSign = try await signedContainer.prepareSignature( + cert: cert, + containerPath: containerPath, + roleData: roleData, + userAgent: userAgent + ) + + let signatureValue = try await cardCommands.calculateSignature(for: hashToSign, withPin2: pin2Number) + + updateAlertMessage(step: 4) + return try await signedContainer.addSignature( + signature: signatureValue, + containerFile: containerPath + ) } } } diff --git a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift index 485282ce..657e3cd4 100644 --- a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift +++ b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift @@ -26,11 +26,6 @@ import nfclib @MainActor public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol { - private var codeType: CodeType? - private var puk: SecureData? - private var newPin: SecureData? - private var continuation: CheckedContinuation? - public func startReading( canNumber: String, codeType: CodeType, @@ -38,86 +33,14 @@ public class OperationUnblockPin: NFCOperationBase, OperationUnblockPinProtocol newPin: SecureData, strings: NFCSessionStrings ) async throws { - self.canNumber = canNumber - self.codeType = codeType - self.puk = puk - self.newPin = newPin - self.strings = strings - - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation - - guard NFCTagReaderSession.readingAvailable else { - clearSensitiveData() - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() - } - } - - override func clearSensitiveData() { - puk?.secureZero() - newPin?.secureZero() - } - - override func resumeContinuation(resolvedError: Error?) { - guard let continuationToResume = continuation else { return } - continuation = nil - if let resolvedError { - continuationToResume.resume(throwing: resolvedError) - } else { - continuationToResume.resume(returning: ()) + defer { + puk.secureZero() + newPin.secureZero() } - } - - // MARK: - NFCTagReaderSessionDelegate - - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { @MainActor in - defer { - self.session = nil - } - - guard let codeType = self.codeType, - let puk = self.puk, - let newPin = self.newPin else { - let error = UnblockPINError.missingRequiredParameter - operationError = error - OperationUnblockPin.logger().error("NFC: \(error.localizedDescription)") - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Missing required parameters") - return - } - do { - updateAlertMessage(step: 1) - let tag = try await self.connection.setup(session, tags: tags) - - updateAlertMessage(step: 2) - let cardCommands = try await self.connection.getCardCommands(session, tag: tag, CAN: self.canNumber) - - updateAlertMessage(step: 3) - try await cardCommands.unblockCode(codeType, puk: puk, newCode: newPin) - - success() - } catch { - if handleNFCError(error, session: session) { - return - } - - if let unblockPINError = error as? UnblockPINError { - operationError = unblockPINError - OperationReadCertAndSign.logger() - .error("NFC: UnblockPINError: \(unblockPINError.localizedDescription)") - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? "") - return - } - handleUnknownError(error, session: session) - } + try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + updateAlertMessage(step: 3) + try await cardCommands.unblockCode(codeType, puk: puk, newCode: newPin) } } } From 0f66047c3f567422ccf84bfa94b61e55e89121a3 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Thu, 2 Jul 2026 19:04:29 +0300 Subject: [PATCH 6/7] Refactor OperationReadCardData --- RIADigiDoc/Domain/NFC/NFCOperationBase.swift | 3 - .../Domain/NFC/OperationReadCardData.swift | 165 ++++-------------- 2 files changed, 33 insertions(+), 135 deletions(-) diff --git a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift index 082a6ca9..2a18482f 100644 --- a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift +++ b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift @@ -26,10 +26,8 @@ import UtilsLib @MainActor public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessionDelegate { var session: NFCTagReaderSession? - var canNumber: String = "" var nfcError: String = "" var strings: NFCSessionStrings? - var didCompleteSuccessfully = false var operationError: Error? let connection = NFCConnection() @@ -52,7 +50,6 @@ public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessio } func success() { - didCompleteSuccessfully = true session?.alertMessage = strings?.successMessage ?? "" session?.invalidate() } diff --git a/RIADigiDoc/Domain/NFC/OperationReadCardData.swift b/RIADigiDoc/Domain/NFC/OperationReadCardData.swift index aa4547be..d6abbeec 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCardData.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCardData.swift @@ -24,142 +24,43 @@ import UtilsLib @MainActor final public class OperationReadCardData: NFCOperationBase, OperationReadCardDataProtocol { - private var continuation: CheckedContinuation? - private var returnData: NFCCardData? - public func startReading( canNumber: String, strings: NFCSessionStrings, ) async throws -> NFCCardData { - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation - - guard NFCTagReaderSession.readingAvailable else { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - self.canNumber = canNumber - self.strings = strings - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + return try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + OperationReadCardData.logger().info("Reading public data...") + let cardInfo = try await cardCommands.readPublicData() + + updateAlertMessage(step: 3) + OperationReadCardData.logger().info("Reading authentication certificate") + let authenticationCertificate = try await cardCommands.readAuthenticationCertificate() + + OperationReadCardData.logger().info("Reading signature certificate") + let signatureCertificate = try await cardCommands.readSignatureCertificate() + + updateAlertMessage(step: 4) + OperationReadCardData.logger().info("Reading PIN retry counts...") + let pin1Response = try await cardCommands.readCodeTryCounterRecord(.pin1) + let pin2Response = try await cardCommands.readCodeTryCounterRecord(.pin2) + let pukResponse = try await cardCommands.readCodeTryCounterRecord(.puk) + + let pinResponse = PinResponse( + pin1RetryCount: pin1Response.retryCount, + pin1Active: pin1Response.pinActive, + pin2RetryCount: pin2Response.retryCount, + pin2Active: pin2Response.pinActive, + pukRetryCount: pukResponse.retryCount, + pukActive: pukResponse.pinActive, + ) + + return NFCCardData( + publicData: cardInfo, + authenticationCertificate: authenticationCertificate, + signatureCertificate: signatureCertificate, + pinResponse: pinResponse, + isPUKChangable: cardCommands.canChangePUK + ) } } - - // MARK: - NFCTagReaderSessionDelegate - - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { - defer { - self.session = nil - } - - do { - updateAlertMessage(step: 1) - OperationReadCardData.logger().info("Setting up NFC connection...") - let tag = try await connection.setup(session, tags: tags) - - updateAlertMessage(step: 2) - OperationReadCardData.logger().info("Establishing secure channel with CAN...") - let cardCommands = try await connection.getCardCommands(session, tag: tag, CAN: canNumber) - - OperationReadCardData.logger().info("Reading public data...") - let cardInfo = try await cardCommands.readPublicData() - OperationReadCardData.logger().info("Public data read successfully") - - updateAlertMessage(step: 3) - OperationReadCardData.logger().info("Reading authentication certificate") - let authenticationCertificate = try await cardCommands.readAuthenticationCertificate() - - OperationReadCardData.logger().info("Reading signature certificate") - let signatureCertificate = try await cardCommands.readSignatureCertificate() - - updateAlertMessage(step: 4) - OperationReadCardData.logger().info("Reading PIN retry counts...") - - let pin1Response = try await cardCommands.readCodeTryCounterRecord(.pin1) - OperationReadCardData.logger().info("PIN1 retry count: \(pin1Response.retryCount)") - OperationReadCardData.logger().info("PIN1 active: \(pin1Response.pinActive)") - let pin2Response = try await cardCommands.readCodeTryCounterRecord(.pin2) - OperationReadCardData.logger().info("PIN2 retry count: \(pin2Response.retryCount)") - OperationReadCardData.logger().info("PIN2 active: \(pin2Response.pinActive)") - let pukResponse = try await cardCommands.readCodeTryCounterRecord(.puk) - OperationReadCardData.logger().info("PUK retry count: \(pukResponse.retryCount)") - OperationReadCardData.logger().info("PUK active: \(pukResponse.pinActive)") - - let pinResponse = PinResponse( - pin1RetryCount: pin1Response.retryCount, - pin1Active: pin1Response.pinActive, - pin2RetryCount: pin2Response.retryCount, - pin2Active: pin2Response.pinActive, - pukRetryCount: pukResponse.retryCount, - pukActive: pukResponse.pinActive, - ) - - OperationReadCardData.logger().info("NFC: reading can change PUK") - let canChangePUK = cardCommands.canChangePUK - OperationReadCardData.logger().info("NFC: can change PUK: \(canChangePUK)") - - returnData = NFCCardData( - publicData: cardInfo, - authenticationCertificate: authenticationCertificate, - signatureCertificate: signatureCertificate, - pinResponse: pinResponse, - isPUKChangable: canChangePUK - ) - - success() - } catch { - if (error as NSError).localizedDescription == "Failed to find lock for cert" { - handleNoCertLockError(error: error, session: session) - return - } - - if let idCardInternalError = error as? IdCardInternalError { - handleIdCardInternalError(idCardInternalError, session: session) - return - } - - if let nfcIdCardError = error as? nfclib.IdCardInternalError { - handleIdCardInternalError(nfcIdCardError, session: session) - return - } - - handleUnknownError(error, session: session) - } - } - } - - public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { - Self.logger().info("NFC: Reader session finished with error: \(error)") - self.session = nil - - guard let continuationToResume = self.continuation else { return } - self.continuation = nil - - if let returnData, didCompleteSuccessfully { - continuationToResume.resume(with: .success(returnData)) - return - } - - if let storedError = self.operationError { - continuationToResume.resume(throwing: storedError) - return - } - - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return - - default: - break - } - } - - continuationToResume.resume(throwing: error) - } } From e447940ccb38465b3cfe7d7c5ef7b6d2c5ba4928 Mon Sep 17 00:00:00 2001 From: Marten Rebane Date: Fri, 3 Jul 2026 13:51:59 +0300 Subject: [PATCH 7/7] Fix clearing PINs --- .../Utilities/SecureDataTests.swift | 100 ------------------ .../Domain/NFC/OperationReadCertAndSign.swift | 1 + .../Container/Signing/NFC/NFCView.swift | 4 +- .../MyEid/MyEidPinChangeViewModel.swift | 1 + 4 files changed, 5 insertions(+), 101 deletions(-) delete mode 100644 Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift diff --git a/Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift b/Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift deleted file mode 100644 index 8343f850..00000000 --- a/Modules/IdCardLib/Tests/IdCardLibTests/Utilities/SecureDataTests.swift +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright 2017 - 2026 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -import Foundation -import Testing - -@testable import IdCardLib - -struct SecureDataTests { - - private func bytes(of secureData: SecureData) -> [UInt8] { - secureData.withUnsafeBytes { Array($0) } - } - - @Test - func init_fromByteArray_exposesSameBytes() { - let input: [UInt8] = [1, 2, 3, 4, 5, 6] - let secureData = SecureData(input) - - #expect(bytes(of: secureData) == input) - } - - @Test - func init_fromData_exposesSameBytes() { - let input = Data([0xAA, 0xBB, 0xCC]) - let secureData = SecureData(input) - - #expect(bytes(of: secureData) == Array(input)) - } - - @Test - func init_empty_exposesNoBytes() { - let secureData = SecureData([]) - - #expect(bytes(of: secureData).isEmpty) - } - - @Test - func init_preservesNullAndAllByteValues() { - let input: [UInt8] = [0x00, 0x30, 0xFF, 0x00, 0x01] - let secureData = SecureData(input) - - #expect(bytes(of: secureData) == input) - } - - @Test - func secureZero_clearsTheBuffer() { - let secureData = SecureData([1, 2, 3, 4]) - - secureData.secureZero() - - #expect(bytes(of: secureData).isEmpty) - } - - @Test - func secureZero_staysEmptyWhenCalledTwice() { - let secureData = SecureData([9, 9, 9]) - - secureData.secureZero() - secureData.secureZero() - - #expect(bytes(of: secureData).isEmpty) - } - - @Test - func secureZero_staysEmptyWhenAlreadyEmpty() { - let secureData = SecureData([]) - - secureData.secureZero() - - #expect(bytes(of: secureData).isEmpty) - } - - @Test - func withUnsafeBytes_returnsValueFromClosure() { - let secureData = SecureData([2, 4, 6, 8]) - - let sum = secureData.withUnsafeBytes { raw in - raw.reduce(0) { $0 + Int($1) } - } - - #expect(sum == 20) - } -} diff --git a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift index 65edb751..1d586903 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift @@ -67,6 +67,7 @@ public class OperationReadCertAndSign: NFCOperationBase, OperationReadCertAndSig ) let signatureValue = try await cardCommands.calculateSignature(for: hashToSign, withPin2: pin2Number) + pin2Number.secureZero() updateAlertMessage(step: 4) return try await signedContainer.addSignature( diff --git a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift index 748fba9a..1b5b9793 100644 --- a/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift +++ b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift @@ -270,7 +270,9 @@ struct NFCView: View { } .onChange(of: scenePhase) { _, newPhase in if newPhase == .background { - resetPinState() + cancelSigning() + cancelDecrypt() + cancelMyeid() } } } diff --git a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift index 2590945d..471914e8 100644 --- a/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift +++ b/RIADigiDoc/ViewModel/MyEid/MyEidPinChangeViewModel.swift @@ -136,6 +136,7 @@ final class MyEidPinChangeViewModel: MyEidPinChangeViewModelProtocol, Loggable { } func clearSensitiveDataOnBackground() { + input = "" clearPinCodes() resetErrors() }