Skip to content

Commit 42b2f50

Browse files
authored
Merge pull request #12 from figo-connect/cert-update
Support for multiple certificates
2 parents 7d87bf8 + 9948856 commit 42b2f50

File tree

14 files changed

+76
-45
lines changed

14 files changed

+76
-45
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ Figo.xcworkspace/xcuserdata
44
Carthage
55
Paw
66
build
7+
Figo.xcodeproj/project.xcworkspace/xcuserdata

Figo.podspec

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
Pod::Spec.new do |s|
33

44
s.name = "Figo"
5-
s.version = "2.0.1"
5+
s.version = "2.0.2"
66
s.summary = "Wraps the figo Connect API endpoints in nicely typed Swift functions and types for your conveniece."
77
s.description = <<-DESC
88
The figo Connect API allows you to easily access your bank account including transaction history and submitting payments.
@@ -25,6 +25,6 @@ Pod::Spec.new do |s|
2525

2626
s.source = { :git => "https://github.com/figo-connect/ios-sdk.git", :tag => "#{s.version}" }
2727
s.source_files = "Source/**/*.swift"
28-
s.resource = "api.figo.me.cer"
28+
s.resources = "*.cer"
2929

3030
end

Figo.xcodeproj/project.pbxproj

Lines changed: 27 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@
77
objects = {
88

99
/* Begin PBXBuildFile section */
10+
7B6025731EF169E2005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
11+
7B6025741EF169E2005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
12+
7B6025751EF169E7005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
13+
7B6025761EF169E8005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
1014
830E63D91C05A4050048F7BF /* TANScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830E63D81C05A4050048F7BF /* TANScheme.swift */; };
1115
830E63DE1C05A7070048F7BF /* SyncStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830E63DD1C05A7070048F7BF /* SyncStatus.swift */; };
1216
830E63E31C05AB890048F7BF /* PaymentParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830E63E21C05AB890048F7BF /* PaymentParameters.swift */; };
@@ -16,10 +20,10 @@
1620
831183761E3DF13D000DA80C /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8311836F1E3DF136000DA80C /* Logging.swift */; };
1721
831183771E3DF141000DA80C /* URLRequest+curlCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 831183701E3DF136000DA80C /* URLRequest+curlCommand.swift */; };
1822
831183781E3DF141000DA80C /* URLRequest+curlCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 831183701E3DF136000DA80C /* URLRequest+curlCommand.swift */; };
19-
831183B81E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
20-
831183B91E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
21-
831183BA1E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
22-
831183BB1E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
23+
831183B81E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
24+
831183B91E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
25+
831183BA1E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
26+
831183BB1E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
2327
832CABAE1E242F8700D48895 /* Unbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832CABAD1E242F8700D48895 /* Unbox.swift */; };
2428
833C01001E2AA72A00AA5E7C /* Figo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 833C00EF1E2AA6A100AA5E7C /* Figo.framework */; };
2529
833C01061E2AA7F400AA5E7C /* FigoClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83F0E46B1C08A91400FB3709 /* FigoClient.swift */; };
@@ -179,6 +183,7 @@
179183
/* End PBXContainerItemProxy section */
180184

