From 0f07433f3abefc731f401ac63766c77b34c9fad8 Mon Sep 17 00:00:00 2001 From: Boriss Melikjan Date: Wed, 11 Feb 2026 16:20:29 +0200 Subject: [PATCH] MOPPIOS-1677 Fixes to CDOC2 Recipient info display, CDOC2 settings saving, and Encrypt flow. --- .../Sources/CryptoObjC/include/Decrypt.mm | 10 +- .../CryptoObjCWrapper/Domain/Addressee.swift | 25 +++ RIADigiDoc/CryptoSetup.swift | 28 ++-- RIADigiDoc/CryptoSetupProtocol.swift | 5 +- RIADigiDoc/DI/AppContainer.swift | 6 +- RIADigiDoc/LibrarySetup.swift | 2 +- .../Supporting files/Localizable.xcstrings | 144 ++++++++++++++++++ .../Container/Crypto/EncryptView.swift | 22 +-- .../ViewModel/AdvancedSettingsViewModel.swift | 2 +- .../ViewModel/DiagnosticsViewModel.swift | 7 +- RIADigiDoc/ViewModel/EncryptViewModel.swift | 17 ++- .../EncryptionSettingsViewModel.swift | 3 +- .../ViewModel/ProxySettingsViewModel.swift | 6 +- 13 files changed, 238 insertions(+), 39 deletions(-) diff --git a/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm b/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm index 58c06b57..7fab49b4 100644 --- a/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm +++ b/Modules/CryptoLib/Sources/CryptoObjC/include/Decrypt.mm @@ -33,24 +33,24 @@ @implementation Addressee (label) - (instancetype)initWithLabel:(const std::string &)label pub:(NSData*)pub concatKDFAlgorithmURI:(NSString *)concatKDFAlgorithmURI { std::map info = libcdoc::Recipient::parseLabel(label); id cn = info.contains("cn") ? [NSString stringWithStdString:info["cn"]] : nil; - id first = info.contains("first_name") ? [NSString stringWithStdString:info["first_name"]] : nil; - id last = info.contains("last_name") ? [NSString stringWithStdString:info["last_name"]] : nil; + id type = info.contains("last_name") ? [NSString stringWithStdString:info["type"]] : nil; id serial = info.contains("serial_number") ? [NSString stringWithStdString:info["serial_number"]] : nil; - id type = info.contains("type") ? [NSString stringWithStdString:info["type"]] : nil; CertType certType = CertTypeUnknownType; - if ([type isEqualToString:@"ID-card"]) { + if ([type isEqualToString:@"ID-card"] || [type isEqualToString:@"cert"]) { certType = CertTypeIDCardType; } else if ([type isEqualToString:@"Digi-ID"]) { certType = CertTypeDigiIDType; } else if ([type isEqualToString:@"Digi-ID E-RESIDENT"]) { certType = CertTypeEResidentType; + } else if (type == nil) { + certType = CertTypeESealType; } id validTo = nil; if (info.contains("server_exp")) { long long epochTime = [[NSString stringWithStdString:info["server_exp"]] longLongValue]; validTo = [NSDate dateWithTimeIntervalSince1970:epochTime]; } - if (self = [self initWithData:pub cnVal:cn givenName:first surname:last serialNumber:serial certType:certType validTo:validTo concatKDFAlgorithmURI:concatKDFAlgorithmURI]) { + if (self = [self initWithCnVal:cn serialNumber:serial certType:certType validTo:validTo data:pub concatKDFAlgorithmURI:concatKDFAlgorithmURI]) { } return self; } diff --git a/Modules/CryptoLib/Sources/CryptoObjCWrapper/Domain/Addressee.swift b/Modules/CryptoLib/Sources/CryptoObjCWrapper/Domain/Addressee.swift index d14d81fc..f37f5c4b 100644 --- a/Modules/CryptoLib/Sources/CryptoObjCWrapper/Domain/Addressee.swift +++ b/Modules/CryptoLib/Sources/CryptoObjCWrapper/Domain/Addressee.swift @@ -63,6 +63,31 @@ import Foundation ) } + @objc public init( + cnVal: String, + serialNumber: String?, + certType: CertType, + validTo: Date?, + data: Data, + concatKDFAlgorithmURI: String = "" + ) { + let split = cnVal.split(separator: ",").map { String($0) } + if split.count > 1 { + surname = split[0] + givenName = split[1] + identifier = split[2] + } else { + surname = nil + givenName = nil + identifier = cnVal + } + self.serialNumber = serialNumber + self.certType = certType + self.validTo = validTo + self.data = data + self.concatKDFAlgorithmURI = concatKDFAlgorithmURI + } + public init(cert: Data, x509: X509Certificate?) { data = cert let cnVal = x509?.subject(oid: .commonName)?.joined(separator: ",") ?? "" diff --git a/RIADigiDoc/CryptoSetup.swift b/RIADigiDoc/CryptoSetup.swift index 4052c08d..97fde1bf 100644 --- a/RIADigiDoc/CryptoSetup.swift +++ b/RIADigiDoc/CryptoSetup.swift @@ -101,7 +101,7 @@ actor CryptoSetup: CryptoSetupProtocol { } } - public func setCdoc2Settings(_ configurationProvider: ConfigurationProvider?, _ certData: Data? = nil) async { + public func setCdoc2Settings(_ configurationProvider: ConfigurationProvider?) async { var defaultUseCdoc2Encryption = Constants.CryptoDefaultValues.encryptionUseCdoc2 if let useCdoc2Encryption = configurationProvider?.cdoc2Default { defaultUseCdoc2Encryption = useCdoc2Encryption @@ -129,8 +129,23 @@ actor CryptoSetup: CryptoSetupProtocol { Decrypt.setCdoc2Config(cdoc2Conf.asNSDictionary()) } + if let certBundle = configurationProvider?.certBundle { + Encrypt.setCerts(certBundle) + Decrypt.setCerts(certBundle) + } + let proxyInfo = await proxyUtil.getProxyInfo() + await setCdoc2ProxyInfo(proxyInfo) + } + + public func setCdoc2CustomCert(_ certData: Data? = nil) async { + if let certData { + Encrypt.setCert(certData) + Decrypt.setCert(certData) + } + } + public func setCdoc2ProxyInfo(_ proxyInfo: ProxyInfo) async { Encrypt.setProxy( proxyInfo.host, port: proxyInfo.port, @@ -144,16 +159,5 @@ actor CryptoSetup: CryptoSetupProtocol { username: proxyInfo.username, password: proxyInfo.password ) - - if let certBundle = configurationProvider?.certBundle { - Encrypt.setCerts(certBundle) - Decrypt.setCerts(certBundle) - } - - if let certData { - Encrypt.setCert(certData) - Decrypt.setCert(certData) - } } - } diff --git a/RIADigiDoc/CryptoSetupProtocol.swift b/RIADigiDoc/CryptoSetupProtocol.swift index b7a0ce21..122e228e 100644 --- a/RIADigiDoc/CryptoSetupProtocol.swift +++ b/RIADigiDoc/CryptoSetupProtocol.swift @@ -18,11 +18,14 @@ */ import Foundation +import CommonsLib import ConfigLib /// @mockable public protocol CryptoSetupProtocol: Sendable { func setLdapConfig(_ configurationProvider: ConfigurationProvider?) async func setCdoc2Config(_ configurationProvider: ConfigurationProvider?) async - func setCdoc2Settings(_ configurationProvider: ConfigurationProvider?, _ certData: Data?) async + func setCdoc2Settings(_ configurationProvider: ConfigurationProvider?) async + func setCdoc2CustomCert(_ certData: Data?) async + func setCdoc2ProxyInfo(_ proxyInfo: ProxyInfo) async } diff --git a/RIADigiDoc/DI/AppContainer.swift b/RIADigiDoc/DI/AppContainer.swift index fee6f9d3..fe60d75f 100644 --- a/RIADigiDoc/DI/AppContainer.swift +++ b/RIADigiDoc/DI/AppContainer.swift @@ -255,7 +255,8 @@ extension Container { dataStore: self.dataStore(), proxyUtil: self.proxyUtil(), userAgentUtil: self.userAgentUtil(), - fileUtil: self.fileUtil() + fileUtil: self.fileUtil(), + cryptoSetup: self.cryptoSetup() ) } } @@ -324,7 +325,8 @@ extension Container { ProxySettingsViewModel( proxyUtil: self.proxyUtil(), userAgentUtil: self.userAgentUtil(), - dataStore: self.dataStore() + dataStore: self.dataStore(), + cryptoSetup: self.cryptoSetup() ) } } diff --git a/RIADigiDoc/LibrarySetup.swift b/RIADigiDoc/LibrarySetup.swift index d4c48817..0e2c88a7 100644 --- a/RIADigiDoc/LibrarySetup.swift +++ b/RIADigiDoc/LibrarySetup.swift @@ -119,7 +119,7 @@ actor LibrarySetup: Loggable { await cryptoSetup.setLdapConfig(configurationProvider) await cryptoSetup.setCdoc2Config(configurationProvider) - await cryptoSetup.setCdoc2Settings(configurationProvider, nil) + await cryptoSetup.setCdoc2Settings(configurationProvider) try saveLDAPCertsToLibrary(ldapCertsBundle: configurationProvider?.ldapCerts) } catch let error { diff --git a/RIADigiDoc/Supporting files/Localizable.xcstrings b/RIADigiDoc/Supporting files/Localizable.xcstrings index d85ca895..94eca52b 100644 --- a/RIADigiDoc/Supporting files/Localizable.xcstrings +++ b/RIADigiDoc/Supporting files/Localizable.xcstrings @@ -287,6 +287,42 @@ } } }, + "Cannot create an empty crypto container" : { + "comment" : "CryptoContainer empty error message", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cannot create an empty crypto container" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei saa luua tühja krüptokonteinerit" + } + } + } + }, + "Cannot create crypto container without recipients" : { + "comment" : "CryptoContainer no recipients error message", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Cannot create crypto container without recipients" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ei saa luua krüptokonteinerit ilma adressaatideta" + } + } + } + }, "Certificate details" : { "comment" : "Title of Certificate Details view", "localizations" : { @@ -304,6 +340,24 @@ } } }, + "Certificate for Encryption" : { + "comment" : "Certificate type", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Certificate for Encryption" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Krüpteerimissertifikaat" + } + } + } + }, "Certificate has expired" : { "comment" : "OperationAuthenticateWithWebEID Certificate validity check", "extractionState" : "manual", @@ -1164,6 +1218,24 @@ } } }, + "Digi-ID" : { + "comment" : "Certificate type", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Digi-ID" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Digi-ID" + } + } + } + }, "DigiDoc" : { "comment" : "DigiDoc title on homesview", "extractionState" : "manual", @@ -1236,6 +1308,24 @@ } } }, + "E-Resident" : { + "comment" : "Certificate type", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "E-Resident" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "E-Resident" + } + } + } + }, "Empty file in container" : { "comment" : "Empty file message in container notifications", "extractionState" : "manual", @@ -1290,6 +1380,24 @@ } } }, + "Encrypt general error" : { + "comment" : "CryptoContainer encrypt error message", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Container encryption was unsuccessful" + } + }, + "et" : { + "stringUnit" : { + "state" : "needs_review", + "value" : "Ümbriku krüpteerimine ebaõnnestus" + } + } + } + }, "Enter current PIN code" : { "comment" : "My eID current PIN or PUK code step title", "extractionState" : "manual", @@ -1865,6 +1973,24 @@ } } }, + "ID-card" : { + "comment" : "Certificate type", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID-card" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "ID-kaart" + } + } + } + }, "ID-card via NFC" : { "comment" : "ID-card via NFC signing method", "extractionState" : "manual", @@ -7513,6 +7639,24 @@ } } }, + "Unknown" : { + "comment" : "Certificate type", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Unknown" + } + }, + "et" : { + "stringUnit" : { + "state" : "translated", + "value" : "Tundmatu" + } + } + } + }, "Unknown signature" : { "comment" : "Unknown signature message in container notifications", "extractionState" : "manual", diff --git a/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift b/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift index f679a707..aa8cbe3a 100644 --- a/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift +++ b/RIADigiDoc/UI/Component/Container/Crypto/EncryptView.swift @@ -364,11 +364,8 @@ struct EncryptView: View { await updateAsyncLabels() await viewModel.updateAsyncProperties() - Toast.show(languageSettings.localized( - "Container successfully encrypted" - )) - encryptionButtonEnabled = true + isWithEncryption = false } } } @@ -407,10 +404,6 @@ struct EncryptView: View { await updateAsyncLabels() await viewModel.updateAsyncProperties() - Toast.show(languageSettings.localized( - "Container successfully encrypted" - )) - encryptionButtonEnabled = true isWithEncryption = false } else if isWithDecryption { @@ -479,12 +472,19 @@ struct EncryptView: View { .animation(.easeInOut, value: showRenameModal) .onChange(of: viewModel.errorMessage) { _, error in guard let error else { return } - Toast.show(String( - format: languageSettings.localized(error.key), - error.args.joined(separator: ", ")) + Toast.show( + languageSettings.localized(error.key, [error.args.joined(separator: ", ")]) ) + viewModel.resetErrorMessage() encryptionButtonEnabled = true } + .onChange(of: viewModel.successMessage) { _, message in + guard let message else { return } + Toast.show( + languageSettings.localized(message.key, [message.args.joined(separator: ", ")]) + ) + viewModel.resetSuccessMessage() + } } func updateAsyncLabels() async { diff --git a/RIADigiDoc/ViewModel/AdvancedSettingsViewModel.swift b/RIADigiDoc/ViewModel/AdvancedSettingsViewModel.swift index 1ad2f1e9..5eebd4f2 100644 --- a/RIADigiDoc/ViewModel/AdvancedSettingsViewModel.swift +++ b/RIADigiDoc/ViewModel/AdvancedSettingsViewModel.swift @@ -62,7 +62,7 @@ class AdvancedSettingsViewModel: AdvancedSettingsViewModelProtocol, Loggable { await cryptoSetup.setLdapConfig(configuration) await cryptoSetup.setCdoc2Config(configuration) - await cryptoSetup.setCdoc2Settings(configuration, nil) + await cryptoSetup.setCdoc2Settings(configuration) } private func removeCertificates() async { diff --git a/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift b/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift index 84f67125..aef9ce24 100644 --- a/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift +++ b/RIADigiDoc/ViewModel/DiagnosticsViewModel.swift @@ -54,6 +54,7 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { private let proxyUtil: ProxyUtilProtocol private let userAgentUtil: UserAgentUtilProtocol private let fileUtil: FileUtilProtocol + private let cryptoSetup: CryptoSetupProtocol private var configurationObservationTask: Task? @@ -66,7 +67,8 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { dataStore: DataStoreProtocol, proxyUtil: ProxyUtilProtocol, userAgentUtil: UserAgentUtilProtocol, - fileUtil: FileUtilProtocol + fileUtil: FileUtilProtocol, + cryptoSetup: CryptoSetupProtocol ) { self.containerWrapper = containerWrapper self.fileManager = fileManager @@ -77,6 +79,7 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { self.proxyUtil = proxyUtil self.userAgentUtil = userAgentUtil self.fileUtil = fileUtil + self.cryptoSetup = cryptoSetup configurationObservationTask = Task { await observeConfigurationUpdates() @@ -501,6 +504,8 @@ class DiagnosticsViewModel: DiagnosticsViewModelProtocol, Loggable { await MainActor.run { configuration = config } + await cryptoSetup.setCdoc2Config(config) + await cryptoSetup.setCdoc2Settings(config) } } catch { DiagnosticsViewModel.logger().error("Unable to get configuration from stream") diff --git a/RIADigiDoc/ViewModel/EncryptViewModel.swift b/RIADigiDoc/ViewModel/EncryptViewModel.swift index 3e9b8ea9..c3aa5110 100644 --- a/RIADigiDoc/ViewModel/EncryptViewModel.swift +++ b/RIADigiDoc/ViewModel/EncryptViewModel.swift @@ -40,6 +40,7 @@ class EncryptViewModel: EncryptViewModelProtocol, Loggable { var showRecipientRemoveButton = false var isLastDataFileRemoved = false private(set) var errorMessage: ToastMessage? + private(set) var successMessage: ToastMessage? private let sharedContainerViewModel: SharedContainerViewModelProtocol private let fileOpeningService: FileOpeningServiceProtocol @@ -158,7 +159,7 @@ class EncryptViewModel: EncryptViewModelProtocol, Loggable { await cryptoContainer?.addDataFiles(files) EncryptViewModel.logger().info("Added data files to container") - errorMessage = ToastMessage( + successMessage = ToastMessage( key: files.count == 1 ? "File successfully added" : "Files successfully added", args: [] ) @@ -246,6 +247,8 @@ class EncryptViewModel: EncryptViewModelProtocol, Loggable { await loadContainerData( cryptoContainer: encryptedContainer ) + + successMessage = ToastMessage(key: "Container successfully encrypted", args: []) } catch { EncryptViewModel.logger().error("Unable to encrypt container: \(error)") handleEncryptionError(error) @@ -258,7 +261,7 @@ class EncryptViewModel: EncryptViewModelProtocol, Loggable { case .containerCreationFailed(let detail): errorMessage = ToastMessage(key: detail.message, args: []) default: - errorMessage = ToastMessage(key: "General error", args: []) + errorMessage = ToastMessage(key: "Encrypt general error", args: []) } return } @@ -268,7 +271,7 @@ class EncryptViewModel: EncryptViewModelProtocol, Loggable { return } - errorMessage = ToastMessage(key: "General error", args: []) + errorMessage = ToastMessage(key: "Encrypt general error", args: []) } @discardableResult @@ -560,6 +563,14 @@ class EncryptViewModel: EncryptViewModelProtocol, Loggable { } } + func resetErrorMessage() { + errorMessage = nil + } + + func resetSuccessMessage() { + successMessage = nil + } + func updateAsyncProperties() async { let isContainerWithoutRecipients = await isContainerWithoutRecipients(cryptoContainer: cryptoContainer) let isContainerEncrypted = await isEncryptedContainer(cryptoContainer: cryptoContainer) diff --git a/RIADigiDoc/ViewModel/EncryptionSettingsViewModel.swift b/RIADigiDoc/ViewModel/EncryptionSettingsViewModel.swift index 0bcfd68a..f2e0c1d0 100644 --- a/RIADigiDoc/ViewModel/EncryptionSettingsViewModel.swift +++ b/RIADigiDoc/ViewModel/EncryptionSettingsViewModel.swift @@ -175,7 +175,8 @@ class EncryptionSettingsViewModel: EncryptionSettingsViewModelProtocol, Loggable serverInfo.postURL = serverInfo.postURL.trimmingCharacters(in: .whitespacesAndNewlines) } await dataStore.setEncryptionServerInfo(serverInfo) - await cryptoSetup.setCdoc2Settings(configuration, certData) + await cryptoSetup.setCdoc2Settings(configuration) + await cryptoSetup.setCdoc2CustomCert(certData) } // MARK: - Cert Info Getters diff --git a/RIADigiDoc/ViewModel/ProxySettingsViewModel.swift b/RIADigiDoc/ViewModel/ProxySettingsViewModel.swift index 5af71208..966e06b4 100644 --- a/RIADigiDoc/ViewModel/ProxySettingsViewModel.swift +++ b/RIADigiDoc/ViewModel/ProxySettingsViewModel.swift @@ -33,15 +33,18 @@ class ProxySettingsViewModel: ProxySettingsViewModelProtocol, Loggable { private let proxyUtil: ProxyUtilProtocol private let userAgentUtil: UserAgentUtilProtocol private let dataStore: DataStoreProtocol + private let cryptoSetup: CryptoSetupProtocol init( proxyUtil: ProxyUtilProtocol, userAgentUtil: UserAgentUtilProtocol, - dataStore: DataStoreProtocol + dataStore: DataStoreProtocol, + cryptoSetup: CryptoSetupProtocol ) { self.proxyUtil = proxyUtil self.userAgentUtil = userAgentUtil self.dataStore = dataStore + self.cryptoSetup = cryptoSetup Task { await loadSettings() @@ -69,6 +72,7 @@ class ProxySettingsViewModel: ProxySettingsViewModelProtocol, Loggable { public func saveSettings() async { await proxyUtil.saveSetting(proxyInfo: proxyInfo) + await cryptoSetup.setCdoc2ProxyInfo(proxyInfo) } // MARK: - Check connection