From 737cdaa7660aad7a44a3e950485f5345a58256e9 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Wed, 25 Oct 2023 18:13:05 +0200 Subject: [PATCH 01/26] Added support of QR Code & Deeplink - Proximity check --- .../Model/Requests/WMTAuthorizationData.swift | 37 +++++++- .../UserOperation/WMTProximityCheck.swift | 44 ++++++++++ .../UserOperation/WMTUserOperation.swift | 3 + .../Operations/Model/WMTOperation.swift | 8 ++ .../Service/WMTOperationsImpl.swift | 2 +- .../Operations/Utils/WMTTOTPUtils.swift | 84 +++++++++++++++++++ 6 files changed, 174 insertions(+), 4 deletions(-) create mode 100644 WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift create mode 100644 WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift diff --git a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift index 4da61c0..1065617 100644 --- a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift +++ b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift @@ -25,8 +25,39 @@ class WMTAuthorizationData: Codable { /// Operation id let id: String - init(operationId: String, operationData: String) { - self.data = operationData - self.id = operationId + /// Proximity Otp Object + let proximityCheck: WMTProximityCheckData? + + init(operation: WMTOperation) { + self.id = operation.id + self.data = operation.data + + guard let proximityCheck = operation.proximityCheck else { + self.proximityCheck = nil + return + } + + self.proximityCheck = WMTProximityCheckData( + otp: proximityCheck.otp, + type: proximityCheck.type, + timestampRequested: proximityCheck.timestampRequested, + timestampSigned: Date() + ) } } + +/// Internal proximity check data used for authorization +struct WMTProximityCheckData: Codable { + + /// Tha actual otp code + let otp: String + + /// Type of the Proximity check + let type: WMTProximityCheckType + + /// Timestamp when the operation was delivered to the app + let timestampRequested: Date + + /// Timestamp when the operation was signed + let timestampSigned: Date +} diff --git a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift new file mode 100644 index 0000000..c133fe8 --- /dev/null +++ b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift @@ -0,0 +1,44 @@ +// +// Copyright 2023 Wultra s.r.o. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions +// and limitations under the License. +// + +import Foundation + +/// Object which is used to hold data about proximity check +/// +/// Data shall be assigned to the operation when accessed +public class WMTProximityCheck: Codable { + + /// Tha actual otp code + public let otp: String + + /// Type of the Proximity check + public let type: WMTProximityCheckType + + /// Timestamp when the operation was delivered to the device + public let timestampRequested: Date + + public init(otp: String, type: WMTProximityCheckType, timestampRequested: Date = Date()) { + self.otp = otp + self.type = type + self.timestampRequested = timestampRequested + } +} + +/// Types of possible Proximity Checks +public enum WMTProximityCheckType: String, Codable { + case qrCode = "QR_CODE" + case deeplink = "DEEPLINK" +} diff --git a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift index b9bf8d0..9442b7f 100644 --- a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift +++ b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift @@ -58,4 +58,7 @@ open class WMTUserOperation: WMTOperation, Codable { /// /// Additional UI data such as Pre-Approval Screen or Post-Approval Screen should be presented. public let ui: WMTOperationUIData? + + /// Proximity Check Data to be passed when otp is handed to the app + public var proximityCheck: WMTProximityCheck? } diff --git a/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift b/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift index 2bf7fc9..57205d4 100644 --- a/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift +++ b/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift @@ -25,4 +25,12 @@ public protocol WMTOperation { /// Data for signing var data: String { get } + + /// Additional informations with proximity check data + var proximityCheck: WMTProximityCheck? { get } +} + +/// WMTOperation extension which sets proximityCheck to be nil for backwards compatibility +public extension WMTOperation { + var proximityCheck: WMTProximityCheck? { nil } } diff --git a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift index ec027bf..6297701 100644 --- a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift +++ b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift @@ -241,7 +241,7 @@ class WMTOperationsImpl: WMTOperations, WMTService { return nil } - let data = WMTAuthorizationData(operationId: operation.id, operationData: operation.data) + let data = WMTAuthorizationData(operation: operation) return networking.post(data: .init(data), signedWith: authentication, to: WMTOperationEndpoints.Authorize.endpoint) { response, error in self.processResult(response: response, error: error) { result in diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift new file mode 100644 index 0000000..5f63805 --- /dev/null +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -0,0 +1,84 @@ +// +// Copyright 2023 Wultra s.r.o. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions +// and limitations under the License. +// + +import Foundation + +/// Utility class used for handling TOTP +public class WMTTOTPUtils { + + /// Data payload which is + public struct WMTOperationOTPData: Codable { + + /// The actual otp code + public let otp: String + + /// Id of the operations to which the otp belongs to + public let operationId: String + + public enum Keys: String, CodingKey { + case otp = "potp" + case operationId = "oid" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Keys.self) + otp = try container.decode(String.self, forKey: .otp) + operationId = try container.decode(String.self, forKey: .operationId) + } + } + + /// Method accepts deeeplink URL and returns payload data + public static func tryLoginDeeplink(url: URL) -> WMTOperationOTPData? { + + guard let components = URLComponents(string: url.absoluteString) else { return nil } + + guard let host = components.host, host == "login" else { return nil } + + guard let queryItems = components.queryItems else { return nil } + + guard let code = queryItems.first?.value else { return nil } + + guard let data = parseJWT(code: code) else { return nil } + + return data + } + + /// Method accepts scanned code as a String and returns payload data + public static func getTOTPFromQR(code: String) -> WMTOperationOTPData? { + return parseJWT(code: code) + } + + private static func parseJWT(code: String) -> WMTOperationOTPData? { + let jwtParts = code.split(separator: ".") + + // At this moment we dont care about header, we want only payload which is the second part of JWT + let jwtBase64String = String(jwtParts[1]) + if jwtBase64String.isEmpty == false { + + if let base64EncodedData = jwtBase64String.data(using: .utf8), + let dataPayload = Data(base64Encoded: base64EncodedData) { + + do { + return try JSONDecoder().decode(WMTOperationOTPData.self, from: dataPayload) + } catch { + return nil + } + + } + } + return nil + } +} From 5223bdc1bcde8f6e7f66e1990188a71dd3f120ed Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 27 Oct 2023 12:14:48 +0200 Subject: [PATCH 02/26] Return incorrectly removed init for backward compatibility --- .../Operations/Model/Requests/WMTAuthorizationData.swift | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift index 1065617..e68d7f2 100644 --- a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift +++ b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift @@ -28,6 +28,12 @@ class WMTAuthorizationData: Codable { /// Proximity Otp Object let proximityCheck: WMTProximityCheckData? + init(operationId: String, operationData: String, proximityCheck: WMTProximityCheckData? = nil) { + self.id = operationId + self.data = operationData + self.proximityCheck = proximityCheck + } + init(operation: WMTOperation) { self.id = operation.id self.data = operation.data From ded75b27053e2a56b3e1ffc3be5aca65fb390844 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 27 Oct 2023 12:15:23 +0200 Subject: [PATCH 03/26] Rename otp to totp --- WultraMobileTokenSDK.xcodeproj/project.pbxproj | 6 +++++- .../Operations/Model/Requests/WMTAuthorizationData.swift | 4 ++-- .../Model/UserOperation/WMTProximityCheck.swift | 8 ++++---- WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift | 6 +++--- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/WultraMobileTokenSDK.xcodeproj/project.pbxproj b/WultraMobileTokenSDK.xcodeproj/project.pbxproj index 2bd5988..553dae6 100644 --- a/WultraMobileTokenSDK.xcodeproj/project.pbxproj +++ b/WultraMobileTokenSDK.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 52; + objectVersion = 54; objects = { /* Begin PBXBuildFile section */ @@ -71,6 +71,7 @@ EA6DDF0F29F8036B0011E234 /* WMTPreApprovalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF0E29F8036B0011E234 /* WMTPreApprovalScreen.swift */; }; EA6DDF1A29F804D60011E234 /* WMTPostApprovalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1929F804D60011E234 /* WMTPostApprovalScreen.swift */; }; EA6DDF1C29F807230011E234 /* OperationUIDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */; }; + EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */; }; EACAF7B02A126B7D0021CA54 /* WMTJsonValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */; }; /* End PBXBuildFile section */ @@ -154,6 +155,7 @@ EA6DDF0E29F8036B0011E234 /* WMTPreApprovalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPreApprovalScreen.swift; sourceTree = ""; }; EA6DDF1929F804D60011E234 /* WMTPostApprovalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovalScreen.swift; sourceTree = ""; }; EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationUIDataTests.swift; sourceTree = ""; }; + EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTProximityCheck.swift; sourceTree = ""; }; EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTJsonValue.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -223,6 +225,7 @@ DC8CB205244DD007009DDAA3 /* WMTAllowedOperationSignature.swift */, DCE5EAAF26BD81150061861A /* WMTOperationHistoryEntry.swift */, EA294F3C29F6A07A00A0494E /* WMTOperationUIData.swift */, + EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */, ); path = UserOperation; sourceTree = ""; @@ -632,6 +635,7 @@ DCAB7BCA24580BAC0006989D /* WMTQROperation.swift in Sources */, DCC5CCBF2449F981004679AC /* WMTOperationAttributePartyInfo.swift in Sources */, DC81D1CB244F451E00F80CD6 /* WMTPushImpl.swift in Sources */, + EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */, DC488041292282FF00DB844B /* WMTInboxEndpoints.swift in Sources */, BF53DFC82971905600829814 /* WMTInboxContentType.swift in Sources */, DCA43C6D2993F63E0059A163 /* WMTOperationAttributeImage.swift in Sources */, diff --git a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift index e68d7f2..a2baeae 100644 --- a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift +++ b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift @@ -44,7 +44,7 @@ class WMTAuthorizationData: Codable { } self.proximityCheck = WMTProximityCheckData( - otp: proximityCheck.otp, + totp: proximityCheck.totp, type: proximityCheck.type, timestampRequested: proximityCheck.timestampRequested, timestampSigned: Date() @@ -56,7 +56,7 @@ class WMTAuthorizationData: Codable { struct WMTProximityCheckData: Codable { /// Tha actual otp code - let otp: String + let totp: String /// Type of the Proximity check let type: WMTProximityCheckType diff --git a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift index c133fe8..ef9e485 100644 --- a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift +++ b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift @@ -21,8 +21,8 @@ import Foundation /// Data shall be assigned to the operation when accessed public class WMTProximityCheck: Codable { - /// Tha actual otp code - public let otp: String + /// Tha actual Time-based one time password + public let totp: String /// Type of the Proximity check public let type: WMTProximityCheckType @@ -30,8 +30,8 @@ public class WMTProximityCheck: Codable { /// Timestamp when the operation was delivered to the device public let timestampRequested: Date - public init(otp: String, type: WMTProximityCheckType, timestampRequested: Date = Date()) { - self.otp = otp + public init(totp: String, type: WMTProximityCheckType, timestampRequested: Date = Date()) { + self.totp = totp self.type = type self.timestampRequested = timestampRequested } diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift index 5f63805..0ba7aca 100644 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -22,14 +22,14 @@ public class WMTTOTPUtils { /// Data payload which is public struct WMTOperationOTPData: Codable { - /// The actual otp code - public let otp: String + /// The actual Time-based one time password + public let totp: String /// Id of the operations to which the otp belongs to public let operationId: String public enum Keys: String, CodingKey { - case otp = "potp" + case totp = "totp" case operationId = "oid" } From 9ce0f4c7da535e13a6a763338945fca9381b53fe Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 27 Oct 2023 12:16:10 +0200 Subject: [PATCH 04/26] Fix networking serialization tests --- .../NetworkingObjectsTests.swift | 32 +++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift index e93d30f..69b57bd 100644 --- a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift +++ b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift @@ -273,11 +273,39 @@ extension WPNRequestBase { func testSerialization(expectation: String) { - guard let data = try? JSONEncoder().encode(self), let jsonData = String(data: data, encoding: .utf8) else { + // Convert WPNRequestBase object to Data + guard let data = try? JSONEncoder().encode(self) else { XCTFail("Failed to encode request data") return } + + // Convert String to Data + guard let expectationData = expectation.data(using: .utf8) else { + XCTFail("Failed to encode expectation string") + return + } + + // Convert to [String: Any] + guard let expectationDict = try? JSONSerialization.jsonObject(with: expectationData) as? [String: Any], + let jsonDataDict = try? JSONSerialization.jsonObject(with: data) as? [String: Any] else { + XCTFail("Failed to cast data to format [String: Any]") + return + } - XCTAssert(jsonData == expectation, "Serialized JSON doesn't match expected string") + // Convert back to data with sorted keys + guard let sortedExpectationData = try? JSONSerialization.data(withJSONObject: expectationDict, options: [.sortedKeys]), + let sortedJsonData = try? JSONSerialization.data(withJSONObject: jsonDataDict, options: [.sortedKeys]) else { + XCTFail("Failed to sort data") + return + } + + // Convert Data back to strings for comparison + guard let sortedExpectationString = String(data: sortedExpectationData, encoding: .utf8), + let sortedJsonDataString = String(data: sortedJsonData, encoding: .utf8) else { + XCTFail("Failed to cast data to string") + return + } + + XCTAssertEqual(sortedExpectationString, sortedJsonDataString, "Serialized Strings doesn't match") } } From 34285089157910e5611abe6acda69ae379847361 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 27 Oct 2023 12:16:57 +0200 Subject: [PATCH 05/26] Add offline totp --- .../Operations/QR/WMTQROperation.swift | 8 +++- .../Operations/QR/WMTQROperationParser.swift | 9 ++-- .../QROperationParserTests.swift | 44 ++++++++++++++++++- 3 files changed, 55 insertions(+), 6 deletions(-) diff --git a/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift b/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift index 2a11093..465610e 100644 --- a/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift +++ b/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift @@ -37,6 +37,9 @@ public struct WMTQROperation { /// Flags associated with the operation public let flags: QROperationFlags + /// Additional Time-based one time password for proximity check + public let totp: String? + /// Data for signature validation public let signedData: Data @@ -52,7 +55,10 @@ public struct WMTQROperation { } internal var dataForOfflineSigning: Data { - return "\(operationId)&\(operationData.sourceString)".data(using: .utf8)! + guard let totp = totp else { + return "\(operationId)&\(operationData.sourceString)".data(using: .utf8)! + } + return "\(operationId)&\(operationData.sourceString)&\(totp)".data(using: .utf8)! } } diff --git a/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift b/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift index b779b56..ceb01d3 100644 --- a/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift +++ b/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift @@ -30,10 +30,10 @@ public class WMTQROperationParser { private static let minimumAttributeFields = 7 /// Current number of lines in input string, supported by this parser - private static let currentAttributeFields = 7 + private static let currentAttributeFields = 8 /// Maximum number of operation data fields supported in this version. - private static let maximumDataFields = 5 + private static let maximumDataFields = 6 /// Parses input string into `WMTQROperationData` structure. public func parse(string: String) -> WMTQROperationParseResult { @@ -47,6 +47,7 @@ public class WMTQROperationParser { let message = parseAttributeText(from: String(attributes[2])) let dataString = String(attributes[3]) let flagsString = String(attributes[4]) + let totp = attributes.count > WMTQROperationParser.minimumAttributeFields ? String(attributes[5]) : nil // Signature and nonce are always located at last lines let nonce = String(attributes[attributes.count - 2]) let signatureString = attributes[attributes.count - 1] @@ -74,7 +75,8 @@ public class WMTQROperationParser { // Parse flags let flags = parseOperationFlags(string: flagsString) - let isNewerFormat = attributes.count > WMTQROperationParser.currentAttributeFields + let current = WMTQROperationParser.currentAttributeFields + let isNewerFormat = attributes.count > current // Build final structure return .success(WMTQROperation( @@ -84,6 +86,7 @@ public class WMTQROperationParser { operationData: formData, nonce: nonce, flags: flags, + totp: totp, signedData: signedData, signature: signature, isNewerFormat: isNewerFormat) diff --git a/WultraMobileTokenSDKTests/QROperationParserTests.swift b/WultraMobileTokenSDKTests/QROperationParserTests.swift index d685e8e..5f374ce 100644 --- a/WultraMobileTokenSDKTests/QROperationParserTests.swift +++ b/WultraMobileTokenSDKTests/QROperationParserTests.swift @@ -36,7 +36,7 @@ class QROperationParserTests: XCTestCase { // MARK: - Main tests - func testCurrentFormat() { + func testCurrentFormat() { // Without TOTP let parser = WMTQROperationParser() let qrcode = makeCode() let expectedSignedData = @@ -102,15 +102,55 @@ class QROperationParserTests: XCTestCase { } } + func testCurrentFormatWithTOTP() { + let parser = WMTQROperationParser() + let qrcode = makeCode(otherAttrs: ["12345678"]) + let expectedSignedData = + ("5ff1b1ed-a3cc-45a3-8ab0-ed60950312b6\n" + + "Payment\n" + + "Please confirm this payment\n" + + "A1*A100CZK*ICZ2730300000001165254011*D20180425*Thello world\n" + + "BCFX\n" + + "12345678\n" + + "AD8bOO0Df73kNaIGb3Vmpg==\n" + + "0").data(using: .utf8) + + guard case .success(let operation) = parser.parse(string: qrcode) else { + XCTFail("This should be parsed") + return + } + + XCTAssertTrue(operation.operationId == "5ff1b1ed-a3cc-45a3-8ab0-ed60950312b6") + XCTAssertTrue(operation.title == "Payment") + XCTAssertTrue(operation.message == "Please confirm this payment") + XCTAssertTrue(operation.flags.allowBiometryFactor == true) + XCTAssertTrue(operation.flags.flipButtons == true) + XCTAssertTrue(operation.flags.fraudWarning == true) + XCTAssertTrue(operation.flags.blockWhenOnCall == true) + XCTAssertTrue(operation.totp == "12345678") + XCTAssertTrue(operation.nonce == "AD8bOO0Df73kNaIGb3Vmpg==") + XCTAssertTrue(operation.signature.signature == "MEYCIQDby1Uq+MaxiAAGzKmE/McHzNOUrvAP2qqGBvSgcdtyjgIhAMo1sgqNa1pPZTFBhhKvCKFLGDuHuTTYexdmHFjUUIJW") + XCTAssertTrue(operation.signature.signingKey == .master) + XCTAssertTrue(operation.signedData == expectedSignedData) + + // Operation data + XCTAssertTrue(operation.operationData.version == .v1) + XCTAssertTrue(operation.operationData.templateId == 1) + XCTAssertTrue(operation.operationData.fields.count == 4) + XCTAssertTrue(operation.operationData.sourceString == "A1*A100CZK*ICZ2730300000001165254011*D20180425*Thello world") + } + + func testForwardCompatibility() { let parser = WMTQROperationParser() - let qrcode = makeCode(operationData:"B2*Xtest", otherAttrs:["Some Additional Information"]) + let qrcode = makeCode(operationData:"B2*Xtest", otherAttrs:["12345678", "Some Additional Information"]) let expectedSignedData = ("5ff1b1ed-a3cc-45a3-8ab0-ed60950312b6\n" + "Payment\n" + "Please confirm this payment\n" + "B2*Xtest\n" + "BCFX\n" + + "12345678\n" + "Some Additional Information\n" + "AD8bOO0Df73kNaIGb3Vmpg==\n" + "0").data(using: .utf8) From a5489a330d88ed8b8bf37e03477f36b973637f42 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 27 Oct 2023 13:38:16 +0200 Subject: [PATCH 06/26] Test totp authorization --- .../NetworkingObjectsTests.swift | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift index 69b57bd..6601236 100644 --- a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift +++ b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift @@ -234,6 +234,35 @@ class NetworkingObjectsTests: XCTestCase { request.testSerialization(expectation: expectation) } + func testTOTPOperationAuthorizationRequest() { + + let response = """ + {"status":"OK","responseObject":[{"id":"47825519-35b8-469d-ad76-e42f85b9a31d","name":"login_preApproval","data":"A2","status":"PENDING","operationCreated":"2023-10-27T11:04:00+0000","operationExpires":"2023-10-27T11:54:00+0000","ui":{"preApprovalScreen":{"type":"QR_SCAN","heading":"Scan the QR code!","message":"To verify that you are close by, please scan the code from the monitor."}},"allowedSignatureType":{"type":"2FA","variants":["possession_knowledge","possession_biometry"]},"formData":{"title":"Login Approval","message":"Are you logging in to the internet banking?","attributes":[]}}],"currentTimestamp":"2023-10-27T11:04:15+0000"} + """ + guard let result = try? jsonDecoder.decode(WMTOperationListResponse.self, from: response.data(using: .utf8)!) else { + XCTFail("Failed to parse JSON data") + return + } + + guard let operations = result.responseObject else { + XCTFail("response object nil") + return + } + + let op = operations[0] + op.proximityCheck = WMTProximityCheck(totp: "12345678", type: .qrCode) + + let request = WMTOperationEndpoints.Authorize.EndpointType.RequestData(.init(operation: op)) + + let proximityCheck = request.requestObject?.proximityCheck + + XCTAssertEqual(request.requestObject?.data, "A2") + XCTAssertEqual(request.requestObject?.id, "47825519-35b8-469d-ad76-e42f85b9a31d") + XCTAssertEqual(proximityCheck?.type, .qrCode) + XCTAssertEqual(proximityCheck?.totp, "12345678") + } + + func testOperationRejectionRequest() { let expectation = """ From 916845c1670c120d27545c0dd2a69ee0eedb5f97 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 27 Oct 2023 13:50:51 +0200 Subject: [PATCH 07/26] Fix lint and missing file --- WultraMobileTokenSDK.xcodeproj/project.pbxproj | 4 ++++ WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/WultraMobileTokenSDK.xcodeproj/project.pbxproj b/WultraMobileTokenSDK.xcodeproj/project.pbxproj index 553dae6..2122cbb 100644 --- a/WultraMobileTokenSDK.xcodeproj/project.pbxproj +++ b/WultraMobileTokenSDK.xcodeproj/project.pbxproj @@ -72,6 +72,7 @@ EA6DDF1A29F804D60011E234 /* WMTPostApprovalScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1929F804D60011E234 /* WMTPostApprovalScreen.swift */; }; EA6DDF1C29F807230011E234 /* OperationUIDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */; }; EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */; }; + EA9CE2C22AEBDB0D00FE4E35 /* WMTTOTPUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */; }; EACAF7B02A126B7D0021CA54 /* WMTJsonValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */; }; /* End PBXBuildFile section */ @@ -156,6 +157,7 @@ EA6DDF1929F804D60011E234 /* WMTPostApprovalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovalScreen.swift; sourceTree = ""; }; EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationUIDataTests.swift; sourceTree = ""; }; EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTProximityCheck.swift; sourceTree = ""; }; + EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTTOTPUtils.swift; sourceTree = ""; }; EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTJsonValue.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -290,6 +292,7 @@ DC6E52D4259C959900FC25BE /* Utils */ = { isa = PBXGroup; children = ( + EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */, DC6E52D5259C964600FC25BE /* WMTOperationExpirationWatcher.swift */, EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */, ); @@ -619,6 +622,7 @@ BFEEB2092937A2680047941D /* WMTInboxGetList.swift in Sources */, BFEEB20729379F960047941D /* WMTInboxSetMessageRead.swift in Sources */, EA44366A29F9294600DDEC1C /* WMTPostApprovaScreenReview.swift in Sources */, + EA9CE2C22AEBDB0D00FE4E35 /* WMTTOTPUtils.swift in Sources */, EA294F3D29F6A07A00A0494E /* WMTOperationUIData.swift in Sources */, DCC5CCB32449F8CD004679AC /* WMTOperationAttribute.swift in Sources */, DCC5CCAC2449F765004679AC /* WMTOperationsImpl.swift in Sources */, diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift index 0ba7aca..3e22ba7 100644 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -35,7 +35,7 @@ public class WMTTOTPUtils { public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: Keys.self) - otp = try container.decode(String.self, forKey: .otp) + totp = try container.decode(String.self, forKey: .totp) operationId = try container.decode(String.self, forKey: .operationId) } } From b7a909eab94c2470bf2c9c255dcd94cafb64b4a3 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Tue, 31 Oct 2023 14:23:00 +0100 Subject: [PATCH 08/26] Implement remarks --- .../Model/Requests/WMTAuthorizationData.swift | 16 ++--- .../UserOperation/WMTProximityCheck.swift | 4 +- .../Service/WMTOperationsImpl.swift | 2 +- .../Operations/Utils/WMTTOTPUtils.swift | 71 ++++++++++--------- 4 files changed, 47 insertions(+), 46 deletions(-) diff --git a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift index a2baeae..02e1026 100644 --- a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift +++ b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift @@ -25,18 +25,18 @@ class WMTAuthorizationData: Codable { /// Operation id let id: String - /// Proximity Otp Object + /// Proximity OTP data let proximityCheck: WMTProximityCheckData? init(operationId: String, operationData: String, proximityCheck: WMTProximityCheckData? = nil) { - self.id = operationId - self.data = operationData - self.proximityCheck = proximityCheck + self.id = operationId + self.data = operationData + self.proximityCheck = proximityCheck } - init(operation: WMTOperation) { - self.id = operation.id - self.data = operation.data + init(operation: WMTOperation, timestampSigned: Date = Date()) { + self.id = operation.id + self.data = operation.data guard let proximityCheck = operation.proximityCheck else { self.proximityCheck = nil @@ -47,7 +47,7 @@ class WMTAuthorizationData: Codable { totp: proximityCheck.totp, type: proximityCheck.type, timestampRequested: proximityCheck.timestampRequested, - timestampSigned: Date() + timestampSigned: timestampSigned ) } } diff --git a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift index ef9e485..e05411e 100644 --- a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift +++ b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTProximityCheck.swift @@ -18,7 +18,7 @@ import Foundation /// Object which is used to hold data about proximity check /// -/// Data shall be assigned to the operation when accessed +/// Data shall be assigned to the operation when obtained public class WMTProximityCheck: Codable { /// Tha actual Time-based one time password @@ -27,7 +27,7 @@ public class WMTProximityCheck: Codable { /// Type of the Proximity check public let type: WMTProximityCheckType - /// Timestamp when the operation was delivered to the device + /// Timestamp when the operation was scanned (qrCode) or delivered to the device (deeplink) public let timestampRequested: Date public init(totp: String, type: WMTProximityCheckType, timestampRequested: Date = Date()) { diff --git a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift index 6297701..2ff9492 100644 --- a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift +++ b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift @@ -241,7 +241,7 @@ class WMTOperationsImpl: WMTOperations, WMTService { return nil } - let data = WMTAuthorizationData(operation: operation) + let data = WMTAuthorizationData(operation: operation, timestampSigned: currentServerDate ?? Date()) return networking.post(data: .init(data), signedWith: authentication, to: WMTOperationEndpoints.Authorize.endpoint) { response, error in self.processResult(response: response, error: error) { result in diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift index 3e22ba7..5364481 100644 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -19,29 +19,8 @@ import Foundation /// Utility class used for handling TOTP public class WMTTOTPUtils { - /// Data payload which is - public struct WMTOperationOTPData: Codable { - - /// The actual Time-based one time password - public let totp: String - - /// Id of the operations to which the otp belongs to - public let operationId: String - - public enum Keys: String, CodingKey { - case totp = "totp" - case operationId = "oid" - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: Keys.self) - totp = try container.decode(String.self, forKey: .totp) - operationId = try container.decode(String.self, forKey: .operationId) - } - } - /// Method accepts deeeplink URL and returns payload data - public static func tryLoginDeeplink(url: URL) -> WMTOperationOTPData? { + public static func parseLoginDeeplink(url: URL) -> WMTOperationTOTPData? { guard let components = URLComponents(string: url.absoluteString) else { return nil } @@ -57,28 +36,50 @@ public class WMTTOTPUtils { } /// Method accepts scanned code as a String and returns payload data - public static func getTOTPFromQR(code: String) -> WMTOperationOTPData? { + public static func getTOTPFromQR(code: String) -> WMTOperationTOTPData? { return parseJWT(code: code) } - private static func parseJWT(code: String) -> WMTOperationOTPData? { + private static func parseJWT(code: String) -> WMTOperationTOTPData? { let jwtParts = code.split(separator: ".") // At this moment we dont care about header, we want only payload which is the second part of JWT - let jwtBase64String = String(jwtParts[1]) - if jwtBase64String.isEmpty == false { + let jwtBase64String = jwtParts.count > 1 ? String(jwtParts[1]) : "" + + if let base64EncodedData = jwtBase64String.data(using: .utf8), + let dataPayload = Data(base64Encoded: base64EncodedData) { - if let base64EncodedData = jwtBase64String.data(using: .utf8), - let dataPayload = Data(base64Encoded: base64EncodedData) { - - do { - return try JSONDecoder().decode(WMTOperationOTPData.self, from: dataPayload) - } catch { - return nil - } - + do { + return try JSONDecoder().decode(WMTOperationTOTPData.self, from: dataPayload) + } catch { + D.error("Failed to decode QR JWT: \(code)") + D.error("With error: \(error)") + return nil } } + + D.error("Failed to decode QR JWT from: \(jwtBase64String)") return nil } } + +/// Data payload which is +public struct WMTOperationTOTPData: Codable { + + /// The actual Time-based one time password + public let totp: String + + /// Id of the operations to which the otp belongs to + public let operationId: String + + public enum Keys: String, CodingKey { + case totp = "totp" + case operationId = "oid" + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: Keys.self) + totp = try container.decode(String.self, forKey: .totp) + operationId = try container.decode(String.self, forKey: .operationId) + } +} From 11897f210e16ebaa2362c4e7b0a8f4ce19ab39e8 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Tue, 31 Oct 2023 14:23:56 +0100 Subject: [PATCH 09/26] Add `TOTPParserTests` --- .../project.pbxproj | 4 ++ .../TOTPParserTests.swift | 59 +++++++++++++++++++ 2 files changed, 63 insertions(+) create mode 100644 WultraMobileTokenSDKTests/TOTPParserTests.swift diff --git a/WultraMobileTokenSDK.xcodeproj/project.pbxproj b/WultraMobileTokenSDK.xcodeproj/project.pbxproj index 2122cbb..06ff6ba 100644 --- a/WultraMobileTokenSDK.xcodeproj/project.pbxproj +++ b/WultraMobileTokenSDK.xcodeproj/project.pbxproj @@ -73,6 +73,7 @@ EA6DDF1C29F807230011E234 /* OperationUIDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */; }; EA9CE2BE2AEAA9FD00FE4E35 /* WMTProximityCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */; }; EA9CE2C22AEBDB0D00FE4E35 /* WMTTOTPUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */; }; + EAB7054A2AF1161500756AC2 /* TOTPParserTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = EAB705492AF1161500756AC2 /* TOTPParserTests.swift */; }; EACAF7B02A126B7D0021CA54 /* WMTJsonValue.swift in Sources */ = {isa = PBXBuildFile; fileRef = EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */; }; /* End PBXBuildFile section */ @@ -158,6 +159,7 @@ EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationUIDataTests.swift; sourceTree = ""; }; EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTProximityCheck.swift; sourceTree = ""; }; EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTTOTPUtils.swift; sourceTree = ""; }; + EAB705492AF1161500756AC2 /* TOTPParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPParserTests.swift; sourceTree = ""; }; EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTJsonValue.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -275,6 +277,7 @@ DC395C0924E55B9B0007C36E /* PushParserTests.swift */, DC6EDB7825A49ED900A229E4 /* OperationExpirationTests.swift */, DC616235248508F8000DED17 /* QROperationParserTests.swift */, + EAB705492AF1161500756AC2 /* TOTPParserTests.swift */, ); path = WultraMobileTokenSDKTests; sourceTree = ""; @@ -582,6 +585,7 @@ DC61624224852B6D000DED17 /* NetworkingObjectsTests.swift in Sources */, DC395C0A24E55B9B0007C36E /* PushParserTests.swift in Sources */, DC6EDB7925A49ED900A229E4 /* OperationExpirationTests.swift in Sources */, + EAB7054A2AF1161500756AC2 /* TOTPParserTests.swift in Sources */, DC616236248508F8000DED17 /* QROperationParserTests.swift in Sources */, EA6DDF1C29F807230011E234 /* OperationUIDataTests.swift in Sources */, DCE660D124CEBECA00870E53 /* IntegrationTests.swift in Sources */, diff --git a/WultraMobileTokenSDKTests/TOTPParserTests.swift b/WultraMobileTokenSDKTests/TOTPParserTests.swift new file mode 100644 index 0000000..343d43b --- /dev/null +++ b/WultraMobileTokenSDKTests/TOTPParserTests.swift @@ -0,0 +1,59 @@ +// +// Copyright 2023 Wultra s.r.o. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions +// and limitations under the License. +// + +import XCTest +import WultraMobileTokenSDK + +final class TOTPParserTest: XCTestCase { + + func testQRTOTPParserWithEmptyCode() { + let code = "" + + XCTAssertNil(WMTTOTPUtils.getTOTPFromQR(code: code)) + } + + func testQRTOTPParserWithShortCode() { + let code = "abc" + + XCTAssertNil(WMTTOTPUtils.getTOTPFromQR(code: code)) + } + + func testQRTOTPParserWithValidCode() { + let code = "eyJhbGciOiJub25lIiwidHlwZSI6IkpXVCJ9.eyJvaWQiOiI2YTFjYjAwNy1mZjc1LTRmNDAtYTIxYi0wYjU0NmYwZjZjYWQiLCJ0b3RwIjoiNzM3NDMxOTQifQ==" + + XCTAssertEqual(WMTTOTPUtils.getTOTPFromQR(code: code)?.totp, "73743194", "Parsing of totp failed") + XCTAssertEqual(WMTTOTPUtils.getTOTPFromQR(code: code)?.operationId, "6a1cb007-ff75-4f40-a21b-0b546f0f6cad", "Parsing of operationId failed") + } + + + func testDeeplinkTOTPParserWithInvalidURL() { + let url = URL(string: "mtoken://an-invalid-url.com")! + XCTAssertNil(WMTTOTPUtils.parseLoginDeeplink(url: url)) + } + + func testDeeplinkTOTPParserWithInvalidJWTCode() { + let url = URL(string: "mtoken://login?code=abc")! + + XCTAssertNil(WMTTOTPUtils.parseLoginDeeplink(url: url)) + } + + func testDeeplinkTOTPParserWithValidJWTCode() { + let url = URL(string: "mtoken://login?code=eyJhbGciOiJub25lIiwidHlwZSI6IkpXVCJ9.eyJvaWQiOiJkZjYxMjhmYy1jYTUxLTQ0YjctYmVmYS1jYTBlMTQwOGFhNjMiLCJ0b3RwIjoiNTY3MjU0OTQifQ==")! + + XCTAssertEqual(WMTTOTPUtils.parseLoginDeeplink(url: url)?.totp, "56725494", "Parsing of totp failed") + XCTAssertEqual(WMTTOTPUtils.parseLoginDeeplink(url: url)?.operationId, "df6128fc-ca51-44b7-befa-ca0e1408aa63", "Parsing of operationId failed") + } +} From f25cf71f67b3cbf51b4a2030248978e932f8d8c0 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Tue, 31 Oct 2023 14:33:56 +0100 Subject: [PATCH 10/26] Minor fixes --- .../Operations/Model/UserOperation/WMTUserOperation.swift | 2 +- WultraMobileTokenSDK/Operations/Model/WMTOperation.swift | 2 +- WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift index 9442b7f..639a1ea 100644 --- a/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift +++ b/WultraMobileTokenSDK/Operations/Model/UserOperation/WMTUserOperation.swift @@ -59,6 +59,6 @@ open class WMTUserOperation: WMTOperation, Codable { /// Additional UI data such as Pre-Approval Screen or Post-Approval Screen should be presented. public let ui: WMTOperationUIData? - /// Proximity Check Data to be passed when otp is handed to the app + /// Proximity Check Data to be passed when OTP is handed to the app public var proximityCheck: WMTProximityCheck? } diff --git a/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift b/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift index 57205d4..1185c56 100644 --- a/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift +++ b/WultraMobileTokenSDK/Operations/Model/WMTOperation.swift @@ -26,7 +26,7 @@ public protocol WMTOperation { /// Data for signing var data: String { get } - /// Additional informations with proximity check data + /// Additional information with proximity check data var proximityCheck: WMTProximityCheck? { get } } diff --git a/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift b/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift index ceb01d3..61df439 100644 --- a/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift +++ b/WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift @@ -75,8 +75,7 @@ public class WMTQROperationParser { // Parse flags let flags = parseOperationFlags(string: flagsString) - let current = WMTQROperationParser.currentAttributeFields - let isNewerFormat = attributes.count > current + let isNewerFormat = attributes.count > WMTQROperationParser.currentAttributeFields // Build final structure return .success(WMTQROperation( From 5a63912abeb1daec0a7f36d829a8eaec376d34ba Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Thu, 2 Nov 2023 14:24:22 +0100 Subject: [PATCH 11/26] Fix incorrect name of otp in `WMTProximityCheckData` --- .../Operations/Model/Requests/WMTAuthorizationData.swift | 4 ++-- WultraMobileTokenSDKTests/NetworkingObjectsTests.swift | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift index 02e1026..2d07eae 100644 --- a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift +++ b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift @@ -44,7 +44,7 @@ class WMTAuthorizationData: Codable { } self.proximityCheck = WMTProximityCheckData( - totp: proximityCheck.totp, + otp: proximityCheck.totp, type: proximityCheck.type, timestampRequested: proximityCheck.timestampRequested, timestampSigned: timestampSigned @@ -56,7 +56,7 @@ class WMTAuthorizationData: Codable { struct WMTProximityCheckData: Codable { /// Tha actual otp code - let totp: String + let otp: String /// Type of the Proximity check let type: WMTProximityCheckType diff --git a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift index 6601236..6996faa 100644 --- a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift +++ b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift @@ -259,7 +259,7 @@ class NetworkingObjectsTests: XCTestCase { XCTAssertEqual(request.requestObject?.data, "A2") XCTAssertEqual(request.requestObject?.id, "47825519-35b8-469d-ad76-e42f85b9a31d") XCTAssertEqual(proximityCheck?.type, .qrCode) - XCTAssertEqual(proximityCheck?.totp, "12345678") + XCTAssertEqual(proximityCheck?.otp, "12345678") } From 82e752aca651fa119caa4d99788a5b7ec7ae72d5 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Tue, 7 Nov 2023 10:16:16 +0100 Subject: [PATCH 12/26] Bump networking dependency to 1.2.0. --- Package.resolved | 23 +++++++++++++++++++ Package.swift | 2 +- .../Service/WMTOperationsImpl.swift | 4 ++++ 3 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 Package.resolved diff --git a/Package.resolved b/Package.resolved new file mode 100644 index 0000000..26bccf3 --- /dev/null +++ b/Package.resolved @@ -0,0 +1,23 @@ +{ + "pins" : [ + { + "identity" : "networking-apple", + "kind" : "remoteSourceControl", + "location" : "https://github.com/wultra/networking-apple.git", + "state" : { + "revision" : "b7ebe23f441e614d13bd6bc803d658e66767e31a", + "version" : "1.2.0" + } + }, + { + "identity" : "powerauth-mobile-sdk-spm", + "kind" : "remoteSourceControl", + "location" : "https://github.com/wultra/powerauth-mobile-sdk-spm.git", + "state" : { + "revision" : "095be6adfc057501a7cb9a7351697a1b6ed9e64b", + "version" : "1.7.8" + } + } + ], + "version" : 2 +} diff --git a/Package.swift b/Package.swift index bd9bf5f..f26b9ba 100644 --- a/Package.swift +++ b/Package.swift @@ -12,7 +12,7 @@ let package = Package( ], dependencies: [ .package(url: "https://github.com/wultra/powerauth-mobile-sdk-spm.git", .upToNextMinor(from: "1.7.8")), - .package(url: "https://github.com/wultra/networking-apple.git", .upToNextMinor(from: "1.1.7")) + .package(url: "https://github.com/wultra/networking-apple.git", .upToNextMinor(from: "1.2.0")) ], targets: [ .target( diff --git a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift index 2ff9492..4df9e2c 100644 --- a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift +++ b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift @@ -82,6 +82,8 @@ public extension WMTErrorReason { static let operations_authExpired = WMTErrorReason(rawValue: "operations_authExpired") /// Operation has expired when trying to reject the operation. static let operations_rejectExpired = WMTErrorReason(rawValue: "operations_rejectExpired") + /// Operation authentication failed (e.g. incorrect totp). + static let operations_failed = WMTErrorReason(rawValue: "operations_failed") /// Couldn't sign QR operation. static let operations_QROperationFailed = WMTErrorReason(rawValue: "operations_QRFailed") @@ -436,6 +438,8 @@ class WMTOperationsImpl: WMTOperations, WMTService { } else { reason = .operations_rejectExpired } + case .operationFailed: + reason = .operations_failed default: break } From 89e91ef179ab08bdbfca8f658664b0ceec481195 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Wed, 8 Nov 2023 10:58:58 +0100 Subject: [PATCH 13/26] Remove Package.swift from repo --- Package.swift | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 Package.swift diff --git a/Package.swift b/Package.swift deleted file mode 100644 index f26b9ba..0000000 --- a/Package.swift +++ /dev/null @@ -1,30 +0,0 @@ -// swift-tools-version:5.7 - -import PackageDescription - -let package = Package( - name: "WultraMobileTokenSDK", - platforms: [ - .iOS(.v11) - ], - products: [ - .library(name: "WultraMobileTokenSDK", targets: ["WultraMobileTokenSDK"]) - ], - dependencies: [ - .package(url: "https://github.com/wultra/powerauth-mobile-sdk-spm.git", .upToNextMinor(from: "1.7.8")), - .package(url: "https://github.com/wultra/networking-apple.git", .upToNextMinor(from: "1.2.0")) - ], - targets: [ - .target( - name: "WultraMobileTokenSDK", - dependencies: [ - .product(name: "PowerAuth2", package: "powerauth-mobile-sdk-spm"), - .product(name: "PowerAuthCore", package: "powerauth-mobile-sdk-spm"), - .product(name: "WultraPowerAuthNetworking", package: "networking-apple") - ], - path: "WultraMobileTokenSDK", - exclude: ["ConfigFiles/Config.xcconfig", "ConfigFiles/Debug.xcconfig", "ConfigFiles/Release.xcconfig", "Info.plist", "Podfile"] - ) - ], - swiftLanguageVersions: [.v5] -) From e4bf60791212281f46edb4c1ca5d5cd6dd106ad7 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Wed, 8 Nov 2023 12:55:44 +0100 Subject: [PATCH 14/26] Implement remarks --- .../Operations/QR/WMTQROperation.swift | 5 ++-- .../Service/WMTOperationsImpl.swift | 2 +- .../Operations/Utils/WMTTOTPUtils.swift | 27 +++++++++++++------ 3 files changed, 23 insertions(+), 11 deletions(-) diff --git a/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift b/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift index 465610e..f53b5b2 100644 --- a/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift +++ b/WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift @@ -55,10 +55,11 @@ public struct WMTQROperation { } internal var dataForOfflineSigning: Data { - guard let totp = totp else { + if let totp = totp { + return "\(operationId)&\(operationData.sourceString)&\(totp)".data(using: .utf8)! + } else { return "\(operationId)&\(operationData.sourceString)".data(using: .utf8)! } - return "\(operationId)&\(operationData.sourceString)&\(totp)".data(using: .utf8)! } } diff --git a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift index 4df9e2c..673c96f 100644 --- a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift +++ b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift @@ -82,7 +82,7 @@ public extension WMTErrorReason { static let operations_authExpired = WMTErrorReason(rawValue: "operations_authExpired") /// Operation has expired when trying to reject the operation. static let operations_rejectExpired = WMTErrorReason(rawValue: "operations_rejectExpired") - /// Operation authentication failed (e.g. incorrect totp). + /// Operation default failure for rejection and authorization. static let operations_failed = WMTErrorReason(rawValue: "operations_failed") /// Couldn't sign QR operation. diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift index 5364481..bc32dfc 100644 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -22,14 +22,26 @@ public class WMTTOTPUtils { /// Method accepts deeeplink URL and returns payload data public static func parseLoginDeeplink(url: URL) -> WMTOperationTOTPData? { - guard let components = URLComponents(string: url.absoluteString) else { return nil } - - guard let host = components.host, host == "login" else { return nil } + guard let components = URLComponents(string: url.absoluteString) else { + D.error("Failed to get URLComponents: URLString is malformed") + return nil + } - guard let queryItems = components.queryItems else { return nil } + guard let host = components.host else { + D.error("Failed to get URLComponents host") + return nil + } - guard let code = queryItems.first?.value else { return nil } + guard let queryItems = components.queryItems else { + D.error("Failed to get URLComponents queryItems") + return nil + } + guard let code = queryItems.first?.value else { + D.error("Failed to get Query Items value for parsing") + return nil + } + guard let data = parseJWT(code: code) else { return nil } return data @@ -48,11 +60,10 @@ public class WMTTOTPUtils { if let base64EncodedData = jwtBase64String.data(using: .utf8), let dataPayload = Data(base64Encoded: base64EncodedData) { - do { return try JSONDecoder().decode(WMTOperationTOTPData.self, from: dataPayload) } catch { - D.error("Failed to decode QR JWT: \(code)") + D.error("Failed to decode JWT from: \(code)") D.error("With error: \(error)") return nil } @@ -69,7 +80,7 @@ public struct WMTOperationTOTPData: Codable { /// The actual Time-based one time password public let totp: String - /// Id of the operations to which the otp belongs to + /// The ID of the operations associated with the TOTP public let operationId: String public enum Keys: String, CodingKey { From b6a4c0399a9f65a8a572d834d6dcb5d016d9e087 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Wed, 8 Nov 2023 13:40:00 +0100 Subject: [PATCH 15/26] Remove unused host in TOTPUtils --- WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift | 5 ----- 1 file changed, 5 deletions(-) diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift index bc32dfc..f2257ce 100644 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -27,11 +27,6 @@ public class WMTTOTPUtils { return nil } - guard let host = components.host else { - D.error("Failed to get URLComponents host") - return nil - } - guard let queryItems = components.queryItems else { D.error("Failed to get URLComponents queryItems") return nil From 8b617681e47dd8a4da93f3c82140d5b5b6c26ff1 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Wed, 8 Nov 2023 13:40:17 +0100 Subject: [PATCH 16/26] Comment changed --- WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift index 673c96f..9a6d98a 100644 --- a/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift +++ b/WultraMobileTokenSDK/Operations/Service/WMTOperationsImpl.swift @@ -82,7 +82,7 @@ public extension WMTErrorReason { static let operations_authExpired = WMTErrorReason(rawValue: "operations_authExpired") /// Operation has expired when trying to reject the operation. static let operations_rejectExpired = WMTErrorReason(rawValue: "operations_rejectExpired") - /// Operation default failure for rejection and authorization. + /// Operation action failed. static let operations_failed = WMTErrorReason(rawValue: "operations_failed") /// Couldn't sign QR operation. From 60ae1bda364ac42df5ca041722591bafc1e350f3 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Wed, 8 Nov 2023 16:47:46 +0100 Subject: [PATCH 17/26] Bump Networking version to 1.2.0. --- Cartfile | 2 +- Cartfile.resolved | 2 +- Deploy/WultraMobileTokenSDK.podspec | 2 +- WultraMobileTokenSDK.podspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Cartfile b/Cartfile index 6d0ec97..5bcb826 100644 --- a/Cartfile +++ b/Cartfile @@ -1 +1 @@ -github "wultra/networking-apple" "1.1.7" \ No newline at end of file +github "wultra/networking-apple" "1.2.0" diff --git a/Cartfile.resolved b/Cartfile.resolved index 8e488d6..2eecbdd 100644 --- a/Cartfile.resolved +++ b/Cartfile.resolved @@ -1,3 +1,3 @@ binary "https://raw.githubusercontent.com/wultra/powerauth-mobile-sdk-spm/1.7.6/PowerAuth2.json" "1.7.6" binary "https://raw.githubusercontent.com/wultra/powerauth-mobile-sdk-spm/1.7.6/PowerAuthCore.json" "1.7.6" -github "wultra/networking-apple" "1.1.7" +github "wultra/networking-apple" "1.2.0" diff --git a/Deploy/WultraMobileTokenSDK.podspec b/Deploy/WultraMobileTokenSDK.podspec index 3e2bbe4..9b6e899 100644 --- a/Deploy/WultraMobileTokenSDK.podspec +++ b/Deploy/WultraMobileTokenSDK.podspec @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.subspec 'Common' do |sub| sub.source_files = 'WultraMobileTokenSDK/Common/**/*.swift' sub.dependency 'PowerAuth2', '>= 1.7.3' - sub.dependency 'WultraPowerAuthNetworking', '>= 1.1.7' + sub.dependency 'WultraPowerAuthNetworking', '~> 1.2.0' end # 'Operations' subspec diff --git a/WultraMobileTokenSDK.podspec b/WultraMobileTokenSDK.podspec index 515f902..f494ed4 100644 --- a/WultraMobileTokenSDK.podspec +++ b/WultraMobileTokenSDK.podspec @@ -19,7 +19,7 @@ Pod::Spec.new do |s| s.subspec 'Common' do |sub| sub.source_files = 'WultraMobileTokenSDK/Common/**/*.swift' sub.dependency 'PowerAuth2', '>= 1.7.3' - sub.dependency 'WultraPowerAuthNetworking', '>= 1.1.7' + sub.dependency 'WultraPowerAuthNetworking', '~> 1.2.0' end # 'Operations' subspec From 48d3114ad934d6c986376d4107a7b75e889c7093 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Thu, 9 Nov 2023 12:25:08 +0100 Subject: [PATCH 18/26] Increase minimal ios version to 12 + Fix comment on WMTOperationTOTPData --- WultraMobileTokenSDK.podspec | 2 +- WultraMobileTokenSDK/ConfigFiles/Config.xcconfig | 2 +- WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/WultraMobileTokenSDK.podspec b/WultraMobileTokenSDK.podspec index f494ed4..9271f19 100644 --- a/WultraMobileTokenSDK.podspec +++ b/WultraMobileTokenSDK.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/wultra/mtoken-sdk-ios.git', :tag => s.version } # Deployment targets s.swift_version = '5.7' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' # Sources s.default_subspec = 'Operations' diff --git a/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig b/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig index 4cac2f0..630288b 100644 --- a/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig +++ b/WultraMobileTokenSDK/ConfigFiles/Config.xcconfig @@ -20,7 +20,7 @@ SWIFT_SWIFT3_OBJC_INFERENCE = Off // SDK SDKROOT = iphoneos -IPHONEOS_DEPLOYMENT_TARGET = 10.0 +IPHONEOS_DEPLOYMENT_TARGET = 12.0 // FRAMEWORKS FRAMEWORK_SEARCH_PATHS = $(inherited) $(PROJECT_DIR)/Carthage/Build/ diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift index f2257ce..6c0dc1d 100644 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -69,7 +69,7 @@ public class WMTTOTPUtils { } } -/// Data payload which is +/// Data payload which is returned from JWT parser public struct WMTOperationTOTPData: Codable { /// The actual Time-based one time password From 5d33384771f607b7029c9254f27d8f4cb7c7fbc0 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Thu, 9 Nov 2023 14:58:13 +0100 Subject: [PATCH 19/26] Minor naming changes --- .../Model/Requests/WMTAuthorizationData.swift | 2 +- .../Operations/Utils/WMTTOTPUtils.swift | 4 ++-- WultraMobileTokenSDKTests/TOTPParserTests.swift | 16 ++++++++-------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift index 2d07eae..9c3e48b 100644 --- a/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift +++ b/WultraMobileTokenSDK/Operations/Model/Requests/WMTAuthorizationData.swift @@ -55,7 +55,7 @@ class WMTAuthorizationData: Codable { /// Internal proximity check data used for authorization struct WMTProximityCheckData: Codable { - /// Tha actual otp code + /// Tha actual OTP code let otp: String /// Type of the Proximity check diff --git a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift index 6c0dc1d..0ffb766 100644 --- a/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift +++ b/WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift @@ -20,7 +20,7 @@ import Foundation public class WMTTOTPUtils { /// Method accepts deeeplink URL and returns payload data - public static func parseLoginDeeplink(url: URL) -> WMTOperationTOTPData? { + public static func parseDeeplink(url: URL) -> WMTOperationTOTPData? { guard let components = URLComponents(string: url.absoluteString) else { D.error("Failed to get URLComponents: URLString is malformed") @@ -43,7 +43,7 @@ public class WMTTOTPUtils { } /// Method accepts scanned code as a String and returns payload data - public static func getTOTPFromQR(code: String) -> WMTOperationTOTPData? { + public static func parseQRCode(code: String) -> WMTOperationTOTPData? { return parseJWT(code: code) } diff --git a/WultraMobileTokenSDKTests/TOTPParserTests.swift b/WultraMobileTokenSDKTests/TOTPParserTests.swift index 343d43b..586de14 100644 --- a/WultraMobileTokenSDKTests/TOTPParserTests.swift +++ b/WultraMobileTokenSDKTests/TOTPParserTests.swift @@ -22,38 +22,38 @@ final class TOTPParserTest: XCTestCase { func testQRTOTPParserWithEmptyCode() { let code = "" - XCTAssertNil(WMTTOTPUtils.getTOTPFromQR(code: code)) + XCTAssertNil(WMTTOTPUtils.parseQRCode(code: code)) } func testQRTOTPParserWithShortCode() { let code = "abc" - XCTAssertNil(WMTTOTPUtils.getTOTPFromQR(code: code)) + XCTAssertNil(WMTTOTPUtils.parseQRCode(code: code)) } func testQRTOTPParserWithValidCode() { let code = "eyJhbGciOiJub25lIiwidHlwZSI6IkpXVCJ9.eyJvaWQiOiI2YTFjYjAwNy1mZjc1LTRmNDAtYTIxYi0wYjU0NmYwZjZjYWQiLCJ0b3RwIjoiNzM3NDMxOTQifQ==" - XCTAssertEqual(WMTTOTPUtils.getTOTPFromQR(code: code)?.totp, "73743194", "Parsing of totp failed") - XCTAssertEqual(WMTTOTPUtils.getTOTPFromQR(code: code)?.operationId, "6a1cb007-ff75-4f40-a21b-0b546f0f6cad", "Parsing of operationId failed") + XCTAssertEqual(WMTTOTPUtils.parseQRCode(code: code)?.totp, "73743194", "Parsing of totp failed") + XCTAssertEqual(WMTTOTPUtils.parseQRCode(code: code)?.operationId, "6a1cb007-ff75-4f40-a21b-0b546f0f6cad", "Parsing of operationId failed") } func testDeeplinkTOTPParserWithInvalidURL() { let url = URL(string: "mtoken://an-invalid-url.com")! - XCTAssertNil(WMTTOTPUtils.parseLoginDeeplink(url: url)) + XCTAssertNil(WMTTOTPUtils.parseDeeplink(url: url)) } func testDeeplinkTOTPParserWithInvalidJWTCode() { let url = URL(string: "mtoken://login?code=abc")! - XCTAssertNil(WMTTOTPUtils.parseLoginDeeplink(url: url)) + XCTAssertNil(WMTTOTPUtils.parseDeeplink(url: url)) } func testDeeplinkTOTPParserWithValidJWTCode() { let url = URL(string: "mtoken://login?code=eyJhbGciOiJub25lIiwidHlwZSI6IkpXVCJ9.eyJvaWQiOiJkZjYxMjhmYy1jYTUxLTQ0YjctYmVmYS1jYTBlMTQwOGFhNjMiLCJ0b3RwIjoiNTY3MjU0OTQifQ==")! - XCTAssertEqual(WMTTOTPUtils.parseLoginDeeplink(url: url)?.totp, "56725494", "Parsing of totp failed") - XCTAssertEqual(WMTTOTPUtils.parseLoginDeeplink(url: url)?.operationId, "df6128fc-ca51-44b7-befa-ca0e1408aa63", "Parsing of operationId failed") + XCTAssertEqual(WMTTOTPUtils.parseDeeplink(url: url)?.totp, "56725494", "Parsing of totp failed") + XCTAssertEqual(WMTTOTPUtils.parseDeeplink(url: url)?.operationId, "df6128fc-ca51-44b7-befa-ca0e1408aa63", "Parsing of operationId failed") } } From bd1699d00ec22ae0bcae15d0d90b216c042582fd Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 10 Nov 2023 10:50:09 +0100 Subject: [PATCH 20/26] Remove `Packege.resolved` add `Package` --- Package.resolved | 23 ----------------------- Package.swift | 30 ++++++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 23 deletions(-) delete mode 100644 Package.resolved create mode 100644 Package.swift diff --git a/Package.resolved b/Package.resolved deleted file mode 100644 index 26bccf3..0000000 --- a/Package.resolved +++ /dev/null @@ -1,23 +0,0 @@ -{ - "pins" : [ - { - "identity" : "networking-apple", - "kind" : "remoteSourceControl", - "location" : "https://github.com/wultra/networking-apple.git", - "state" : { - "revision" : "b7ebe23f441e614d13bd6bc803d658e66767e31a", - "version" : "1.2.0" - } - }, - { - "identity" : "powerauth-mobile-sdk-spm", - "kind" : "remoteSourceControl", - "location" : "https://github.com/wultra/powerauth-mobile-sdk-spm.git", - "state" : { - "revision" : "095be6adfc057501a7cb9a7351697a1b6ed9e64b", - "version" : "1.7.8" - } - } - ], - "version" : 2 -} diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..f26b9ba --- /dev/null +++ b/Package.swift @@ -0,0 +1,30 @@ +// swift-tools-version:5.7 + +import PackageDescription + +let package = Package( + name: "WultraMobileTokenSDK", + platforms: [ + .iOS(.v11) + ], + products: [ + .library(name: "WultraMobileTokenSDK", targets: ["WultraMobileTokenSDK"]) + ], + dependencies: [ + .package(url: "https://github.com/wultra/powerauth-mobile-sdk-spm.git", .upToNextMinor(from: "1.7.8")), + .package(url: "https://github.com/wultra/networking-apple.git", .upToNextMinor(from: "1.2.0")) + ], + targets: [ + .target( + name: "WultraMobileTokenSDK", + dependencies: [ + .product(name: "PowerAuth2", package: "powerauth-mobile-sdk-spm"), + .product(name: "PowerAuthCore", package: "powerauth-mobile-sdk-spm"), + .product(name: "WultraPowerAuthNetworking", package: "networking-apple") + ], + path: "WultraMobileTokenSDK", + exclude: ["ConfigFiles/Config.xcconfig", "ConfigFiles/Debug.xcconfig", "ConfigFiles/Release.xcconfig", "Info.plist", "Podfile"] + ) + ], + swiftLanguageVersions: [.v5] +) From 5fda4580bc398630f9c8dbde20f3415874d9427d Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 10 Nov 2023 10:50:48 +0100 Subject: [PATCH 21/26] Remove duplicated empty lines --- WultraMobileTokenSDKTests/NetworkingObjectsTests.swift | 1 - WultraMobileTokenSDKTests/QROperationParserTests.swift | 1 - 2 files changed, 2 deletions(-) diff --git a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift index 6996faa..9974f2d 100644 --- a/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift +++ b/WultraMobileTokenSDKTests/NetworkingObjectsTests.swift @@ -262,7 +262,6 @@ class NetworkingObjectsTests: XCTestCase { XCTAssertEqual(proximityCheck?.otp, "12345678") } - func testOperationRejectionRequest() { let expectation = """ diff --git a/WultraMobileTokenSDKTests/QROperationParserTests.swift b/WultraMobileTokenSDKTests/QROperationParserTests.swift index 5f374ce..59767f5 100644 --- a/WultraMobileTokenSDKTests/QROperationParserTests.swift +++ b/WultraMobileTokenSDKTests/QROperationParserTests.swift @@ -140,7 +140,6 @@ class QROperationParserTests: XCTestCase { XCTAssertTrue(operation.operationData.sourceString == "A1*A100CZK*ICZ2730300000001165254011*D20180425*Thello world") } - func testForwardCompatibility() { let parser = WMTQROperationParser() let qrcode = makeCode(operationData:"B2*Xtest", otherAttrs:["12345678", "Some Additional Information"]) From 0ef03b0cb4ee18fe8138246795911536e592c3ba Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 10 Nov 2023 10:59:25 +0100 Subject: [PATCH 22/26] Update podspec --- Deploy/WultraMobileTokenSDK.podspec | 4 ++-- WultraMobileTokenSDK.podspec | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Deploy/WultraMobileTokenSDK.podspec b/Deploy/WultraMobileTokenSDK.podspec index 9b6e899..62ed148 100644 --- a/Deploy/WultraMobileTokenSDK.podspec +++ b/Deploy/WultraMobileTokenSDK.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/wultra/mtoken-sdk-ios.git', :tag => s.version } # Deployment targets s.swift_version = '5.7' - s.ios.deployment_target = '11.0' + s.ios.deployment_target = '12.0' # Sources s.default_subspec = 'Operations' @@ -18,7 +18,7 @@ Pod::Spec.new do |s| # 'Common' subspec s.subspec 'Common' do |sub| sub.source_files = 'WultraMobileTokenSDK/Common/**/*.swift' - sub.dependency 'PowerAuth2', '>= 1.7.3' + sub.dependency 'PowerAuth2', '~> 1.7.3' sub.dependency 'WultraPowerAuthNetworking', '~> 1.2.0' end diff --git a/WultraMobileTokenSDK.podspec b/WultraMobileTokenSDK.podspec index 9271f19..1179b8e 100644 --- a/WultraMobileTokenSDK.podspec +++ b/WultraMobileTokenSDK.podspec @@ -18,7 +18,7 @@ Pod::Spec.new do |s| # 'Common' subspec s.subspec 'Common' do |sub| sub.source_files = 'WultraMobileTokenSDK/Common/**/*.swift' - sub.dependency 'PowerAuth2', '>= 1.7.3' + sub.dependency 'PowerAuth2', '~> 1.7.3' sub.dependency 'WultraPowerAuthNetworking', '~> 1.2.0' end From de3bf058a8793c6870fa578df43beea404bd5dd8 Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 10 Nov 2023 13:41:49 +0100 Subject: [PATCH 23/26] Add docs --- Package.swift | 2 +- docs/Error-Handling.md | 1 + docs/SDK-Integration.md | 13 ++++--- docs/Using-Operations-Service.md | 63 +++++++++++++++++++++++++++++++- 4 files changed, 71 insertions(+), 8 deletions(-) diff --git a/Package.swift b/Package.swift index f26b9ba..3b75e5f 100644 --- a/Package.swift +++ b/Package.swift @@ -5,7 +5,7 @@ import PackageDescription let package = Package( name: "WultraMobileTokenSDK", platforms: [ - .iOS(.v11) + .iOS(.v12) ], products: [ .library(name: "WultraMobileTokenSDK", targets: ["WultraMobileTokenSDK"]) diff --git a/docs/Error-Handling.md b/docs/Error-Handling.md index 17fcef0..e89508f 100644 --- a/docs/Error-Handling.md +++ b/docs/Error-Handling.md @@ -24,6 +24,7 @@ Every error produced by this library is of a `WMTError` type. This error contain |`operationAlreadyFailed`|Operation is already failed| |`operationAlreadyCancelled`|Operation is canceled| |`operationExpired`|Operation is expired| +|`operationFailed`|Default operation action failure| ## WMTErrorReason diff --git a/docs/SDK-Integration.md b/docs/SDK-Integration.md index 814887e..fcc030d 100644 --- a/docs/SDK-Integration.md +++ b/docs/SDK-Integration.md @@ -2,7 +2,7 @@ ## Requirements -- iOS 10.0+ +- iOS 12.0+ - [PowerAuth Mobile SDK](https://github.com/wultra/powerauth-mobile-sdk) needs to be available in your project ## Swift Package Manager @@ -17,7 +17,7 @@ import PackageDescription let package = Package( name: "YourLibrary", platforms: [ - .iOS(.v11) + .iOS(.v12) ], products: [ .library( @@ -26,7 +26,7 @@ let package = Package( ), ], dependencies: [ - .package(url: "https://github.com/wultra/mtoken-sdk-ios.git", .from("1.4.1")) + .package(url: "https://github.com/wultra/mtoken-sdk-ios.git", .from("1.7.0")) ], targets: [ .target( @@ -39,15 +39,16 @@ let package = Package( ## Cocoapods -Ddd the following dependencies to your Podfile: +Add the following dependencies to your Podfile: ```rb pod 'WultraMobileTokenSDK/Operations' pod 'WultraMobileTokenSDK/Push' +pod 'WultraMobileTokenSDK/Inbox' ``` -Note: If you want to use only operations, you can omit the Push dependency. +Note: If you want to use only operations, you can omit the Push dependency & Inbox dependency. ## Guaranteed PowerAuth Compatibility @@ -60,4 +61,4 @@ Note: If you want to use only operations, you can omit the Push dependency. ## Xcode Compatibility -We recommend using Xcode version 13.2 or newer. +We recommend using Xcode version 14.3 or newer. diff --git a/docs/Using-Operations-Service.md b/docs/Using-Operations-Service.md index e29ae2f..f68b35d 100644 --- a/docs/Using-Operations-Service.md +++ b/docs/Using-Operations-Service.md @@ -436,6 +436,9 @@ class WMTUserOperation: WMTOperation { /// /// Additional UI data such as Pre-Approval Screen or Post-Approval Screen should be presented. public let ui: WMTOperationUIData? + + /// Proximity Check Data to be passed when OTP is handed to the app + public var proximityCheck: WMTProximityCheck? } ``` @@ -492,7 +495,7 @@ PreApprovalScreen types: - `WARNING` - `INFO` -- `QR_SCAN` +- `QR_SCAN` this type indicates that the `WMTProximityCheck` must be used - `UNKNOWN` PostApprovalScreen types: @@ -502,6 +505,50 @@ PostApprovalScreen types: - `REDIRECT` providing text for button, countdown, and redirection URL - `GENERIC` may contain any object +Definition of `WMTProximityCheck`: + +```swift +public class WMTProximityCheck: Codable { + /// Tha actual Time-based one time password + public let totp: String + /// Type of the Proximity check + public let type: WMTProximityCheckType + /// Timestamp when the operation was scanned (QR Code) or delivered to the device (Deeplink) + public let timestampRequested: Date +} +``` + +WMTProximityCheckType types: + +- `qrCode` TOTP was scanned from QR code +- `deeplink` TOTP was delivered to the app via Deeplink + + +## TOTP WMTProximityCheck + +Two-Factor Authentication (2FA) using Time-Based One-Time Passwords (TOTP) in the Operations Service is facilitated through the use of WMTProximityCheck. This allows secure approval of operations through QR code scanning or deeplink handling. + +This TOTP-based WMTProximityCheck enhances the security of the approval process, providing a robust mechanism for 2FA in the Operations Service. + +- QR Code Flow: + +When the `WMTUserOperation` contains a `WMTPreApprovalScreen.qr`, the app should open the camera to scan the QR code before confirming the operation. Use the camera to scan the QR code containing the necessary data payload for the operation. + +- Deeplink Flow: + +When the app is launched via a deeplink, preserve the data from the deeplink to extract the relevant information. When operations are loaded compare the operation ID from the deeplink data to the operations within the app to find a match. + +- Assign TOTP and Type for the Operation +Once the QR code is scanned or match from deeplink is found, create a `WMTProximityCheck` with: + - `totp`: The actual Time-Based One-Time Password. + - `type`: Set to `WMTProximityCheckType.qrCode` or `WMTProximityCheckType.deeplink`. + - `timestampRequested`: The timestamp when the QR code was scanned (by default, it is created as the current timestamp). + +- Authorizing the WMTProximityCheck +When authorization, the SDK will by default add `timestampSigned` to the `WMTProximityCheck` object. This timestamp indicates when the operation was signed. + +This TOTP-based WMTProximityCheck enhances the security of the approval process, providing a robust mechanism for 2FA in the Operations Service. + ### Subclassing WMTUserOperation `WMTUserOperation` class is `open` and can be subclassed. This is useful when your backend adds additional properties to operations retrieved via the `getOperations` API. @@ -561,6 +608,20 @@ public protocol WMTOperation { /// Data for signing var data: String { get } + + /// Additional information with proximity check data + var proximityCheck: WMTProximityCheck? { get } +} +``` + +### Utilizing the Proximity Check +When creating custom operations, you can now include proximity check data by conforming to the updated WMTOperation protocol. This enables you to enhance the security of your operations by considering proximity information during the authorization process. + +To maintain backward compatibility, a public extension has been added to the WMTOperation protocol. If your existing codebase does not require the use of the proximity check feature, the extension ensures seamless integration: + +```swift +public extension WMTOperation { + var proximityCheck: WMTProximityCheck? { nil } } ``` From 0af2e5767fe6109b13ed387d70dfec82728a639a Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 10 Nov 2023 13:49:33 +0100 Subject: [PATCH 24/26] Fix placement of the TOTP WMPTProximityCheck --- docs/Using-Operations-Service.md | 51 ++++++++++++++++---------------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/docs/Using-Operations-Service.md b/docs/Using-Operations-Service.md index f68b35d..b07348d 100644 --- a/docs/Using-Operations-Service.md +++ b/docs/Using-Operations-Service.md @@ -9,8 +9,9 @@ - [Reject an Operation](#reject-an-operation) - [Off-line Authorization](#off-line-authorization) - [Operations API Reference](#operations-api-reference) -- [WMTUserOperation](#WMTUserOperation) +- [WMTUserOperation](#wmtuseroperation) - [Creating a Custom Operation](#creating-a-custom-operation) +- [TOTP WMTProximityCheck](#totp-wmtproximitycheck) - [Error handling](#error-handling) ## Introduction @@ -524,31 +525,6 @@ WMTProximityCheckType types: - `deeplink` TOTP was delivered to the app via Deeplink -## TOTP WMTProximityCheck - -Two-Factor Authentication (2FA) using Time-Based One-Time Passwords (TOTP) in the Operations Service is facilitated through the use of WMTProximityCheck. This allows secure approval of operations through QR code scanning or deeplink handling. - -This TOTP-based WMTProximityCheck enhances the security of the approval process, providing a robust mechanism for 2FA in the Operations Service. - -- QR Code Flow: - -When the `WMTUserOperation` contains a `WMTPreApprovalScreen.qr`, the app should open the camera to scan the QR code before confirming the operation. Use the camera to scan the QR code containing the necessary data payload for the operation. - -- Deeplink Flow: - -When the app is launched via a deeplink, preserve the data from the deeplink to extract the relevant information. When operations are loaded compare the operation ID from the deeplink data to the operations within the app to find a match. - -- Assign TOTP and Type for the Operation -Once the QR code is scanned or match from deeplink is found, create a `WMTProximityCheck` with: - - `totp`: The actual Time-Based One-Time Password. - - `type`: Set to `WMTProximityCheckType.qrCode` or `WMTProximityCheckType.deeplink`. - - `timestampRequested`: The timestamp when the QR code was scanned (by default, it is created as the current timestamp). - -- Authorizing the WMTProximityCheck -When authorization, the SDK will by default add `timestampSigned` to the `WMTProximityCheck` object. This timestamp indicates when the operation was signed. - -This TOTP-based WMTProximityCheck enhances the security of the approval process, providing a robust mechanism for 2FA in the Operations Service. - ### Subclassing WMTUserOperation `WMTUserOperation` class is `open` and can be subclassed. This is useful when your backend adds additional properties to operations retrieved via the `getOperations` API. @@ -625,6 +601,29 @@ public extension WMTOperation { } ``` +## TOTP WMTProximityCheck + +Two-Factor Authentication (2FA) using Time-Based One-Time Passwords (TOTP) in the Operations Service is facilitated through the use of WMTProximityCheck. This allows secure approval of operations through QR code scanning or deeplink handling. + +- QR Code Flow: + +When the `WMTUserOperation` contains a `WMTPreApprovalScreen.qr`, the app should open the camera to scan the QR code before confirming the operation. Use the camera to scan the QR code containing the necessary data payload for the operation. + +- Deeplink Flow: + +When the app is launched via a deeplink, preserve the data from the deeplink and extract the relevant data. When operations are loaded compare the operation ID from the deeplink data to the operations within the app to find a match. + +- Assign TOTP and Type to the Operation +Once the QR code is scanned or match from the deeplink is found, create a `WMTProximityCheck` with: + - `totp`: The actual Time-Based One-Time Password. + - `type`: Set to `WMTProximityCheckType.qrCode` or `WMTProximityCheckType.deeplink`. + - `timestampRequested`: The timestamp when the QR code was scanned (by default, it is created as the current timestamp). + +- Authorizing the WMTProximityCheck +When authorization, the SDK will by default add `timestampSigned` to the `WMTProximityCheck` object. This timestamp indicates when the operation was signed. + +This TOTP-based WMTProximityCheck enhances the security of the approval process, providing a robust mechanism for 2FA in the Operations Service. + ## Error handling Every error produced by the Operations Service is of a `WMTError` type. For more information see detailed [error handling documentation](Error-Handling.md). From d6ec093013b8ba5fe1f77cbf0f7c33e01972229b Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 10 Nov 2023 16:40:17 +0100 Subject: [PATCH 25/26] Remove unnecessary info from docs --- docs/Using-Operations-Service.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/docs/Using-Operations-Service.md b/docs/Using-Operations-Service.md index b07348d..69e9de8 100644 --- a/docs/Using-Operations-Service.md +++ b/docs/Using-Operations-Service.md @@ -622,8 +622,6 @@ Once the QR code is scanned or match from the deeplink is found, create a `WMTPr - Authorizing the WMTProximityCheck When authorization, the SDK will by default add `timestampSigned` to the `WMTProximityCheck` object. This timestamp indicates when the operation was signed. -This TOTP-based WMTProximityCheck enhances the security of the approval process, providing a robust mechanism for 2FA in the Operations Service. - ## Error handling Every error produced by the Operations Service is of a `WMTError` type. For more information see detailed [error handling documentation](Error-Handling.md). From fd8b80e98e6053fd9d341169f3e03b15079794fd Mon Sep 17 00:00:00 2001 From: Marek Stransky Date: Fri, 10 Nov 2023 16:49:14 +0100 Subject: [PATCH 26/26] Add info to PowerAuth compatibility table in SDK-Integration.md --- docs/SDK-Integration.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/docs/SDK-Integration.md b/docs/SDK-Integration.md index fcc030d..45d95f4 100644 --- a/docs/SDK-Integration.md +++ b/docs/SDK-Integration.md @@ -58,7 +58,10 @@ Note: If you want to use only operations, you can omit the Push dependency & Inb | `1.0.x` - `1.2.x` | `1.x.x` | | `1.3.x` | `1.6.x` | | `1.4.x` | `1.6.x` | +| `1.5.x` | `1.6.x` | +| `1.6.x` | `1.7.x` | +| `1.7.x` | `1.7.x` | ## Xcode Compatibility -We recommend using Xcode version 14.3 or newer. +We recommend using Xcode version 15.0 or newer.