Why is ssl pinning not working on synchronous requests? - swift

I want to do ssl pinning with Alamofire library, but it doesn't work for sync requests.
I am using the following library to sync Alamofire request:
https://github.com/Dalodd/Alamofire-Synchronous
In async call I get cancaled with code -999 but when I try with sync I get all responses with 200.
My code is like this:
let hostname = "..."
let cert = "..." // e.g. for cert.der, this should just be "cert"
let pathToCert = Bundle.main.path(forResource: cert, ofType: "der")
let localCertificate = NSData(contentsOfFile: pathToCert!)
let certificates = [SecCertificateCreateWithData(nil,
localCertificate!)!]
// Configure the trust policy manager
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: certificates,
validateCertificateChain: true,
validateHost: true
)
let serverTrustPolicies = [hostname: serverTrustPolicy]
let serverTrustPolicyManager = ServerTrustPolicyManager(policies:
serverTrustPolicies)
// Configure session manager with trust policy
let defaultManager = Alamofire.SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: serverTrustPolicyManager
)
let manager = defaultManager
manager.session.configuration.timeoutIntervalForRequest = 120
let request = getRequest(object, endPoint: endPoint)
let response = manager.request(request).responseString()
If I don't use semaphore in the code below the request is aborted but if I use it I get 200 responses
public func response<T: DataResponseSerializerProtocol>(responseSerializer: T) ->
DataResponse<T.SerializedObject> {
let semaphore = DispatchSemaphore(value: 0)
var result: DataResponse<T.SerializedObject>!
self.response(queue: DispatchQueue.global(qos: .default), responseSerializer: responseSerializer) { response in
result = response
semaphore.signal()
}
_ = semaphore.wait(timeout: DispatchTime.distantFuture)
return result
}
How is this possible?

Using Alamofire synchronously is not supported so any misbehaviors you see when doing this are unlikely to be fixed.
Additionally, that dependency is using Alamofire 4, where 5 is the latest version, so if you really want the behavior I suggest implementing it manually using the latest version.

Related

Upload file to S3 service 403 ERROR (com.amazonaws.AWSS3TransferUtilityErrorDomain error 2.) Swift iOS

