diff --git a/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Container/DigiDocContainerWrapper.mm b/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Container/DigiDocContainerWrapper.mm index 26c3e819..dc69f89f 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Container/DigiDocContainerWrapper.mm +++ b/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Container/DigiDocContainerWrapper.mm @@ -34,6 +34,7 @@ @interface DigiDocContainerWrapper () + (DigiDocSignatureStatus)determineSignatureStatus:(int)status; ++ (DigiDocSignatureWarning)determineSignatureWarning:(digidoc::Exception::ExceptionCode)code; @end struct DigiDocContainerOpenCB: public digidoc::ContainerOpenCB { @@ -148,6 +149,25 @@ + (DigiDocSignatureStatus)determineSignatureStatus:(int)status { } } ++ (DigiDocSignatureWarning)determineSignatureWarning:(digidoc::Exception::ExceptionCode)code { + switch (code) { + case digidoc::Exception::ReferenceDigestWeak: + return DigiDocSignatureWarningReferenceDigestWeak; + case digidoc::Exception::SignatureDigestWeak: + return DigiDocSignatureWarningSignatureDigestWeak; + case digidoc::Exception::DataFileNameSpaceWarning: + return DigiDocSignatureWarningDataFileNameSpace; + case digidoc::Exception::IssuerNameSpaceWarning: + return DigiDocSignatureWarningIssuerNameSpace; + case digidoc::Exception::ProducedATLateWarning: + return DigiDocSignatureWarningProducedATLate; + case digidoc::Exception::MimeTypeWarning: + return DigiDocSignatureWarningMimeType; + default: + return DigiDocSignatureWarningOther; + } +} + + (DigiDocSignature *)getSignature:(digidoc::Signature *)signature pos:(int)pos mediaType:(const std::string&)mediaType dataFileCount:(NSInteger)dataFileCount { digidoc::X509Cert signingCert = signature->signingCertificate(); @@ -209,7 +229,14 @@ + (DigiDocSignature *)getSignature:(digidoc::Signature *)signature pos:(int)pos digidoc::Signature::Validator::Status status = validator.status(); digiDocSignature.diagnosticsInfo = [NSString stringWithUTF8String:validator.diagnostics().c_str()]; digiDocSignature.status = [DigiDocContainerWrapper determineSignatureStatus:status]; - digiDocSignature.diagnosticsInfo = [NSString stringWithUTF8String:validator.diagnostics().c_str()]; + + std::vector validatorWarnings = validator.warnings(); + NSMutableArray *warnings = [NSMutableArray arrayWithCapacity:validatorWarnings.size()]; + for (digidoc::Exception::ExceptionCode warning : validatorWarnings) { + [warnings addObject:@([DigiDocContainerWrapper determineSignatureWarning:warning])]; + } + digiDocSignature.warnings = warnings; + return digiDocSignature; } diff --git a/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Model/DigiDocSignature.h b/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Model/DigiDocSignature.h index bdb2b350..a8a9b03c 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Model/DigiDocSignature.h +++ b/Modules/LibdigidocLib/Sources/LibdigidocObjC/include/Model/DigiDocSignature.h @@ -19,6 +19,16 @@ #import +typedef NS_ENUM(int, DigiDocSignatureWarning) { + DigiDocSignatureWarningReferenceDigestWeak, + DigiDocSignatureWarningSignatureDigestWeak, + DigiDocSignatureWarningDataFileNameSpace, + DigiDocSignatureWarningIssuerNameSpace, + DigiDocSignatureWarningProducedATLate, + DigiDocSignatureWarningMimeType, + DigiDocSignatureWarningOther +}; + typedef NS_ENUM(int, DigiDocSignatureStatus) { Valid, Warning, @@ -51,6 +61,7 @@ typedef NS_ENUM(int, DigiDocSignatureStatus) { @property (nonatomic, assign) DigiDocSignatureStatus status; @property (nonatomic, strong) NSString *diagnosticsInfo; +@property (nonatomic, strong) NSArray *warnings; @property (nonatomic, strong) NSString *archiveTimestampTime; @property (nonatomic, strong) NSData *archiveTimestampCert; diff --git a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift index 86019342..18c9e3ce 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift +++ b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Container/ContainerWrapper.swift @@ -353,6 +353,25 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable { } } + private static func signatureWarningToDigiDocWarning(_ warning: DigiDocSignatureWarning) -> SignatureWarning { + switch warning { + case .referenceDigestWeak: + return .referenceDigestWeak + case .signatureDigestWeak: + return .signatureDigestWeak + case .dataFileNameSpace: + return .dataFileNameSpace + case .issuerNameSpace: + return .issuerNameSpace + case .producedATLate: + return .producedATLate + case .mimeType: + return .mimeType + default: + return .other + } + } + @discardableResult private func updateContainer( datafiles: [DataFileWrapper], @@ -414,7 +433,12 @@ public actor ContainerWrapper: ContainerWrapperProtocol, Loggable { messageImprint: signature.messageImprint, diagnosticsInfo: signature.diagnosticsInfo, archiveTimestampTime: signature.archiveTimestampTime, - archiveTimestampCert: signature.archiveTimestampCert + archiveTimestampCert: signature.archiveTimestampCert, + warnings: signature.warnings?.map { warning in + signatureWarningToDigiDocWarning( + DigiDocSignatureWarning(rawValue: warning.int32Value) ?? .other + ) + } ?? [] ) } } diff --git a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/SignatureWrapper.swift b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/SignatureWrapper.swift index d8586404..d79ff5eb 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/SignatureWrapper.swift +++ b/Modules/LibdigidocLib/Sources/LibdigidocSwift/Domain/Models/SignatureWrapper.swift @@ -19,6 +19,16 @@ import Foundation +public enum SignatureWarning: Sendable, Hashable { + case referenceDigestWeak + case signatureDigestWeak + case dataFileNameSpace + case issuerNameSpace + case producedATLate + case mimeType + case other +} + public enum SignatureStatus: String, Sendable { case valid case warning @@ -55,6 +65,17 @@ public struct SignatureWrapper: Sendable, Identifiable, Hashable { public var archiveTimestampTime: String public var archiveTimestampCert: Data + private static let weakDigestWarnings: Set = [ + .referenceDigestWeak, + .signatureDigestWeak + ] + + public var warnings: [SignatureWarning] + + public var hasOnlyWeakDigestWarnings: Bool { + !warnings.isEmpty && warnings.allSatisfy { SignatureWrapper.weakDigestWarnings.contains($0) } + } + public var isLTAExtended: Bool { !archiveTimestampCert.isEmpty } @@ -80,7 +101,8 @@ public struct SignatureWrapper: Sendable, Identifiable, Hashable { messageImprint: Data, diagnosticsInfo: String, archiveTimestampTime: String = "", - archiveTimestampCert: Data = Data()) { + archiveTimestampCert: Data = Data(), + warnings: [SignatureWarning] = []) { self.pos = pos self.signingCert = signingCert self.timestampCert = timestampCert @@ -103,5 +125,6 @@ public struct SignatureWrapper: Sendable, Identifiable, Hashable { self.diagnosticsInfo = diagnosticsInfo self.archiveTimestampTime = archiveTimestampTime self.archiveTimestampCert = archiveTimestampCert + self.warnings = warnings } } diff --git a/Modules/LibdigidocLib/Sources/LibdigidocSwift/SignedContainer.swift b/Modules/LibdigidocLib/Sources/LibdigidocSwift/SignedContainer.swift index 5fde2bb7..e046dc57 100644 --- a/Modules/LibdigidocLib/Sources/LibdigidocSwift/SignedContainer.swift +++ b/Modules/LibdigidocLib/Sources/LibdigidocSwift/SignedContainer.swift @@ -27,6 +27,8 @@ public actor SignedContainer: SignedContainerProtocol, Loggable { private static let signedContainerLogTag: String = "SignedContainer" + private static let weakDigestExemptionTime = "2018-07-01T00:00:00Z" + private var containerFile: URL? private let isExistingContainer: Bool private let container: ContainerWrapperProtocol @@ -55,7 +57,24 @@ public actor SignedContainer: SignedContainerProtocol, Loggable { } public func getSignatures() async -> [SignatureWrapper] { - return await container.getSignatures() + let signatures = await container.getSignatures() + + guard isExemptFromWeakDigestWarning else { return signatures } + + return signatures.map { signature in + guard signature.status == .warning, signature.hasOnlyWeakDigestWarnings else { return signature } + + var validSignature = signature + validSignature.status = .valid + return validSignature + } + } + + private var isExemptFromWeakDigestWarning: Bool { + guard let trustedSigningTime = timestamps.first?.trustedSigningTime, + !trustedSigningTime.isEmpty else { return false } + + return trustedSigningTime < SignedContainer.weakDigestExemptionTime } public func getTimestamps() async -> [SignatureWrapper] { diff --git a/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/SignedContainerTests.swift b/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/SignedContainerTests.swift index 25e391de..2d586c43 100644 --- a/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/SignedContainerTests.swift +++ b/Modules/LibdigidocLib/Tests/LibdigidocLibTests/LibdigidocSwift/SignedContainerTests.swift @@ -407,6 +407,97 @@ final class SignedContainerTests { #expect(timestamps.first == timestamp) } + private func containerTimestamped(at trustedSigningTime: String) -> SignedContainer { + SignedContainer( + containerFile: URL(fileURLWithPath: "/mock/path/container.asics"), + isExistingContainer: true, + container: mockContainerWrapper, + timestamps: [MockSignatureWrapper.mockSignatureWrapper(trustedSigningTime: trustedSigningTime)], + fileManager: mockFileManager, + containerUtil: mockContainerUtil + ) + } + + @Test + func getSignatures_dropWeakDigestWarningWhenTimestampedBefore2018() async { + mockContainerWrapper.getSignaturesHandler = { + [MockSignatureWrapper.mockSignatureWrapper(status: .warning, warnings: [.referenceDigestWeak])] + } + + let signatures = await containerTimestamped(at: "2016-05-11T10:20:30Z").getSignatures() + + #expect(signatures.first?.status == .valid) + } + + @Test + func getSignatures_keepWeakDigestWarningWhenTimestampedAfter2018() async { + mockContainerWrapper.getSignaturesHandler = { + [MockSignatureWrapper.mockSignatureWrapper(status: .warning, warnings: [.referenceDigestWeak])] + } + + let signatures = await containerTimestamped(at: "2019-05-11T10:20:30Z").getSignatures() + + #expect(signatures.first?.status == .warning) + } + + @Test + func getSignatures_keepWeakDigestWarningAtExemptionTime() async { + mockContainerWrapper.getSignaturesHandler = { + [MockSignatureWrapper.mockSignatureWrapper(status: .warning, warnings: [.signatureDigestWeak])] + } + + let signatures = await containerTimestamped(at: "2018-07-01T00:00:00Z").getSignatures() + + #expect(signatures.first?.status == .warning) + } + + @Test + func getSignatures_keepOtherWarningsWhenTimestampedBefore2018() async { + mockContainerWrapper.getSignaturesHandler = { + [MockSignatureWrapper.mockSignatureWrapper( + status: .warning, + warnings: [.referenceDigestWeak, .dataFileNameSpace] + )] + } + + let signatures = await containerTimestamped(at: "2016-05-11T10:20:30Z").getSignatures() + + #expect(signatures.first?.status == .warning) + } + + @Test + func getSignatures_keepWarningWhenValidatorReportsNoWarningCodes() async { + mockContainerWrapper.getSignaturesHandler = { + [MockSignatureWrapper.mockSignatureWrapper(status: .warning, warnings: [])] + } + + let signatures = await containerTimestamped(at: "2016-05-11T10:20:30Z").getSignatures() + + #expect(signatures.first?.status == .warning) + } + + @Test + func getSignatures_keepWeakDigestWarningWhenContainerIsNotTimestamped() async { + mockContainerWrapper.getSignaturesHandler = { + [MockSignatureWrapper.mockSignatureWrapper(status: .warning, warnings: [.referenceDigestWeak])] + } + + let signatures = await signedContainer.getSignatures() + + #expect(signatures.first?.status == .warning) + } + + @Test + func getSignatures_leaveInvalidSignatureUntouchedWhenTimestampedBefore2018() async { + mockContainerWrapper.getSignaturesHandler = { + [MockSignatureWrapper.mockSignatureWrapper(status: .invalid, warnings: [.referenceDigestWeak])] + } + + let signatures = await containerTimestamped(at: "2016-05-11T10:20:30Z").getSignatures() + + #expect(signatures.first?.status == .invalid) + } + @Test func isExistingContainer_returnTrue() async { let existingSignedContainer = SignedContainer( diff --git a/Modules/LibdigidocLib/Tests/Mocks/MockSignatureWrapper.swift b/Modules/LibdigidocLib/Tests/Mocks/MockSignatureWrapper.swift index 083bb873..5f9c4d20 100644 --- a/Modules/LibdigidocLib/Tests/Mocks/MockSignatureWrapper.swift +++ b/Modules/LibdigidocLib/Tests/Mocks/MockSignatureWrapper.swift @@ -41,7 +41,8 @@ public struct MockSignatureWrapper { status: SignatureStatus = .valid, format: String = "BES/time-stamp", messageImprint: Data = Data(), - diagnosticsInfo: String = "" + diagnosticsInfo: String = "", + warnings: [SignatureWarning] = [] ) -> SignatureWrapper { SignatureWrapper( pos: pos, @@ -63,7 +64,8 @@ public struct MockSignatureWrapper { status: status, format: format, messageImprint: messageImprint, - diagnosticsInfo: diagnosticsInfo + diagnosticsInfo: diagnosticsInfo, + warnings: warnings ) } } diff --git a/RIADigiDoc/ViewModel/SigningViewModel.swift b/RIADigiDoc/ViewModel/SigningViewModel.swift index c8d49594..8b99592a 100644 --- a/RIADigiDoc/ViewModel/SigningViewModel.swift +++ b/RIADigiDoc/ViewModel/SigningViewModel.swift @@ -498,11 +498,15 @@ class SigningViewModel: SigningViewModelProtocol, Loggable { let isEmptyFileInContainer = await signedContainer?.isEmptyFileInContainer() ?? false return signedContainer != nil && - (!Constants.MimeType.UnsignableContainers.contains(mimetype)) && - (!Constants.Extension.UnsignableContainerExtensions.contains((name as NSString).pathExtension)) && + !isUnsignableContainer(mimetype: mimetype, name: name) && !isNestedContainer && !isEmptyFileInContainer && !isCadesContainer && !isXadesContainer } + private func isUnsignableContainer(mimetype: String, name: String) -> Bool { + return Constants.MimeType.UnsignableContainers.contains(mimetype.lowercased()) || + Constants.Extension.UnsignableContainerExtensions.contains((name as NSString).pathExtension.lowercased()) + } + func isEncryptButtonShown( signedContainer: SignedContainerProtocol?, isNestedContainer: Bool, @@ -513,7 +517,8 @@ class SigningViewModel: SigningViewModelProtocol, Loggable { } func isSignatureRemoveButtonShown() -> Bool { - return !isNestedContainer() && !isCadesContainer && !isXadesContainer + return !isNestedContainer() && !isCadesContainer && !isXadesContainer && + !isUnsignableContainer(mimetype: containerMimetype, name: containerName) } func isTimestampedContainer() async -> Bool { diff --git a/RIADigiDocTests/ViewModel/SigningViewModelTests.swift b/RIADigiDocTests/ViewModel/SigningViewModelTests.swift index c20645f3..880c2547 100644 --- a/RIADigiDocTests/ViewModel/SigningViewModelTests.swift +++ b/RIADigiDocTests/ViewModel/SigningViewModelTests.swift @@ -850,6 +850,60 @@ struct SigningViewModelTests: Loggable { #expect(!isSignButtonShown) } + @Test + func isSignatureRemoveButtonShown_returnTrueWithAsiceContainer() { + mockSharedContainerViewModel.isNestedContainerHandler = { _ in false } + viewModel.containerMimetype = Constants.MimeType.Asice + viewModel.containerName = "mockContainer.asice" + + #expect(viewModel.isSignatureRemoveButtonShown()) + } + + @Test + func isSignatureRemoveButtonShown_returnFalseWithDdocContainer() { + mockSharedContainerViewModel.isNestedContainerHandler = { _ in false } + viewModel.containerMimetype = Constants.MimeType.Ddoc + viewModel.containerName = "mockContainer.ddoc" + + #expect(!viewModel.isSignatureRemoveButtonShown()) + } + + @Test + func isSignatureRemoveButtonShown_returnFalseWithAsicsContainer() { + mockSharedContainerViewModel.isNestedContainerHandler = { _ in false } + viewModel.containerMimetype = Constants.MimeType.Asics + viewModel.containerName = "mockContainer.asics" + + #expect(!viewModel.isSignatureRemoveButtonShown()) + } + + @Test + func isSignatureRemoveButtonShown_returnFalseWithAdocContainer() { + mockSharedContainerViewModel.isNestedContainerHandler = { _ in false } + viewModel.containerMimetype = Constants.MimeType.Adoc + viewModel.containerName = "mockContainer.adoc" + + #expect(!viewModel.isSignatureRemoveButtonShown()) + } + + @Test + func isSignatureRemoveButtonShown_returnFalseWithUppercaseScsExtension() { + mockSharedContainerViewModel.isNestedContainerHandler = { _ in false } + viewModel.containerMimetype = Constants.MimeType.Container + viewModel.containerName = "mockContainer.SCS" + + #expect(!viewModel.isSignatureRemoveButtonShown()) + } + + @Test + func isSignatureRemoveButtonShown_returnFalseWithNestedContainer() { + mockSharedContainerViewModel.isNestedContainerHandler = { _ in true } + viewModel.containerMimetype = Constants.MimeType.Asice + viewModel.containerName = "mockContainer.asice" + + #expect(!viewModel.isSignatureRemoveButtonShown()) + } + @Test func isEncryptButtonShown_returnTrueWithExistingContainer() async { let mockSignedContainer = SignedContainerProtocolMock()