diff --git a/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift b/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift index c356ea6e..88562528 100644 --- a/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift +++ b/Modules/CryptoLib/Sources/CryptoSwift/CryptoContainer.swift @@ -216,7 +216,6 @@ extension CryptoContainer { CryptoContainer.logger().info("Is single file: \(isSingleFile, privacy: .public)") CryptoContainer.logger().info("Is crypto container: \(isCryptoContainer, privacy: .public)") - guard isSingleFile && isCryptoContainer else { var defaultExtension = CommonsLib.Constants.Extension.DefaultCrypto if CDoc2Setting.isEncryptionEnabled { diff --git a/Modules/Test/CommonsTestShared/Sources/CommonsTestShared/Certificate/TestCertificateUtil.swift b/Modules/Test/CommonsTestShared/Sources/CommonsTestShared/Certificate/TestCertificateUtil.swift index da749b57..2e3649e2 100644 --- a/Modules/Test/CommonsTestShared/Sources/CommonsTestShared/Certificate/TestCertificateUtil.swift +++ b/Modules/Test/CommonsTestShared/Sources/CommonsTestShared/Certificate/TestCertificateUtil.swift @@ -23,15 +23,20 @@ public class TestCertificateUtil { public init() {} - public static func getSampleCertificate() -> Data { - // swiftlint:disable line_length - let cert = """ + // swiftlint:disable line_length + private static let cert = """ MIIEwjCCA6qgAwIBAgIUeYCoFyEHBfraNnsp4BCgKyVYfywwDQYJKoZIhvcNAQELBQAwgbIxCzAJBgNVBAYTAkVFMRIwEAYDVQQIDAlUZXN0U3RhdGUxETAPBgNVBAcMCFRlc3RDaXR5MRkwFwYDVQQKDBBUZXN0T3JnYW5pemF0aW9uMSMwIQYDVQQLDBpUZXN0T3JnYW5pemF0aW9uYWxVbml0TmFtZTEXMBUGA1UEAwwOVGVzdENvbW1vbk5hbWUxIzAhBgkqhkiG9w0BCQEWFHRlc3RAZW1haWwudGVzdGVtYWlsMB4XDTI1MDEzMTE3MDIxMloXDTI3MDUwNjE3MDIxMlowgcwxCzAJBgNVBAYTAkVFMRUwEwYDVQQIDAxTdWJqZWN0U3RhdGUxFDASBgNVBAcMC1N1YmplY3RDaXR5MSAwHgYDVQQKDBdTdWJqZWN0T3JnYW5pemF0aW9uTmFtZTEmMCQGA1UECwwdU3ViamVjdE9yZ2FuaXphdGlvbmFsVW5pdE5hbWUxGjAYBgNVBAMMEVN1YmplY3RDb21tb25OYW1lMSowKAYJKoZIhvcNAQkBFht0ZXN0c3ViamVjdEBlbWFpbC50ZXN0ZW1haWwwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQDjOB/5LMTY7HaIDQtPI0rNszyO1GwNepf2Ol6XayOJOP3V8Y3bC3pcUobnlfXfphTqoNznYKLuV2Tz9bzALI1HCK9TsiAFA9DAJqEII+BhhK0Ei5LlaHwOSHWPBt5Tn8SYvPt37hcIz/fQrRR7Ezbxj8ukW6NV7LMcVr2rsRRFnvPNCkeEjGy7mpzJP2U7Ya4yiBaQpodlcLlYws9QnMrwwnPV5NpPTJ+ko6WwKKeqcuxOUkH1j3lJb5MdNaUtLjHVW++EpeYGRn9Ibz1xda9MgBFT4JMGhgjLx7CYUWt+DD4kcL5N5dpDGs52eRzG/tnOck3zrWHlO3Vf6QWrDX6zAgMBAAGjgbMwgbAwCQYDVR0TBAIwADALBgNVHQ8EBAMCBaAwEwYDVR0lBAwwCgYIKwYBBQUHAwEwQQYDVR0RBDowOIIYc3ViamVjdC50ZXN0c3ViamVjdC50ZXN0ghx3d3cuc3ViamVjdC50ZXN0c3ViamVjdC50ZXN0MB0GA1UdDgQWBBTWjQtDdX0TrwKP/Tzgtp5x9fKXYjAfBgNVHSMEGDAWgBRxc4NGP1MWIHuIeGrjDN3udJThpzANBgkqhkiG9w0BAQsFAAOCAQEAcK2rdOzhWo6mnlYTjgbZdyCk1nKs4/jvmbx4MfTy/tKMq+OImEvC8TUg2myUzxL284p0WCCVWoALo2hwsYeYLSblfnDAsj90RMZQlDyA7rIEqrfXugqamj+hPLwPoEyZKipTkImT0mAGqakE63BkiP+SSEwveZ8YUr0XG369gHyaP8zv6XTqDkQYHx7kJQCHI87+wN+3XPIiYN5sqrf0Z147w/LO8a+XkOCD5JvTbAZB6sLI9dCvoeifd34l9JhDnlnb4SjnszC2k5gx78CNM1pF9jATS3A7mdz+TyttG4ks/i5/Mor416foXurIEh1oZTKOoxppMowq73c66rrH1g== """ - // swiftlint:enable line_length + // swiftlint:enable line_length + + public static func getSampleCertificate() -> Data { return Data(base64Encoded: cert) ?? Data() } + public static func getSampleCertificateString() -> String { + return cert + } + public static func getSampleCertificateWithHeaders() -> Data? { let certString = """ -----BEGIN CERTIFICATE----- diff --git a/Modules/UtilsLib/Sources/UtilsLib/Mimetype/Parser/XMLParserHandler.swift b/Modules/UtilsLib/Sources/UtilsLib/Mimetype/Parser/XMLParserHandler.swift index f6e40f36..69df0419 100644 --- a/Modules/UtilsLib/Sources/UtilsLib/Mimetype/Parser/XMLParserHandler.swift +++ b/Modules/UtilsLib/Sources/UtilsLib/Mimetype/Parser/XMLParserHandler.swift @@ -38,9 +38,7 @@ final class XMLParserHandler: NSObject, XMLParserDelegate { ) { let formatAttribute = attributeDict["format"] let nameAttribute = attributeDict["Name"] - if elementName == "SignedDoc", ( - formatAttribute == "DIGIDOC-XML" || formatAttribute == "SK-XML" - ) { + if elementName == "SignedDoc", formatAttribute == "DIGIDOC-XML" || formatAttribute == "SK-XML" { foundElement = .ddoc parser.abortParsing() } else if elementName == "denc:EncryptionProperty" && diff --git a/Modules/WebEidLib/Package.resolved b/Modules/WebEidLib/Package.resolved new file mode 100644 index 00000000..9a7e388a --- /dev/null +++ b/Modules/WebEidLib/Package.resolved @@ -0,0 +1,42 @@ +{ + "originHash" : "09cf10bbdfe9573d1bc8b9752a7e17f47f09b91367237876cc824fa8fe6f719f", + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "513364f870f6bfc468f9d2ff0a95caccc10044c5", + "version" : "5.10.2" + } + }, + { + "identity" : "asn1decoder", + "kind" : "remoteSourceControl", + "location" : "https://github.com/filom/ASN1Decoder", + "state" : { + "revision" : "7e1523662c69d019a3299bbfb22fb824141dd1f6", + "version" : "1.10.0" + } + }, + { + "identity" : "factory", + "kind" : "remoteSourceControl", + "location" : "https://github.com/hmlongco/Factory", + "state" : { + "revision" : "ccc898f21992ebc130bc04cc197460a5ae230bcf", + "version" : "2.5.3" + } + }, + { + "identity" : "zipfoundation", + "kind" : "remoteSourceControl", + "location" : "https://github.com/weichsel/ZIPFoundation", + "state" : { + "revision" : "02b6abe5f6eef7e3cbd5f247c5cc24e246efcfe0", + "version" : "0.9.19" + } + } + ], + "version" : 3 +} diff --git a/Modules/WebEidLib/Package.swift b/Modules/WebEidLib/Package.swift index 55dc7bf2..77f9bf18 100644 --- a/Modules/WebEidLib/Package.swift +++ b/Modules/WebEidLib/Package.swift @@ -18,7 +18,8 @@ let package = Package( .package(url: "https://github.com/hmlongco/Factory", exact: .init(2, 5, 3)), .package(url: "https://github.com/Alamofire/Alamofire.git", exact: .init(5, 10, 2)), .package(path: "../UtilsLib"), - .package(path: "../CommonsLib") + .package(path: "../CommonsLib"), + .package(path: "../Test/CommonsTestShared") ], targets: [ .target( @@ -49,6 +50,7 @@ let package = Package( "WebEidLibMocks", "UtilsLib", "CommonsLib", + "CommonsTestShared", .product(name: "UtilsLibMocks", package: "utilslib"), .product(name: "CommonsLibMocks", package: "commonslib"), .product(name: "FactoryTesting", package: "Factory") diff --git a/Modules/WebEidLib/Sources/WebEidLib/Error/WebEidError.swift b/Modules/WebEidLib/Sources/WebEidLib/Error/WebEidError.swift index 1d6fa893..cce9189d 100644 --- a/Modules/WebEidLib/Sources/WebEidLib/Error/WebEidError.swift +++ b/Modules/WebEidLib/Sources/WebEidLib/Error/WebEidError.swift @@ -51,7 +51,7 @@ enum WebEidBuilderError: Error, LocalizedError, Sendable { } } -enum WebEidAlgorithmUtilError: Error, LocalizedError { +enum WebEidAlgorithmUtilError: Error, LocalizedError, Sendable, Equatable { case unsupportedKeyType case unsupportedECKeyLength(Int) case invalidBase64 diff --git a/Modules/WebEidLib/Sources/WebEidLib/Service/WebEidSignService.swift b/Modules/WebEidLib/Sources/WebEidLib/Service/WebEidSignService.swift index 40fac840..c9c25773 100644 --- a/Modules/WebEidLib/Sources/WebEidLib/Service/WebEidSignService.swift +++ b/Modules/WebEidLib/Sources/WebEidLib/Service/WebEidSignService.swift @@ -46,6 +46,7 @@ public actor WebEidSignService: WebEidSignServiceProtocol, Loggable { guard JSONSerialization.isValidJSONObject(payload) else { throw WebEidBuilderError.invalidJSON } + return try JSONSerialization.data(withJSONObject: payload, options: []) } @@ -74,6 +75,7 @@ public actor WebEidSignService: WebEidSignServiceProtocol, Loggable { guard JSONSerialization.isValidJSONObject(payload) else { throw WebEidBuilderError.invalidJSON } + return try JSONSerialization.data(withJSONObject: payload, options: []) } } diff --git a/Modules/WebEidLib/Tests/WebEidLibTests/Service/WebEidAuthServiceTests.swift b/Modules/WebEidLib/Tests/WebEidLibTests/Service/WebEidAuthServiceTests.swift new file mode 100644 index 00000000..38137fe2 --- /dev/null +++ b/Modules/WebEidLib/Tests/WebEidLibTests/Service/WebEidAuthServiceTests.swift @@ -0,0 +1,92 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation +import Testing +import CommonsLib +import CommonsTestShared +import WebEidLibMocks + +@testable import WebEidLib + +struct WebEidAuthServiceTests { + private var service: WebEidAuthServiceProtocol + + // swiftlint:disable line_length + private let testCert = "MIIEDjCCA2+gAwIBAgIQfS1XPVaqF6id70AX3+4UQzAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTI1MDQyMjEwMTg0OFoXDTMwMDQyMTIwNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAASJtW601r+uC3ipDFbn4st6lxtAjqICVTUTIQ0Wq/hsxHPjzSUfWDJqhWXuDBg0E9hDnnQlkIiX+c7vYeBOhHG0kbzjhQ+iz9xF3fnuDHVb/QtXBbXrh4fXWu5tVOb6IkejggHNMIIByTAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFMCEmSnETp87AjT2meEKVgAIKT57MHMGCCsGAQUFBwEBBGcwZTA1BggrBgEFBQcwAoYpaHR0cDovL2Muc2suZWUvVGVzdF9vZl9FU1RFSUQyMDE4LmRlci5jcnQwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MB8GA1UdEQQYMBaBFDM4MDAxMDg1NzE4QGVlc3RpLmVlMEcGA1UdIARAMD4wMgYLKwYBBAGDkSEBAQEwIzAhBggrBgEFBQcCARYVaHR0cHM6Ly93d3cuc2suZWUvQ1BTMAgGBgQAj3oBAjAgBgNVHSUBAf8EFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwawYIKwYBBQUHAQMEXzBdMAgGBgQAjkYBATBRBgYEAI5GAQUwRzBFFj9odHRwczovL3NrLmVlL2VuL3JlcG9zaXRvcnkvY29uZGl0aW9ucy1mb3ItdXNlLW9mLWNlcnRpZmljYXRlcy8TAmVuMB0GA1UdDgQWBBRR540dJ/FCuVZGORkQFu/jLdK1PDAOBgNVHQ8BAf8EBAMCA4gwCgYIKoZIzj0EAwQDgYwAMIGIAkIBSoNaxY9V3Z7w0/tKUcLvzHLfJVb0v6OPHPlBm1wXQBw0dXSOoz3b67OFINismuBWLnvSHvIzLWZv73wth37ERIICQgDCQAFgi70IOKSLBbEGJEmJpjPq+r3VcbfBy/lXhuPOxzaIkAaCejOuehBl31gogGSIQp4LmFmR/4OOszWPOvu41w==" + private let testSignature = + "UYyRpzkKNwFgtgcbI1YQc2l1XQQTj7gy+FW/x94TsEberwzS2Rnu4dqC/JhYB3se2iOk1c6FAK2TN5WJTiIcQ9Nt3o/x7kfEsdkc5c39eUXuD83GXfUsyUxR9IQBQrpL" + // swiftlint:enable line_length + + init() async throws { + service = WebEidAuthService() + } + + @Test + func buildAuthToken_returnJSONPayloadData() async throws { + let authCert = Data(base64Encoded: testCert) ?? Data() + let signature = Data(base64Encoded: testSignature) ?? Data() + let token: [String: Any] = [ + "unverifiedCertificate": testCert, + "issuerApp": "https://web-eid.eu/web-eid-mobile-app/releases/v1.0.0", + "algorithm": "ES384", + "format": "web-eid:1.0", + "signature": testSignature] + + let expected = try JSONSerialization.data( + withJSONObject: token, + options: [] + ) + let result = try await service.buildAuthToken( + authCert: authCert, + signingCert: nil, + signature: signature + ) + + #expect(result.count == expected.count) + } + + @Test + func buildAuthToken_throwinvalidCertificateWhenCertIsInvalid() async throws { + let invalidCert = Data([0x00, 0x01, 0x02]) + let signature = Data(base64Encoded: testSignature) ?? Data() + + await #expect(throws: WebEidBuilderError.invalidCertificate) { + try await service.buildAuthToken( + authCert: invalidCert, + signingCert: nil, + signature: signature + ) + } + } + + @Test + func buildAuthToken_throwUnsupportedKeyTypeWhenCertKeyIsUnsupported() async throws { + let cert = TestCertificateUtil.getSampleCertificate() + let signature = Data(base64Encoded: testSignature) ?? Data() + + await #expect(throws: WebEidAlgorithmUtilError.unsupportedKeyType) { + try await service.buildAuthToken( + authCert: cert, + signingCert: nil, + signature: signature + ) + } + } +} diff --git a/Modules/WebEidLib/Tests/WebEidLibTests/Service/WebEidSignServiceTests.swift b/Modules/WebEidLib/Tests/WebEidLibTests/Service/WebEidSignServiceTests.swift new file mode 100644 index 00000000..6032e91c --- /dev/null +++ b/Modules/WebEidLib/Tests/WebEidLibTests/Service/WebEidSignServiceTests.swift @@ -0,0 +1,146 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation +import Testing +import CommonsLib +import CommonsTestShared +import WebEidLibMocks + +@testable import WebEidLib + +struct WebEidSignServiceTests { + private var service: WebEidSignServiceProtocol + + // swiftlint:disable line_length + private let testCert = "MIID7DCCA02gAwIBAgIQK33iqGajpAnSrLD7w+X3TjAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTI1MDQyMjEwMTg0OVoXDTMwMDQyMTIwNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATYWYk4C8W5+RAMeuvIQVa0sVdobkxXKASvA4lUh5K/whRAT5f3p8n2rw8O3nsCt/1LFyKXVVrZdtWZ1Vh894TA2QHEm6xaXnJs4ZmYo4blrm/nXE1PcEZan9023+73sE+jggGrMIIBpzAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFMCEmSnETp87AjT2meEKVgAIKT57MHMGCCsGAQUFBwEBBGcwZTA1BggrBgEFBQcwAoYpaHR0cDovL2Muc2suZWUvVGVzdF9vZl9FU1RFSUQyMDE4LmRlci5jcnQwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MEgGA1UdIARBMD8wMgYLKwYBBAGDkSEBAQEwIzAhBggrBgEFBQcCARYVaHR0cHM6Ly93d3cuc2suZWUvQ1BTMAkGBwQAi+xAAQIwgYoGCCsGAQUFBwEDBH4wfDAIBgYEAI5GAQEwCAYGBACORgEEMBMGBgQAjkYBBjAJBgcEAI5GAQYBMFEGBgQAjkYBBTBHMEUWP2h0dHBzOi8vc2suZWUvZW4vcmVwb3NpdG9yeS9jb25kaXRpb25zLWZvci11c2Utb2YtY2VydGlmaWNhdGVzLxMCZW4wHQYDVR0OBBYEFFh+R2KDfE2Tdj///kXTCqcz6rRuMA4GA1UdDwEB/wQEAwIGQDAKBggqhkjOPQQDBAOBjAAwgYgCQgD7B3WI1xpXX94+9e3TdaIcUNCj5JkCX15pj1mjRqv/Vx9Hlg3tbgwW2yOhqnTF04+e9rVHCtA8YRINp5BfDFqj/wJCAVuUlCu7GNVSFeU7A6lEORkB6obIALZusUFxT4bsaFWTpKllmvlX6lZm3QEbHgeiD8k7VMPdcw5V51p+B+2WUWBh" + private let testSignature = + "jfrC/H3mn+ySpYCJrzIMm5Wm7sC0VRLyyuA6Jkc7cTt1JwjobbdAleQucJfc71f0MOeGtXouKIjs/HvETPZZNfjtgx/9bzwQCnws9TvZly1XCbscFFYP4rAbz4HNF+wk" + // swiftlint:enable line_length + + init() async throws { + service = WebEidSignService() + } + + @Test + func buildCertificatePayload_returnJSONPayloadData() async throws { + let signingCert = Data(base64Encoded: testCert) ?? Data() + + let payload: [String: Any] = [ + "certificate": testCert, + "supportedSignatureAlgorithms": [ + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-224", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-256", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-384", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-512", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-224", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-256", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-384", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-512", + "paddingScheme": "NONE" + ] + ] + ] + + let expected = try JSONSerialization.data(withJSONObject: payload, options: []) + let result = try await service.buildCertificatePayload(signingCert: signingCert) + + #expect(result.count == expected.count) + } + + @Test + func buildCertificatePayload_throwInvalidCertificateWhenCertIsInvalid() async throws { + let invalidCert = Data([0x00, 0x01, 0x02]) + + await #expect(throws: WebEidBuilderError.invalidCertificate) { + try await service.buildCertificatePayload(signingCert: invalidCert) + } + } + + @Test + func buildCertificatePayload_throwUnsupportedKeyTypeWhenCertKeyIsUnsupported() async throws { + let cert = TestCertificateUtil.getSampleCertificate() + + await #expect(throws: WebEidAlgorithmUtilError.unsupportedKeyType) { + try await service.buildCertificatePayload(signingCert: cert) + } + } + + @Test + func buildSignPayload_returnJSONPayloadData() async throws { + let signature = Data(base64Encoded: testSignature) ?? Data() + let payload: [String: Any] = [ + "signatureAlgorithm": + [ + "hashFunction": "SHA-256", + "paddingScheme": "NONE", + "cryptoAlgorithm": "ECC" + ], + "signature": testSignature + ] + + let expected = try JSONSerialization.data(withJSONObject: payload, options: []) + let result = try await service.buildSignPayload(signingCert: testCert, + signature: signature, + hashFunction: "SHA-256") + + #expect(result.count == expected.count) + } + + @Test + func buildSignPayload_throwUnsupportedHashFunctionWhenHashIsUnsupported() async throws { + let cert = TestCertificateUtil.getSampleCertificateString() + let signature = Data(base64Encoded: testSignature) ?? Data() + await #expect(throws: WebEidAlgorithmUtilError.unsupportedHashFunction("SHA-255")) { + try await service.buildSignPayload(signingCert: cert, + signature: signature, + hashFunction: "SHA-255") + } + } +} diff --git a/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidAlgorithmUtilTests.swift b/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidAlgorithmUtilTests.swift new file mode 100644 index 00000000..74e26b1d --- /dev/null +++ b/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidAlgorithmUtilTests.swift @@ -0,0 +1,239 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation +import Testing +import CommonsLib +import CommonsTestShared +import WebEidLibMocks +import Security + +@testable import WebEidLib + +struct WebEidAlgorithmUtilTests { + + // swiftlint:disable line_length + private let testCert = "MIID7DCCA02gAwIBAgIQK33iqGajpAnSrLD7w+X3TjAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTI1MDQyMjEwMTg0OVoXDTMwMDQyMTIwNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATYWYk4C8W5+RAMeuvIQVa0sVdobkxXKASvA4lUh5K/whRAT5f3p8n2rw8O3nsCt/1LFyKXVVrZdtWZ1Vh894TA2QHEm6xaXnJs4ZmYo4blrm/nXE1PcEZan9023+73sE+jggGrMIIBpzAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFMCEmSnETp87AjT2meEKVgAIKT57MHMGCCsGAQUFBwEBBGcwZTA1BggrBgEFBQcwAoYpaHR0cDovL2Muc2suZWUvVGVzdF9vZl9FU1RFSUQyMDE4LmRlci5jcnQwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MEgGA1UdIARBMD8wMgYLKwYBBAGDkSEBAQEwIzAhBggrBgEFBQcCARYVaHR0cHM6Ly93d3cuc2suZWUvQ1BTMAkGBwQAi+xAAQIwgYoGCCsGAQUFBwEDBH4wfDAIBgYEAI5GAQEwCAYGBACORgEEMBMGBgQAjkYBBjAJBgcEAI5GAQYBMFEGBgQAjkYBBTBHMEUWP2h0dHBzOi8vc2suZWUvZW4vcmVwb3NpdG9yeS9jb25kaXRpb25zLWZvci11c2Utb2YtY2VydGlmaWNhdGVzLxMCZW4wHQYDVR0OBBYEFFh+R2KDfE2Tdj///kXTCqcz6rRuMA4GA1UdDwEB/wQEAwIGQDAKBggqhkjOPQQDBAOBjAAwgYgCQgD7B3WI1xpXX94+9e3TdaIcUNCj5JkCX15pj1mjRqv/Vx9Hlg3tbgwW2yOhqnTF04+e9rVHCtA8YRINp5BfDFqj/wJCAVuUlCu7GNVSFeU7A6lEORkB6obIALZusUFxT4bsaFWTpKllmvlX6lZm3QEbHgeiD8k7VMPdcw5V51p+B+2WUWBh" + private let testSignature = + "jfrC/H3mn+ySpYCJrzIMm5Wm7sC0VRLyyuA6Jkc7cTt1JwjobbdAleQucJfc71f0MOeGtXouKIjs/HvETPZZNfjtgx/9bzwQCnws9TvZly1XCbscFFYP4rAbz4HNF+wk" + // swiftlint:enable line_length + + @Test + func buildSupportedSignatureAlgorithms_returnJSONObject() async throws { + let signingCert = try #require(Data(base64Encoded: testCert)) + let secCert = try #require(SecCertificateCreateWithData(nil, signingCert as CFData)) + let publicKey = try #require(SecCertificateCopyKey(secCert)) + + let expected = [ + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-224", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-256", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-384", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA-512", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-224", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-256", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-384", + "paddingScheme": "NONE" + ], + [ + "cryptoAlgorithm": "ECC", + "hashFunction": "SHA3-512", + "paddingScheme": "NONE" + ] + ] + let result = try WebEidAlgorithmUtil.buildSupportedSignatureAlgorithms( + publicKey: publicKey + ) + let resultTyped = result as? [[String: String]] + + #expect(resultTyped == expected) + } + + @Test + func buildSupportedSignatureAlgorithms_throwUnsupportedKeyTypeWhenCertKeyIsUnsupported() async throws { + let cert = TestCertificateUtil.getSampleCertificate() + let secCert = try #require(SecCertificateCreateWithData(nil, cert as CFData)) + let publicKey = try #require(SecCertificateCopyKey(secCert)) + #expect(throws: WebEidAlgorithmUtilError.unsupportedKeyType) { + try WebEidAlgorithmUtil.buildSupportedSignatureAlgorithms( + publicKey: publicKey + ) + } + } + + @Test + func getAlgorithm_returnAlgorithmString() async throws { + let signingCert = try #require(Data(base64Encoded: testCert)) + let secCert = try #require(SecCertificateCreateWithData(nil, signingCert as CFData)) + let publicKey = try #require(SecCertificateCopyKey(secCert)) + let result = try WebEidAlgorithmUtil.getAlgorithm(publicKey: publicKey) + #expect(result == "ES384") + } + + @Test + func getAlgorithm_throwUnsupportedKeyTypeWhenCertKeyIsUnsupported() async throws { + let cert = TestCertificateUtil.getSampleCertificate() + let secCert = try #require(SecCertificateCreateWithData(nil, cert as CFData)) + let publicKey = try #require(SecCertificateCopyKey(secCert)) + + #expect(throws: WebEidAlgorithmUtilError.unsupportedKeyType) { + try WebEidAlgorithmUtil.getAlgorithm(publicKey: publicKey) + } + } + + @Test + func buildSignatureAlgorithm_returnJSONObjectWhenECCKey() async throws { + let signingCert = Data(base64Encoded: testCert) ?? Data() + let secCert = try #require(SecCertificateCreateWithData(nil, signingCert as CFData)) + let publicKey = try #require(SecCertificateCopyKey(secCert)) + + let expected = ["cryptoAlgorithm": "ECC", + "hashFunction": "SHA-256", + "paddingScheme": "NONE"] + + let result = try WebEidAlgorithmUtil.buildSignatureAlgorithm( + publicKey: publicKey, + hashFunction: "SHA-256" + ) + let resultTyped = result as? [String: String] + #expect(resultTyped == expected) + } + + @Test + func buildSignatureAlgorithm_returnJSONObjectWhenRSAKey() async throws { + let cert = TestCertificateUtil.getSampleCertificate() + let secCert = try #require(SecCertificateCreateWithData(nil, cert as CFData)) + let publicKey = try #require(SecCertificateCopyKey(secCert)) + + let expected = [ + "cryptoAlgorithm": "RSA", + "hashFunction": "SHA-256", + "paddingScheme": "PKCS1.5" + ] + + let result = try WebEidAlgorithmUtil.buildSignatureAlgorithm( + publicKey: publicKey, + hashFunction: "SHA-256" + ) + let resultTyped = result as? [String: String] + #expect(resultTyped == expected) + } + + @Test + func buildSignatureAlgorithm_throwUnsupportedHashFunctionWhenHashIsUnsupported() async throws { + let cert = TestCertificateUtil.getSampleCertificate() + let secCert = try #require(SecCertificateCreateWithData(nil, cert as CFData)) + let publicKey = try #require(SecCertificateCopyKey(secCert)) + + #expect(throws: WebEidAlgorithmUtilError.unsupportedHashFunction("SHA-255")) { + try WebEidAlgorithmUtil.buildSignatureAlgorithm( + publicKey: publicKey, + hashFunction: "SHA-255" + ) + } + } + + @Test + func parseCertificate_returnSecCertificatedWhenValidBase64String() async throws { + let signingCertBase64 = TestCertificateUtil.getSampleCertificateString() + + let result = try WebEidAlgorithmUtil.parseCertificate(signingCertBase64: signingCertBase64) + + #expect(SecCertificateCopyKey(result) != nil) + } + + @Test + func parseCertificate_throwInvalidCertificateWhenInvalidCertString() async throws { + let invalidCert = "MIIEwjC" + + #expect(throws: WebEidAlgorithmUtilError.invalidCertificate) { + try WebEidAlgorithmUtil.parseCertificate(signingCertBase64: invalidCert) + } + } + + @Test + func parseCertificate_throwInvalidBase64WhenInvalidBase64String() async throws { + let invalidBase64String = "ÖÖÖÖÖÖÖ" + + #expect(throws: WebEidAlgorithmUtilError.invalidBase64) { + try WebEidAlgorithmUtil.parseCertificate(signingCertBase64: invalidBase64String) + } + } + + @Test + func certificate_returnSecCertificatedWhenValidDataBytes() async throws { + let signingCert = TestCertificateUtil.getSampleCertificate() + + let result = try #require(WebEidAlgorithmUtil.certificate(from: signingCert)) + _ = try #require(SecCertificateCopyKey(result)) + } + + @Test + func certificate_returnNilWhenInvalidDataBytes() async throws { + let invalidCert = Data([0x00, 0x01, 0x02]) + + let result = WebEidAlgorithmUtil.certificate(from: invalidCert) + + #expect(result == nil) + } + + @Test + func certificate_returnDataWhenBase64StringValid() async throws { + let expected = Data("test result".utf8) + let base64String = expected.base64EncodedString() + + let result = WebEidAlgorithmUtil.base64DecodeFlexible(base64String) + + #expect(result == expected) + } + + @Test + func certificate_returnNilWhenBase64StringInvalid() async throws { + let base64String = "ÖÖÖÖÖÖÖ" + + let result = WebEidAlgorithmUtil.base64DecodeFlexible(base64String) + + #expect(result == nil) + } +} diff --git a/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidRequestParserTests.swift b/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidRequestParserTests.swift new file mode 100644 index 00000000..6cfbcf96 --- /dev/null +++ b/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidRequestParserTests.swift @@ -0,0 +1,519 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation +import Testing +import Security +import CommonsLib +import CommonsTestShared +import UtilsLib + +@testable import WebEidLib + +struct WebEidRequestParserTests { + + // swiftlint:disable line_length + private let eccCertBase64 = "MIID7DCCA02gAwIBAgIQK33iqGajpAnSrLD7w+X3TjAKBggqhkjOPQQDBDBgMQswCQYDVQQGEwJFRTEbMBkGA1UECgwSU0sgSUQgU29sdXRpb25zIEFTMRcwFQYDVQRhDA5OVFJFRS0xMDc0NzAxMzEbMBkGA1UEAwwSVEVTVCBvZiBFU1RFSUQyMDE4MB4XDTI1MDQyMjEwMTg0OVoXDTMwMDQyMTIwNTk1OVowfzELMAkGA1UEBhMCRUUxKjAoBgNVBAMMIUrDlUVPUkcsSkFBSy1LUklTVEpBTiwzODAwMTA4NTcxODEQMA4GA1UEBAwHSsOVRU9SRzEWMBQGA1UEKgwNSkFBSy1LUklTVEpBTjEaMBgGA1UEBRMRUE5PRUUtMzgwMDEwODU3MTgwdjAQBgcqhkjOPQIBBgUrgQQAIgNiAATYWYk4C8W5+RAMeuvIQVa0sVdobkxXKASvA4lUh5K/whRAT5f3p8n2rw8O3nsCt/1LFyKXVVrZdtWZ1Vh894TA2QHEm6xaXnJs4ZmYo4blrm/nXE1PcEZan9023+73sE+jggGrMIIBpzAJBgNVHRMEAjAAMB8GA1UdIwQYMBaAFMCEmSnETp87AjT2meEKVgAIKT57MHMGCCsGAQUFBwEBBGcwZTA1BggrBgEFBQcwAoYpaHR0cDovL2Muc2suZWUvVGVzdF9vZl9FU1RFSUQyMDE4LmRlci5jcnQwLAYIKwYBBQUHMAGGIGh0dHA6Ly9haWEuZGVtby5zay5lZS9lc3RlaWQyMDE4MEgGA1UdIARBMD8wMgYLKwYBBAGDkSEBAQEwIzAhBggrBgEFBQcCARYVaHR0cHM6Ly93d3cuc2suZWUvQ1BTMAkGBwQAi+xAAQIwgYoGCCsGAQUFBwEDBH4wfDAIBgYEAI5GAQEwCAYGBACORgEEMBMGBgQAjkYBBjAJBgcEAI5GAQYBMFEGBgQAjkYBBTBHMEUWP2h0dHBzOi8vc2suZWUvZW4vcmVwb3NpdG9yeS9jb25kaXRpb25zLWZvci11c2Utb2YtY2VydGlmaWNhdGVzLxMCZW4wHQYDVR0OBBYEFFh+R2KDfE2Tdj///kXTCqcz6rRuMA4GA1UdDwEB/wQEAwIGQDAKBggqhkjOPQQDBAOBjAAwgYgCQgD7B3WI1xpXX94+9e3TdaIcUNCj5JkCX15pj1mjRqv/Vx9Hlg3tbgwW2yOhqnTF04+e9rVHCtA8YRINp5BfDFqj/wJCAVuUlCu7GNVSFeU7A6lEORkB6obIALZusUFxT4bsaFWTpKllmvlX6lZm3QEbHgeiD8k7VMPdcw5V51p+B+2WUWBh" + // swiftlint:enable line_length + + // MARK: - Auth + + @Test + func parseAuthURL_returnsAuthRequest_whenValid() throws { + let challenge = String(repeating: "A", count: 44) + let loginUri = "https://example.com/login" + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge, + "loginUri": loginUri, + "getSigningCertificate": true + ] + ) + + let result = try WebEidRequestParser.parseAuthURL(authURL) + + #expect(result.challenge == challenge) + #expect(result.loginUri == loginUri) + #expect(result.getSigningCertificate == true) + #expect(result.origin == "https://example.com") + } + + @Test + func parseAuthURL_returnsFalseForGetSigningCertificate_whenMissing() throws { + let challenge = String(repeating: "B", count: 44) + let loginUri = "https://example.com/login" + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge, + "loginUri": loginUri + ] + ) + + let result = try WebEidRequestParser.parseAuthURL(authURL) + + #expect(result.challenge == challenge) + #expect(result.loginUri == loginUri) + #expect(result.getSigningCertificate == false) + #expect(result.origin == "https://example.com") + } + + @Test + func parseAuthURL_throws_whenFragmentMissing() throws { + let url = try #require(URL(string: "web-eid://authenticate")) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseAuthURL(url) + } + } + + @Test + func parseAuthURL_throwsWebEidException_whenChallengeTooShort() throws { + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": "short", + "loginUri": "https://example.com/login" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseAuthURL(authURL) + } + } + + @Test + func parseAuthURL_throwsWebEidException_whenChallengeTooLong() throws { + let challenge = String(repeating: "A", count: 129) + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge, + "loginUri": "https://example.com/login" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseAuthURL(authURL) + } + } + + @Test + func parseAuthURL_throwsWebEidException_whenChallengeBlank() throws { + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": " ", + "loginUri": "https://example.com/login" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseAuthURL(authURL) + } + } + + @Test + func parseAuthURL_throwsWebEidException_whenLoginUriMissing() throws { + let challenge = String(repeating: "A", count: 44) + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseAuthURL(authURL) + } + } + + @Test + func parseAuthURL_throwsWebEidException_whenLoginUriIsNotHttps() throws { + let challenge = String(repeating: "A", count: 44) + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge, + "loginUri": "http://example.com/login" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseAuthURL(authURL) + } + } + + @Test + func parseAuthURL_throwsWebEidException_whenLoginUriContainsUserInfo() throws { + let challenge = String(repeating: "A", count: 44) + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge, + "loginUri": "https://user:pass@example.com/login" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseAuthURL(authURL) + } + } + + @Test + func parseAuthURL_returnsOriginWithPort_whenResponseUriContainsPort() throws { + let challenge = String(repeating: "A", count: 44) + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge, + "loginUri": "https://example.com:8443/login" + ] + ) + + let result = try WebEidRequestParser.parseAuthURL(authURL) + + #expect(result.origin == "https://example.com:8443") + } + + // MARK: - Certificate + + @Test + func parseCertificateURL_returnsCertificateRequest_whenValid() throws { + let responseUri = "https://example.com/certificate" + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": responseUri + ] + ) + + let result = try WebEidRequestParser.parseCertificateURL(url) + + #expect(result.responseUri == responseUri) + #expect(result.origin == "https://example.com") + } + + @Test + func parseCertificateURL_throwsWebEidException_whenResponseUriMissing() throws { + let url = try makeURL( + scheme: "web-eid", + payload: [:] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseCertificateURL(url) + } + } + + @Test + func parseCertificateURL_throwsWebEidException_whenResponseUriSchemeInvalid() throws { + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "custom://example.com/certificate" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseCertificateURL(url) + } + } + + // MARK: - Sign + + @Test + func parseSignURL_returnsSignRequest_whenValid() throws { + let responseUri = "https://example.com/sign" + let hashData = Data(repeating: 0xAB, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": responseUri, + "hash": hashBase64, + "hashFunction": "SHA-256", + "signingCertificate": eccCertBase64 + ] + ) + + let result = try WebEidRequestParser.parseSignURL(url) + + #expect(result.responseUri == responseUri) + #expect(result.origin == "https://example.com") + #expect(result.hash == hashBase64) + #expect(result.hashFunction == "SHA-256") + #expect(SecCertificateCopyKey(result.signingCertificate) != nil) + + #expect(result.personalData?.surname == "JÕEORG") + #expect(result.personalData?.givenNames == "JAAK-KRISTJAN") + #expect(result.personalData?.personalCode == "38001085718") + } + + @Test + func parseSignURL_throwsWebEidException_whenHashMissing() throws { + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hashFunction": "SHA-256", + "signingCertificate": eccCertBase64 + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throwsWebEidException_whenHashFunctionMissing() throws { + let hashData = Data(repeating: 0xAB, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": hashBase64, + "signingCertificate": eccCertBase64 + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throwsWebEidException_whenHashEncodingInvalid() throws { + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": "ÖÖÖÖÖÖÖ", + "hashFunction": "SHA-256", + "signingCertificate": eccCertBase64 + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throwsWebEidException_whenHashFunctionUnsupported() throws { + let hashData = Data(repeating: 0xAB, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": hashBase64, + "hashFunction": "SHA-999", + "signingCertificate": eccCertBase64 + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throwsWebEidException_whenHashFunctionTooLong() throws { + let hashData = Data(repeating: 0xAB, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": hashBase64, + "hashFunction": "SHA3-256X", + "signingCertificate": eccCertBase64 + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throwsWebEidException_whenHashLengthDoesNotMatchHashFunction() throws { + let hashData = Data(repeating: 0xAB, count: 31) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": hashBase64, + "hashFunction": "SHA-256", + "signingCertificate": eccCertBase64 + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throws_whenSigningCertificateMissing() throws { + let hashData = Data(repeating: 0xAB, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": hashBase64, + "hashFunction": "SHA-256" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throwsWebEidException_whenSigningCertificateEncodingInvalid() throws { + let hashData = Data(repeating: 0xAB, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": hashBase64, + "hashFunction": "SHA-256", + "signingCertificate": "ÖÖÖÖÖÖÖ" + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + @Test + func parseSignURL_throwsWebEidException_whenResponseUriInvalid() throws { + let hashData = Data(repeating: 0xAB, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "http://example.com/sign", + "hash": hashBase64, + "hashFunction": "SHA-256", + "signingCertificate": eccCertBase64 + ] + ) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseSignURL(url) + } + } + + // MARK: - Fragment / JSON decoding + + @Test + func parseCertificateURL_throwsWebEidException_whenFragmentIsInvalidBase64() throws { + let url = try #require(URL(string: "web-eid://certificate#ÖÖÖÖÖÖÖ")) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseCertificateURL(url) + } + } + + @Test + func parseCertificateURL_throwsWebEidException_whenFragmentIsNotJSONObject() throws { + let arrayJSON = "[1,2,3]" + let fragment = Data(arrayJSON.utf8).base64EncodedString() + let url = try #require(URL(string: "web-eid://certificate#\(fragment)")) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseCertificateURL(url) + } + } + + @Test + func parseCertificateURL_throwsWebEidException_whenFragmentIsNotJSON() throws { + let fragment = Data("not-json".utf8).base64EncodedString() + let url = try #require(URL(string: "web-eid://certificate#\(fragment)")) + + #expect(throws: WebEidException.self) { + try WebEidRequestParser.parseCertificateURL(url) + } + } + + // MARK: - Edge cases + + @Test + func parseAuthURL_acceptsChallengeAtMaxLength() throws { + let challenge = String(repeating: "C", count: 128) + let authURL = try makeURL( + scheme: "web-eid", + payload: [ + "challenge": challenge, + "loginUri": "https://example.com/login" + ] + ) + + let result = try WebEidRequestParser.parseAuthURL(authURL) + + #expect(result.challenge == challenge) + } + + @Test + func parseSignURL_acceptsLowercaseHashFunction() throws { + let hashData = Data(repeating: 0xCD, count: 32) + let hashBase64 = hashData.base64EncodedString() + + let url = try makeURL( + scheme: "web-eid", + payload: [ + "responseUri": "https://example.com/sign", + "hash": hashBase64, + "hashFunction": "sha-256", + "signingCertificate": eccCertBase64 + ] + ) + + let result = try WebEidRequestParser.parseSignURL(url) + + #expect(result.hashFunction == "sha-256") + } + + // MARK: - Helpers + + private func makeURL(scheme: String, payload: [String: Any]) throws -> URL { + let jsonData = try JSONSerialization.data(withJSONObject: payload, options: []) + let fragment = jsonData.base64EncodedString() + return try #require(URL(string: "\(scheme)://request#\(fragment)")) + } +} diff --git a/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidResponseUtilTests.swift b/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidResponseUtilTests.swift new file mode 100644 index 00000000..0526bf1e --- /dev/null +++ b/Modules/WebEidLib/Tests/WebEidLibTests/Utils/WebEidResponseUtilTests.swift @@ -0,0 +1,60 @@ +/* + * Copyright 2017 - 2025 Riigi Infosüsteemi Amet + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +import Foundation +import Testing +import CommonsLib +import CommonsTestShared +import WebEidLibMocks +import Security + +@testable import WebEidLib + +struct WebEidResponseUtilTests { + + @Test + func createErrorPayload_returnJSONObject() async throws { + let code = WebEidErrorCode.ERR_WEBEID_MOBILE_INVALID_REQUEST + let message = "Some error occured!" + + let expected = [ + "error": true, + "code": String(describing: code), + "message": message + ] as [String: Any] + + let result = WebEidResponseUtil.createErrorPayload( + code: code, + message: message + ) + + #expect(result.count == expected.count) + } + + @Test + func createResponseURL_returnURL() async throws { + let result = try WebEidResponseUtil.createResponseURL( + responseUri: "https://riadigidoc.ee/auth", + payload: ["test": "test"] + ) + let expected = "https://riadigidoc.ee/auth#eyJ0ZXN0IjoidGVzdCJ9" + + #expect(result.absoluteString == expected) + } +} diff --git a/Modules/WebEidLib/Tests/WebEidLibTests/WebEidTests.swift b/Modules/WebEidLib/Tests/WebEidLibTests/WebEidTests.swift deleted file mode 100644 index f373bb84..00000000 --- a/Modules/WebEidLib/Tests/WebEidLibTests/WebEidTests.swift +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2017 - 2025 Riigi Infosüsteemi Amet - * - * This library is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 2.1 of the License, or (at your option) any later version. - * - * This library is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public - * License along with this library; if not, write to the Free Software - * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA - * - */ - -import Testing -@testable import WebEidLib - -@Test func example() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. -} diff --git a/RIADigiDocTests/TestPlans/AllTests.xctestplan b/RIADigiDocTests/TestPlans/AllTests.xctestplan index e85965ee..23baa880 100644 --- a/RIADigiDocTests/TestPlans/AllTests.xctestplan +++ b/RIADigiDocTests/TestPlans/AllTests.xctestplan @@ -14,16 +14,23 @@ "testTargets" : [ { "target" : { - "containerPath" : "container:Modules\/IdCardLib", - "identifier" : "IdCardLibTests", - "name" : "IdCardLibTests" + "containerPath" : "container:Modules\/LibdigidocLib", + "identifier" : "LibdigidocLibTests", + "name" : "LibdigidocLibTests" } }, { "target" : { - "containerPath" : "container:RIADigiDoc.xcodeproj", - "identifier" : "DFCE022A2D148B4D00216362", - "name" : "FileImportShareExtensionTests" + "containerPath" : "container:Modules\/MobileIdLib", + "identifier" : "MobileIdLibTests", + "name" : "MobileIdLibTests" + } + }, + { + "target" : { + "containerPath" : "container:Modules\/ConfigLib", + "identifier" : "ConfigLibTests", + "name" : "ConfigLibTests" } }, { @@ -42,30 +49,30 @@ }, { "target" : { - "containerPath" : "container:Modules\/UtilsLib", - "identifier" : "UtilsLibTests", - "name" : "UtilsLibTests" + "containerPath" : "container:Modules\/WebEidLib", + "identifier" : "WebEidLibTests", + "name" : "WebEidLibTests" } }, { "target" : { - "containerPath" : "container:Modules\/MobileIdLib", - "identifier" : "MobileIdLibTests", - "name" : "MobileIdLibTests" + "containerPath" : "container:Modules\/UtilsLib", + "identifier" : "UtilsLibTests", + "name" : "UtilsLibTests" } }, { "target" : { - "containerPath" : "container:Modules\/ConfigLib", - "identifier" : "ConfigLibTests", - "name" : "ConfigLibTests" + "containerPath" : "container:RIADigiDoc.xcodeproj", + "identifier" : "DFCE022A2D148B4D00216362", + "name" : "FileImportShareExtensionTests" } }, { "target" : { - "containerPath" : "container:Modules\/LibdigidocLib", - "identifier" : "LibdigidocLibTests", - "name" : "LibdigidocLibTests" + "containerPath" : "container:Modules\/IdCardLib", + "identifier" : "IdCardLibTests", + "name" : "IdCardLibTests" } } ],