Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added support of QR Code & Deeplink - Proximity check #122

Merged
merged 26 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
737cdaa
Added support of QR Code & Deeplink - Proximity check
Hopsaheysa Oct 25, 2023
5223bdc
Return incorrectly removed init for backward compatibility
Hopsaheysa Oct 27, 2023
ded75b2
Rename otp to totp
Hopsaheysa Oct 27, 2023
9ce0f4c
Fix networking serialization tests
Hopsaheysa Oct 27, 2023
3428508
Add offline totp
Hopsaheysa Oct 27, 2023
a5489a3
Test totp authorization
Hopsaheysa Oct 27, 2023
916845c
Fix lint and missing file
Hopsaheysa Oct 27, 2023
b7a909e
Implement remarks
Hopsaheysa Oct 31, 2023
11897f2
Add `TOTPParserTests`
Hopsaheysa Oct 31, 2023
f25cf71
Minor fixes
Hopsaheysa Oct 31, 2023
5a63912
Fix incorrect name of otp in `WMTProximityCheckData`
Hopsaheysa Nov 2, 2023
82e752a
Bump networking dependency to 1.2.0.
Hopsaheysa Nov 7, 2023
89e91ef
Remove Package.swift from repo
Hopsaheysa Nov 8, 2023
e4bf607
Implement remarks
Hopsaheysa Nov 8, 2023
b6a4c03
Remove unused host in TOTPUtils
Hopsaheysa Nov 8, 2023
8b61768
Comment changed
Hopsaheysa Nov 8, 2023
60ae1bd
Bump Networking version to 1.2.0.
Hopsaheysa Nov 8, 2023
48d3114
Increase minimal ios version to 12 + Fix comment on WMTOperationTOTPData
Hopsaheysa Nov 9, 2023
5d33384
Minor naming changes
Hopsaheysa Nov 9, 2023
bd1699d
Remove `Packege.resolved` add `Package`
Hopsaheysa Nov 10, 2023
5fda458
Remove duplicated empty lines
Hopsaheysa Nov 10, 2023
0ef03b0
Update podspec
Hopsaheysa Nov 10, 2023
de3bf05
Add docs
Hopsaheysa Nov 10, 2023
0af2e57
Fix placement of the TOTP WMPTProximityCheck
Hopsaheysa Nov 10, 2023
d6ec093
Remove unnecessary info from docs
Hopsaheysa Nov 10, 2023
fd8b80e
Add info to PowerAuth compatibility table in SDK-Integration.md
Hopsaheysa Nov 10, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion WultraMobileTokenSDK.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 52;
objectVersion = 54;
objects = {

/* Begin PBXBuildFile section */
Expand Down Expand Up @@ -71,6 +71,8 @@
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 */; };
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 */

