Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions RIADigiDoc.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@
Domain/Model/Error/ErrorMessage.swift,
Domain/Model/Error/FileOpeningErrors.swift,
"Domain/Model/Error/My eID/MyEidCodeChangeError.swift",
Domain/Model/Error/ReadCertAndSignError.swift,
Domain/Model/FileItem.swift,
Domain/Model/IdCard/IdCardData.swift,
Domain/Model/IdCard/RetryCount.swift,
Expand All @@ -163,8 +164,10 @@
Domain/Model/SupportedLanguage.swift,
Domain/Model/SupportedTheme.swift,
Domain/Model/ToastMessage.swift,
Domain/NFC/DecryptError.swift,
Domain/NFC/NFCSessionStrings.swift,
Domain/NFC/OperationDecrypt.swift,
Domain/NFC/OperationReadCertAndSign.swift,
Domain/Preferences/DataStore.swift,
Domain/Preferences/DataStoreProtocol.swift,
Domain/Preferences/KeychainStore.swift,
Expand Down Expand Up @@ -247,7 +250,6 @@
ViewModel/Protocols/RecentDocumentsViewModelProtocol.swift,
ViewModel/Protocols/Shared/SharedContainerViewModelProtocol.swift,
ViewModel/Protocols/Shared/SharedIdCardViewModelProtocol.swift,
ViewModel/Protocols/Shared/SharedNFCViewModelProtocol.swift,
ViewModel/Protocols/SignatureDetailViewModelProtocol.swift,
ViewModel/Protocols/SigningViewModelProtocol.swift,
ViewModel/Protocols/TimeStampSettingsViewModelProtocol.swift,
Expand All @@ -256,7 +258,6 @@
ViewModel/RecentDocumentsViewModel.swift,
ViewModel/Shared/SharedContainerViewModel.swift,
ViewModel/Shared/SharedIdCardViewModel.swift,
ViewModel/Shared/SharedNFCViewModel.swift,
ViewModel/SignatureDetailViewModel.swift,
ViewModel/Signing/ActionMethod/ActionMethodSelectionViewModel.swift,
ViewModel/Signing/ActionMethod/ActionMethodSelectionViewModelProtocol.swift,
Expand Down
9 changes: 2 additions & 7 deletions RIADigiDoc/DI/AppContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -502,7 +502,8 @@ extension Container {
var nfcViewModel: Factory<NFCViewModel> {
self { @MainActor in
NFCViewModel(
dataStore: self.dataStore()
dataStore: self.dataStore(),
userAgentUtil: self.userAgentUtil()
)
}
}
Expand All @@ -516,12 +517,6 @@ extension Container {
}
}

@MainActor
var sharedNfcViewModel: Factory<SharedNFCViewModel> {
self { @MainActor in SharedNFCViewModel() }
.shared
}

