From 59008903360f9f4c698d8a09d7e832413f99f75c Mon Sep 17 00:00:00 2001 From: Raul Metsma Date: Mon, 19 Oct 2026 21:56:10 +0300 Subject: [PATCH] Use new upstream NFCOperation base class Signed-off-by: Raul Metsma --- Modules/CryptoLib/Package.swift | 2 +- RIADigiDoc.xcodeproj/project.pbxproj | 2 +- RIADigiDoc/Domain/NFC/NFCOperationBase.swift | 298 +++++------------- .../Domain/NFC/OperationChangePin.swift | 1 - RIADigiDoc/Domain/NFC/OperationDecrypt.swift | 1 - .../Domain/NFC/OperationReadCardData.swift | 1 - RIADigiDoc/Domain/NFC/OperationReadCert.swift | 95 +----- .../Domain/NFC/OperationReadCertAndSign.swift | 1 - .../Domain/NFC/OperationUnblockPin.swift | 1 - .../Domain/NFC/OperationWebEidAuth.swift | 192 ++--------- .../Domain/NFC/OperationWebEidSign.swift | 201 ++---------- 11 files changed, 158 insertions(+), 637 deletions(-) diff --git a/Modules/CryptoLib/Package.swift b/Modules/CryptoLib/Package.swift index 0459254e..2ed4822e 100644 --- a/Modules/CryptoLib/Package.swift +++ b/Modules/CryptoLib/Package.swift @@ -25,7 +25,7 @@ let package = Package( .package(url: "https://github.com/hmlongco/Factory", exact: .init(3, 3, 2)), .package(path: "../ConfigLib"), .package(path: "../CommonsLib"), - .package(url: "https://github.com/open-eid/nfc-iOS-lib.git", revision: "1b7bb16d684f248a4fba74a3413f9e5d7c0e5f4a"), + .package(url: "https://github.com/open-eid/nfc-iOS-lib.git", revision: "d935df04827d89e84b952f8e68c2c8e81b310f75"), .package(path: "../UtilsLib") ], targets: [ diff --git a/RIADigiDoc.xcodeproj/project.pbxproj b/RIADigiDoc.xcodeproj/project.pbxproj index 72b5dff4..fc828227 100644 --- a/RIADigiDoc.xcodeproj/project.pbxproj +++ b/RIADigiDoc.xcodeproj/project.pbxproj @@ -1694,8 +1694,8 @@ isa = XCRemoteSwiftPackageReference; repositoryURL = "https://github.com/open-eid/nfc-iOS-lib.git"; requirement = { - revision = 1b7bb16d684f248a4fba74a3413f9e5d7c0e5f4a; kind = revision; + revision = d935df04827d89e84b952f8e68c2c8e81b310f75; }; }; DF1D59582F3BF88000855E2E /* XCRemoteSwiftPackageReference "firebase-ios-sdk" */ = { diff --git a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift index f3469c47..8501a6b3 100644 --- a/RIADigiDoc/Domain/NFC/NFCOperationBase.swift +++ b/RIADigiDoc/Domain/NFC/NFCOperationBase.swift @@ -2,26 +2,24 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import nfclib import LibdigidocLibSwift import CryptoSwift import UtilsLib @MainActor -public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessionDelegate { - var session: NFCTagReaderSession? - var canNumber: String = "" - var nfcError: String = "" +public class NFCOperationBase: NSObject, Loggable { var strings: NFCSessionStrings? - var didCompleteSuccessfully = false - var operationError: Error? - - let connection = NFCConnection() + private var connection: NFCConnection? public var onStepChange: (@MainActor (Int) -> Void)? func updateAlertMessage(step: Int) { + connection?.message(alertMessage(step: step)) + onStepChange?(step) + } + + private func alertMessage(step: Int) -> String { let stepMessages = [ strings?.initialMessage ?? "", strings?.step1Message ?? "", @@ -32,229 +30,107 @@ public class NFCOperationBase: NSObject, Loggable, @MainActor NFCTagReaderSessio let stepMessage = stepMessages[min(step, stepMessages.count - 1)] let progressBar = ProgressBar(currentStep: step) - var message = stepMessage - Self.logger().info("NFC: Updating alert message to: \(message)") - message += "\n\n\(progressBar.generate())" - session?.alertMessage = message - onStepChange?(step) + Self.logger().info("NFC: Updating alert message to: \(stepMessage)") + return stepMessage + "\n\n\(progressBar.generate())" } - func success() { - didCompleteSuccessfully = true - session?.alertMessage = strings?.successMessage ?? "" - session?.invalidate() + func withCardCommands( + canNumber: String, + strings: NFCSessionStrings, + _ body: (any CardCommands) async throws -> T + ) async throws -> T { + self.strings = strings + return try await NFCConnection.run(successMessage: strings.successMessage, onError: { error in + Self.logger().error("Unable to complete NFC operation: \(String(describing: error))") + return failure(for: error, strings: strings) + }) { connection in + self.connection = connection + defer { self.connection = nil } + try Task.checkCancellation() + onStepChange?(0) + let cardCommands = try await connection.connect( + CAN: canNumber, + message: alertMessage(step: 0), + detectedMessage: alertMessage(step: 2) + ) + return try await body(cardCommands) + } } - func handleIdCardError(_ error: IdCardError) { - Self.logger().error("NFC: Handling IdCardError: \(error)") + // MARK: - Error handling + /// The error to throw to the caller and the text to show in the NFC sheet. + private func failure(for error: Error, strings: NFCSessionStrings) -> (error: Error, message: String) { switch error { - case .wrongCAN: - nfcError = strings?.canErrorMessage ?? "" - case .wrongPIN(let triesLeft): - if triesLeft > 1 { - nfcError = strings?.pinWrongMultipleErrorMessage ?? "" - } else if triesLeft == 1 { - nfcError = strings?.pinWrongErrorMessage ?? "" - } else { - nfcError = strings?.pinBlockedErrorMessage ?? "" - } - case .sessionError: - nfcError = strings?.sessionErrorMessage ?? "" - case .notActivated: - nfcError = strings?.courierCardErrorMessage ?? "" - case .pinLocked: - nfcError = strings?.pinLockedErrorMessage ?? "" + case nfclib.IdCardInternalError.cancelledByUser: + return (IdCardInternalError.cancelledByUser, strings.sessionErrorMessage) + case let nsError as NSError where nsError.localizedDescription == "Failed to find lock for cert": + Self.logger().error("NFC: Failed to find lock for cert, error: \(error)") + return (DecryptError.noCertLock, strings.wrongCardErrorMessage) + case let idCardInternalError as IdCardInternalError: + let idCardError = idCardInternalError.getIdCardError() + Self.logger().error("NFC: IdCardError detected: \(idCardError)") + return (idCardInternalError, idCardErrorMessage(idCardError, strings: strings)) + case let nfcIdCardError as nfclib.IdCardInternalError: + let idCardError = IdCardError(nfcIdCardError.getIdCardError()) + Self.logger().error("NFC: IdCardError detected: \(idCardError)") + return (nfcIdCardError, idCardErrorMessage(idCardError, strings: strings)) + case let readCertSignError as ReadCertAndSignError: + Self.logger().error("NFC: ReadCertAndSignError: \(readCertSignError.localizedDescription)") + return (readCertSignError, strings.technicalErrorMessage) + case let digiDocError as DigiDocError: + Self.logger().error("NFC: Handling DigiDocError: \(digiDocError)") + return (digiDocError, digiDocErrorMessage(digiDocError, strings: strings)) + case let nsError as NSError where nsError.isCryptoNetworkError: + Self.logger().error("NFC: Unable to reach the key server") + return (error, strings.networkErrorMessage) default: - nfcError = strings?.technicalErrorMessage ?? "" + Self.logger().error("NFC: Unknown error type: \(type(of: error))") + Self.logger().error("NFC: Error details: \(String(describing: error))") + return (error, strings.sessionErrorMessage) } } - func handleNoCertLockError( - error: Error, - session: NFCTagReaderSession - ) { - Self.logger().error("NFC: Failed to find lock for cert, error: \(error)") - nfcError = strings?.wrongCardErrorMessage ?? "" - operationError = DecryptError.noCertLock - session.invalidate(errorMessage: nfcError) - } - - func handleIdCardInternalError( - _ error: IdCardInternalError, - session: NFCTagReaderSession - ) { - let idCardError = error.getIdCardError() - Self.logger().error("NFC: IdCardError detected: \(idCardError)") - operationError = error - handleIdCardError(idCardError) - session.invalidate(errorMessage: nfcError) - } - - func handleIdCardInternalError( - _ error: nfclib.IdCardInternalError, - session: NFCTagReaderSession - ) { - let idCardError = IdCardError(error.getIdCardError()) - Self.logger().error("NFC: IdCardError detected: \(idCardError)") - operationError = error - handleIdCardError(idCardError) - session.invalidate(errorMessage: nfcError) - } - - func handleDigiDocError( - _ error: DigiDocError, - session: NFCTagReaderSession - ) { - Self.logger().error("NFC: Handling DigiDocError: \(error)") + private func idCardErrorMessage(_ error: IdCardError, strings: NFCSessionStrings) -> String { + Self.logger().error("NFC: Handling IdCardError: \(error)") - switch error { - case .signatureAddingFailed(let underlying): - handleDigiDocSignError(errorDetail: underlying) + return switch error { + case .wrongCAN: + strings.canErrorMessage + case .wrongPIN(let triesLeft) where triesLeft > 1: + strings.pinWrongMultipleErrorMessage + case .wrongPIN(1): + strings.pinWrongErrorMessage + case .wrongPIN: + strings.pinBlockedErrorMessage + case .sessionError: + strings.sessionErrorMessage + case .notActivated: + strings.courierCardErrorMessage + case .pinLocked: + strings.pinLockedErrorMessage default: - nfcError = strings?.technicalErrorMessage ?? "" + strings.technicalErrorMessage } - operationError = error - session.invalidate(errorMessage: nfcError) } - private func handleDigiDocSignError(errorDetail: ErrorDetail) { - switch errorDetail.code { + private func digiDocErrorMessage(_ error: DigiDocError, strings: NFCSessionStrings) -> String { + guard case .signatureAddingFailed(let errorDetail) = error else { + return strings.technicalErrorMessage + } + return switch errorDetail.code { case 5, 6: - nfcError = strings?.certificateRevokedErrorMessage ?? "" + strings.certificateRevokedErrorMessage case 7: - nfcError = strings?.ocspTimeslotErrorMessage ?? "" + strings.ocspTimeslotErrorMessage case 18: - nfcError = strings?.tooManyRequestsErrorMessage ?? "" + strings.tooManyRequestsErrorMessage case 20: - nfcError = strings?.networkErrorMessage ?? "" + strings.networkErrorMessage case 101, 102: - nfcError = strings?.sslErrorMessage ?? "" + strings.sslErrorMessage default: - nfcError = strings?.technicalErrorMessage ?? "" - } - } - - func handleUnknownError( - _ error: Error, - session: NFCTagReaderSession - ) { - Self.logger().error("NFC: Unknown error type: \(type(of: error))") - 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 - } - if (error as NSError).isCryptoNetworkError { - Self.logger().error("NFC: Unable to reach the key server") - operationError = error - session.invalidate(errorMessage: strings?.networkErrorMessage ?? "") - return error - } - handleUnknownError(error, session: session) - return error - } - - private var tagContinuation: CheckedContinuation<[NFCTag], Error>? - - func waitForTag() async throws -> [NFCTag] { - 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, queue: DispatchQueue.main) - updateAlertMessage(step: 0) - session?.begin() - } - } - - func withCardCommands( - canNumber: String, - strings: NFCSessionStrings, - _ body: (any CardCommands) async throws -> T - ) async throws -> T { - self.strings = strings - return try await withTaskCancellationHandler { - do { - try Task.checkCancellation() - let tags = try await waitForTag() - guard let session else { - Self.logger().error("Unable to get session") - throw IdCardInternalError.nfcNotSupported - } - 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) - let result = try await body(cardCommands) - success() - return result - } catch { - if Task.isCancelled { - throw IdCardInternalError.cancelledByUser - } - Self.logger().error("Unable to complete NFC operation: \(String(describing: error))") - if let session { - throw handleSessionError(error, session: session) - } - throw error - } - } onCancel: { - Task { @MainActor in - self.session?.invalidate() - } - } - } - - // MARK: - NFCTagReaderSessionDelegate - - public func tagReaderSessionDidBecomeActive(_: NFCTagReaderSession) { } - - 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 - 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) + strings.technicalErrorMessage } } } diff --git a/RIADigiDoc/Domain/NFC/OperationChangePin.swift b/RIADigiDoc/Domain/NFC/OperationChangePin.swift index 8aaf0cdd..319cc4b7 100644 --- a/RIADigiDoc/Domain/NFC/OperationChangePin.swift +++ b/RIADigiDoc/Domain/NFC/OperationChangePin.swift @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import UtilsLib import nfclib diff --git a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift index 335307a4..ee41b727 100644 --- a/RIADigiDoc/Domain/NFC/OperationDecrypt.swift +++ b/RIADigiDoc/Domain/NFC/OperationDecrypt.swift @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import CommonCrypto import CryptoTokenKit import CryptoKit diff --git a/RIADigiDoc/Domain/NFC/OperationReadCardData.swift b/RIADigiDoc/Domain/NFC/OperationReadCardData.swift index 6ac78b83..7e3081c5 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCardData.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCardData.swift @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import nfclib import UtilsLib diff --git a/RIADigiDoc/Domain/NFC/OperationReadCert.swift b/RIADigiDoc/Domain/NFC/OperationReadCert.swift index d0e513b3..dbca0791 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCert.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCert.swift @@ -2,107 +2,24 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import nfclib import UtilsLib @MainActor final public class OperationReadCert: NFCOperationBase, OperationReadCertProtocol { - private var continuation: CheckedContinuation? - private var returnData: String? - public func startReading( canNumber: String, strings: NFCSessionStrings, ) async throws -> String { - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation + return try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + updateAlertMessage(step: 3) - guard NFCTagReaderSession.readingAvailable else { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } + Self.logger().info("Reading signature certificate") + let signerCert = try await cardCommands.readSignatureCertificate() - self.canNumber = canNumber - self.strings = strings + updateAlertMessage(step: 4) - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + return signerCert.base64EncodedString() } } - - // MARK: - NFCTagReaderSessionDelegate - - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { - defer { - self.session = nil - } - - do { - updateAlertMessage(step: 1) - Self.logger().info("Setting up NFC connection...") - let tag = try await connection.setup(session, tags: tags) - - updateAlertMessage(step: 2) - Self.logger().info("Establishing secure channel with CAN...") - let cardCommands = try await connection.getCardCommands(session, tag: tag, CAN: canNumber) - - updateAlertMessage(step: 3) - - Self.logger().info("Reading signature certificate") - let signerCert = try await cardCommands.readSignatureCertificate() - - updateAlertMessage(step: 4) - - returnData = signerCert.base64EncodedString() - - success() - } catch { - 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 8e2ede6e..77d3bd12 100644 --- a/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationReadCertAndSign.swift @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import CommonCrypto import CommonsLib import CryptoTokenKit diff --git a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift index b4f93e6e..6a7f9661 100644 --- a/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift +++ b/RIADigiDoc/Domain/NFC/OperationUnblockPin.swift @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import CommonCrypto import CryptoTokenKit import Security diff --git a/RIADigiDoc/Domain/NFC/OperationWebEidAuth.swift b/RIADigiDoc/Domain/NFC/OperationWebEidAuth.swift index 181a42c1..087342e7 100644 --- a/RIADigiDoc/Domain/NFC/OperationWebEidAuth.swift +++ b/RIADigiDoc/Domain/NFC/OperationWebEidAuth.swift @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import CommonCrypto import CommonsLib import CryptoKit @@ -20,14 +19,6 @@ public struct WebEidAuthReturnData: Sendable { @MainActor public class OperationWebEidAuth: NFCOperationBase, OperationWebEidAuthProtocol { - private var pin1Number: SecureData = SecureData([0x00]) - private var origin: String = "" - private var challenge: String = "" - private var userAgent: String = "" - private var returnData: WebEidAuthReturnData? - - private var continuation: CheckedContinuation? - // swiftlint:disable:next function_parameter_count public func startOperation( canNumber: String, @@ -37,171 +28,54 @@ public class OperationWebEidAuth: NFCOperationBase, OperationWebEidAuthProtocol userAgent: String, strings: NFCSessionStrings ) async throws -> WebEidAuthReturnData { - - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation - - guard NFCTagReaderSession.readingAvailable else { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - self.canNumber = canNumber - self.pin1Number = pin1Number - self.origin = origin - self.challenge = challenge - self.userAgent = userAgent - self.strings = strings - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + guard !userAgent.isEmpty else { + Self.logger().error("NFC: \(ReadCertAndSignError.userAgentEmpty.localizedDescription)") + throw ReadCertAndSignError.userAgentEmpty } - } - // MARK: - NFCTagReaderSessionDelegate + return try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in + Self.logger().info("NFC: Checks complete starting authentication") + updateAlertMessage(step: 3) - // swiftlint:disable:next cyclomatic_complexity - public override func tagReaderSession(_ session: NFCTagReaderSession, didDetect tags: [NFCTag]) { - Task { @MainActor in - defer { - self.session = nil - } + let (retryCount, pinActive) = try await cardCommands.readCodeTryCounterRecord(.pin1) - if userAgent.isEmpty { - let error = ReadCertAndSignError.userAgentEmpty - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to initialize user agent") - return + if retryCount == 0 { + throw IdCardInternalError.remainingPinRetryCount(Int(retryCount)) } + if !pinActive { + throw IdCardInternalError.pinLocked + } + let authCert = try await cardCommands.readAuthenticationCertificate() + let signerCert = try await cardCommands.readSignatureCertificate() - Self.logger().info("NFC: Checks complete starting authentication") + guard let certificate = SecCertificateCreateWithData(nil, authCert as CFData) else { + throw ReadCertAndSignError.invalidCertificate + } - 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(.pin1) - - if retryCount == 0 { - throw IdCardInternalError.remainingPinRetryCount(Int(retryCount)) - } - if !pinActive { - throw IdCardInternalError.pinLocked - } - let authCert = try await cardCommands.readAuthenticationCertificate() - let signerCert = try await cardCommands.readSignatureCertificate() - - guard let certificate = SecCertificateCreateWithData(nil, authCert as CFData) else { - let error = ReadCertAndSignError.invalidCertificate - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Invalid certificate") - return - } - - guard let publicKey = SecCertificateCopyKey(certificate) else { - let error = ReadCertAndSignError.missingPublicKey - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Missing public key") - return - } - - updateAlertMessage(step: 4) - - guard let hashAlgorithm = try resolveHashAlgorithm(from: publicKey) else { - let error = ReadCertAndSignError.missingPublicKey - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Unsupported algorithm") - - return - } - - let originHash = digest(Data(origin.utf8), using: hashAlgorithm) - let challengeHash = digest(Data(challenge.utf8), using: hashAlgorithm) - - let signedData = originHash + challengeHash - let tbsHash = digest(signedData, using: hashAlgorithm) - - let signatureArray = try await cardCommands.authenticate(for: tbsHash, withPin1: pin1Number) - - returnData = WebEidAuthReturnData( - authCert: authCert, - signingCert: signerCert, - signatureArray: signatureArray - ) - - success() - } catch { - 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 { - Self.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) + guard let publicKey = SecCertificateCopyKey(certificate) else { + throw ReadCertAndSignError.missingPublicKey } - } - } - public override func tagReaderSession(_: NFCTagReaderSession, didInvalidateWithError error: Error) { - Self.logger().info("NFC: Reader session finished with error: \(error)") - self.session = nil + updateAlertMessage(step: 4) - guard let continuationToResume = self.continuation else { return } - self.continuation = nil + guard let hashAlgorithm = try resolveHashAlgorithm(from: publicKey) else { + throw ReadCertAndSignError.missingPublicKey + } - if let returnData, didCompleteSuccessfully { - continuationToResume.resume(with: .success(returnData)) - return - } + let originHash = digest(Data(origin.utf8), using: hashAlgorithm) + let challengeHash = digest(Data(challenge.utf8), using: hashAlgorithm) - if let storedError = self.operationError { - continuationToResume.resume(throwing: storedError) - return - } + let signedData = originHash + challengeHash + let tbsHash = digest(signedData, using: hashAlgorithm) - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return + let signatureArray = try await cardCommands.authenticate(for: tbsHash, withPin1: pin1Number) - default: - break - } + return WebEidAuthReturnData( + authCert: authCert, + signingCert: signerCert, + signatureArray: signatureArray + ) } - - continuationToResume.resume(throwing: error) } // MARK: - Helpers diff --git a/RIADigiDoc/Domain/NFC/OperationWebEidSign.swift b/RIADigiDoc/Domain/NFC/OperationWebEidSign.swift index f40e0a2f..14908286 100644 --- a/RIADigiDoc/Domain/NFC/OperationWebEidSign.swift +++ b/RIADigiDoc/Domain/NFC/OperationWebEidSign.swift @@ -2,7 +2,6 @@ // SPDX-License-Identifier: LGPL-2.1-or-later import Foundation -import CoreNFC import CommonCrypto import CommonsLib import CryptoTokenKit @@ -19,15 +18,6 @@ public struct WebEidSignReturnData: Sendable { @MainActor public class OperationWebEidSign: NFCOperationBase, OperationWebEidSignProtocol { - private var pin2Number: SecureData = SecureData([0x00]) - private var responseUri: String = "" - private var hashToSign: String = "" - private var expectedSigningCertBase64: String? - private var userAgent: String = "" - private var returnData: WebEidSignReturnData? - - private var continuation: CheckedContinuation? - // swiftlint:disable:next function_parameter_count public func startOperation( canNumber: String, @@ -38,179 +28,48 @@ public class OperationWebEidSign: NFCOperationBase, OperationWebEidSignProtocol userAgent: String, strings: NFCSessionStrings ) async throws -> WebEidSignReturnData { - - return try await withCheckedThrowingContinuation { continuation in - self.continuation = continuation - - guard NFCTagReaderSession.readingAvailable else { - continuation.resume(throwing: IdCardInternalError.nfcNotSupported) - return - } - - guard let expectedCert = expectedSigningCertBase64, - !expectedCert.isEmpty, - Data(base64Encoded: expectedCert) != nil else { - continuation.resume(throwing: ReadCertAndSignError.invalidCertificate) - return - } - - guard let hashBytes = Data(base64Encoded: hash), !hashBytes.isEmpty else { - continuation.resume(throwing: ReadCertAndSignError.hashInvalid) - return - } - - self.canNumber = canNumber - self.pin2Number = pin2Number - self.responseUri = responseUri - self.hashToSign = hash - self.expectedSigningCertBase64 = expectedSigningCertBase64 - self.userAgent = userAgent - self.strings = strings - - session = NFCTagReaderSession(pollingOption: .iso14443, delegate: self) - updateAlertMessage(step: 0) - session?.begin() + guard let expectedSigningCertBase64, + !expectedSigningCertBase64.isEmpty, + let expectedCert = Data(base64Encoded: expectedSigningCertBase64) else { + throw ReadCertAndSignError.invalidCertificate } - } - - // 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 hashBytes = Data(base64Encoded: hash), !hashBytes.isEmpty else { + throw ReadCertAndSignError.hashInvalid + } - if userAgent.isEmpty { - let error = ReadCertAndSignError.userAgentEmpty - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Failed to initialize user agent") - return - } + guard !userAgent.isEmpty else { + Self.logger().error("NFC: \(ReadCertAndSignError.userAgentEmpty.localizedDescription)") + throw ReadCertAndSignError.userAgentEmpty + } + return try await withCardCommands(canNumber: canNumber, strings: strings) { cardCommands in Self.logger().info("NFC: Checks complete starting signing") + updateAlertMessage(step: 3) - 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 (retryCount, pinActive) = try await cardCommands.readCodeTryCounterRecord(.pin2) - let signerCert = try await cardCommands.readSignatureCertificate() - - guard let expectedSigningCertBase64, - !expectedSigningCertBase64.isEmpty, - let expectedCert = Data(base64Encoded: expectedSigningCertBase64) else { - let error = ReadCertAndSignError.invalidCertificate - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Missing Web eID signing certificate") - return - } - - if expectedCert != signerCert { - let error = ReadCertAndSignError.certMismatch - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Web eID signing certificate mismatch") - return - } - - let signerCertB64 = signerCert.base64EncodedString() - - guard let hashBytes = Data(base64Encoded: hashToSign), !hashBytes.isEmpty else { - let error = ReadCertAndSignError.hashInvalid - Self.logger().error("NFC: \(error.localizedDescription)") - operationError = error - session.invalidate(errorMessage: strings?.technicalErrorMessage ?? - "Invalid hash encoding") - return - } - - let signatureArray = try await cardCommands.calculateSignature(for: hashBytes, withPin2: pin2Number) - - updateAlertMessage(step: 4) - returnData = WebEidSignReturnData( - signerCertB64: signerCertB64, - signatureArray: signatureArray, - responseUri: responseUri - ) - - success() - } catch { - 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 { - Self.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 retryCount == 0 { + throw IdCardInternalError.remainingPinRetryCount(Int(retryCount)) + } + 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 - } + let signerCert = try await cardCommands.readSignatureCertificate() - if let storedError = self.operationError { - continuationToResume.resume(throwing: storedError) - return - } + if expectedCert != signerCert { + throw ReadCertAndSignError.certMismatch + } - if let nfcError = error as? NFCReaderError { - switch nfcError.code { - case .readerSessionInvalidationErrorUserCanceled: - continuationToResume.resume(throwing: IdCardInternalError.cancelledByUser) - return + let signatureArray = try await cardCommands.calculateSignature(for: hashBytes, withPin2: pin2Number) - default: - break - } + updateAlertMessage(step: 4) + return WebEidSignReturnData( + signerCertB64: signerCert.base64EncodedString(), + signatureArray: signatureArray, + responseUri: responseUri + ) } - - continuationToResume.resume(throwing: error) } }