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