181185
/* Begin PBXFileReference section */
186+
7B6025721EF169E2005EF5B0 /* figo_2017.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = figo_2017.cer; sourceTree = "<group>"; };
182187
83017B771C0A2FE80062FC08 /* TaskState.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = TaskState.json; path = Resources/TaskState.json; sourceTree = "<group>"; };
183188
830E63D81C05A4050048F7BF /* TANScheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TANScheme.swift; path = Types/TANScheme.swift; sourceTree = "<group>"; };
184189
830E63DB1C05A4960048F7BF /* TanScheme.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = TanScheme.json; path = Resources/TanScheme.json; sourceTree = "<group>"; };
@@ -188,7 +193,7 @@
188193
831183651E3DEAE1000DA80C /* ServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTests.swift; sourceTree = "<group>"; };
189194
8311836F1E3DF136000DA80C /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
190195
831183701E3DF136000DA80C /* URLRequest+curlCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLRequest+curlCommand.swift"; sourceTree = "<group>"; };
191-
831183B71E3E2F66000DA80C /* api.figo.me.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = api.figo.me.cer; sourceTree = "<group>"; };
196+
831183B71E3E2F66000DA80C /* figo_2016.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = figo_2016.cer; sourceTree = "<group>"; };
192197
832CABAD1E242F8700D48895 /* Unbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unbox.swift; sourceTree = "<group>"; };
193198
833285EE1C04D45900A9FE73 /* Balance.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Balance.json; path = Resources/Balance.json; sourceTree = "<group>"; };
194199
833285F01C04D48E00A9FE73 /* Resources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resources.swift; sourceTree = "<group>"; };
@@ -454,7 +459,8 @@
454459
isa = PBXGroup;
455460
children = (
456461
83D3A7201C03471D003EDE45 /* README.md */,
457-
831183B71E3E2F66000DA80C /* api.figo.me.cer */,
462+
831183B71E3E2F66000DA80C /* figo_2016.cer */,
463+
7B6025721EF169E2005EF5B0 /* figo_2017.cer */,
458464
83D3A67F1BFF2953003EDE45 /* Source */,
459465
83D3A7081BFFB6A7003EDE45 /* Tests */,
460466
83F3AF231BFF28D900767D77 /* Products */,
@@ -571,7 +577,7 @@
571577
isa = PBXProject;
572578
attributes = {
573579
LastSwiftUpdateCheck = 0820;
574-
LastUpgradeCheck = 0820;
580+
LastUpgradeCheck = 0830;
575581
ORGANIZATIONNAME = CodeStage;
576582
TargetAttributes = {
577583
833C00EE1E2AA6A100AA5E7C = {
@@ -584,11 +590,12 @@
584590
};
585591
833C01391E2AAB4200AA5E7C = {
586592
CreatedOnToolsVersion = 8.2.1;
593+
LastSwiftMigration = 0830;
587594
ProvisioningStyle = Manual;
588595
};
589596
83F3AF211BFF28D900767D77 = {
590597
CreatedOnToolsVersion = 7.1.1;
591-
LastSwiftMigration = 0820;
598+
LastSwiftMigration = 0830;
592599
ProvisioningStyle = Manual;
593600
};
594601
};
@@ -618,15 +625,16 @@
618625
isa = PBXResourcesBuildPhase;
619626
buildActionMask = 2147483647;
620627
files = (
621-
831183BA1E3E2F66000DA80C /* api.figo.me.cer in Resources */,
628+
831183BA1E3E2F66000DA80C /* figo_2016.cer in Resources */,
629+
7B6025741EF169E2005EF5B0 /* figo_2017.cer in Resources */,
622630
);
623631
runOnlyForDeploymentPostprocessing = 0;
624632
};
625633
833C00F91E2AA72A00AA5E7C /* Resources */ = {
626634
isa = PBXResourcesBuildPhase;
627635
buildActionMask = 2147483647;
628636
files = (
629-
831183BB1E3E2F66000DA80C /* api.figo.me.cer in Resources */,
637+
831183BB1E3E2F66000DA80C /* figo_2016.cer in Resources */,
630638
833C01991E2AADB400AA5E7C /* Account.json in Resources */,
631639
833C019A1E2AADB400AA5E7C /* User.json in Resources */,
632640
833C019B1E2AADB400AA5E7C /* Balance.json in Resources */,
@@ -640,14 +648,15 @@
640648
833C01A31E2AADB400AA5E7C /* Security.json in Resources */,
641649
833C01A41E2AADB400AA5E7C /* StandingOrder.json in Resources */,
642650
833C01A51E2AADB400AA5E7C /* Payment.json in Resources */,
651+
7B6025761EF169E8005EF5B0 /* figo_2017.cer in Resources */,
643652
);
644653
runOnlyForDeploymentPostprocessing = 0;
645654
};
646655
833C01381E2AAB4200AA5E7C /* Resources */ = {
647656
isa = PBXResourcesBuildPhase;
648657
buildActionMask = 2147483647;
649658
files = (
650-
831183B91E3E2F66000DA80C /* api.figo.me.cer in Resources */,
659+
831183B91E3E2F66000DA80C /* figo_2016.cer in Resources */,
651660
833C01511E2AAC0600AA5E7C /* Account.json in Resources */,
652661
833C01521E2AAC0600AA5E7C /* User.json in Resources */,
653662
833C01531E2AAC0600AA5E7C /* Balance.json in Resources */,
@@ -661,14 +670,16 @@
661670
833C015B1E2AAC0600AA5E7C /* Security.json in Resources */,
662671
833C015C1E2AAC0600AA5E7C /* StandingOrder.json in Resources */,
663672
833C015D1E2AAC0600AA5E7C /* Payment.json in Resources */,
673+
7B6025751EF169E7005EF5B0 /* figo_2017.cer in Resources */,
664674
);
665675
runOnlyForDeploymentPostprocessing = 0;
666676
};
667677
83F3AF201BFF28D900767D77 /* Resources */ = {
668678
isa = PBXResourcesBuildPhase;
669679
buildActionMask = 2147483647;
670680
files = (
671-
831183B81E3E2F66000DA80C /* api.figo.me.cer in Resources */,
681+
831183B81E3E2F66000DA80C /* figo_2016.cer in Resources */,
682+
7B6025731EF169E2005EF5B0 /* figo_2017.cer in Resources */,
672683
);
673684
runOnlyForDeploymentPostprocessing = 0;
674685
};
@@ -982,6 +993,7 @@
982993
DYLIB_INSTALL_NAME_BASE = "@rpath";
983994
ENABLE_STRICT_OBJC_MSGSEND = YES;
984995
ENABLE_TESTABILITY = YES;
996+
GCC_NO_COMMON_BLOCKS = YES;
985997
GCC_OPTIMIZATION_LEVEL = 0;
986998
GCC_PREPROCESSOR_DEFINITIONS = (
987999
"DEBUG=1",
@@ -1032,6 +1044,7 @@
10321044
DYLIB_INSTALL_NAME_BASE = "@rpath";
10331045
ENABLE_NS_ASSERTIONS = NO;
10341046
ENABLE_STRICT_OBJC_MSGSEND = YES;
1047+
GCC_NO_COMMON_BLOCKS = YES;
10351048
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
10361049
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
10371050
GCC_WARN_UNDECLARED_SELECTOR = YES;
@@ -1066,6 +1079,7 @@
10661079
PRODUCT_BUNDLE_IDENTIFIER = io.figo.iOS;
10671080
PRODUCT_NAME = Figo;
10681081
SKIP_INSTALL = YES;
1082+
SWIFT_VERSION = 3.0;
10691083
};
10701084
name = Debug;
10711085
};
@@ -1081,6 +1095,7 @@
10811095
PRODUCT_BUNDLE_IDENTIFIER = io.figo.iOS;
10821096
PRODUCT_NAME = Figo;
10831097
SKIP_INSTALL = YES;
1098+
SWIFT_VERSION = 3.0;
10841099
};
10851100
name = Release;
10861101
};
Binary file not shown.