I tried to upload my file to S3 service vie AWSS3 SDK swift.
My code:
let credentialsProvider = AWSStaticCredentialsProvider(accessKey: Config.main.accessKey, secretKey: Config.main.secretKey)
let configuration = AWSServiceConfiguration(region: .USEast1, endpoint: AWSEndpoint(url: URL(string: Config.main.AWS_ENDPOINT)!), credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = configuration
let image = UIImage(named: "photo")!
let data: Data = image.pngData()!
let remoteName = generateRandomStringWithLength(length: 12) + "." + data.format
let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!
let fileURL = documentsURL.appendingPathComponent(remoteName)
try! data.write(to: fileURL, options: .atomic)
upload(fileUrl: fileURL, fileData: data, fileName: remoteName, type: .image, completionHandler: {_ in})
func upload(fileUrl: URL, fileData: Data, fileName: String, type: FileTypes, completionHandler: #escaping (URL?) -> ()) {
let expression = AWSS3TransferUtilityUploadExpression()
expression.progressBlock = { task, progress in
DispatchQueue.main.async {
print("Progress = \(progress.completedUnitCount)/\(progress.totalUnitCount)")
}
}
let util = AWSS3TransferUtility.default()
util.uploadData(
fileData,
bucket: self.getBucket(type: type),
key: "\(self.getDir(type: type))_\(fileName)",
contentType: "image/png",
expression: expression) { task, error in
print("ERROR: \(error?.localizedDescription)")
print("response: \(task.response)")
print("response: \(task.response)")
}.continueWith { task in
if let error = task.error {
print("ERROR1: \(error.localizedDescription)")
}
return nil
}
}
It returns me something like 5 times progress response and after that
ERROR: Optional("The operation couldn’t be completed.
(com.amazonaws.AWSS3TransferUtilityErrorDomain error 2.)")
response: Optional( { URL:
http://(bucket).(host)/image_L24i8RGCeAaj.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=59589007eea780cf27c5%2F20200131%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20200131T125453Z&X-Amz-Expires=2999&X-Amz-SignedHeaders=content-type%3Bhost&X-Amz-Signature=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
} { Status Code: 403, Headers {
"Content-Length" = (
186
);
Date = (
"Fri, 31 Jan 2020 12:54:53 GMT"
);
Server = (
LeoFS
); } })
Android app with same setup works perfect.
What is the solution of the problem?
I was having same issue. Everything was working fine on android, While IOS was not working with same configuration.
Then I contacted AWS support, and they told me iPhones Date and time is wrong. I just adjusted date and time to correct date and time and then tried to upload and it worked.
hi if you are not using custom endpoints, change this line:
let configuration = AWSServiceConfiguration(region: .USEast1, endpoint: AWSEndpoint(url: URL(string: Config.main.AWS_ENDPOINT)!), credentialsProvider: credentialsProvider)
to:
let configuration = AWSServiceConfiguration(region: .USEast1, credentialsProvider: credentialsProvider)
and it will work just fine. The SDK can resolve the endpoint of the service by itself without providing an endpoint. You only need to provide an endpoint if you are using custom endpoints. It is also worth noting here that if you did want to pass the endpoint, you need to make sure you are passing the correct endpoint and us-east-1 for s3 has a special endpoint that does not include region as shown below:
let configuration = AWSServiceConfiguration(region: .USEast1,endpoint: AWSEndpoint(url: URL(string: "https://s3.amazonaws.com")) , credentialsProvider: credentialsProvider)
There is some useful info about this at https://medium.com/#lewisjkl/signing-aws4-31dcff1bf1f0, from Jeff Lewis.
It uses the CryptoSwift library; I'm working now to convert it to use the new CryptoKit.
Just check your device date and time. Because, AWS don't accept the wrong date or time.

Integrate Apollo subscriptions on iOS with Action Cable being used on the backend for websockets

I'm trying to make Apollo subscriptions on iOS work with a backend that is using Action Cable to implement websockets. I learned that the iOS app needs to send command, channel and channel id to the backend to make subscriptions work (see here). I have tried to use the function write func write(_ str: String, force forced: Bool = false, id: Int? = nil) in WebSocketTransport.swift on WebSocketTransport object when initializing instance of Apollo. Below you can see how I'm doing that.
let userDefault = UserDefaults.standard
var authPayloads = Dictionary<String, String> ()
var authToken = ""
if let token = userDefault.object(forKey: "token") {
authToken = "\(token)"
authPayloads.updateValue(authToken, forKey: "authorization")
}
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = authPayloads
let map: GraphQLMap = authPayloads
let wsEndpointURL = URL(string: "ws://localhost:8080/subscriptions/\(authToken)")!
let endpointURL = URL(string: "http://localhost:8080/api")!
websocket = WebSocketTransport(request: URLRequest(url: wsEndpointURL), connectingPayload: map)
var channelId = Int(arc4random_uniform(100000))
websocket?.write(stringify(json: ["command":"subscribe", "identifier": stringify(json: ["channel":"channelName", "channelId": channelId])]))
let splitNetworkTransport = SplitNetworkTransport(
httpNetworkTransport: HTTPNetworkTransport(
url: endpointURL,
configuration: configuration
),
webSocketNetworkTransport: websocket!
)
return ApolloClient(networkTransport: splitNetworkTransport)
}()
However, the backend isn't seeing what I'm writing to the WebSocket Transport object in their logs and I'm not able to subscribe to that specific channel. Any idea how I can make use Apollo subscriptions on iOS if the backend is using Action Cable, and make the two work together?
The only solution for now is to use Swift-ActionCableClient to recieve streams, and use the mutations/queries from ApolloClient.
Unfortenatly! Apollo iOS doesn't know how to communicate with ActionCable channels, this issue is reported on Apollo-iOS Github Issue #634 & Your issue Github Issue #454

Install TLS certificates on web server iOS app

I'm trying to integrate Swish payment in one of the apps I develop.
In order to be able to connect to the swish api I have to "set up TLS certificates from Swish Certificate Management and install it on "my" web server" according to the documentation.Here is the full technical documentation https://developer.getswish.se/merchants-api-manual/4-merchant-setup-process/.
The problem I don't understand is that I don't use a web server and I can't install those certificates there.
My app just offers some services for the client and after pressing the pay button should open the Swish app to finish the transaction in short.
What I tried is to make a post request to get the request token with which I can open the swish app loaded with the payment details.
I'm sure the problems are the certificates but couldn't find a good source explaining how to import(integrate) them.
let strURL = "https://mss.cpc.getswish.net/swish-cpcapi/api/v1/paymentrequests/"
guard let postURL = URL(string: strURL ) else {
print("Can't create url")
return
}
var request = URLRequest(url: postURL)
request.httpMethod = "POST"
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
request.setValue("application/json", forHTTPHeaderField: "Accept")
let data: [String: Any] = [
"callbackUrl": "https://example.com/api/swishcb/paymentrequests",
"payeeAlias": "123xxxxxxx", // The Swish number of the payee. It needs to match with Merchant Swish number.
"amount": "100",
"currency": "SEK",
"message": "Test request to get the token"
]
do {
let jsonParams = try JSONSerialization.data(withJSONObject: data, options: [])
request.httpBody = jsonParams
} catch {
print("Error serializing the parameters of the post request")
return
}
// response will contain a Token, unique for each payment request
let config = URLSessionConfiguration.default
config.timeoutIntervalForResource = 120
config.timeoutIntervalForRequest = 120
let session = URLSession(configuration: config)
session.dataTask(with: request) { (data, response, error) in
print("Data \(data)")
print("Response \(response)")
if error != nil {
print("Error post request \(error?.localizedDescription)")
}
}.resume()
The error I got is:
Error post request Optional("An SSL error has occurred and a secure connection to the server cannot be made.")
018-12-21 12:24:55.549759+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_context_alert_callback_handler(3718) [C6.1:2][0x7fce4a77bf00] Alert level: fatal, description: handshake failure
2018-12-21 12:24:55.550047+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_session_errorlog(224) [C6.1:2][0x7fce4a77bf00] [boringssl_session_handshake_incomplete] SSL_ERROR_SSL(1): operation failed within the library
2018-12-21 12:24:55.550332+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_session_handshake_error_print(205) [C6.1:2][0x7fce4a77bf00] 140523985879704:error:10000410:SSL routines:OPENSSL_internal:SSLV3_ALERT_HANDSHAKE_FAILURE:/BuildRoot/Library/Caches/com.apple.xbs/Sources/boringssl_Sim/boringssl-109.220.4/ssl/tls_record.cc:586:SSL alert number 40
2018-12-21 12:24:55.550585+0200 tolk-24-7[7230:111102] [BoringSSL] boringssl_context_get_error_code(3539) [C6.1:2][0x7fce4a77bf00] SSL_AD_HANDSHAKE_FAILURE
2018-12-21 12:24:55.552299+0200 tolk-24-7[7230:111102] TIC TCP Conn Failed [6:0x600002dd6c40]: 3:-9824 Err(-9824)
2018-12-21 12:24:55.555924+0200 tolk-24-7[7230:111102] NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9824)
2018-12-21 12:24:55.556052+0200 tolk-24-7[7230:111102] Task <7888D080-D175-4DBF-8F66-4183F0D653E6>.<1> HTTP load failed (error code: -1200 [3:-9824])
2018-12-21 12:24:55.556234+0200 tolk-24-7[7230:111613] Task <7888D080-D175-4DBF-8F66-4183F0D653E6>.<1> finished with error - code: -1200
I feel your frustrations, I haven't worked with the Swish API per se, but it looks like URLSession is failing to perform the client certificate request. The handshake is failing on that step.
There is an option to add a URLSessionDelegate to URLSession in order to handle authentication challenges such as ServerTrust and ClientCertificate. They are discussing it here:
Swift 3 UrlSession with client authentication by certificate
If you're able to create a p12/pfx with the client certificate and private key, you can use SecPKCS12Import to import it and use it for the URLCredential trust in the NSURLAuthenticationMethodClientCertificate received in URLSessionDelegate. Here's an implementation I wrote:
func urlSession(
_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void)
{
let authenticationMethod = challenge.protectionSpace.authenticationMethod
if authenticationMethod == NSURLAuthenticationMethodServerTrust {
//Handle server trust if necessary
} else if authenticationMethod == NSURLAuthenticationMethodClientCertificate {
if let clientCredential = try? getClientUrlCredential() {
completionHandler(.useCredential, clientCredential)
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
}
getClientUrlCredential function:
private func getClientUrlCredential() throws -> URLCredential {
let p12Data = getP12Data() //Get the data from the bundle if it's bundled in the app
let p12Key = getP12Key() //you need the key set for creating the p12/pfx
let certOptions: NSDictionary = [
kSecImportExportPassphrase as NSString : p12Key as NSString
]
// import certificate to read its entries
var items: CFArray?
let status = SecPKCS12Import(p12Data, certOptions, &items)
if status == errSecSuccess,
let items = items,
let dict = (items as Array).first as? [String: AnyObject],
let certChain = dict[kSecImportItemCertChain as String] as? [SecTrust] {
// Check if SecIdentityGetTypeID is present
guard let cfIdentity = dict[kSecImportItemIdentity as String] as CFTypeRef?,
CFGetTypeID(cfIdentity) == SecIdentityGetTypeID() else {
throw URLSessionPinningDelegateError.localClientCertificateError
}
let identity = dict[kSecImportItemIdentity as String] as! SecIdentity
return URLCredential(
identity: identity,
certificates: certChain,
persistence: .forSession
)
}
//Failed to read local certificate, throw error
throw URLSessionPinningDelegateError.localClientCertificateError
}
With a valid client certificate you should be able to fulfill the client hello and set up the TLS towards the server since this seems to be where you are failing right now. The SSL alert number 40 you're getting by BoringSSL suggests so to me at least.
Hope this points you in the right direction at least, happy to support further if needed.

Swift: Alamofire HTTP Proxy

I can't seem a to find a way to create http requests using proxies, let say i have a Sock5 proxy or HTTP Proxy how would i go about creating a GET/POST request via a proxy, without having to apply the proxy to the system.
I only want the single request to go through a proxy.
Let's say i create a http request using Alamofire, then the request would go through my ip address, but what if i want to apply a http proxy or socks5 proxy to send the request through.
Let's say I make a request like that:
Alamofire.request("https://httpbin.org/get").responseJSON { response in
if let JSON = response.result.value {
print("JSON: \(JSON)")
}
}
How would i apply the http or socks proxy to this request?
Can't seem to find anything about it.
Taken from this thread:
You create proxy details:
struct ProxyItem: Equatable, Hashable {
let host: String
let port: String
let HTTPOnly = true
var hashValue: Int {
return host.hashValue ^ port.hashValue
}
}
then create proxy configuration
var proxyConfiguration = [NSObject: AnyObject]()
proxyConfiguration[kCFNetworkProxiesHTTPProxy] = item.host
proxyConfiguration[kCFNetworkProxiesHTTPPort] = port
proxyConfiguration[kCFNetworkProxiesHTTPEnable] = 1
set the Alamo configuration:
let sessionConfiguration = AFManager.sharedInstance.session.configuration
sessionConfiguration.connectionProxyDictionary = proxyConfiguration
Create an alamo manager from this configuration:
manager = Alamofire.Manager(configuration: sessionConfiguration)
Finally use the manager to connect to your proxy:
manager.request(.GET, urlString)
.response {
(request, response, data, error) in
if let response = response {
var statusCode = response.statusCode
println("-->statusCode: \(statusCode)")
}
if (error == nil) {
var serializationError: NSError?
let jsonData: AnyObject? = NSJSONSerialization.JSONObjectWithData(data! as! NSData, options: NSJSONReadingOptions.AllowFragments, error: &serializationError)
var parser: Parser = Parser()
let menu: Menu = parser.parseMenuJSON(jsonData)
var dataAccess: DataAccess = DataAccess.sharedInstance
dataAccess.addMenu(menu)
} else {
println("Webservice error: \(error)")
}
}

How to connect to self signed servers using Alamofire 1.3

I get the below error while connecting to self signed server.
Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “maskeddomain.com” which could put your confidential information at risk." UserInfo=0x7fb6dec259e0 {NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorCodeKey=-9813, NSUnderlyingError=0x7fb6dbe0dd90 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error -1202.)"
Looks like Alamofire 1.3 (https://github.com/Alamofire/Alamofire#security) allows disabling this validation. Has anyone implemented this? I'm using Alamofire API's on my swift project, not sure where exactly "Server Trust Policy Manager" needs to be implemented. Please advice.
Manager configuration for Swift 3 or Swift 4 and Alamofire 4:
private static var manager: Alamofire.SessionManager = {
// Create the server trust policies
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"test.example.com": .disableEvaluation
]
// Create custom manager
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let manager = Alamofire.SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return manager
}()
There is a way to change the Server Trust Policy of the Alamofire manager shared instance, but it's not recommended. Instead you should create your own customised instance of the manager. Here is the recommended solution, code is Swift 2.0 with Alamofire from swift-2.0 branch, compiled in Xcode7 beta 5.
Creating customised instance of the manager
Because you will not use the request method on the Alamofire, but use the one on your custom manager instead, you need to think of where to store the manager. What I do is to store it as static in my networking wrapper (the class that utilizes Alamofire and deals with my application networking needs). I set it up like this:
private static var Manager : Alamofire.Manager = {
// Create the server trust policies
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"maskeddomain.com": .DisableEvaluation
]
// Create custom manager
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
let man = Alamofire.Manager(
configuration: NSURLSessionConfiguration.defaultSessionConfiguration(),
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return man
}()
Next step is to switch all your calls that use Alamofire.request() with Manager.request(), so you should have something like this:
Manager.request(.GET, "http://stackoverflow.com").responseJSON(
completionHandler: { (_, respose, result) -> Void in
if result.isSuccess {
// enjoy your success
} else if result.isFailure {
// deal with your failure
}
})
If you want to change the shared instance of the manager anyway, go here for more info.
An example is posted right in the README demonstrating exactly how to disable evaluation if you need to do so.
Since you are going to need to create your own Manager instance as well, you'll want to do something like the following:
class NetworkManager {
static let sharedInstance = NetworkManager()
let defaultManager: Alamofire.Manager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"test.example.com": .PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: true,
validateHost: true
),
"insecure.expired-apis.com": .DisableEvaluation
]
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.HTTPAdditionalHeaders = Alamofire.Manager.defaultHTTPHeaders
return Alamofire.Manager(
configuration: configuration,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}()
}
This will then allow you to make requests with the NetworkManager.sharedInstance.defaultManager object.
Another approach for my project. The ServerTrustPolicyManager is an open class, and it's serverTrustPolicy function is open too. So it can be override.
// For Swift 3 and Alamofire 4.0
open class MyServerTrustPolicyManager: ServerTrustPolicyManager {
// Override this function in order to trust any self-signed https
open override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
return ServerTrustPolicy.disableEvaluation
// or, if `host` contains substring, return `disableEvaluation`
// Ex: host contains `my_company.com`, then trust it.
}
}
Then,
let trustPolicies = MyServerTrustPolicyManager(policies: [:])
let manager = Alamofire.SessionManager(configuration: sessionConfig, delegate: SessionDelegate(), serverTrustPolicyManager: trustPolicies)
UPDATE #2018,01
In order to trigger the ServerTrustPolicyManager, the project's Info.plist needs to be configured. I found the solution, detail at this post, cnoon's comment # 1 Nov 2015.
For example, if there are urls named site1.foo.com, site2.foo.com, ....
Then add App Transport Security Settings -> Exception Domains -> foo.com dictionary, with following entries.
NSExceptionRequiresForwardSecrecy : NO
NSExceptionAllowsInsecureHTTPLoads : YES
NSIncludesSubdomains : YES
More detail you can refer the post.
Anyway, the answer of #cnoon almost full. But I met another trouble of the ssl validation, so I want to put my code here if someone can get help from it.The manager init as:
private var manager: Manager?
// Import the certificates like xxx.cer to your project(anywhere will be fine), then the ServerTrustPolicy.certificatesInBundle() can find them
let serverTrustPolicy = ServerTrustPolicy.PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: false,
validateHost: true
)
let serverTrustPolicies: [String : ServerTrustPolicy] = [
"sub.server.com": .DisableEvaluation, // because the certificates only add the main domain, so disable evaluation for subdomain
"192.168.0.2:8090": .DisableEvaluation, // the IP address for request data
"www.server.com": serverTrustPolicy
]
manager = Manager(serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
Then use the manager to request:
// this function in the class which for manager the Alamofire request
public func request(method: Alamofire.Method, _ URLString: URLStringConvertible,
parameters: [String : AnyObject]?) -> Alamofire.Request
{
// we do not need use Alamofire.request now, just use the manager you have initialized
return manager!.request(method, URLString, parameters: parameters,
headers: ["tokenId": UserManager_Inst.tokenID])
}
p.s.: it is the swift 2.3 sample