Skip to content

Commit

Permalink
Merge pull request #12 from figo-connect/cert-update
Browse files Browse the repository at this point in the history
Support for multiple certificates
  • Loading branch information
CodeStage authored Jul 2, 2017
2 parents 7d87bf8 + 9948856 commit 42b2f50
Show file tree
Hide file tree
Showing 14 changed files with 76 additions and 45 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ Figo.xcworkspace/xcuserdata
Carthage
Paw
build
Figo.xcodeproj/project.xcworkspace/xcuserdata
4 changes: 2 additions & 2 deletions Figo.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
Pod::Spec.new do |s|

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

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

end
39 changes: 27 additions & 12 deletions Figo.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@
objects = {

/* Begin PBXBuildFile section */
7B6025731EF169E2005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
7B6025741EF169E2005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
7B6025751EF169E7005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
7B6025761EF169E8005EF5B0 /* figo_2017.cer in Resources */ = {isa = PBXBuildFile; fileRef = 7B6025721EF169E2005EF5B0 /* figo_2017.cer */; };
830E63D91C05A4050048F7BF /* TANScheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830E63D81C05A4050048F7BF /* TANScheme.swift */; };
830E63DE1C05A7070048F7BF /* SyncStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830E63DD1C05A7070048F7BF /* SyncStatus.swift */; };
830E63E31C05AB890048F7BF /* PaymentParameters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 830E63E21C05AB890048F7BF /* PaymentParameters.swift */; };
Expand All @@ -16,10 +20,10 @@
831183761E3DF13D000DA80C /* Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8311836F1E3DF136000DA80C /* Logging.swift */; };
831183771E3DF141000DA80C /* URLRequest+curlCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 831183701E3DF136000DA80C /* URLRequest+curlCommand.swift */; };
831183781E3DF141000DA80C /* URLRequest+curlCommand.swift in Sources */ = {isa = PBXBuildFile; fileRef = 831183701E3DF136000DA80C /* URLRequest+curlCommand.swift */; };
831183B81E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
831183B91E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
831183BA1E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
831183BB1E3E2F66000DA80C /* api.figo.me.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* api.figo.me.cer */; };
831183B81E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
831183B91E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
831183BA1E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
831183BB1E3E2F66000DA80C /* figo_2016.cer in Resources */ = {isa = PBXBuildFile; fileRef = 831183B71E3E2F66000DA80C /* figo_2016.cer */; };
832CABAE1E242F8700D48895 /* Unbox.swift in Sources */ = {isa = PBXBuildFile; fileRef = 832CABAD1E242F8700D48895 /* Unbox.swift */; };
833C01001E2AA72A00AA5E7C /* Figo.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 833C00EF1E2AA6A100AA5E7C /* Figo.framework */; };
833C01061E2AA7F400AA5E7C /* FigoClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = 83F0E46B1C08A91400FB3709 /* FigoClient.swift */; };
Expand Down Expand Up @@ -179,6 +183,7 @@
/* End PBXContainerItemProxy section */