Expand Down Expand Up @@ -154,6 +156,8 @@
EA6DDF0E29F8036B0011E234 /* WMTPreApprovalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPreApprovalScreen.swift; sourceTree = "<group>"; };
EA6DDF1929F804D60011E234 /* WMTPostApprovalScreen.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTPostApprovalScreen.swift; sourceTree = "<group>"; };
EA6DDF1B29F807230011E234 /* OperationUIDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationUIDataTests.swift; sourceTree = "<group>"; };
EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTProximityCheck.swift; sourceTree = "<group>"; };
EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WMTTOTPUtils.swift; sourceTree = "<group>"; };
EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WMTJsonValue.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

Expand Down Expand Up @@ -223,6 +227,7 @@
DC8CB205244DD007009DDAA3 /* WMTAllowedOperationSignature.swift */,
DCE5EAAF26BD81150061861A /* WMTOperationHistoryEntry.swift */,
EA294F3C29F6A07A00A0494E /* WMTOperationUIData.swift */,
EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */,
);
path = UserOperation;
sourceTree = "<group>";
Expand Down Expand Up @@ -287,6 +292,7 @@
DC6E52D4259C959900FC25BE /* Utils */ = {
isa = PBXGroup;
children = (
EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */,
DC6E52D5259C964600FC25BE /* WMTOperationExpirationWatcher.swift */,
EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */,
);
Expand Down Expand Up @@ -616,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 */,
Expand All @@ -632,6 +639,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 */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,45 @@ class WMTAuthorizationData: Codable {
/// Operation id
let id: String

init(operationId: String, operationData: String) {
self.data = operationData
self.id = operationId
/// Proximity Otp Object
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
let proximityCheck: WMTProximityCheckData?

init(operationId: String, operationData: String, proximityCheck: WMTProximityCheckData? = nil) {
self.id = operationId
self.data = operationData
self.proximityCheck = proximityCheck
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
}

init(operation: WMTOperation) {
self.id = operation.id
self.data = operation.data

guard let proximityCheck = operation.proximityCheck else {
self.proximityCheck = nil
return
}

self.proximityCheck = WMTProximityCheckData(
totp: proximityCheck.totp,
type: proximityCheck.type,
timestampRequested: proximityCheck.timestampRequested,
timestampSigned: Date()
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
)
}
}

/// Internal proximity check data used for authorization
struct WMTProximityCheckData: Codable {

/// Tha actual otp code
let totp: 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
}
Original file line number Diff line number Diff line change
@@ -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
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
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 delivered to the device
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
public let timestampRequested: Date

public init(totp: String, type: WMTProximityCheckType, timestampRequested: Date = Date()) {
self.totp = totp
self.type = type
self.timestampRequested = timestampRequested
}
}

/// Types of possible Proximity Checks
public enum WMTProximityCheckType: String, Codable {
case qrCode = "QR_CODE"
case deeplink = "DEEPLINK"
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
public var proximityCheck: WMTProximityCheck?
}
8 changes: 8 additions & 0 deletions WultraMobileTokenSDK/Operations/Model/WMTOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,4 +25,12 @@ public protocol WMTOperation {

/// Data for signing
var data: String { get }

/// Additional informations with proximity check data
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
var proximityCheck: WMTProximityCheck? { get }
}

/// WMTOperation extension which sets proximityCheck to be nil for backwards compatibility
public extension WMTOperation {
var proximityCheck: WMTProximityCheck? { nil }
}
8 changes: 7 additions & 1 deletion WultraMobileTokenSDK/Operations/QR/WMTQROperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -52,7 +55,10 @@ public struct WMTQROperation {
}

internal var dataForOfflineSigning: Data {
return "\(operationId)&\(operationData.sourceString)".data(using: .utf8)!
guard let totp = totp else {
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
return "\(operationId)&\(operationData.sourceString)".data(using: .utf8)!
}
return "\(operationId)&\(operationData.sourceString)&\(totp)".data(using: .utf8)!
}
}

Expand Down
9 changes: 6 additions & 3 deletions WultraMobileTokenSDK/Operations/QR/WMTQROperationParser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved

/// 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 {
Expand All @@ -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]
Expand Down Expand Up @@ -74,7 +75,8 @@ public class WMTQROperationParser {

// Parse flags
let flags = parseOperationFlags(string: flagsString)
let isNewerFormat = attributes.count > WMTQROperationParser.currentAttributeFields
let current = WMTQROperationParser.currentAttributeFields
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
let isNewerFormat = attributes.count > current
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved

// Build final structure
return .success(WMTQROperation(
Expand All @@ -84,6 +86,7 @@ public class WMTQROperationParser {
operationData: formData,
nonce: nonce,
flags: flags,
totp: totp,
signedData: signedData,
signature: signature,
isNewerFormat: isNewerFormat)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ class WMTOperationsImpl<T: WMTUserOperation>: 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
Expand Down
84 changes: 84 additions & 0 deletions WultraMobileTokenSDK/Operations/Utils/WMTTOTPUtils.swift
Original file line number Diff line number Diff line change
@@ -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 {
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved

/// 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? {
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved

guard let components = URLComponents(string: url.absoluteString) else { return nil }

guard let host = components.host, host == "login" else { return nil }
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved

guard let queryItems = components.queryItems else { return nil }

guard let code = queryItems.first?.value else { return nil }
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved

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])
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
if jwtBase64String.isEmpty == false {
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved

if let base64EncodedData = jwtBase64String.data(using: .utf8),
let dataPayload = Data(base64Encoded: base64EncodedData) {

do {
return try JSONDecoder().decode(WMTOperationOTPData.self, from: dataPayload)
Hopsaheysa marked this conversation as resolved.
Show resolved Hide resolved
} catch {
return nil
}

}
}
return nil
}
}
Loading
Loading