Figo.xcodeproj/xcshareddata/xcschemes/Figo iOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "0820"
3+
LastUpgradeVersion = "0830"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "NO"

Figo.xcodeproj/xcshareddata/xcschemes/Figo macOS.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "0820"
3+
LastUpgradeVersion = "0830"
44
version = "1.3">
55
<BuildAction
66
parallelizeBuildables = "YES"

Source/Core/Response.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,5 @@ internal func decodeUnboxableResponse<T: Unboxable>(_ data: FigoResult<Data>, co
5353
internal func base64EncodeBasicAuthCredentials(_ clientID: String, _ clientSecret: String) -> String {
5454
let clientCode: String = clientID + ":" + clientSecret
5555
let utf8str: Data = clientCode.data(using: String.Encoding.utf8)!
56-
return utf8str.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
56+
return utf8str.base64EncodedString(options: Data.Base64EncodingOptions.endLineWithCarriageReturn)
5757
}

Source/FigoClient.swift

Lines changed: 38 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ internal let POLLING_INTERVAL_MSECS: Int64 = Int64(400) * Int64(NSEC_PER_MSEC)
1616
internal let POLLING_COUNTDOWN_INITIAL_VALUE = 100 // 100 x 400 ms = 40 s
1717

1818
/// Name of certificate file for public key pinning
19-
internal let CERTIFICATE_FILE = "api.figo.me"
19+
internal let CERTIFICATE_FILES = ["figo_2016", "figo_2017"]
2020

2121

2222
/**
@@ -33,27 +33,22 @@ internal let CERTIFICATE_FILE = "api.figo.me"
3333
*/
3434
public class FigoClient: NSObject {
3535

36-
private lazy var session: URLSession = {
36+
fileprivate lazy var session: URLSession = {
3737
return URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
3838
}()
3939

4040
/// Used for Basic HTTP authentication, derived from CliendID and ClientSecret
41-
private var basicAuthCredentials: String?
41+
fileprivate var basicAuthCredentials: String?
4242

4343
/// OAuth2 access token
4444
var accessToken: String?
4545

4646
/// OAuth2 refresh token
4747
var refreshToken: String?
4848

49-
/// Public key extracted from certificate file in bundle
50-
lazy var publicKey: SecKey = {
51-
let url = Bundle(for: FigoClient.self).url(forResource: CERTIFICATE_FILE, withExtension: "cer")!
52-
let data = try? Data(contentsOf: url)
53-
assert(data != nil, "Failed to load contents of certificate file '\(CERTIFICATE_FILE).cer'")
54-
let key = publicKeyForCertificateData(data: data!)
55-
assert(key != nil, "Failed to extract public key from certificate file '\(CERTIFICATE_FILE).cer'")
56-
return key!
49+
/// Public keys extracted from certificate files in bundle
50+
lazy var publicKeys: [SecKey] = {
51+
return publicKeysForResources(CERTIFICATE_FILES)
5752
}()
5853

5954

@@ -155,25 +150,28 @@ public class FigoClient: NSObject {
155150
Checks the server's certificates to make sure that you are really talking to the figo server
156151
*/
157152
public func dispositionForChallenge(_ challenge: URLAuthenticationChallenge) -> URLSession.AuthChallengeDisposition {
158-
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
159-
160153
if let serverTrust = challenge.protectionSpace.serverTrust {
154+
if !trustIsValid(serverTrust) {
155+
return .cancelAuthenticationChallenge
156+
}
157+
161158
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
162-
let serverKeys = publicKeysForServerTrust(serverTrust: serverTrust) as NSArray
163-
if !serverKeys.contains(self.publicKey) {
164-
disposition = .cancelAuthenticationChallenge
159+
let serverKeys = publicKeysForServerTrust(serverTrust) as NSArray
160+
for clientKey in self.publicKeys {
161+
if serverKeys.contains(clientKey) {
162+
return .performDefaultHandling
163+
}
165164
}
165+
return .cancelAuthenticationChallenge
166166
}
167-
if !trustIsValid(serverTrust) {
168-
disposition = .cancelAuthenticationChallenge
169-
}
167+
170168
} else {
171169
if challenge.previousFailureCount > 0 {
172-
disposition = .cancelAuthenticationChallenge
170+
return .cancelAuthenticationChallenge
173171
}
174172
}
175173

176-
return disposition
174+
return .performDefaultHandling
177175
}
178176
}
179177

@@ -183,7 +181,7 @@ extension FigoClient: URLSessionDelegate {
183181
}
184182
}
185183

186-
private func publicKeyForCertificateData(data: Data) -> SecKey? {
184+
private func publicKeyForCertificateData(_ data: Data) -> SecKey? {
187185
if let certificate = SecCertificateCreateWithData(nil, data as CFData) {
188186
var trust: SecTrust?
189187
let status = SecTrustCreateWithCertificates(certificate, SecPolicyCreateBasicX509(), &trust)
@@ -197,7 +195,7 @@ private func publicKeyForCertificateData(data: Data) -> SecKey? {
197195
return nil
198196
}
199197

200-
private func publicKeysForServerTrust(serverTrust: SecTrust) -> [SecKey] {
198+
private func publicKeysForServerTrust(_ serverTrust: SecTrust) -> [SecKey] {
201199
var keys: [SecKey] = []
202200

203201
for index in 0 ..< SecTrustGetCertificateCount(serverTrust) {
@@ -229,3 +227,20 @@ private func trustIsValid(_ trust: SecTrust) -> Bool {
229227
}
230228
return isValid
231229
}
230+
231+
private func publicKeysForResources(_ resources: [String]) -> [SecKey] {
232+
var publicKeys: [SecKey] = []
233+
234+
for resource in resources {
235+
let url = Bundle(for: FigoClient.self).url(forResource: resource, withExtension: "cer")!
236+
let data = try? Data(contentsOf: url)
237+
assert(data != nil, "Failed to load contents of certificate file '\(resource).cer'")
238+
let key = publicKeyForCertificateData(data!)
239+
assert(key != nil, "Failed to extract public key from certificate file '\(resource).cer'")
240+
if let key = key {
241+
publicKeys.append(key)
242+
}
243+
}
244+
245+
return publicKeys
246+
}

Source/Logging/URLRequest+curlCommand.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ extension URLRequest {
3131
command.append(" -H \"\(key): \(value)\"")
3232
}
3333
command.append(" --compressed")
34-
command.append(" \"\(self.url?.absoluteString)\"")
34+
command.append(" \"\(self.url!.absoluteString)\"")
3535
command.append(" | python -mjson.tool")
3636
return command
3737
}

Tests/AccountTests.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ class AccountTests: BaseTestCaseWithLogin {
2121
XCTAssertGreaterThan(accounts.count, 0)
2222
print("\(accounts.count) accounts:")
2323
for account in accounts {
24-
print("\(account.accountID) \(account.bankID) \(account.bankCode) \(account.name) \(account.balanceFormatted ?? "")")
24+
print("\(account.accountID) \(account.bankID ?? "null") \(account.bankCode) \(account.name) \(account.balanceFormatted ?? "")")
2525
}
2626
break
2727
case .failure(let error):

0 commit comments

Comments
 (0)