/* Begin PBXFileReference section */
7B6025721EF169E2005EF5B0 /* figo_2017.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = figo_2017.cer; sourceTree = "<group>"; };
83017B771C0A2FE80062FC08 /* TaskState.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = TaskState.json; path = Resources/TaskState.json; sourceTree = "<group>"; };
830E63D81C05A4050048F7BF /* TANScheme.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TANScheme.swift; path = Types/TANScheme.swift; sourceTree = "<group>"; };
830E63DB1C05A4960048F7BF /* TanScheme.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = TanScheme.json; path = Resources/TanScheme.json; sourceTree = "<group>"; };
Expand All @@ -188,7 +193,7 @@
831183651E3DEAE1000DA80C /* ServiceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ServiceTests.swift; sourceTree = "<group>"; };
8311836F1E3DF136000DA80C /* Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logging.swift; sourceTree = "<group>"; };
831183701E3DF136000DA80C /* URLRequest+curlCommand.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "URLRequest+curlCommand.swift"; sourceTree = "<group>"; };
831183B71E3E2F66000DA80C /* api.figo.me.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = api.figo.me.cer; sourceTree = "<group>"; };
831183B71E3E2F66000DA80C /* figo_2016.cer */ = {isa = PBXFileReference; lastKnownFileType = file; path = figo_2016.cer; sourceTree = "<group>"; };
832CABAD1E242F8700D48895 /* Unbox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Unbox.swift; sourceTree = "<group>"; };
833285EE1C04D45900A9FE73 /* Balance.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; name = Balance.json; path = Resources/Balance.json; sourceTree = "<group>"; };
833285F01C04D48E00A9FE73 /* Resources.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Resources.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -454,7 +459,8 @@
isa = PBXGroup;
children = (
83D3A7201C03471D003EDE45 /* README.md */,
831183B71E3E2F66000DA80C /* api.figo.me.cer */,
831183B71E3E2F66000DA80C /* figo_2016.cer */,
7B6025721EF169E2005EF5B0 /* figo_2017.cer */,
83D3A67F1BFF2953003EDE45 /* Source */,
83D3A7081BFFB6A7003EDE45 /* Tests */,
83F3AF231BFF28D900767D77 /* Products */,
Expand Down Expand Up @@ -571,7 +577,7 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0820;
LastUpgradeCheck = 0820;
LastUpgradeCheck = 0830;
ORGANIZATIONNAME = CodeStage;
TargetAttributes = {
833C00EE1E2AA6A100AA5E7C = {
Expand All @@ -584,11 +590,12 @@
};
833C01391E2AAB4200AA5E7C = {
CreatedOnToolsVersion = 8.2.1;
LastSwiftMigration = 0830;
ProvisioningStyle = Manual;
};
83F3AF211BFF28D900767D77 = {
CreatedOnToolsVersion = 7.1.1;
LastSwiftMigration = 0820;
LastSwiftMigration = 0830;
ProvisioningStyle = Manual;
};
};
Expand Down Expand Up @@ -618,15 +625,16 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
831183BA1E3E2F66000DA80C /* api.figo.me.cer in Resources */,
831183BA1E3E2F66000DA80C /* figo_2016.cer in Resources */,
7B6025741EF169E2005EF5B0 /* figo_2017.cer in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
833C00F91E2AA72A00AA5E7C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
831183BB1E3E2F66000DA80C /* api.figo.me.cer in Resources */,
831183BB1E3E2F66000DA80C /* figo_2016.cer in Resources */,
833C01991E2AADB400AA5E7C /* Account.json in Resources */,
833C019A1E2AADB400AA5E7C /* User.json in Resources */,
833C019B1E2AADB400AA5E7C /* Balance.json in Resources */,
Expand All @@ -640,14 +648,15 @@
833C01A31E2AADB400AA5E7C /* Security.json in Resources */,
833C01A41E2AADB400AA5E7C /* StandingOrder.json in Resources */,
833C01A51E2AADB400AA5E7C /* Payment.json in Resources */,
7B6025761EF169E8005EF5B0 /* figo_2017.cer in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
833C01381E2AAB4200AA5E7C /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
831183B91E3E2F66000DA80C /* api.figo.me.cer in Resources */,
831183B91E3E2F66000DA80C /* figo_2016.cer in Resources */,
833C01511E2AAC0600AA5E7C /* Account.json in Resources */,
833C01521E2AAC0600AA5E7C /* User.json in Resources */,
833C01531E2AAC0600AA5E7C /* Balance.json in Resources */,
Expand All @@ -661,14 +670,16 @@
833C015B1E2AAC0600AA5E7C /* Security.json in Resources */,
833C015C1E2AAC0600AA5E7C /* StandingOrder.json in Resources */,
833C015D1E2AAC0600AA5E7C /* Payment.json in Resources */,
7B6025751EF169E7005EF5B0 /* figo_2017.cer in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
83F3AF201BFF28D900767D77 /* Resources */ = {
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
831183B81E3E2F66000DA80C /* api.figo.me.cer in Resources */,
831183B81E3E2F66000DA80C /* figo_2016.cer in Resources */,
7B6025731EF169E2005EF5B0 /* figo_2017.cer in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down Expand Up @@ -982,6 +993,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_OPTIMIZATION_LEVEL = 0;
GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1",
Expand Down Expand Up @@ -1032,6 +1044,7 @@
DYLIB_INSTALL_NAME_BASE = "@rpath";
ENABLE_NS_ASSERTIONS = NO;
ENABLE_STRICT_OBJC_MSGSEND = YES;
GCC_NO_COMMON_BLOCKS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
GCC_WARN_UNDECLARED_SELECTOR = YES;
Expand Down Expand Up @@ -1066,6 +1079,7 @@
PRODUCT_BUNDLE_IDENTIFIER = io.figo.iOS;
PRODUCT_NAME = Figo;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Debug;
};
Expand All @@ -1081,6 +1095,7 @@
PRODUCT_BUNDLE_IDENTIFIER = io.figo.iOS;
PRODUCT_NAME = Figo;
SKIP_INSTALL = YES;
SWIFT_VERSION = 3.0;
};
name = Release;
};
Expand Down
Binary file not shown.
2 changes: 1 addition & 1 deletion Figo.xcodeproj/xcshareddata/xcschemes/Figo iOS.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0820"
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "NO"
Expand Down
2 changes: 1 addition & 1 deletion Figo.xcodeproj/xcshareddata/xcschemes/Figo macOS.xcscheme
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0820"
LastUpgradeVersion = "0830"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
Expand Down
2 changes: 1 addition & 1 deletion Source/Core/Response.swift
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,5 @@ internal func decodeUnboxableResponse<T: Unboxable>(_ data: FigoResult<Data>, co
internal func base64EncodeBasicAuthCredentials(_ clientID: String, _ clientSecret: String) -> String {
let clientCode: String = clientID + ":" + clientSecret
let utf8str: Data = clientCode.data(using: String.Encoding.utf8)!
return utf8str.base64EncodedString(options: NSData.Base64EncodingOptions.endLineWithCarriageReturn)
return utf8str.base64EncodedString(options: Data.Base64EncodingOptions.endLineWithCarriageReturn)
}
61 changes: 38 additions & 23 deletions Source/FigoClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal let POLLING_INTERVAL_MSECS: Int64 = Int64(400) * Int64(NSEC_PER_MSEC)
internal let POLLING_COUNTDOWN_INITIAL_VALUE = 100 // 100 x 400 ms = 40 s

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


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

private lazy var session: URLSession = {
fileprivate lazy var session: URLSession = {
return URLSession(configuration: URLSessionConfiguration.default, delegate: self, delegateQueue: nil)
}()

/// Used for Basic HTTP authentication, derived from CliendID and ClientSecret
private var basicAuthCredentials: String?
fileprivate var basicAuthCredentials: String?

/// OAuth2 access token
var accessToken: String?

/// OAuth2 refresh token
var refreshToken: String?

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


Expand Down Expand Up @@ -155,25 +150,28 @@ public class FigoClient: NSObject {
Checks the server's certificates to make sure that you are really talking to the figo server
*/
public func dispositionForChallenge(_ challenge: URLAuthenticationChallenge) -> URLSession.AuthChallengeDisposition {
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling

if let serverTrust = challenge.protectionSpace.serverTrust {
if !trustIsValid(serverTrust) {
return .cancelAuthenticationChallenge
}

if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let serverKeys = publicKeysForServerTrust(serverTrust: serverTrust) as NSArray
if !serverKeys.contains(self.publicKey) {
disposition = .cancelAuthenticationChallenge
let serverKeys = publicKeysForServerTrust(serverTrust) as NSArray
for clientKey in self.publicKeys {
if serverKeys.contains(clientKey) {
return .performDefaultHandling
}
}
return .cancelAuthenticationChallenge
}
if !trustIsValid(serverTrust) {
disposition = .cancelAuthenticationChallenge
}

} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
return .cancelAuthenticationChallenge
}
}

