From 6b8a735728e6cb8cf872928e44cb4aece659ed9d Mon Sep 17 00:00:00 2001 From: Marten Rebane <54431068+martenrebane@users.noreply.github.com> Date: Tue, 23 Jan 2024 12:24:24 +0200 Subject: [PATCH] Add support for SiVa setting --- .../xcschemes/xcschememanagement.plist | 2 +- MoppApp/MoppApp.xcodeproj/project.pbxproj | 28 +- .../{TSACertUtil.swift => CertUtil.swift} | 46 +-- .../CertificateDetailViewController.swift | 5 - MoppApp/MoppApp/Constants.swift | 2 +- MoppApp/MoppApp/ContainerActions.swift | 4 + MoppApp/MoppApp/ContainerViewController.swift | 19 +- MoppApp/MoppApp/DefaultsHelper.swift | 31 +- MoppApp/MoppApp/IdCardViewController.swift | 2 +- MoppApp/MoppApp/LocalizationKeys.swift | 8 + .../MoppApp/MobileIDEditViewController.swift | 2 +- MoppApp/MoppApp/RadioButton.swift | 73 ++++ MoppApp/MoppApp/RadioButton.xib | 68 ++++ MoppApp/MoppApp/ScaledButton.swift | 2 +- MoppApp/MoppApp/ScaledLabel.swift | 3 +- MoppApp/MoppApp/ScaledTextField.swift | 2 +- MoppApp/MoppApp/Settings.storyboard | 281 +++++++++++++- MoppApp/MoppApp/SettingsConfiguration.swift | 2 +- .../MoppApp/SettingsDefaultValueCell.swift | 11 + MoppApp/MoppApp/SettingsFieldCell.swift | 18 +- MoppApp/MoppApp/SettingsHeaderCell.swift | 6 +- MoppApp/MoppApp/SettingsSivaCertCell.swift | 342 ++++++++++++++++++ MoppApp/MoppApp/SettingsTSACertCell.swift | 14 +- MoppApp/MoppApp/SettingsViewController.swift | 180 +++++---- .../SignatureDetailsViewController.swift | 4 + .../SivaChoiceTapGestureRecognizer.swift | 28 ++ .../MoppApp/SmartIDEditViewController.swift | 2 +- MoppApp/MoppApp/en.lproj/Localizable.strings | 8 + MoppApp/MoppApp/et.lproj/Localizable.strings | 8 + MoppApp/MoppApp/ru.lproj/Localizable.strings | 8 + MoppLib/MoppLib/MoppLibDigidocManager.mm | 62 +++- MoppLib/MoppLib/MoppLibError.h | 1 + MoppLib/MoppLib/MoppLibError.m | 4 + .../PublicInterface/MoppLibConstants.h | 3 +- .../xcschemes/xcschememanagement.plist | 2 +- 35 files changed, 1133 insertions(+), 148 deletions(-) rename MoppApp/MoppApp/{TSACertUtil.swift => CertUtil.swift} (55%) create mode 100644 MoppApp/MoppApp/RadioButton.swift create mode 100644 MoppApp/MoppApp/RadioButton.xib create mode 100644 MoppApp/MoppApp/SettingsSivaCertCell.swift create mode 100644 MoppApp/MoppApp/SivaChoiceTapGestureRecognizer.swift diff --git a/CryptoLib/CryptoLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist b/CryptoLib/CryptoLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist index 58673806..5745016a 100644 --- a/CryptoLib/CryptoLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/CryptoLib/CryptoLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ CryptoLib.xcscheme_^#shared#^_ orderHint - 16 + 17 diff --git a/MoppApp/MoppApp.xcodeproj/project.pbxproj b/MoppApp/MoppApp.xcodeproj/project.pbxproj index e6524c32..4c515165 100644 --- a/MoppApp/MoppApp.xcodeproj/project.pbxproj +++ b/MoppApp/MoppApp.xcodeproj/project.pbxproj @@ -191,6 +191,7 @@ DF4B8418284008CF005CB875 /* SignatureDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4B8417284008CF005CB875 /* SignatureDetail.swift */; }; DF4B841A2840F8FC005CB875 /* CertificateDetailsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4B84192840F8FC005CB875 /* CertificateDetailsCell.swift */; }; DF4B841E284417CD005CB875 /* CertificateDetail.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF4B841D284417CD005CB875 /* CertificateDetail.swift */; }; + DF50BBCF2AF06A0800C1A7D0 /* SivaChoiceTapGestureRecognizer.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF50BBCE2AF06A0800C1A7D0 /* SivaChoiceTapGestureRecognizer.swift */; }; DF51499E29E98127008AB161 /* ScaledTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF51499D29E98127008AB161 /* ScaledTextField.swift */; }; DF53DCEA2A0A9D8A00F5DAF6 /* NotificationMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF53DCE92A0A9D8A00F5DAF6 /* NotificationMessage.swift */; }; DF5BE6D229C8AE5200331609 /* SignatureWarningsCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF5BE6D129C8AE5200331609 /* SignatureWarningsCell.swift */; }; @@ -202,6 +203,9 @@ DF8EFB1A287498B900A96DE3 /* SettingsDefaultValueCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF8EFB19287498B900A96DE3 /* SettingsDefaultValueCell.swift */; }; DF900C972386768F00887385 /* tslFiles.bundle in Resources */ = {isa = PBXBuildFile; fileRef = DF900C94238567CA00887385 /* tslFiles.bundle */; }; DF97A51129DC8CAB006FB917 /* ContainerAddAllButtonCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DF97A51029DC8CAB006FB917 /* ContainerAddAllButtonCell.swift */; }; + DFA56CEE2AEFEE45007D7F7E /* SettingsSivaCertCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA56CED2AEFEE45007D7F7E /* SettingsSivaCertCell.swift */; }; + DFA56CF22AF003F8007D7F7E /* RadioButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFA56CF12AF003F8007D7F7E /* RadioButton.swift */; }; + DFA56CF42AF009E5007D7F7E /* RadioButton.xib in Resources */ = {isa = PBXBuildFile; fileRef = DFA56CF32AF009E5007D7F7E /* RadioButton.xib */; }; DFA5821A264AAB6600564971 /* digidoc_76.png in Resources */ = {isa = PBXBuildFile; fileRef = DFA58218264AAB6500564971 /* digidoc_76.png */; }; DFA5821B264AAB6600564971 /* digidoc_1024.png in Resources */ = {isa = PBXBuildFile; fileRef = DFA58219264AAB6600564971 /* digidoc_1024.png */; }; DFAFD38629E8794200377EF5 /* SearchField.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFAFD38529E8794200377EF5 /* SearchField.swift */; }; @@ -229,7 +233,7 @@ DFD176D423F427AD00E2CC52 /* TSLVersionChecker.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD176D223F427AD00E2CC52 /* TSLVersionChecker.swift */; }; DFD176D523F427AD00E2CC52 /* TSLUpdater.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD176D323F427AD00E2CC52 /* TSLUpdater.swift */; }; DFD54CF529CDDB0600CD92C7 /* SettingsTSACertCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD54CF429CDDB0600CD92C7 /* SettingsTSACertCell.swift */; }; - DFD54CF729D1CBAF00CD92C7 /* TSACertUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD54CF629D1CBAF00CD92C7 /* TSACertUtil.swift */; }; + DFD54CF729D1CBAF00CD92C7 /* CertUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD54CF629D1CBAF00CD92C7 /* CertUtil.swift */; }; DFD8BEF5291C432400FE8F07 /* ScaledButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD8BEF4291C432400FE8F07 /* ScaledButton.swift */; }; DFD8BEF7291C434500FE8F07 /* FontUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFD8BEF6291C434500FE8F07 /* FontUtil.swift */; }; DFDC0ABA29FAD8F2002D1D1D /* ViewUtil.swift in Sources */ = {isa = PBXBuildFile; fileRef = DFDC0AB929FAD8F2002D1D1D /* ViewUtil.swift */; }; @@ -525,6 +529,7 @@ DF4B8417284008CF005CB875 /* SignatureDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureDetail.swift; sourceTree = ""; }; DF4B84192840F8FC005CB875 /* CertificateDetailsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDetailsCell.swift; sourceTree = ""; }; DF4B841D284417CD005CB875 /* CertificateDetail.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertificateDetail.swift; sourceTree = ""; }; + DF50BBCE2AF06A0800C1A7D0 /* SivaChoiceTapGestureRecognizer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SivaChoiceTapGestureRecognizer.swift; sourceTree = ""; }; DF51499D29E98127008AB161 /* ScaledTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScaledTextField.swift; sourceTree = ""; }; DF53DCE92A0A9D8A00F5DAF6 /* NotificationMessage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationMessage.swift; sourceTree = ""; }; DF5BE6D129C8AE5200331609 /* SignatureWarningsCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignatureWarningsCell.swift; sourceTree = ""; }; @@ -536,6 +541,9 @@ DF8EFB19287498B900A96DE3 /* SettingsDefaultValueCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsDefaultValueCell.swift; sourceTree = ""; }; DF900C94238567CA00887385 /* tslFiles.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = tslFiles.bundle; sourceTree = ""; }; DF97A51029DC8CAB006FB917 /* ContainerAddAllButtonCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContainerAddAllButtonCell.swift; sourceTree = ""; }; + DFA56CED2AEFEE45007D7F7E /* SettingsSivaCertCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsSivaCertCell.swift; sourceTree = ""; }; + DFA56CF12AF003F8007D7F7E /* RadioButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RadioButton.swift; sourceTree = ""; }; + DFA56CF32AF009E5007D7F7E /* RadioButton.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = RadioButton.xib; sourceTree = ""; }; DFA58218264AAB6500564971 /* digidoc_76.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = digidoc_76.png; sourceTree = ""; }; DFA58219264AAB6600564971 /* digidoc_1024.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = digidoc_1024.png; sourceTree = ""; }; DFAFD38529E8794200377EF5 /* SearchField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchField.swift; sourceTree = ""; }; @@ -564,7 +572,7 @@ DFD176D223F427AD00E2CC52 /* TSLVersionChecker.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSLVersionChecker.swift; sourceTree = ""; }; DFD176D323F427AD00E2CC52 /* TSLUpdater.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TSLUpdater.swift; sourceTree = ""; }; DFD54CF429CDDB0600CD92C7 /* SettingsTSACertCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsTSACertCell.swift; sourceTree = ""; }; - DFD54CF629D1CBAF00CD92C7 /* TSACertUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TSACertUtil.swift; sourceTree = ""; }; + DFD54CF629D1CBAF00CD92C7 /* CertUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CertUtil.swift; sourceTree = ""; }; DFD8BEF4291C432400FE8F07 /* ScaledButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScaledButton.swift; sourceTree = ""; }; DFD8BEF6291C434500FE8F07 /* FontUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FontUtil.swift; sourceTree = ""; }; DFDC0AB929FAD8F2002D1D1D /* ViewUtil.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewUtil.swift; sourceTree = ""; }; @@ -799,6 +807,9 @@ C50DCD231FD69B0C00D48E16 /* SpinnerView.swift */, C54EA7392057E58D0039AC78 /* MoppButton.swift */, DFB970A22626136300464791 /* PreviewFileTapGestureRecognizer.swift */, + DFA56CF12AF003F8007D7F7E /* RadioButton.swift */, + DFA56CF32AF009E5007D7F7E /* RadioButton.xib */, + DF50BBCE2AF06A0800C1A7D0 /* SivaChoiceTapGestureRecognizer.swift */, ); name = "Custom views"; sourceTree = ""; @@ -934,6 +945,7 @@ DF8EFB19287498B900A96DE3 /* SettingsDefaultValueCell.swift */, DFD54CF429CDDB0600CD92C7 /* SettingsTSACertCell.swift */, DFBDF1F127D79B8B00A5CF3C /* SettingsStateCell.swift */, + DFA56CED2AEFEE45007D7F7E /* SettingsSivaCertCell.swift */, ); name = Settings; sourceTree = ""; @@ -1192,11 +1204,11 @@ DF46181E2962E464003A1B56 /* FileUtil.swift */, DFBA32A529A5A64000788A87 /* CancelUtil.swift */, DFDC0AB929FAD8F2002D1D1D /* ViewUtil.swift */, - DFD54CF629D1CBAF00CD92C7 /* TSACertUtil.swift */, - DFB66BC22AE9BD4D00731B47 /* TextUtil.swift */, - DFBDF20327DF89A100A5CF3C /* RoleAndAddressUtil.swift */, + DFB66BC22AE9BD4D00731B47 /* TextUtil.swift */, + DFBDF20327DF89A100A5CF3C /* RoleAndAddressUtil.swift */, DF39505F2AD766DF005D96AC /* KeychainUtil.swift */, DF7936332AE98AAD00441B41 /* AccessibilityUtil.swift */, + DFD54CF629D1CBAF00CD92C7 /* CertUtil.swift */, ); name = Utility; sourceTree = ""; @@ -1423,6 +1435,7 @@ E4250D0A1E0AA7AF00530370 /* LaunchScreen.xib in Resources */, 54A5729F1E82AC760099BFAF /* AppStore.plist in Resources */, C50DCCF61FC5741900D48E16 /* Roboto-BoldCondensedItalic.ttf in Resources */, + DFA56CF42AF009E5007D7F7E /* RadioButton.xib in Resources */, DFA5821A264AAB6600564971 /* digidoc_76.png in Resources */, C593D9EC208F19D7000B3BF6 /* Settings.storyboard in Resources */, C5AAAF8020C93B920087D6DA /* main_about_fonds_en.png in Resources */, @@ -1564,6 +1577,7 @@ C50DCD481FDED76400D48E16 /* SignatureDetailsCell.swift in Sources */, DF0C2B3F29150EAB007E1745 /* ScaledLabel.swift in Sources */, C5B918E01FE280A7000EDD36 /* ContainerNoSignaturesCell.swift in Sources */, + DFA56CF22AF003F8007D7F7E /* RadioButton.swift in Sources */, C593D9F6208F39DF000B3BF6 /* SettingsChoiceButton.swift in Sources */, DFEFF8DD2AD6DCE3003D452B /* TokenFlowSigning.swift in Sources */, C58FCD5F20650BBE00FE3B57 /* MyeIDInfoCell.swift in Sources */, @@ -1576,6 +1590,7 @@ C50DCD241FD69B0C00D48E16 /* SpinnerView.swift in Sources */, DF0075C223324AC600BAB3DE /* MoppConfigurationDecoder.swift in Sources */, C52E827E1FC1DBFE0074B280 /* MoppViewController.swift in Sources */, + DF50BBCF2AF06A0800C1A7D0 /* SivaChoiceTapGestureRecognizer.swift in Sources */, C5E7D08D2031B1040081416F /* NativeShare.swift in Sources */, DFB970A32626136400464791 /* PreviewFileTapGestureRecognizer.swift in Sources */, DFC2ADC8294377BF008A1CD2 /* PersonalCodeValidator.swift in Sources */, @@ -1618,6 +1633,7 @@ C50FC431224BA3B20041925C /* ScreenDisguise.swift in Sources */, DF32A690290C983100AE5F82 /* MessageUtil.swift in Sources */, C50787881FEA807500D9AAC0 /* UIViewController+Additions.swift in Sources */, + DFA56CEE2AEFEE45007D7F7E /* SettingsSivaCertCell.swift in Sources */, C506EC7C1FB9CBFD00E07226 /* Constants.swift in Sources */, DFD54CF529CDDB0600CD92C7 /* SettingsTSACertCell.swift in Sources */, C58FCD5B206505BE00FE3B57 /* MyeIDStatusViewController.swift in Sources */, @@ -1685,7 +1701,7 @@ 3921CA9720B2D8A600BF3178 /* SigningActions.swift in Sources */, C52E827A1FC1D7000074B280 /* main.swift in Sources */, C5964CEA200F8607001FE732 /* ContainerImportFilesCell.swift in Sources */, - DFD54CF729D1CBAF00CD92C7 /* TSACertUtil.swift in Sources */, + DFD54CF729D1CBAF00CD92C7 /* CertUtil.swift in Sources */, 4E59080024B0F914001B23A6 /* SmartIDEditViewController.swift in Sources */, DFD8BEF7291C434500FE8F07 /* FontUtil.swift in Sources */, C50DCD1B1FD1576B00D48E16 /* SigningTableViewHeaderView.swift in Sources */, diff --git a/MoppApp/MoppApp/TSACertUtil.swift b/MoppApp/MoppApp/CertUtil.swift similarity index 55% rename from MoppApp/MoppApp/TSACertUtil.swift rename to MoppApp/MoppApp/CertUtil.swift index c744475b..2f789ff8 100644 --- a/MoppApp/MoppApp/TSACertUtil.swift +++ b/MoppApp/MoppApp/CertUtil.swift @@ -1,5 +1,5 @@ // -// TSACertUtil.swift +// CertUtil.swift // MoppApp // /* @@ -24,43 +24,40 @@ import Foundation import ASN1Decoder -class TSACertUtil { +class CertUtil { - static let tsaFileFolder = "tsa-cert" - - static func getTsaCertFile() -> URL? { - if !DefaultsHelper.tsaCertFileName.isNilOrEmpty { - do { - let documentsUrl = URL(fileURLWithPath: MoppFileManager.shared.documentsDirectoryPath()) - let tsaCertLocation = documentsUrl.appendingPathComponent(tsaFileFolder, isDirectory: true).appendingPathComponent(DefaultsHelper.tsaCertFileName ?? "-", isDirectory: false) - if try tsaCertLocation.checkResourceIsReachable() { - return tsaCertLocation - } - } catch let openFileError { - printLog("Failed to get '\(DefaultsHelper.tsaCertFileName ?? "TSA certificate")'. Error: \(openFileError.localizedDescription)") - return nil + static func getCertFile(folder: String, fileName: String) -> URL? { + do { + let documentsUrl = URL(fileURLWithPath: MoppFileManager.shared.documentsDirectoryPath()) + let certLocation = documentsUrl.appendingPathComponent(folder, isDirectory: true).appendingPathComponent(fileName, isDirectory: false) + if try certLocation.checkResourceIsReachable() { + return certLocation } + } catch let openFileError { + printLog("Failed to get '\(fileName)' certificate. Error: \(openFileError.localizedDescription)") + return nil } + return nil } - static func getCertificate() -> X509Certificate? { - let tsaCertLocation = getTsaCertFile() + static func getCertificate(folder: String, fileName: String) -> X509Certificate? { + let certLocation = getCertFile(folder: folder, fileName: fileName) do { - return try openCertificate(tsaCertLocation) + return try openCertificate(certLocation) } catch let openFileError { - printLog("Failed to open '\(tsaCertLocation?.lastPathComponent ?? "TSA certificate")'. Error: \(openFileError.localizedDescription)") + printLog("Failed to open '\(certLocation?.lastPathComponent ?? "certificate")'. Error: \(openFileError.localizedDescription)") return nil } } - static func openCertificate(_ certificateLocation: URL? = getTsaCertFile()) throws -> X509Certificate? { + static func openCertificate(_ certificateLocation: URL?) throws -> X509Certificate? { guard let certLocation = certificateLocation else { return nil } let fileData = try Data(contentsOf: certLocation) return try X509Certificate(data: fileData) } - static func certificateString(_ certificateLocation: URL? = getTsaCertFile()) -> String? { + static func certificateString(_ certificateLocation: URL?) -> String? { guard let certLocation = certificateLocation else { return nil } do { return try String(contentsOf: certLocation) @@ -72,4 +69,11 @@ class TSACertUtil { return nil } } + + static func removeCertificate(folder: String, fileName: String) { + let certLocation = getCertFile(folder: folder, fileName: fileName) + if let certPath = certLocation?.path { + MoppFileManager.shared.removeFile(withPath: certPath) + } + } } diff --git a/MoppApp/MoppApp/CertificateDetailViewController.swift b/MoppApp/MoppApp/CertificateDetailViewController.swift index d58e3a2c..715a8e7a 100644 --- a/MoppApp/MoppApp/CertificateDetailViewController.swift +++ b/MoppApp/MoppApp/CertificateDetailViewController.swift @@ -72,11 +72,6 @@ class CertificateDetailViewController: MoppViewController { tableView.backgroundColor = UIColor.white tableView.separatorStyle = UITableViewCell.SeparatorStyle.none - - // Remove buttons from tab bar - if let landingViewController = LandingViewController.shared { - landingViewController.presentButtons([]) - } navigationItem.titleView = getTitleLabel(forTitle: L(.certificateDetailsTitle)) navigationItem.setLeftBarButton(getBackBarButtomItem(), animated: true) diff --git a/MoppApp/MoppApp/Constants.swift b/MoppApp/MoppApp/Constants.swift index 59a9bea9..9be53ee1 100644 --- a/MoppApp/MoppApp/Constants.swift +++ b/MoppApp/MoppApp/Constants.swift @@ -62,6 +62,6 @@ extension Notification.Name { static let filesImportedNotificationName = Notification.Name("FilesImportedNotificationName") static let startImportingFilesWithDocumentPickerNotificationName = Notification.Name("StartImportingFilesNotificationName") static let didOpenUrlNotificationName = Notification.Name("DidOpenUrlNotificationName") - static let hideKeyboardAccessibility = NSNotification.Name("HideKeyboardAccessibility") + static let focusedAccessibilityElement = NSNotification.Name("FocusedAccessibilityElement") static let isBackButtonPressed = NSNotification.Name("IsBackButtonPressed") } diff --git a/MoppApp/MoppApp/ContainerActions.swift b/MoppApp/MoppApp/ContainerActions.swift index d7415bc4..76302e9e 100644 --- a/MoppApp/MoppApp/ContainerActions.swift +++ b/MoppApp/MoppApp/ContainerActions.swift @@ -142,6 +142,10 @@ extension ContainerActions where Self: UIViewController { navController?.viewControllers.last!.present(alert, animated: true) return + } else if err?.code == 10027 { + let alert = AlertUtil.messageAlert(message: L(.sslHandshakeMessage), alertAction: nil) + + navController?.viewControllers.last!.present(alert, animated: true) } else { let alert = AlertUtil.messageAlert(message: L(.fileImportOpenExistingFailedAlertMessage, [fileName]), alertAction: nil) navController?.viewControllers.last!.present(alert, animated: true) diff --git a/MoppApp/MoppApp/ContainerViewController.swift b/MoppApp/MoppApp/ContainerViewController.swift index 2b67eaa0..d6f84b72 100644 --- a/MoppApp/MoppApp/ContainerViewController.swift +++ b/MoppApp/MoppApp/ContainerViewController.swift @@ -717,9 +717,22 @@ extension ContainerViewController : UITableViewDataSource { self.reloadData() } failure: { error in - self.isLoadingNestedAsicsDone = true - self.isSendingToSivaAgreed = false - self.reloadContainer() + guard let nsError = error as NSError? else { + self.infoAlert(message: L(.genericErrorMessage)) + return + } + + if nsError.code == 10027 { + let alert = AlertUtil.messageAlert(message: L(.sslHandshakeMessage), alertAction: nil) + self.navigationController?.popViewController(animated: true) + self.navigationController?.viewControllers.last!.present(alert, animated: true) + return + } else { + self.isLoadingNestedAsicsDone = true + self.isSendingToSivaAgreed = false + self.reloadContainer() + } + } } failure: { error in diff --git a/MoppApp/MoppApp/DefaultsHelper.swift b/MoppApp/MoppApp/DefaultsHelper.swift index b9a2bdcf..9d41d956 100644 --- a/MoppApp/MoppApp/DefaultsHelper.swift +++ b/MoppApp/MoppApp/DefaultsHelper.swift @@ -72,6 +72,9 @@ fileprivate let kIsFileLoggingEnabled = "kIsFileLoggingEnabled" fileprivate let kIsFileLoggingRunning = "kIsFileLoggingRunning" fileprivate let kTSAFileCertName = "kTSAFileCertName" fileprivate let kIsRoleAndAddressEnabled = "kIsRoleAndAddressEnabled" +fileprivate let kSivaAccessState = "kSivaAccessState" +fileprivate let kSivaUrl = "kSivaUrl" +fileprivate let kSivaFileCertName = "kSivaFileCertName" class DefaultsHelper { @@ -157,7 +160,6 @@ class DefaultsHelper } } - class var timestampUrl: String? { set { UserDefaults.standard.set(newValue, forKey: kTimestampUrlKey) @@ -305,4 +307,31 @@ class DefaultsHelper return (UserDefaults.standard.value(forKey: kRoleZipKey) as? String) ?? String() } } + + class var sivaAccessState: SivaAccess { + set { + UserDefaults.standard.set(newValue.rawValue, forKey: kSivaAccessState) + } + get { + return SivaAccess(rawValue: UserDefaults.standard.value(forKey: kSivaAccessState) as? String ?? "") ?? .defaultAccess + } + } + + class var sivaUrl: String? { + set { + UserDefaults.standard.set(newValue, forKey: kSivaUrl) + } + get { + return UserDefaults.standard.value(forKey: kSivaUrl) as? String + } + } + + class var sivaCertFileName: String? { + set { + UserDefaults.standard.set(newValue, forKey: kSivaFileCertName) + } + get { + return UserDefaults.standard.value(forKey: kSivaFileCertName) as? String + } + } } diff --git a/MoppApp/MoppApp/IdCardViewController.swift b/MoppApp/MoppApp/IdCardViewController.swift index da67d371..a91c417f 100644 --- a/MoppApp/MoppApp/IdCardViewController.swift +++ b/MoppApp/MoppApp/IdCardViewController.swift @@ -189,7 +189,7 @@ class IdCardViewController : MoppViewController, TokenFlowSigning { self?.keyboardDelegate?.idCardPINKeyboardWillDisappear() } - NotificationCenter.default.addObserver(self, selector: #selector(hideKeyboardAccessibility), name: .hideKeyboardAccessibility, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(hideKeyboardAccessibility), name: .focusedAccessibilityElement, object: nil) MoppLibCardReaderManager.sharedInstance().delegate = self MoppLibCardReaderManager.sharedInstance().startDiscoveringReaders() diff --git a/MoppApp/MoppApp/LocalizationKeys.swift b/MoppApp/MoppApp/LocalizationKeys.swift index 53bc25a3..dcc2853f 100644 --- a/MoppApp/MoppApp/LocalizationKeys.swift +++ b/MoppApp/MoppApp/LocalizationKeys.swift @@ -210,6 +210,7 @@ enum LocKey : String case decryptionErrorMessage = "decryption-error-message" case signingErrorTooManyRequests = "signing-error-too-many-requests" case noConnectionMessage = "no-response-error" + case sslHandshakeMessage = "ssl-handshake-error" case ocspInvalidTimeSlot = "ocsp-invalid-time-slot" case signingAbortedMessage = "signing-aborted-message" case myEidStatusReaderNotFound = "my-eid-status-reader-not-found" @@ -312,6 +313,10 @@ enum LocKey : String case settingsTimestampCertValidToLabel = "settings-timestamp-cert-valid-to-label" case settingsTimestampCertAddCertificateButton = "settings-timestamp-cert-add-certificate-button" case settingsTimestampCertShowCertificateButton = "settings-timestamp-cert-show-certificate-button" + case settingsSivaServiceTitle = "settings-siva-service-title" + case settingsSivaDefaultAccessTitle = "settings-siva-default-access-title" + case settingsSivaDefaultManualAccessTitle = "settings-siva-default-manual-access-title" + case settingsSivaDefaultCertificateTitle = "settings-siva-default-certificate-title" case diagnosticsTitle = "diagnostics-title" case diagnosticsActivateOneTimeLogging = "diagnostics-activate-one-time-logging" case diagnosticsSaveLog = "diagnostics-save-log" @@ -479,4 +484,7 @@ enum LocKey : String case voiceControlRoleCountry = "voice-control-role-country"; case voiceControlRoleZip = "voice-control-role-zip"; case voiceControlRoleAndAddress = "voice-control-role-and-address"; + case voiceControlSivaService = "voice-control-siva-service"; + case voiceControlSivaDefaultAccess = "voice-control-siva-default-access"; + case voiceControlSivaManualAccess = "voice-control-siva-manual-access"; } diff --git a/MoppApp/MoppApp/MobileIDEditViewController.swift b/MoppApp/MoppApp/MobileIDEditViewController.swift index d1a3674d..9f4c739a 100644 --- a/MoppApp/MoppApp/MobileIDEditViewController.swift +++ b/MoppApp/MoppApp/MobileIDEditViewController.swift @@ -127,7 +127,7 @@ class MobileIDEditViewController : MoppViewController, TokenFlowSigning { self.view.accessibilityElements = [titleUILabel, phoneUILabel, phoneUITextField, phoneNumberErrorUILabel, idCodeUILabel, idCodeUITextField, personalCodeUIErrorLabel, rememberUILabel, rememberUISwitch, cancelUIButton, signUIButton] } - NotificationCenter.default.addObserver(self, selector: #selector(handleAccessibilityKeyboard), name: .hideKeyboardAccessibility, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleAccessibilityKeyboard), name: .focusedAccessibilityElement, object: nil) } @objc func dismissKeyboard(_ notification: NSNotification) { diff --git a/MoppApp/MoppApp/RadioButton.swift b/MoppApp/MoppApp/RadioButton.swift new file mode 100644 index 00000000..4445a4d5 --- /dev/null +++ b/MoppApp/MoppApp/RadioButton.swift @@ -0,0 +1,73 @@ +// +// RadioButton.swift +// MoppApp +// +/* + * Copyright 2017 - 2023 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 UIKit +class RadioButton: UIView { + + let nibName = "RadioButton" + + var isSelectedState: Bool = false { + didSet { + setNeedsDisplay() + } + } + + var accessType: SivaAccess = .defaultAccess + + @IBOutlet weak var outerLayer: UIView! + @IBOutlet weak var middleLayer: UIView! + @IBOutlet weak var innerLayer: UIView! + + required init?(coder aDecoder: NSCoder) { + super.init(coder: aDecoder) + + guard let view = loadView() else { return } + outerLayer.layer.cornerRadius = outerLayer.frame.height / 2 + outerLayer.layer.borderWidth = 2 + outerLayer.layer.borderColor = UIColor.moppBase.cgColor + outerLayer.layer.backgroundColor = UIColor.clear.cgColor + + middleLayer.layer.cornerRadius = middleLayer.frame.height / 2 + middleLayer.layer.borderColor = UIColor.clear.cgColor + middleLayer.layer.backgroundColor = UIColor.clear.cgColor + + innerLayer.layer.borderWidth = 2 + innerLayer.layer.cornerRadius = innerLayer.frame.height / 2 + innerLayer.layer.borderColor = UIColor.moppBase.cgColor + innerLayer.layer.backgroundColor = UIColor.moppBase.cgColor + + view.frame = self.bounds + self.addSubview(view) + } + + func setSelectedState(state: Bool) { + isSelectedState = state + innerLayer.isHidden = !isSelectedState + } + + func loadView() -> UIView? { + let bundle = Bundle(for: type(of: self)) + let nib = UINib(nibName: nibName, bundle: bundle) + return nib.instantiate(withOwner: self, options: nil).first as? UIView + } +} diff --git a/MoppApp/MoppApp/RadioButton.xib b/MoppApp/MoppApp/RadioButton.xib new file mode 100644 index 00000000..32a0f1cb --- /dev/null +++ b/MoppApp/MoppApp/RadioButton.xib @@ -0,0 +1,68 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/MoppApp/MoppApp/ScaledButton.swift b/MoppApp/MoppApp/ScaledButton.swift index b942cbca..7472d90e 100644 --- a/MoppApp/MoppApp/ScaledButton.swift +++ b/MoppApp/MoppApp/ScaledButton.swift @@ -67,7 +67,7 @@ class ScaledButton: UIButton { } override func accessibilityElementDidBecomeFocused() { - NotificationCenter.default.post(name: .hideKeyboardAccessibility, object: nil, userInfo: ["view": self]) + NotificationCenter.default.post(name: .focusedAccessibilityElement, object: nil, userInfo: ["view": self]) self.becomeFirstResponder() } diff --git a/MoppApp/MoppApp/ScaledLabel.swift b/MoppApp/MoppApp/ScaledLabel.swift index 29062fbb..4357dcca 100644 --- a/MoppApp/MoppApp/ScaledLabel.swift +++ b/MoppApp/MoppApp/ScaledLabel.swift @@ -56,7 +56,8 @@ class ScaledLabel: UILabel { } override func accessibilityElementDidBecomeFocused() { - NotificationCenter.default.post(name: .hideKeyboardAccessibility, object: nil, userInfo: ["view": self]) + super.accessibilityElementDidBecomeFocused() + NotificationCenter.default.post(name: .focusedAccessibilityElement, object: nil, userInfo: ["view": self]) self.becomeFirstResponder() } } diff --git a/MoppApp/MoppApp/ScaledTextField.swift b/MoppApp/MoppApp/ScaledTextField.swift index 84d82763..9e4e7568 100644 --- a/MoppApp/MoppApp/ScaledTextField.swift +++ b/MoppApp/MoppApp/ScaledTextField.swift @@ -55,6 +55,6 @@ class ScaledTextField: UITextField { } override func accessibilityElementDidBecomeFocused() { - NotificationCenter.default.post(name: .hideKeyboardAccessibility, object: nil, userInfo: ["view": self]) + NotificationCenter.default.post(name: .focusedAccessibilityElement, object: nil, userInfo: ["view": self]) } } diff --git a/MoppApp/MoppApp/Settings.storyboard b/MoppApp/MoppApp/Settings.storyboard index 1ebb600a..f5765d64 100644 --- a/MoppApp/MoppApp/Settings.storyboard +++ b/MoppApp/MoppApp/Settings.storyboard @@ -1,11 +1,12 @@ - + - + + @@ -25,11 +26,11 @@ - + - + @@ -215,14 +216,14 @@ - + - + - + - + + @@ -258,7 +260,7 @@ - + @@ -272,6 +274,9 @@ - + @@ -381,6 +386,257 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -401,7 +657,7 @@ - + @@ -898,5 +1154,8 @@ + + + diff --git a/MoppApp/MoppApp/SettingsConfiguration.swift b/MoppApp/MoppApp/SettingsConfiguration.swift index 62163c52..75387c21 100644 --- a/MoppApp/MoppApp/SettingsConfiguration.swift +++ b/MoppApp/MoppApp/SettingsConfiguration.swift @@ -308,7 +308,7 @@ class SettingsConfiguration: NSObject, URLSessionDelegate, URLSessionTaskDelegat MoppConfiguration.tsaUrl = tsaUrl MoppConfiguration.ocspIssuers = ocspIssuers MoppConfiguration.certBundle = certBundle - MoppConfiguration.tsaCert = TSACertUtil.certificateString() + MoppConfiguration.tsaCert = CertUtil.certificateString(CertUtil.getCertFile(folder: "tsa-cert", fileName: DefaultsHelper.tsaCertFileName ?? "")) } private func setupMoppLDAPConfiguration(ldapCerts: Array, ldapPersonUrl: String, ldapCorpUrl: String) { diff --git a/MoppApp/MoppApp/SettingsDefaultValueCell.swift b/MoppApp/MoppApp/SettingsDefaultValueCell.swift index 0844a5c3..226aab8b 100644 --- a/MoppApp/MoppApp/SettingsDefaultValueCell.swift +++ b/MoppApp/MoppApp/SettingsDefaultValueCell.swift @@ -39,6 +39,17 @@ class SettingsDefaultValueCell: UITableViewCell { } weak var delegate: SettingsDefaultValueCellDelegate! + + override func awakeFromNib() { + updateUI() + + useDefaultSwitch.accessibilityLabel = "\(useDefaultTitleLabel.text ?? L(.settingsTimestampUseDefaultTitle)) \(L(.settingsTimestampUrlTitle))" + useDefaultSwitch.isUserInteractionEnabled = true + useDefaultSwitch.isAccessibilityElement = true + + guard let useDefaultUISwitch = useDefaultSwitch else { return } + accessibilityElements = [useDefaultUISwitch] + } func populate() { updateUI() diff --git a/MoppApp/MoppApp/SettingsFieldCell.swift b/MoppApp/MoppApp/SettingsFieldCell.swift index 2016e851..94fb3eb9 100644 --- a/MoppApp/MoppApp/SettingsFieldCell.swift +++ b/MoppApp/MoppApp/SettingsFieldCell.swift @@ -22,8 +22,10 @@ */ import UIKit -protocol SettingsFieldCellDelegate: AnyObject { - func didEndEditingField(_ field: SettingsViewController.FieldId, with value:String) + +protocol SettingsCellDelegate: AnyObject { + func didStartEditingField(_ field: SettingsViewController.FieldId, _ indexPath: IndexPath) + func didEndEditingField(_ field: SettingsViewController.FieldId, with value: String) } class SettingsFieldCell: UITableViewCell { @@ -31,7 +33,7 @@ class SettingsFieldCell: UITableViewCell { @IBOutlet weak var textField: UITextField! var field: SettingsViewController.Field! - weak var delegate: SettingsFieldCellDelegate! + weak var delegate: SettingsCellDelegate! override func awakeFromNib() { super.awakeFromNib() @@ -72,6 +74,16 @@ class SettingsFieldCell: UITableViewCell { } extension SettingsFieldCell: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + var currentIndexPath = IndexPath(item: 1, section: 1) + if let tableView = superview as? UITableView { + if let indexPath = tableView.indexPath(for: self) { + currentIndexPath = indexPath + } + } + delegate.didStartEditingField(field.id, currentIndexPath) + } + func textFieldDidEndEditing(_ textField: UITextField) { delegate.didEndEditingField(field.id, with: textField.text ?? String()) UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: textField) diff --git a/MoppApp/MoppApp/SettingsHeaderCell.swift b/MoppApp/MoppApp/SettingsHeaderCell.swift index 168adb85..68f7e501 100644 --- a/MoppApp/MoppApp/SettingsHeaderCell.swift +++ b/MoppApp/MoppApp/SettingsHeaderCell.swift @@ -41,11 +41,15 @@ class SettingsHeaderCell: UITableViewCell { super.awakeFromNib() dismissButton.setTitle(L(.closeButton)) + dismissButton.accessibilityTraits = .button + dismissButton.accessibilityLabel = L(.closeButton) + dismissButton.isUserInteractionEnabled = true + dismissButton.isAccessibilityElement = true guard let dismissUIButton: UIButton = dismissButton, let titleUILabel: UILabel = titleLabel else { printLog("Unable to get dismissButton or titleLabel") return } - self.accessibilityElements = [titleUILabel, dismissUIButton] + accessibilityElements = [dismissUIButton, titleUILabel] } } diff --git a/MoppApp/MoppApp/SettingsSivaCertCell.swift b/MoppApp/MoppApp/SettingsSivaCertCell.swift new file mode 100644 index 00000000..3d31b172 --- /dev/null +++ b/MoppApp/MoppApp/SettingsSivaCertCell.swift @@ -0,0 +1,342 @@ +// +// SettingsSivaCertCell.swift +// MoppApp +// +/* + * Copyright 2017 - 2023 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 UIKit +import ASN1Decoder + +enum SivaAccess: String, Codable { + case defaultAccess + case manualAccess +} + +class SettingsSivaCertCell: UITableViewCell { + + static let sivaFileFolder = "siva-cert" + + private var certificate: X509Certificate? + private let configuration: MOPPConfiguration = Configuration.getConfiguration() + + var field: SettingsViewController.Field! + weak var delegate: SettingsCellDelegate! + + weak var topViewController: UIViewController? + + @IBOutlet weak var signatureValidationTitle: ScaledLabel! + + @IBOutlet weak var certificateStackView: UIStackView! + @IBOutlet weak var certificateDataStackView: UIStackView! + + @IBOutlet weak var validationAccessStackView: UIStackView! + @IBOutlet weak var validationAccessChoiceStackView: UIStackView! + + @IBOutlet weak var useDefaultAccessStackView: UIStackView! + @IBOutlet weak var useDefaultAccessView: UIView! + @IBOutlet weak var useDefaultAccessRadioButton: RadioButton! + @IBOutlet weak var useDefaultAccessLabel: UILabel! + + @IBOutlet weak var useManuallyConfiguredAccessStackView: UIStackView! + @IBOutlet weak var useManuallyConfiguredAccessView: UIView! + @IBOutlet weak var useManuallyConfiguredAccessRadioButton: RadioButton! + @IBOutlet weak var useManuallyConfiguredAccessLabel: UILabel! + + @IBOutlet weak var sivaUrlTextField: SettingsTextField! + + @IBOutlet weak var validationServiceCertificateTitle: ScaledLabel! + + @IBOutlet weak var issuedToLabel: ScaledLabel! + @IBOutlet weak var validUntilLabel: ScaledLabel! + + @IBOutlet weak var certificateButtonsStackView: UIStackView! + @IBOutlet weak var addCertificateButton: ScaledButton! + @IBOutlet weak var showCertificateButton: ScaledButton! + + + @IBAction func addCertificate(_ sender: ScaledButton) { + let documentPicker: UIDocumentPickerViewController = { + let allowedDocumentTypes = ["public.x509-certificate"] + let documentPickerViewController = UIDocumentPickerViewController(documentTypes: allowedDocumentTypes, in: .import) + documentPickerViewController.delegate = self + documentPickerViewController.modalPresentationStyle = .overCurrentContext + documentPickerViewController.allowsMultipleSelection = false + return documentPickerViewController + }() + + topViewController?.present(documentPicker, animated: true) + } + + @IBAction func showCertificate(_ sender: ScaledButton) { + guard let cert = self.certificate else { printLog("Unable to show certificate"); return } + let certificateDetailsViewController = UIStoryboard.container.instantiateViewController(of: CertificateDetailViewController.self) + let certificateDetail = SignatureCertificateDetail(x509Certificate: cert, secCertificate: nil) + certificateDetailsViewController.certificateDetail = certificateDetail + certificateDetailsViewController.useDefaultNavigationItems = false + let certificateNC = UINavigationController(rootViewController: certificateDetailsViewController) + certificateNC.modalPresentationStyle = .pageSheet + + if let certificatePC = certificateNC.presentationController as? UISheetPresentationController { + certificatePC.detents = [ + .large() + ] + certificatePC.prefersGrabberVisible = true + } + certificateNC.view.backgroundColor = .white + topViewController?.present(certificateNC, animated: true) + } + + override func awakeFromNib() { + sivaUrlTextField.moppPresentDismissButton() + sivaUrlTextField.layer.borderColor = UIColor.moppContentLine.cgColor + sivaUrlTextField.layer.borderWidth = 1 + sivaUrlTextField.delegate = self + + sivaUrlTextField.isAccessibilityElement = true + sivaUrlTextField.accessibilityLabel = L(.settingsSivaServiceTitle) + sivaUrlTextField.accessibilityUserInputLabels = [L(.voiceControlSivaService)] + + useDefaultAccessView.accessibilityUserInputLabels = [L(.voiceControlSivaDefaultAccess)] + useManuallyConfiguredAccessView.accessibilityUserInputLabels = [L(.voiceControlSivaManualAccess)] + + guard let signatureValidationUITitle = signatureValidationTitle, let useDefaultAccessUIView = useDefaultAccessView, let useManuallyConfiguredAccessUIView = useManuallyConfiguredAccessView, let sivaUrlUITextfield: UITextField = sivaUrlTextField, let validationServiceCertificateUITitle = validationServiceCertificateTitle, let issuedToUILabel = issuedToLabel, let validUntilUILabel = validUntilLabel, let addCertificateUIButton = addCertificateButton, let showCertificateUIButton = showCertificateButton else { + printLog("Unable to get sivaUrlTextField") + return + } + + if UIAccessibility.isVoiceOverRunning { + self.accessibilityElements = [signatureValidationUITitle, useDefaultAccessUIView, useManuallyConfiguredAccessUIView, sivaUrlUITextfield, validationServiceCertificateUITitle, issuedToUILabel, validUntilUILabel, addCertificateUIButton, showCertificateUIButton] + } + + updateUI() + } + + func populate(with field:SettingsViewController.Field) { + certificate = CertUtil.getCertificate(folder: SettingsSivaCertCell.sivaFileFolder, fileName: DefaultsHelper.sivaCertFileName ?? "") + sivaUrlTextField.attributedPlaceholder = getSivaPlaceholder() + self.field = field + + if let _ = certificate { + updateUI() + } + } + + func getSivaPlaceholder() -> NSAttributedString { + let attributes = [ + NSAttributedString.Key.foregroundColor: UIColor.placeholderText, + NSAttributedString.Key.font : UIFont(name: "Roboto-Regular", size: 14) ?? UIFont.systemFont(ofSize: 14) + ] + return NSAttributedString(string: configuration.SIVAURL, attributes: attributes) + } + + func setAccessibilityElementsInStackView(stackView: UIStackView, isAccessibilityElement: Bool) { + for subview in stackView.arrangedSubviews { + subview.isAccessibilityElement = isAccessibilityElement + } + } + + @objc func handleState(_ sender: SivaChoiceTapGestureRecognizer) { + switch sender.accessType { + case .defaultAccess: + self.useDefaultAccessRadioButton.setSelectedState(state: true) + self.useManuallyConfiguredAccessRadioButton.setSelectedState(state: false) + DefaultsHelper.sivaAccessState = .defaultAccess + DefaultsHelper.sivaUrl = configuration.SIVAURL + self.sivaUrlTextField.isEnabled = false + certificateStackView.isHidden = true + sivaUrlTextField.textColor = UIColor.lightGray + if let sivaUrl = sivaUrlTextField.text, sivaUrl.isEmpty { + sivaUrlTextField.text = "" + sivaUrlTextField.attributedPlaceholder = getSivaPlaceholder() + } + case .manualAccess: + self.useDefaultAccessRadioButton.setSelectedState(state: false) + self.useManuallyConfiguredAccessRadioButton.setSelectedState(state: true) + DefaultsHelper.sivaAccessState = .manualAccess + self.sivaUrlTextField.isEnabled = true + certificateStackView.isHidden = false + sivaUrlTextField.textColor = UIColor.black + if let sivaUrl = sivaUrlTextField.text, !sivaUrl.isEmpty { + DefaultsHelper.sivaUrl = sivaUrlTextField.text + } else { + sivaUrlTextField.text = "" + } + } + } + + func updateUI() { + DispatchQueue.main.async { + + self.useDefaultAccessRadioButton.accessType = .defaultAccess + self.useManuallyConfiguredAccessRadioButton.accessType = .manualAccess + + let savedAccessState = DefaultsHelper.sivaAccessState + self.useDefaultAccessRadioButton.setSelectedState(state: savedAccessState == .defaultAccess) + self.useManuallyConfiguredAccessRadioButton.setSelectedState(state: savedAccessState == .manualAccess) + + self.signatureValidationTitle.text = L(.settingsSivaServiceTitle) + + self.validationServiceCertificateTitle.text = L(.settingsSivaDefaultCertificateTitle) + + self.certificateStackView.isHidden = savedAccessState == .defaultAccess + + self.useDefaultAccessLabel.text = L(.settingsSivaDefaultAccessTitle) + self.useManuallyConfiguredAccessLabel.text = L(.settingsSivaDefaultManualAccessTitle) + + self.sivaUrlTextField.isEnabled = savedAccessState != .defaultAccess + + let sivaUrl = DefaultsHelper.sivaUrl + if sivaUrl == self.configuration.SIVAURL { + self.sivaUrlTextField.text = "" + } else { + self.sivaUrlTextField.text = sivaUrl + } + + // Detect which RadioButton was clicked + if !(self.useDefaultAccessView.gestureRecognizers?.contains(where: { $0 is SivaChoiceTapGestureRecognizer }) ?? false) { + + let tapGesture = SivaChoiceTapGestureRecognizer(target: self, action: #selector(self.handleState(_:))) + tapGesture.accessType = .defaultAccess + self.useDefaultAccessView.addGestureRecognizer(tapGesture) + self.useDefaultAccessView.isUserInteractionEnabled = true + } + + if !(self.useManuallyConfiguredAccessView.gestureRecognizers?.contains(where: { $0 is SivaChoiceTapGestureRecognizer }) ?? false) { + let tapGesture = SivaChoiceTapGestureRecognizer(target: self, action: #selector(self.handleState(_:))) + tapGesture.accessType = .manualAccess + self.useManuallyConfiguredAccessView.addGestureRecognizer(tapGesture) + self.useManuallyConfiguredAccessView.isUserInteractionEnabled = true + } + + self.useDefaultAccessView.isAccessibilityElement = true + self.useManuallyConfiguredAccessView.isAccessibilityElement = true + + self.useDefaultAccessView.accessibilityLabel = L(.settingsSivaDefaultAccessTitle) + + self.useManuallyConfiguredAccessView.accessibilityLabel = L(.settingsSivaDefaultManualAccessTitle) + + self.issuedToLabel.text = L(.settingsTimestampCertIssuedToLabel) + self.validUntilLabel.text = L(.settingsTimestampCertValidToLabel) + + self.addCertificateButton.setTitle(L(.settingsTimestampCertAddCertificateButton)) + self.addCertificateButton.accessibilityLabel = self.addCertificateButton.titleLabel?.text?.lowercased() + self.showCertificateButton.setTitle(L(.settingsTimestampCertShowCertificateButton)) + self.showCertificateButton.accessibilityLabel = self.showCertificateButton.titleLabel?.text?.lowercased() + self.showCertificateButton.mediumFont() + self.addCertificateButton.mediumFont() + + self.addCertificateButton.accessibilityUserInputLabels = [self.addCertificateButton.titleLabel?.text ?? ""] + + self.showCertificateButton.accessibilityUserInputLabels = [self.showCertificateButton.titleLabel?.text ?? ""] + + guard let cert = self.certificate else { return } + + self.issuedToLabel.text = "\(self.issuedToLabel.text ?? L(.settingsTimestampCertIssuedToLabel)) \(cert.issuer(oid: .organizationName) ?? cert.issuer(oid: .subjectAltName) ?? cert.issuer(oid: .issuerAltName) ?? "-")" + self.validUntilLabel.text = "\(self.validUntilLabel.text ?? L(.settingsTimestampCertValidToLabel)) \(MoppDateFormatter().dateToString(date: cert.notAfter, false))" + } + } + + private func showErrorMessage(errorMessage: String, topViewController: UIViewController) { + let errorDialog = AlertUtil.errorDialog(errorMessage: errorMessage, topViewController: topViewController) + topViewController.present(errorDialog, animated: true) + } + + private func showErrorMessage(fileName: String) { + let viewController = self.getViewController() + if let tvc = viewController { + self.showErrorMessage(errorMessage: L(.fileImportNewFileOpeningFailedAlertMessage, [fileName]), topViewController: tvc) + } + } +} + +extension SettingsSivaCertCell: UIDocumentPickerDelegate { + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + if !urls.isEmpty { + MoppFileManager.shared.saveFile(fileURL: urls[0], SettingsSivaCertCell.sivaFileFolder) { isSaved, savedFileURL in + + guard let savedFile = savedFileURL else { + printLog("Failed to get saved SiVa cert file") + self.showErrorMessage(fileName: savedFileURL?.lastPathComponent ?? "-") + return + } + + do { + if try savedFile.checkResourceIsReachable() { + self.certificate = try CertUtil.openCertificate(savedFile) + DefaultsHelper.sivaCertFileName = savedFile.lastPathComponent + self.updateUI() + } + } catch let openFileError { + printLog("Failed to open '\(savedFile.lastPathComponent)'. Error: \(openFileError.localizedDescription)") + self.showErrorMessage(fileName: savedFile.lastPathComponent) + return + } + } + } + } + + private func getViewController() -> UIViewController? { + guard let tvc = self.topViewController else { + printLog("Unable to get top view controller") + return nil + } + + return tvc + } +} + +extension SettingsSivaCertCell: UITextFieldDelegate { + func textFieldDidBeginEditing(_ textField: UITextField) { + var currentIndexPath = IndexPath(item: 1, section: 1) + if let tableView = superview as? UITableView { + if let indexPath = tableView.indexPath(for: self) { + let section = indexPath.section + let row = indexPath.row + currentIndexPath = IndexPath(row: row, section: section) + } + } + delegate.didStartEditingField(field.id, currentIndexPath) + } + + func textFieldDidEndEditing(_ textField: UITextField) { + delegate.didEndEditingField(field.id, with: textField.text ?? String()) + UIAccessibility.post(notification: UIAccessibility.Notification.screenChanged, argument: textField) + } + + func textField(_ textField: UITextField, shouldChangeCharactersIn range: NSRange, replacementString string: String) -> Bool { + guard let text = textField.text else { return true } + + if textField.keyboardType == .default { + if string.isEmpty && (text.count <= 1) { + CertUtil.removeCertificate(folder: SettingsSivaCertCell.sivaFileFolder, fileName: DefaultsHelper.sivaCertFileName ?? "") + certificate = nil + DefaultsHelper.sivaUrl = string + updateUI() + } + return true + } + if let text = textField.text as NSString? { + let textAfterUpdate = text.replacingCharacters(in: range, with: string) + return textAfterUpdate.isNumeric || textAfterUpdate.isEmpty + } + return true + } +} + diff --git a/MoppApp/MoppApp/SettingsTSACertCell.swift b/MoppApp/MoppApp/SettingsTSACertCell.swift index 9dba0b4b..d91a3710 100644 --- a/MoppApp/MoppApp/SettingsTSACertCell.swift +++ b/MoppApp/MoppApp/SettingsTSACertCell.swift @@ -26,6 +26,8 @@ import ASN1Decoder class SettingsTSACertCell: UITableViewCell { + static let tsaFileFolder = "tsa-cert" + @IBOutlet weak var tsaCertStackView: UIStackView! @IBOutlet weak var tsaDataStackView: UIStackView! @IBOutlet weak var tsaCertLabelStackView: UIStackView! @@ -74,6 +76,8 @@ class SettingsTSACertCell: UITableViewCell { weak var topViewController: UIViewController? private var certificate: X509Certificate? + + var elements = [Any]() override func awakeFromNib() { updateUI() @@ -81,10 +85,12 @@ class SettingsTSACertCell: UITableViewCell { guard let titleUILabel = titleLabel, let issuedToUILabel = issuedToLabel, let validUntilUILabel = validUntilLabel, let addCertificateUIButton = addCertificateButton, let showCertificateUIButton = showCertificateButton else { return } self.accessibilityElements = [titleUILabel, issuedToUILabel, validUntilUILabel, addCertificateUIButton, showCertificateUIButton] + + elements = self.accessibilityElements ?? [] } func populate() { - self.certificate = TSACertUtil.getCertificate() + self.certificate = CertUtil.getCertificate(folder: SettingsTSACertCell.tsaFileFolder, fileName: DefaultsHelper.tsaCertFileName ?? "") if let _ = certificate { updateUI() } @@ -115,7 +121,7 @@ class SettingsTSACertCell: UITableViewCell { guard let cert = self.certificate else { return } - self.issuedToLabel.text = "\(self.issuedToLabel.text ?? L(.settingsTimestampCertIssuedToLabel)) \(cert.issuer(oid: .organizationName) ?? cert.issuer(oid: OID(rawValue: "2.5.4.97") ?? .subjectAltName) ?? cert.issuer(oid: .issuerAltName) ?? "-")" + self.issuedToLabel.text = "\(self.issuedToLabel.text ?? L(.settingsTimestampCertIssuedToLabel)) \(cert.issuer(oid: .organizationName) ?? cert.issuer(oid: .subjectAltName) ?? cert.issuer(oid: .issuerAltName) ?? "-")" self.validUntilLabel.text = "\(self.validUntilLabel.text ?? L(.settingsTimestampCertValidToLabel)) \(MoppDateFormatter().dateToString(date: cert.notAfter, false))" } } @@ -136,7 +142,7 @@ class SettingsTSACertCell: UITableViewCell { extension SettingsTSACertCell: UIDocumentPickerDelegate { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { if !urls.isEmpty { - MoppFileManager.shared.saveFile(fileURL: urls[0], TSACertUtil.tsaFileFolder) { isSaved, savedFileURL in + MoppFileManager.shared.saveFile(fileURL: urls[0], SettingsTSACertCell.tsaFileFolder) { isSaved, savedFileURL in guard let savedFile = savedFileURL else { printLog("Failed to get saved TSA cert file") @@ -146,7 +152,7 @@ extension SettingsTSACertCell: UIDocumentPickerDelegate { do { if try savedFile.checkResourceIsReachable() { - self.certificate = try TSACertUtil.openCertificate(savedFile) + self.certificate = try CertUtil.openCertificate(savedFile) DefaultsHelper.tsaCertFileName = savedFile.lastPathComponent self.updateUI() } diff --git a/MoppApp/MoppApp/SettingsViewController.swift b/MoppApp/MoppApp/SettingsViewController.swift index db202cde..61d77639 100644 --- a/MoppApp/MoppApp/SettingsViewController.swift +++ b/MoppApp/MoppApp/SettingsViewController.swift @@ -20,12 +20,19 @@ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * */ + +import UIKit + class SettingsViewController: MoppViewController { private(set) var timestampUrl: String! @IBOutlet weak var tableView: UITableView! var isDefaultTimestampValue = true + var currentlyEditingCell: IndexPath? + + var tableViewCells = [IndexPath: UITableViewCell]() + enum Section { case header case fields @@ -37,6 +44,7 @@ class SettingsViewController: MoppViewController { case useDefault case tsaCert case roleAndAddress + case sivaCert } struct Field { @@ -47,6 +55,7 @@ class SettingsViewController: MoppViewController { case defaultSwitch case tsaCert case state + case sivaCert } let id: FieldId @@ -93,7 +102,17 @@ class SettingsViewController: MoppViewController { title: L(.settingsTimestampCertTitle), placeholderText: NSAttributedString(string: L(.settingsTimestampCertTitle)), value: ""), - Field(id: .roleAndAddress, kind: .state, title: L(.roleAndAddressRoleTitle), placeholderText: NSAttributedString(string: L(.roleAndAddressRoleTitle)), value: "") + Field(id: .roleAndAddress, + kind: .state, + title: L(.roleAndAddressRoleTitle), + placeholderText: NSAttributedString(string: L(.roleAndAddressRoleTitle)), + value: ""), + Field( + id: .sivaCert, + kind: .sivaCert, + title: L(.settingsSivaServiceTitle), + placeholderText: NSAttributedString(string: L(.settingsSivaServiceTitle)), + value: "") ] override func viewDidLoad() { @@ -105,13 +124,27 @@ class SettingsViewController: MoppViewController { override func viewDidAppear(_ animated: Bool) { tableView.reloadData() + + NotificationCenter.default.addObserver(self, selector: #selector(accessibilityElementFocused(_:)), name: UIAccessibility.elementFocusedNotification, object: nil) + if UIAccessibility.isVoiceOverRunning { - self.view.accessibilityElements = getAccessibilityElementsOrder() + DispatchQueue.main.async { + self.view.accessibilityElements = [self.getAccessibilityElementsOrder()] + self.tableView.reloadData() + } + } + } + + @objc func accessibilityElementFocused(_ notification: Notification) { + if let element = notification.userInfo?[UIAccessibility.focusedElementUserInfoKey] as? UIView { + let elementRect = element.convert(element.bounds, to: tableView) + let offsetY = elementRect.midY - (tableView.frame.size.height / 4) + tableView.setContentOffset(CGPoint(x: 0, y: offsetY), animated: true) } } override func keyboardWillShow(notification: NSNotification) { - let firstCell = tableView.cellForRow(at: IndexPath(row: 1, section: 1)) + let firstCell = tableView.cellForRow(at: currentlyEditingCell ?? IndexPath(row: 1, section: 1)) tableView.setContentOffset(CGPoint(x: 0, y: (firstCell?.frame.origin.y ?? 0) - 100), animated: false) } @@ -120,77 +153,43 @@ class SettingsViewController: MoppViewController { } func getAccessibilityElementsOrder() -> [Any] { - var headerCellIndex: Int = 0 - var fieldCellIndex: Int = 0 - var timestampCellIndex: Int = 0 - var defaultValueCellIndex: Int = 0 - var tsaCertCellIndex: Int = 0 - var stateCellIndex: Int = 0 - for (index, cell) in tableView.visibleCells.enumerated() { - if cell is SettingsHeaderCell { - headerCellIndex = index - } else if cell is SettingsFieldCell { - fieldCellIndex = index - } else if cell is SettingsTimeStampCell { - timestampCellIndex = index - } else if cell is SettingsDefaultValueCell { - defaultValueCellIndex = index - } else if cell is SettingsTSACertCell { - tsaCertCellIndex = index - } else if cell is SettingsStateCell { - stateCellIndex = index - } - } - - guard let timestampCell = tableView.visibleCells[timestampCellIndex] as? SettingsTimeStampCell, - let timestampTextfield = timestampCell.textField else { - return [] - } - - guard let defaultValueCell = tableView.visibleCells[defaultValueCellIndex] as? SettingsDefaultValueCell, - let timestampDefaultSwitch = defaultValueCell.useDefaultSwitch else { - return [] - } - - guard let fieldCellAccessibilityElements = tableView.visibleCells[fieldCellIndex].accessibilityElements else { - return [] - } - - guard let headerCellAccessibilityElements = tableView.visibleCells[headerCellIndex].accessibilityElements else { - return [] - } - - guard let tsaCertCellAccessibilityElements = tableView.visibleCells[tsaCertCellIndex].accessibilityElements else { - return [] - } + let isDefaultTimestampSettingsEnabled = DefaultsHelper.defaultSettingsSwitch - guard let stateCellAccessibilityElements = tableView.visibleCells[stateCellIndex] - as? SettingsStateCell, - let roleSwitch = stateCellAccessibilityElements.stateSwitch - else { - return [] - } + var accessibilityElementsOrder = [Any]() - if timestampDefaultSwitch.isOff { - return [ - timestampDefaultSwitch, - fieldCellAccessibilityElements, - timestampTextfield, - tsaCertCellAccessibilityElements, - roleSwitch, - headerCellAccessibilityElements, - timestampDefaultSwitch - ] + if !isDefaultTimestampSettingsEnabled { + + let classOrder = [SettingsDefaultValueCell.self, SettingsFieldCell.self, SettingsTimeStampCell.self, SettingsTSACertCell.self, SettingsStateCell.self, SettingsSivaCertCell.self, SettingsHeaderCell.self, SettingsDefaultValueCell.self] + + for className in classOrder { + for key in tableViewCells.keys.sorted() { + if let cell = tableViewCells[key], type(of: cell) == className { + if cell.accessibilityElements != nil { + accessibilityElementsOrder.append(cell.accessibilityElements ?? []) + } + } + } + } + } else { + + let accessibilityClassOrder = [SettingsDefaultValueCell.self, SettingsFieldCell.self, SettingsTimeStampCell.self, SettingsStateCell.self, SettingsSivaCertCell.self, SettingsHeaderCell.self, SettingsDefaultValueCell.self] + + for className in accessibilityClassOrder { + for key in tableViewCells.keys.sorted() { + if let cell = tableViewCells[key], type(of: cell) == className { + if cell.accessibilityElements != nil { + accessibilityElementsOrder.append(cell.accessibilityElements ?? []) + } + } + } + } } - return [ - timestampDefaultSwitch, - fieldCellAccessibilityElements, - timestampTextfield, - roleSwitch, - headerCellAccessibilityElements, - timestampDefaultSwitch - ] + return accessibilityElementsOrder + } + + deinit { + NotificationCenter.default.removeObserver(self, name: UIAccessibility.elementFocusedNotification, object: nil) } } @@ -223,6 +222,7 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { let headerCell = tableView.dequeueReusableCell(withType: SettingsHeaderCell.self, for: indexPath)! headerCell.delegate = self headerCell.populate(with:L(.settingsTitle)) + tableViewCells[indexPath] = headerCell return headerCell case .fields: let field = fields[indexPath.row] @@ -234,6 +234,7 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { let fieldCell = tableView.dequeueReusableCell(withType: SettingsFieldCell.self, for: indexPath)! fieldCell.delegate = self fieldCell.populate(with: field) + tableViewCells[indexPath] = fieldCell switch field.id { case .rpuuid: fieldCell.textField.isSecureTextEntry = true @@ -246,25 +247,37 @@ extension SettingsViewController: UITableViewDelegate, UITableViewDataSource { let timeStampCell = tableView.dequeueReusableCell(withType: SettingsTimeStampCell.self, for: indexPath)! timeStampCell.delegate = self timeStampCell.populate(with: field) + tableViewCells[indexPath] = timeStampCell return timeStampCell case .choice: let choiceCell = tableView.dequeueReusableCell(withType: SettingsChoiceCell.self, for: indexPath)! choiceCell.populate(with: field) + tableViewCells[indexPath] = choiceCell return choiceCell case .defaultSwitch: let useDefaultCell = tableView.dequeueReusableCell(withType: SettingsDefaultValueCell.self, for: indexPath)! useDefaultCell.delegate = self useDefaultCell.populate() + tableViewCells[indexPath] = useDefaultCell return useDefaultCell case .tsaCert: let tsaCertCell = tableView.dequeueReusableCell(withType: SettingsTSACertCell.self, for: indexPath)! tsaCertCell.topViewController = getTopViewController() tsaCertCell.populate() + tableViewCells[indexPath] = tsaCertCell return tsaCertCell case .state: let stateCell = tableView.dequeueReusableCell(withType: SettingsStateCell.self, for: indexPath)! stateCell.populate(with: field) + tableViewCells[indexPath] = stateCell return stateCell + case .sivaCert: + let sivaCert = tableView.dequeueReusableCell(withType: SettingsSivaCertCell.self, for: indexPath)! + sivaCert.topViewController = getTopViewController() + sivaCert.delegate = self + sivaCert.populate(with: field) + tableViewCells[indexPath] = sivaCert + return sivaCert } } return UITableViewCell() @@ -284,15 +297,32 @@ extension SettingsViewController: SettingsHeaderCellDelegate { } } -extension SettingsViewController: SettingsFieldCellDelegate { - func didEndEditingField(_ fieldId: SettingsViewController.FieldId, with value:String) { +extension SettingsViewController: SettingsCellDelegate { + func didStartEditingField(_ field: FieldId, _ indexPath: IndexPath) { + switch field { + case .rpuuid: + currentlyEditingCell = indexPath + break + case .sivaCert: + currentlyEditingCell = indexPath + break + default: + break + } + } + + func didEndEditingField(_ fieldId: SettingsViewController.FieldId, with value: String) { switch fieldId { case .rpuuid: DefaultsHelper.rpUuid = value break + case .sivaCert: + DefaultsHelper.sivaUrl = value + break default: break } + currentlyEditingCell = nil UIAccessibility.post(notification: .screenChanged, argument: L(.settingsValueChanged)) } } @@ -325,12 +355,10 @@ extension SettingsViewController: SettingsDefaultValueCellDelegate { } tableView.reloadData() - DispatchQueue.main.async { - if UIAccessibility.isVoiceOverRunning { + if UIAccessibility.isVoiceOverRunning { + DispatchQueue.main.async { self.view.accessibilityElements = self.getAccessibilityElementsOrder() } } } - - } diff --git a/MoppApp/MoppApp/SignatureDetailsViewController.swift b/MoppApp/MoppApp/SignatureDetailsViewController.swift index c5099b4d..8f69afb6 100644 --- a/MoppApp/MoppApp/SignatureDetailsViewController.swift +++ b/MoppApp/MoppApp/SignatureDetailsViewController.swift @@ -222,6 +222,10 @@ extension SignatureDetailsViewController: UITableViewDelegate, UITableViewDataSo let certificateDetailsViewController = UIStoryboard.container.instantiateViewController(of: CertificateDetailViewController.self) let certificateDetail = SignatureCertificateDetail(x509Certificate: tap.signatureDetail?.x509Certificate, secCertificate: tap.signatureDetail?.secCertificate) certificateDetailsViewController.certificateDetail = certificateDetail + // Remove buttons from tab bar + if let landingViewController = LandingViewController.shared { + landingViewController.presentButtons([]) + } self.navigationController?.pushViewController(certificateDetailsViewController, animated: true) } } diff --git a/MoppApp/MoppApp/SivaChoiceTapGestureRecognizer.swift b/MoppApp/MoppApp/SivaChoiceTapGestureRecognizer.swift new file mode 100644 index 00000000..86818f27 --- /dev/null +++ b/MoppApp/MoppApp/SivaChoiceTapGestureRecognizer.swift @@ -0,0 +1,28 @@ +// +// SivaChoiceTapGestureRecognizer.swift +// MoppApp +// +/* + * Copyright 2017 - 2023 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 + +class SivaChoiceTapGestureRecognizer: UITapGestureRecognizer { + var accessType: SivaAccess = .defaultAccess +} diff --git a/MoppApp/MoppApp/SmartIDEditViewController.swift b/MoppApp/MoppApp/SmartIDEditViewController.swift index 7575e219..b19b0609 100644 --- a/MoppApp/MoppApp/SmartIDEditViewController.swift +++ b/MoppApp/MoppApp/SmartIDEditViewController.swift @@ -126,7 +126,7 @@ class SmartIDEditViewController : MoppViewController, TokenFlowSigning { self.view.accessibilityElements = [titleUILabel, countryUILabel, countryUITextField, idCodeUILabel, idCodeUITextField, personalCodeErrorUILabel, rememberUILabel, rememberUISwitch, cancelUIButton, signUIButton] } - NotificationCenter.default.addObserver(self, selector: #selector(handleAccessibilityKeyboard), name: .hideKeyboardAccessibility, object: nil) + NotificationCenter.default.addObserver(self, selector: #selector(handleAccessibilityKeyboard), name: .focusedAccessibilityElement, object: nil) } @objc func dismissKeyboard(_ notification: NSNotification) { diff --git a/MoppApp/MoppApp/en.lproj/Localizable.strings b/MoppApp/MoppApp/en.lproj/Localizable.strings index 1a838f0e..cd45b1a6 100755 --- a/MoppApp/MoppApp/en.lproj/Localizable.strings +++ b/MoppApp/MoppApp/en.lproj/Localizable.strings @@ -263,6 +263,7 @@ "decryption-in-progress" = "Decryption in progress.\nPlease wait."; "signing-error-too-many-requests" = "The limit for digital signatures per month has been reached for this IP address. https://www.id.ee/en/article/for-organisations-that-sign-large-quantities-of-documents-using-digidoc4-client/"; "no-response-error" = "Please check your internet connection"; +"ssl-handshake-error" = "SSL handshake failed. Check your application settings or software upgrades"; "ocsp-invalid-time-slot" = "Please check your smart device time. https://www.id.ee/en/article/ria-digidoc-error-please-check-your-smart-device-time/"; "wrong-pin2" = "Wrong PIN2. You have %d attempts left."; "wrong-pin1" = "Wrong PIN1. You have %1$d attempts left."; @@ -376,6 +377,10 @@ "settings-timestamp-cert-valid-to-label" = "Valid to:"; "settings-timestamp-cert-add-certificate-button" = "ADD CERTIFICATE"; "settings-timestamp-cert-show-certificate-button" = "SHOW CERTIFICATE"; +"settings-siva-service-title" = "Access to Digital Signature Validation Service SiVa"; +"settings-siva-default-access-title" = "Use default access"; +"settings-siva-default-manual-access-title" = "Use manually configured access"; +"settings-siva-default-certificate-title" = "Digital Signature Validation Service SiVa SSL certificate"; "diagnostics-title" = "Diagnostics"; "diagnostics-activate-one-time-logging" = "Enable one-time log generation"; "diagnostics-save-log" = "SAVE LOG"; @@ -522,3 +527,6 @@ "voice-control-role-country" = "Country"; "voice-control-role-zip" = "Zip"; "voice-control-role-and-address" = "Role and address"; +"voice-control-siva-service" = "Validation service"; +"voice-control-siva-default-access" = "Default validation access"; +"voice-control-siva-manual-access" = "Manual validation access"; diff --git a/MoppApp/MoppApp/et.lproj/Localizable.strings b/MoppApp/MoppApp/et.lproj/Localizable.strings index ec1de9c9..8432c345 100755 --- a/MoppApp/MoppApp/et.lproj/Localizable.strings +++ b/MoppApp/MoppApp/et.lproj/Localizable.strings @@ -274,6 +274,7 @@ "decryption-error-message" = "Midagi läks dekrüpteerimisel valesti. Palun proovi uuesti."; "signing-error-too-many-requests" = "Sinu IP-aadressi tasuta allkirjade kuulimiit on ületatud. https://www.id.ee/artikkel/asutustele-kus-allkirjastatakse-digidoc4-kliendi-kaudu-suuremates-kogustes-dokumente/"; "no-response-error" = "Palun kontrolli internetiühendust"; +"ssl-handshake-error" = "SSL ühenduskanali loomine ebaõnnestus. Kontrolli rakenduse seadeid või tarkvara uuendusi"; "ocsp-invalid-time-slot" = "Palun kontrolli oma nutiseadme kellaaega. https://www.id.ee/artikkel/ria-digidoc-viga-palun-kontrollige-oma-nutiseadme-kellaaega/"; "signing-aborted-message" = "Allkirjastamine katkestatud"; "my-eid-status-reader-not-found" = "Ühenda kaardilugeja nutiseadmega ja sisesta ID-kaart lugejasse."; @@ -377,6 +378,10 @@ "settings-timestamp-cert-valid-to-label" = "Kehtib kuni:"; "settings-timestamp-cert-add-certificate-button" = "LISA SERTIFIKAAT"; "settings-timestamp-cert-show-certificate-button" = "NÄITA SERTIFIKAATI"; +"settings-siva-service-title" = "Ligipääs valideerimisteenusele SiVa"; +"settings-siva-default-access-title" = "Kasutan vaikimisi määratud ligipääsu"; +"settings-siva-default-manual-access-title" = "Kasutan käsitsi määratud ligipääsu"; +"settings-siva-default-certificate-title" = "Valideerimisteenuse SiVa SSL sertifikaat"; "diagnostics-title" = "Diagnostika"; "diagnostics-activate-one-time-logging" = "Aktiveeri ühekordne logifaili genereerimine"; "diagnostics-save-log" = "SALVESTA LOGIFAIL"; @@ -522,3 +527,6 @@ "voice-control-role-country" = "Country"; "voice-control-role-zip" = "Zip"; "voice-control-role-and-address" = "Role and address"; +"voice-control-siva-service" = "Validation service"; +"voice-control-siva-default-access" = "Default validation access"; +"voice-control-siva-manual-access" = "Manual validation access"; diff --git a/MoppApp/MoppApp/ru.lproj/Localizable.strings b/MoppApp/MoppApp/ru.lproj/Localizable.strings index 3906ea50..c2bdefdc 100644 --- a/MoppApp/MoppApp/ru.lproj/Localizable.strings +++ b/MoppApp/MoppApp/ru.lproj/Localizable.strings @@ -264,6 +264,7 @@ "decryption-in-progress" = "Происходит дешифрование. Пожалуйста, подождите."; "signing-error-too-many-requests" = "Предел для цифровых подписей в месяц был достигнут для этого IP-адреса. https://www.id.ee/ru/artikkel/dlya-uchrezhdenij-v-kotoryh-v-bolshom-obeme-podpisyvayutsya-dokumenty-s-pomoshhyu-digidoc4-klienta/"; "no-response-error" = "Пожалуйста проверьте подключение к интернету"; +"ssl-handshake-error" = "Не удалось создать SSL канал передачи данных. Проверьте настройки вашего приложения или обновления программного обеспечения"; "ocsp-invalid-time-slot" = "Пожалуйста, проверьте время на своем смарт-устройстве. https://www.id.ee/ru/artikkel/oshibka-ria-digidoc-pozhalujsta-proverte-vremya-na-svoem-smart-ustrojstve/"; "wrong-pin2" = "Неверный PIN2. У Вас осталось %d попыток."; "wrong-pin1" = "Неверный PIN1. У Вас осталось %1$d попыток."; @@ -377,6 +378,10 @@ "settings-timestamp-cert-valid-to-label" = "Действительно до:"; "settings-timestamp-cert-add-certificate-button" = "ДОБАВИТЬ СЕРТИФИКАТ"; "settings-timestamp-cert-show-certificate-button" = "ПОКАЗАТЬ СЕРТИФИКАТ"; +"settings-siva-service-title" = "Доступ к услуге валидации SiVa"; +"settings-siva-default-access-title" = "Использовать доступ, назначенный по умолчанию"; +"settings-siva-default-manual-access-title" = "Использовать доступ, назначенный вручную"; +"settings-siva-default-certificate-title" = "Сертификат услуги валидации SiVa SSL"; "diagnostics-title" = "Диагностика"; "diagnostics-activate-one-time-logging" = "Активировать одноразовое создание лог-файла"; "diagnostics-save-log" = "СОХРАНИТЬ ЛОГ-ФАЙЛ"; @@ -523,3 +528,6 @@ "voice-control-role-country" = "Country"; "voice-control-role-zip" = "Zip"; "voice-control-role-and-address" = "Role and address"; +"voice-control-siva-service" = "Validation service"; +"voice-control-siva-default-access" = "Default validation access"; +"voice-control-siva-manual-access" = "Manual validation access"; diff --git a/MoppLib/MoppLib/MoppLibDigidocManager.mm b/MoppLib/MoppLib/MoppLibDigidocManager.mm index dc96b420..b07cd103 100644 --- a/MoppLib/MoppLib/MoppLibDigidocManager.mm +++ b/MoppLib/MoppLib/MoppLibDigidocManager.mm @@ -83,7 +83,9 @@ } std::string verifyServiceUri() const override { - return moppLibConfiguration.SIVAURL.UTF8String; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSString *sivaUrl = [defaults stringForKey:@"kSivaUrl"]; + return [sivaUrl length] != 0 ? sivaUrl.UTF8String : moppLibConfiguration.SIVAURL.UTF8String; } std::vector TSLCerts() const override { @@ -102,9 +104,27 @@ return stringsToX509Certs(certBundle); } - std::vector verifyServiceCerts() const override { - return stringsToX509Certs(moppLibConfiguration.CERTBUNDLE); - } + virtual std::vector verifyServiceCerts() const override { + NSMutableArray *certs = [NSMutableArray arrayWithArray:moppLibConfiguration.CERTBUNDLE]; + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + NSString *sivaFileName = [defaults stringForKey:@"kSivaFileCertName"]; + if (!(sivaFileName == nil || [sivaFileName isEqualToString:@""])) { + NSString *sivaCert = getSivaCert(sivaFileName); + + if (!(sivaCert == nil || [sivaCert isEqualToString:@""])) { + // Remove certificate header, footer, whitespaces and newlines + NSCharacterSet *characterSetToRemove = [NSCharacterSet characterSetWithCharactersInString:@" \n\r\t"]; + + NSString * formattedSivaCert = [sivaCert stringByReplacingOccurrencesOfString:@"-----BEGIN CERTIFICATE-----" withString:@""]; + formattedSivaCert = [formattedSivaCert stringByReplacingOccurrencesOfString:@"-----END CERTIFICATE-----" withString:@""]; + formattedSivaCert = [[formattedSivaCert componentsSeparatedByCharactersInSet:characterSetToRemove] componentsJoinedByString:@""]; + + [certs addObject:formattedSivaCert]; + } + } + + return stringsToX509Certs(certs); + } std::string ocsp(const std::string &issuer) const override { NSString *ocspIssuer = [NSString stringWithCString:issuer.c_str() encoding:[NSString defaultCStringEncoding]]; @@ -182,7 +202,32 @@ int logLevel() const override { std::string logFileLocation(NSURL *logsFolderUrl) const { return std::string([[[logsFolderUrl URLByAppendingPathComponent: @"libdigidocpp.log"] path] UTF8String]); } - + + NSString* getSivaCert(NSString *fileName) const { + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + NSString *documentsDirectory = [paths objectAtIndex:0]; + NSString *subfolderName = @"siva-cert"; + + NSString *subfolderPath = [documentsDirectory stringByAppendingPathComponent:subfolderName]; + + NSString *filePath = [subfolderPath stringByAppendingPathComponent:fileName]; + + if ([[NSFileManager defaultManager] fileExistsAtPath:filePath]) { + NSError *error = nil; + NSString *fileContents = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:&error]; + + if (fileContents) { + return fileContents; + } else { + NSLog(@"Error reading file '%@': %@", fileName, [error localizedDescription]); + return nil; + } + } else { + NSLog(@"File '%@' not found at path: %@", fileName, filePath); + return nil; + } + return nil; + } }; @@ -437,7 +482,12 @@ - (MoppLibContainer *)getContainerWithPath:(NSString *)containerPath error:(NSEr if (e.code() == 63) { *error = [MoppLibError fileNameTooLongError]; } else if (e.code() == digidoc::Exception::NetworkError) { - *error = [MoppLibError noInternetConnectionError]; + NSString *message = [NSString stringWithUTF8String:e.msg().c_str()]; + if ([message hasPrefix:@"Failed to create ssl connection with host"]) { + *error = [MoppLibError sslHandshakeError]; + } else { + *error = [MoppLibError noInternetConnectionError]; + } } else { *error = [NSError errorWithDomain:[NSString stringWithUTF8String:e.msg().c_str()] code:e.code() userInfo:@{}]; } diff --git a/MoppLib/MoppLib/MoppLibError.h b/MoppLib/MoppLib/MoppLibError.h index efa90980..4f4985df 100644 --- a/MoppLib/MoppLib/MoppLibError.h +++ b/MoppLib/MoppLib/MoppLibError.h @@ -47,6 +47,7 @@ extern NSString *const MoppLibErrorDomain; + (NSError *)pinBlockedError; + (NSError *)fileNameTooLongError; + (NSError *)noInternetConnectionError; ++ (NSError *)sslHandshakeError; + (NSError *)restrictedAPIError; + (NSError *)duplicatedFilenameError; + (NSError *)tooManyRequests; diff --git a/MoppLib/MoppLib/MoppLibError.m b/MoppLib/MoppLib/MoppLibError.m index ae1c45f4..b1bc7c91 100644 --- a/MoppLib/MoppLib/MoppLibError.m +++ b/MoppLib/MoppLib/MoppLibError.m @@ -112,6 +112,10 @@ + (NSError *)noInternetConnectionError { return [self error:moppLibErrorNoInternetConnection withMessage:@"Internet connection not detected."]; } ++ (NSError *)sslHandshakeError { + return [self error:moppLibErrorSslHandshakeFailed withMessage:@"Failed to create ssl connection with host."]; +} + + (NSError *)restrictedAPIError { return [self error:moppLibErrorRestrictedApi withMessage:@"This API method is not supported on third-party applications."]; } diff --git a/MoppLib/MoppLib/PublicInterface/MoppLibConstants.h b/MoppLib/MoppLib/PublicInterface/MoppLibConstants.h index 136af855..3d5b3b6f 100644 --- a/MoppLib/MoppLib/PublicInterface/MoppLibConstants.h +++ b/MoppLib/MoppLib/PublicInterface/MoppLibConstants.h @@ -73,7 +73,8 @@ typedef NS_ENUM(NSUInteger, MoppLibErrorCode) { moppLibErrorDuplicatedFilename = 10023, // Filename already exists moppLibErrorTooManyRequests = 10024, // Too many requests moppLibErrorOCSPTimeSlot = 10025, // Invalid OCSP time slot - moppLibErrorReaderProcessFailed = 10026 // Reader process failed + moppLibErrorReaderProcessFailed = 10026, // Reader process failed + moppLibErrorSslHandshakeFailed = 10027, // SSL handshake failed }; diff --git a/SkSigningLib/SkSigningLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist b/SkSigningLib/SkSigningLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist index 9e202cc7..a50dab54 100644 --- a/SkSigningLib/SkSigningLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/SkSigningLib/SkSigningLib.xcodeproj/xcuserdata/martenr.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,7 @@ SkSigningLib.xcscheme_^#shared#^_ orderHint - 20 + 16