Skip to content

Commit

Permalink
Added support of QR Code & Deeplink - Proximity check (#122)
Browse files Browse the repository at this point in the history
* Added support of QR Code & Deeplink - Proximity check

* Return incorrectly removed init for backward compatibility

* Rename otp to totp

* Fix networking serialization tests

* Add offline totp

* Test totp authorization

* Fix lint and missing file

* Implement remarks

* Add `TOTPParserTests`

* Minor fixes

* Fix incorrect name of otp in `WMTProximityCheckData`

* Bump networking dependency to 1.2.0.

* Remove Package.swift from repo

* Implement remarks

* Remove unused host in TOTPUtils

* Comment changed

* Bump Networking version to 1.2.0.

* Increase minimal ios version to 12 + Fix comment on WMTOperationTOTPData

* Minor naming changes

* Remove `Packege.resolved` add `Package`

* Remove duplicated empty lines

* Update podspec

* Add docs

* Fix placement of the TOTP WMPTProximityCheck

* Remove unnecessary info from docs

* Add info to PowerAuth compatibility table in SDK-Integration.md
  • Loading branch information
Hopsaheysa authored Nov 10, 2023
1 parent 9699a74 commit 5ab04f7
Show file tree
Hide file tree
Showing 21 changed files with 456 additions and 31 deletions.
2 changes: 1 addition & 1 deletion Cartfile
Original file line number Diff line number Diff line change
@@ -1 +1 @@
github "wultra/networking-apple" "1.1.7"
github "wultra/networking-apple" "1.2.0"
2 changes: 1 addition & 1 deletion Cartfile.resolved
Original file line number Diff line number Diff line change
@@ -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"
6 changes: 3 additions & 3 deletions Deploy/WultraMobileTokenSDK.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ 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'

# 'Common' subspec
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 'PowerAuth2', '~> 1.7.3'
sub.dependency 'WultraPowerAuthNetworking', '~> 1.2.0'
end

# 'Operations' subspec
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import PackageDescription
let package = Package(
name: "WultraMobileTokenSDK",
platforms: [
.iOS(.v11)
.iOS(.v12)
],
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.1.7"))
.package(url: "https://github.com/wultra/networking-apple.git", .upToNextMinor(from: "1.2.0"))
],
targets: [
.target(
Expand Down
6 changes: 3 additions & 3 deletions WultraMobileTokenSDK.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,16 @@ 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'

# 'Common' subspec
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 'PowerAuth2', '~> 1.7.3'
sub.dependency 'WultraPowerAuthNetworking', '~> 1.2.0'
end

# 'Operations' subspec
Expand Down
14 changes: 13 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,9 @@
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 */; };
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 */

Expand Down Expand Up @@ -154,6 +157,9 @@
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>"; };
EAB705492AF1161500756AC2 /* TOTPParserTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TOTPParserTests.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 +229,7 @@
DC8CB205244DD007009DDAA3 /* WMTAllowedOperationSignature.swift */,
DCE5EAAF26BD81150061861A /* WMTOperationHistoryEntry.swift */,
EA294F3C29F6A07A00A0494E /* WMTOperationUIData.swift */,
EA9CE2BD2AEAA9FD00FE4E35 /* WMTProximityCheck.swift */,
);
path = UserOperation;
sourceTree = "<group>";
Expand Down Expand Up @@ -270,6 +277,7 @@
DC395C0924E55B9B0007C36E /* PushParserTests.swift */,
DC6EDB7825A49ED900A229E4 /* OperationExpirationTests.swift */,
DC616235248508F8000DED17 /* QROperationParserTests.swift */,
EAB705492AF1161500756AC2 /* TOTPParserTests.swift */,
);
path = WultraMobileTokenSDKTests;
sourceTree = "<group>";
Expand All @@ -287,6 +295,7 @@
DC6E52D4259C959900FC25BE /* Utils */ = {
isa = PBXGroup;
children = (
EA9CE2C12AEBDB0D00FE4E35 /* WMTTOTPUtils.swift */,
DC6E52D5259C964600FC25BE /* WMTOperationExpirationWatcher.swift */,
EACAF7AF2A126B7D0021CA54 /* WMTJsonValue.swift */,
);
Expand Down Expand Up @@ -576,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 */,
Expand Down Expand Up @@ -616,6 +626,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 +643,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
2 changes: 1 addition & 1 deletion WultraMobileTokenSDK/ConfigFiles/Config.xcconfig
Original file line number Diff line number Diff line change
Expand Up @@ -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/
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) {
/// Proximity OTP data
let proximityCheck: WMTProximityCheckData?

init(operationId: String, operationData: String, proximityCheck: WMTProximityCheckData? = nil) {
self.id = operationId
self.data = operationData
self.id = operationId
self.proximityCheck = proximityCheck
}

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

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

self.proximityCheck = WMTProximityCheckData(
otp: proximityCheck.totp,
type: proximityCheck.type,
timestampRequested: proximityCheck.timestampRequested,
timestampSigned: timestampSigned
)
}
}

/// 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
}
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 obtained
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 (qrCode) or delivered to the device (deeplink)
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
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 information 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 }
}
9 changes: 8 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,11 @@ public struct WMTQROperation {
}

internal var dataForOfflineSigning: Data {
return "\(operationId)&\(operationData.sourceString)".data(using: .utf8)!
if let totp = totp {
return "\(operationId)&\(operationData.sourceString)&\(totp)".data(using: .utf8)!
} else {
return "\(operationId)&\(operationData.sourceString)".data(using: .utf8)!
}
}
}

Expand Down
8 changes: 5 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

/// 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,7 @@ public class WMTQROperationParser {

// Parse flags
let flags = parseOperationFlags(string: flagsString)
let isNewerFormat = attributes.count > WMTQROperationParser.currentAttributeFields
let isNewerFormat = attributes.count > WMTQROperationParser.currentAttributeFields

// Build final structure
return .success(WMTQROperation(
Expand All @@ -84,6 +85,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 @@ -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 action failed.
static let operations_failed = WMTErrorReason(rawValue: "operations_failed")

/// Couldn't sign QR operation.
static let operations_QROperationFailed = WMTErrorReason(rawValue: "operations_QRFailed")
Expand Down Expand Up @@ -241,7 +243,7 @@ class WMTOperationsImpl<T: WMTUserOperation>: WMTOperations, WMTService {
return nil
}

let data = WMTAuthorizationData(operationId: operation.id, operationData: operation.data)
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
Expand Down Expand Up @@ -436,6 +438,8 @@ class WMTOperationsImpl<T: WMTUserOperation>: WMTOperations, WMTService {
} else {
reason = .operations_rejectExpired
}
case .operationFailed:
reason = .operations_failed
default:
break
}
Expand Down
Loading

0 comments on commit 5ab04f7

Please sign in to comment.