return disposition
return .performDefaultHandling
}
}

Expand All @@ -183,7 +181,7 @@ extension FigoClient: URLSessionDelegate {
}
}

private func publicKeyForCertificateData(data: Data) -> SecKey? {
private func publicKeyForCertificateData(_ data: Data) -> SecKey? {
if let certificate = SecCertificateCreateWithData(nil, data as CFData) {
var trust: SecTrust?
let status = SecTrustCreateWithCertificates(certificate, SecPolicyCreateBasicX509(), &trust)
Expand All @@ -197,7 +195,7 @@ private func publicKeyForCertificateData(data: Data) -> SecKey? {
return nil
}

private func publicKeysForServerTrust(serverTrust: SecTrust) -> [SecKey] {
private func publicKeysForServerTrust(_ serverTrust: SecTrust) -> [SecKey] {
var keys: [SecKey] = []

for index in 0 ..< SecTrustGetCertificateCount(serverTrust) {
Expand Down Expand Up @@ -229,3 +227,20 @@ private func trustIsValid(_ trust: SecTrust) -> Bool {
}
return isValid
}

private func publicKeysForResources(_ resources: [String]) -> [SecKey] {
var publicKeys: [SecKey] = []

for resource in resources {
let url = Bundle(for: FigoClient.self).url(forResource: resource, withExtension: "cer")!
let data = try? Data(contentsOf: url)
assert(data != nil, "Failed to load contents of certificate file '\(resource).cer'")
let key = publicKeyForCertificateData(data!)
assert(key != nil, "Failed to extract public key from certificate file '\(resource).cer'")
if let key = key {
publicKeys.append(key)
}
}

return publicKeys
}
2 changes: 1 addition & 1 deletion Source/Logging/URLRequest+curlCommand.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ extension URLRequest {
command.append(" -H \"\(key): \(value)\"")
}
command.append(" --compressed")
command.append(" \"\(self.url?.absoluteString)\"")
command.append(" \"\(self.url!.absoluteString)\"")
command.append(" | python -mjson.tool")
return command
}
Expand Down
2 changes: 1 addition & 1 deletion Tests/AccountTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ class AccountTests: BaseTestCaseWithLogin {
XCTAssertGreaterThan(accounts.count, 0)
print("\(accounts.count) accounts:")
for account in accounts {
print("\(account.accountID) \(account.bankID) \(account.bankCode) \(account.name) \(account.balanceFormatted ?? "")")
print("\(account.accountID) \(account.bankID ?? "null") \(account.bankCode) \(account.name) \(account.balanceFormatted ?? "")")
}
break
case .failure(let error):
Expand Down
4 changes: 2 additions & 2 deletions Tests/BaseTestCaseWithLogin.swift
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class BaseTestCaseWithLogin: XCTestCase {
}

/// Allows you to get rid of the boilerplate code for async callbacks in test cases
func waitForCompletionOfTests(tests: (_ doneWaiting: @escaping () -> ()) -> ()) {
func waitForCompletionOfTests(_ tests: (_ doneWaiting: @escaping () -> ()) -> ()) {
let completionExpectation = self.expectation(description: "Completion should be called")
tests {
completionExpectation.fulfill()
Expand All @@ -77,6 +77,6 @@ class BaseTestCaseWithLogin: XCTestCase {
}

func testThatCertificateIsPresent() {
XCTAssertNotNil(figo.publicKey)
XCTAssertNotNil(figo.publicKeys)
}
}
2 changes: 1 addition & 1 deletion Tests/TransactionTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class TransactionsTests: BaseTestCaseWithLogin {
if case .success(let envelope) = result {
print("Retrieved \(envelope.transactions.count) transactions")
for t in envelope.transactions {
print("\(t.name) \(t.amountFormatted)")
print("\(t.name ?? "null")) \(t.amountFormatted)")
}

figo.retrieveTransaction(envelope.transactions.last!.transactionID) { result in
Expand Down
File renamed without changes.
Binary file added figo_2017.cer
Binary file not shown.

0 comments on commit 42b2f50

Please sign in to comment.