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/NFCOperationBase.swift b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift index 262e3455..2a18482f 100644 --- a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift +++ b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift @@ -26,11 +26,8 @@ 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? - var didCompleteSuccessfully = false var operationError: Error? let connection = NFCConnection() @@ -53,7 +50,6 @@ public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessio } func success() { - didCompleteSuccessfully = true session?.alertMessage = strings?.successMessage ?? "" session?.invalidate() } @@ -149,20 +145,106 @@ 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, 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 + } + + 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() + } + + 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) { - // Override in subclasses + public func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { + Self.logger().info("NFC: Reader session finished with error: \(String(describing: error))") + session = nil + 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 75f51cbf..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,116 +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 { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() - } - } - - // 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 (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 - } - - 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) - } + defer { + currentPin.secureZero() + newPin.secureZero() } - } - - 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 didCompleteSuccessfully { - continuationToResume.resume(with: .success(())) - return + try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + updateAlertMessage(step: 3) + try await cardCommands.changeCode(codeType, to: newPin, verifyCode: currentPin) } - - 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 418c812c..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,145 +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 { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + defer { + pin1Number.secureZero() } - } - - // 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 - } - - 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 (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 - } - - 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) - } + guard !containerFile.path.isEmpty else { + OperationDecrypt.logger().error("NFC: Container file path is empty") + throw DecryptError.containerFileInvalid } - } - - 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 + guard !recipients.isEmpty else { + OperationDecrypt.logger().error("NFC: \(DecryptError.recipientsEmpty.localizedDescription)") + throw DecryptError.recipientsEmpty } - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return - - default: - break + 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)) } - } - continuationToResume.resume(throwing: error) + 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/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) - } } diff --git a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift index 00a9fe86..1d586903 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,170 +39,41 @@ 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 { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + defer { + pin2Number.secureZero() } - } - // MARK: - NFCTagReaderSessionDelegate - - // swiftlint:disable:next cyclomatic_complexity - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { @MainActor in - defer { - self.session = nil - } + guard !userAgent.isEmpty else { + OperationReadCertAndSign.logger().error("NFC: \(ReadCertAndSignError.userAgentEmpty.localizedDescription)") + throw ReadCertAndSignError.userAgentEmpty + } - 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 - } - 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 + 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)) } - 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 (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 - } - - 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) + if !pinActive { + throw IdCardInternalError.pinLocked } - } - } - - 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 + 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) + pin2Number.secureZero() + + updateAlertMessage(step: 4) + return try await signedContainer.addSignature( + signature: signatureValue, + containerFile: containerPath + ) } - - 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 64f4f766..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,112 +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 { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + 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 (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 - } - 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) - } - } - } - - 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 didCompleteSuccessfully { - continuationToResume.resume(with: .success(())) - return + try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + updateAlertMessage(step: 3) + try await cardCommands.unblockCode(codeType, puk: puk, newCode: newPin) } - - 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/UI/Component/Container/Signing/NFC/NFCView.swift b/RIADigiDoc/UI/Component/Container/Signing/NFC/NFCView.swift index 22dc6389..1b5b9793 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,19 @@ struct NFCView: View { .onDisappear { cancelSigning() } + .onChange(of: scenePhase) { _, newPhase in + if newPhase == .background { + cancelSigning() + cancelDecrypt() + cancelMyeid() + } + } + } + + private func resetPinState() { + pinNumber.removeAll() + isActionEnabled = viewModel + .isActionEnabled(canNumber: canNumber, pinNumber: pinNumber, pinType: pinType) } func saveInputData() { @@ -317,17 +331,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..471914e8 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 { @@ -134,6 +135,12 @@ final class MyEidPinChangeViewModel: MyEidPinChangeViewModelProtocol, Loggable { isSuccess = false } + func clearSensitiveDataOnBackground() { + input = "" + 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