@MainActor
var myEidPinChangeViewModel: ParameterFactory<(MyEidPinCodeAction, CodeType, String), MyEidPinChangeViewModel> {
self { @MainActor (pinAction: MyEidPinCodeAction, codeType: CodeType, personalCode: String
Expand Down
49 changes: 49 additions & 0 deletions RIADigiDoc/Domain/Model/Error/ReadCertAndSignError.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright 2017 - 2025 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 IdCardLib

public enum ReadCertAndSignError: Error {
case signedContainerNil
case roleDataNil
case containerPathNil
case userAgentEmpty
case cancelled
case unknown(Error)
}

extension ReadCertAndSignError: LocalizedError {
public var errorDescription: String? {
switch self {
case .signedContainerNil:
return "Signed container is nil"
case .roleDataNil:
return "Role data is nil"
case .containerPathNil:
return "Container path is nil"
case .userAgentEmpty:
return "User agent is empty"
case .cancelled:
return "Operation cancelled by user"
case .unknown(let error):
return "Unknown error: \(error.localizedDescription)"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,25 @@
*/

import Foundation
import LibdigidocLibSwift

/// @mockable
@MainActor
public protocol SharedNFCViewModelProtocol: Sendable {
public enum DecryptError: Error {
case containerFileInvalid
case recipientsEmpty
case cancelled
case unknown(Error)
}

extension DecryptError: LocalizedError {
public var errorDescription: String? {
switch self {
case .containerFileInvalid:
return "Container file is invalid"
case .recipientsEmpty:
return "No recipients found in container"
case .cancelled:
return "Operation cancelled by user"
case .unknown(let error):
return "Unknown error: \(error.localizedDescription)"
}
}
}
8 changes: 4 additions & 4 deletions RIADigiDoc/Domain/NFC/NFCSessionStrings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,17 @@
*
*/

public struct NFCSessionStrings {
public struct NFCSessionStrings: Sendable {
let initialMessage: String
let step1Message: String
let step2Message: String
let step3Message: String
let step4Message: String
let successMessage: String
let canErrorMessage: String
let pin1WrongMultipleErrorMessage: String
let pin1WrongErrorMessage: String
let pin1BlockedErrorMessage: String
let pinWrongMultipleErrorMessage: String
let pinWrongErrorMessage: String
let pinBlockedErrorMessage: String
let technicalErrorMessage: String
let sessionErrorMessage: String
}
93 changes: 69 additions & 24 deletions RIADigiDoc/Domain/NFC/OperationDecrypt.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,25 +27,26 @@ import CryptoKit
import IdCardLib
import CryptoObjCWrapper
import CryptoSwift
import UtilsLib

@MainActor
public class OperationDecrypt: NSObject {
public class OperationDecrypt: NSObject, Loggable {

private var session: NFCTagReaderSession?
private var isFinished = false
private var containerFile: URL = URL(fileURLWithPath: "")
private var containerFile: URL?
private var recipients: [Addressee] = []
private var CAN: String = ""
private var PIN: SecureData = SecureData([0x00])
private var canNumber: String = ""
private var pin1Number: SecureData = SecureData([0x00])
private var continuation: CheckedContinuation<CryptoContainerProtocol, Error>?
private var connection = NFCConnection()

private var nfcError: String? = ""
private var strings: NFCSessionStrings?

public func processDecrypt(
CAN: String,
PIN1: SecureData,
canNumber: String,
pin1Number: SecureData,
containerFile: URL,
recipients: [Addressee],
strings: NFCSessionStrings,
Expand All @@ -58,8 +59,8 @@ public class OperationDecrypt: NSObject {
continuation.resume(throwing: IdCardInternalError.nfcNotSupported)
return
}
self.CAN = CAN
self.PIN = PIN1
self.canNumber = canNumber
self.pin1Number = pin1Number
self.containerFile = containerFile
self.recipients = recipients
self.strings = strings
Expand All @@ -82,6 +83,7 @@ public class OperationDecrypt: NSObject {
let stepMessage = stepMessages[min(step, stepMessages.count - 1)]
let progressBar = ProgressBar(currentStep: step)
var message = stepMessage
OperationDecrypt.logger().info("NFC: Updating alert message to: \(message)")
message += "\n\n\(progressBar.generate())"
session?.alertMessage = message
}
Expand All @@ -91,22 +93,17 @@ public class OperationDecrypt: NSObject {
session?.invalidate()
}

private func failure(_ idCardError: IdCardError) {
handleIdCardError(idCardError)
session?.invalidate(errorMessage: nfcError ?? "")
}

private func handleIdCardError(_ error: IdCardError) {
switch error {
case .wrongCAN:
nfcError = strings?.canErrorMessage ?? ""
case .wrongPIN(let triesLeft):
if triesLeft > 1 {
nfcError = strings?.pin1WrongMultipleErrorMessage ?? ""
nfcError = strings?.pinWrongMultipleErrorMessage ?? ""
} else if triesLeft == 1 {
nfcError = strings?.pin1WrongErrorMessage ?? ""
nfcError = strings?.pinWrongErrorMessage ?? ""
} else {
nfcError = strings?.pin1BlockedErrorMessage ?? ""
nfcError = strings?.pinBlockedErrorMessage ?? ""
}
case .sessionError:
nfcError = strings?.sessionErrorMessage ?? ""
Expand All @@ -120,11 +117,41 @@ extension OperationDecrypt: @MainActor NFCTagReaderSessionDelegate {
public 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)")
session.invalidate(errorMessage: "Failed to read container file")
continuation?.resume(throwing: error)
return
}

if containerFile.path.isEmpty {
let error = DecryptError.containerFileInvalid
OperationDecrypt.logger().error("NFC: Container file path is empty")
session.invalidate(errorMessage: "Failed to read container file")
continuation?.resume(throwing: error)
return
}

if recipients.isEmpty {
let error = DecryptError.recipientsEmpty
OperationDecrypt.logger().error("NFC: \(error.localizedDescription)")
session.invalidate(errorMessage: "No recipients found")
continuation?.resume(throwing: error)
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: CAN)
let cardCommands = try await connection.getCardCommands(session, tag: tag, CAN: canNumber)
updateAlertMessage(step: 3)
let cert = try await cardCommands.readAuthenticationCertificate()
updateAlertMessage(step: 4)
Expand All @@ -133,22 +160,40 @@ extension OperationDecrypt: @MainActor NFCTagReaderSessionDelegate {
recipients: recipients,
cert: cert,
cardCommands: cardCommands,
pin: PIN,
pin: pin1Number,
)
continuation?.resume(with: .success(decryptedContainer))
success()
} catch {
guard !isFinished else { return }
guard !isFinished else {
OperationDecrypt.logger()
.info("NFC: Operation already finished, ignoring error: \(error.localizedDescription)")
return
}
isFinished = true
guard let exception = error as? IdCardInternalError else {
session.invalidate(errorMessage: strings?.sessionErrorMessage ?? "")

if let idCardInternalError = error as? IdCardInternalError {
let idCardError = idCardInternalError.getIdCardError()
OperationDecrypt.logger().error("NFC: IdCardError detected: \(idCardError)")
handleIdCardError(idCardError)
session.invalidate(errorMessage: nfcError ?? "")
continuation?.resume(throwing: error)
return
}

let idCardError = exception.getIdCardError()
failure(idCardError)
continuation?.resume(throwing: error)
if let decryptError = error as? DecryptError {
OperationDecrypt.logger()
.error("NFC: ReadCertAndDecryptError: \(decryptError.localizedDescription)")
session.invalidate(errorMessage: strings?.technicalErrorMessage ?? "")
continuation?.resume(throwing: error)
return
}

OperationDecrypt.logger().error("NFC: Unknown error type: \(type(of: error))")
OperationDecrypt.logger().error("NFC: Error details: \(error.localizedDescription)")
let wrappedError = DecryptError.unknown(error)
session.invalidate(errorMessage: strings?.sessionErrorMessage ?? "")
continuation?.resume(throwing: wrappedError)
}
}
}
Expand Down
Loading