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.
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
here is my alamofire manager, how I can add public key pinning on it ? please help me, I couldn't know the way to do it in my code, if possible I need explanation step by step on how do that with AFManager that has all the requests
class AFManager : NSObject{
///without headers (post)
//used this to registration
class func requestPOSTURL(_ strURL : String, params : [String :
AnyObject]?, success:#escaping (JSON) -> Void, failure:#escaping (Error) -> Void){
URLCache.shared.removeAllCachedResponses()
Alamofire.request(strURL, method: .post, parameters: params, encoding: URLEncoding.httpBody).responseJSON { (responseObject) -> Void in
//print(responseObject)
if responseObject.result.isSuccess {
let resJson = JSON(responseObject.result.value!)
success(resJson)
}
if responseObject.result.isFailure {
let error : Error = responseObject.result.error!
failure(error)
}
}
}
///// response string (post)
//used this in login // used in change password
class func strRequestPOSTURL(_ strURL : String, params : [String : String]?, headers : [String : String]?, success:#escaping (JSON) -> Void, failure:#escaping (Error) -> Void){
URLCache.shared.removeAllCachedResponses()
Alamofire.request(strURL, method: .post, parameters: params, encoding: URLEncoding.httpBody, headers: headers).responseJSON { (response) in
//print(response)
if response.result.isSuccess {
let resJson = JSON(response.result.value!)
success(resJson)
}
if response.result.isFailure {
let error : Error = response.result.error!
failure(error)
}
}
}
}
I saw this sample but didn't know how to do it and where I should put the code see the link below :
https://infinum.co/the-capsized-eight/ssl-pinning-revisited
Security
Using a secure HTTPS connection when communicating with servers and web services is an important step in securing sensitive data. By default, Alamofire will evaluate the certificate chain provided by the server using Apple's built in validation provided by the Security framework. While this guarantees the certificate chain is valid, it does not prevent man-in-the-middle (MITM) attacks or other potential vulnerabilities. In order to mitigate MITM attacks, applications dealing with sensitive customer data or financial information should use certificate or public key pinning provided by the ServerTrustPolicy.
ServerTrustPolicy
The ServerTrustPolicy enumeration evaluates the server trust generally provided by an URLAuthenticationChallenge when connecting to a server over a secure HTTPS connection.
let serverTrustPolicy = ServerTrustPolicy.pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
)
There are many different cases of server trust evaluation giving you complete control over the validation process:
performDefaultEvaluation: Uses the default server trust evaluation
while allowing you to control whether to validate the host provided
by the challenge.
pinCertificates: Uses the pinned certificates to validate the server
trust. The server trust is considered valid if one of the pinned
certificates match one of the server certificates.
pinPublicKeys: Uses the pinned public keys to validate the server
trust. The server trust is considered valid if one of the pinned
public keys match one of the server certificate public keys.
disableEvaluation: Disables all evaluation which in turn will always
consider any server trust as valid.
customEvaluation: Uses the associated closure to evaluate the
validity of the server trust thus giving you complete control over
the validation process. Use with caution.
Server Trust Policy Manager
The ServerTrustPolicyManager is responsible for storing an internal mapping of server trust policies to a particular host. This allows Alamofire to evaluate each host against a different server trust policy.
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"test.example.com": .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
),
"insecure.expired-apis.com": .disableEvaluation
]
let sessionManager = SessionManager(
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
Make sure to keep a reference to the new SessionManager instance, otherwise your requests will all get cancelled when your sessionManager is deallocated.
These server trust policies will result in the following behavior:
test.example.com will always use certificate pinning with certificate chain and host validation enabled thus requiring the following criteria to be met to allow the TLS handshake to succeed:
Certificate chain MUST be valid.
Certificate chain MUST include one of the pinned certificates.
Challenge host MUST match the host in the certificate chain's leaf certificate.
insecure.expired-apis.com will never evaluate the certificate chain and will always allow the TLS handshake to succeed.
All other hosts will use the default evaluation provided by Apple.
Subclassing Server Trust Policy Manager
If you find yourself needing more flexible server trust policy matching behavior (i.e. wildcarded domains), then subclass the ServerTrustPolicyManager and override the serverTrustPolicyForHost method with your own custom implementation.
class CustomServerTrustPolicyManager: ServerTrustPolicyManager {
override func serverTrustPolicy(forHost host: String) -> ServerTrustPolicy? {
var policy: ServerTrustPolicy?
// Implement your custom domain matching behavior...
return policy
}
}
Validating the Host
The .performDefaultEvaluation, .pinCertificates and .pinPublicKeys server trust policies all take a validateHost parameter. Setting the value to true will cause the server trust evaluation to verify that hostname in the certificate matches the hostname of the challenge. If they do not match, evaluation will fail. A validateHost value of false will still evaluate the full certificate chain, but will not validate the hostname of the leaf certificate.
It is recommended that validateHost always be set to true in production environments.
Validating the Certificate Chain
Pinning certificates and public keys both have the option of validating the certificate chain using the validateCertificateChain parameter. By setting this value to true, the full certificate chain will be evaluated in addition to performing a byte equality check against the pinned certificates or public keys. A value of false will skip the certificate chain validation, but will still perform the byte equality check.
There are several cases where it may make sense to disable certificate chain validation. The most common use cases for disabling validation are self-signed and expired certificates. The evaluation would always fail in both of these cases, but the byte equality check will still ensure you are receiving the certificate you expect from the server.
It is recommended that validateCertificateChain always be set to true in production environments.
App Transport Security
With the addition of App Transport Security (ATS) in iOS 9, it is possible that using a custom ServerTrustPolicyManager with several ServerTrustPolicy objects will have no effect. If you continuously see CFNetwork SSLHandshake failed (-9806) errors, you have probably run into this problem. Apple's ATS system overrides the entire challenge system unless you configure the ATS settings in your app's plist to disable enough of it to allow your app to evaluate the server trust.
If you run into this problem (high probability with self-signed certificates), you can work around this issue by adding the following to your Info.plist.
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>example.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
<!-- Optional: Specify minimum TLS version -->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
</dict>
</dict>
</dict>
</dict>
Whether you need to set the NSExceptionRequiresForwardSecrecy to NO depends on whether your TLS connection is using an allowed cipher suite. In certain cases, it will need to be set to NO. The NSExceptionAllowsInsecureHTTPLoads MUST be set to YES in order to allow the SessionDelegate to receive challenge callbacks. Once the challenge callbacks are being called, the ServerTrustPolicyManager will take over the server trust evaluation. You may also need to specify the NSTemporaryExceptionMinimumTLSVersion if you're trying to connect to a host that only supports TLS versions less than 1.2.
It is recommended to always use valid certificates in production environments.
Using Self-Signed Certificates with Local Networking
If you are attempting to connect to a server running on your localhost, and you are using self-signed certificates, you will need to add the following to your Info.plist.
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
According to Apple documentation, setting NSAllowsLocalNetworking to YES allows loading of local resources without disabling ATS for the rest of your app.
Reference:-
https://github.com/Alamofire/Alamofire/blob/master/Documentation/AdvancedUsage.md
For implementation details refer the tests.
https://github.com/Alamofire/Alamofire/blob/master/Tests/TLSEvaluationTests.swift#L290-L450
SSL pinning using TrustKit with Alamofire. Here I have included API Manager class. This will help you solve using Alamofire with TrustKit.
class ApiManager: SessionDelegate{
var sessionManager: SessionManager?
override init(){
super.init()
initReachibility()
sessionManager = SessionManager.init(configuration: URLSessionConfiguration.ephemeral, delegate: self)
}
override func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
// Call into TrustKit here to do pinning validation
if TrustKit.sharedInstance().pinningValidator.handle(challenge, completionHandler: completionHandler) == false {
// TrustKit did not handle this challenge: perhaps it was not for server trust
// or the domain was not pinned. Fall back to the default behavior
completionHandler(.cancelAuthenticationChallenge, nil)
}
}
func makeRequestAlamofire(route:URL, method:HTTPMethod, autherized:Bool, parameter:Parameters,header:[String:String], callback: #escaping (APIResult<Data>) -> Void){
sessionManager?.request(route,method: method,parameters:parameter, encoding: JSONEncoding.default,headers:headers ).validate(statusCode: 200..<300)
.validate(contentType: ["application/json"]).responseData { response in
//Pin Validtion returner
guard response.error == nil else {
// Display Error Alert
print("Result Pinning validation failed for \(route.absoluteString)\n\n\(response.error.debugDescription)")
return
}
switch response.result {
case .success(let val):
print("Success")
case .failure(let error):
print("Faild")
}
}
}
}
For the full tutorial refer this link.
Alamofire has changed sll pinning code snipped with the new version(Alamofire 5.0).
You should use ServerTrustManager just like below,
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = timeoutIntervalForRequest
let trustManager = ServerTrustManager(evaluators: [
"dev.ehliyetcepte.com": PublicKeysTrustEvaluator(),
"uat.ehliyetcepte.com": DisabledEvaluator(),
"pilot.ehliyetcepte.com": DisabledEvaluator(),
"prod.ehliyetcepte.com": DisabledEvaluator()])
self.session = Session(startRequestsImmediately: true,
configuration: configuration,
delegate: self,
serverTrustManager: trustManager)
I would recommend using TrustKit. It is a dedicated library that works with everything base on NSURLSession, including Alamofire. Depending on your use case it may be as simple as adding a few values to Info.plist.
Certificate pinning, same as any security measure, is not something you should implement yourself, but you should use a proven library.
let serverTrustPolicies: [String: ServerTrustPolicy] = [
// or `pinPublicKeys`
"test.example.com": .pinCertificates(
certificates: ServerTrustPolicy.certificates(),
validateCertificateChain: true,
validateHost: true
),
"insecure.expired-apis.com": .disableEvaluation
]
let sessionManager = SessionManager(
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
I found this solution
let session = Session(delegate:CustomSessionDelegate())
session.request....
class CustomSessionDelegate: SessionDelegate {
private static let publicKeyHash = "your_public_key"
let rsa2048Asn1Header:[UInt8] = [
0x30, 0x82, 0x01, 0x22, 0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86,
0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00, 0x03, 0x82, 0x01, 0x0f, 0x00
]
override func urlSession(_ session: URLSession,
task: URLSessionTask,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
completionHandler(.cancelAuthenticationChallenge, nil);
return
}
if let serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0) {
// Server public key
guard let serverPublicKey = SecCertificateCopyKey(serverCertificate) else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
guard let serverPublicKeyData = SecKeyCopyExternalRepresentation(serverPublicKey, nil) else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let data:Data = serverPublicKeyData as Data
// Server Hash key
let serverHashKey = sha256(data: data)
// Local Hash Key
let publickKeyLocal = type(of: self).publicKeyHash
if (serverHashKey == publickKeyLocal) {
// Success! This is our server
print("Public key pinning is successfully completed")
completionHandler(.useCredential, URLCredential(trust:serverTrust))
return
} else {
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
}
}
private func sha256(data : Data) -> String {
var keyWithHeader = Data(rsa2048Asn1Header)
keyWithHeader.append(data)
var hash = [UInt8](repeating: 0, count: Int(CC_SHA256_DIGEST_LENGTH))
keyWithHeader.withUnsafeBytes {
_ = CC_SHA256($0, CC_LONG(keyWithHeader.count), &hash)
}
return Data(hash).base64EncodedString()
}
This my first project using swift. I am usin alamofire to connect the API. I have a local copy form the API I want to use for debugging - so I can set test data - because the remote API has already real data that I can't mess with.
The problem is I am getting the below error when I try to access https://localhost:8443/MyProject
Optional(Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “localhost” which could put your confidential information at risk." UserInfo=0x7fbeb8c61ff0 {NSURLErrorFailingURLPeerTrustErrorKey=, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorCodeKey=-9813, NSUnderlyingError=0x7fbeb8ea5c00 "The operation couldn’t be completed. (kCFErrorDomainCFNetwork error -1202.)", NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “localhost” which could put your confidential information at risk., NSErrorFailingURLKey=https://localhost:8443/myproject/api/loginUser.pdo, NSErrorFailingURLStringKey=https://localhost:8443/myproject/api/loginUser.pdo, _kCFStreamErrorDomainKey=3})
I have found many solution most of them for Objective-c as using setAllowsAnyHTTPSCertificate or using the delegate for Connection.
but I could not find an equevelent method for setAllowsAnyHTTPSCertificate in swift, and I not sure how to set the delegate to the connection while using alamofire.
any ideas what I need to do?
I know that setAllowsAnyHTTPSCertificate is private api and will cause the project to be rejected by Apple. I want to use it only while debugging then it will be removed before the publishing the project.
Thank you in advance.
You can easily override the default challenge behavior in Alamofire using the SessionDelegate override closures. Here is an example of how you can allow Alamofire to accept invalid certificates:
IMPORTANT: Please do not use this in any production code. Security is VERY important and this implementation completely disregards the security mechanisms in Alamofire. Use at your own risk!
let manager = Alamofire.Manager.sharedInstance
manager.delegate.sessionDidReceiveChallenge = { session, challenge in
var disposition: NSURLSessionAuthChallengeDisposition = .PerformDefaultHandling
var credential: NSURLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = NSURLSessionAuthChallengeDisposition.UseCredential
credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust)
} else {
if challenge.previousFailureCount > 0 {
disposition = .CancelAuthenticationChallenge
} else {
credential = manager.session.configuration.URLCredentialStorage?.defaultCredentialForProtectionSpace(challenge.protectionSpace)
if credential != nil {
disposition = .UseCredential
}
}
}
return (disposition, credential)
}
We (the Alamofire TC) are going to implement TLS pinning and several other features related to security in the Alamofire 1.3.0 release.
UPDATE
The Alamofire 1.3.0 release is out and adds MUCH better support for customizing server trust authentication challenges. For further info, please check out the Security section of the README.
Swift 3 version of #cnoon's code
manager.delegate.sessionDidReceiveChallenge = { session, challenge in
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust, let trust = challenge.protectionSpace.serverTrust {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: trust)
} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
} else {
credential = self.manager.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
return (disposition, credential)
}
Swift 3
In my case when I use swagger client library, I changed my code to test the local server like this:
open func createSessionManager() -> Alamofire.SessionManager {
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = buildHeaders()
let serverTrustPolicies: [String: ServerTrustPolicy] = ["localhost": .disableEvaluation]
return Alamofire.SessionManager(configuration: configuration, serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies))
}
Replacing with https to http of my base url. resolved the error.
Im a newb here but I have an app that is subject to MITM attacks.
After I bit of research it sounds like I need to do SSL Pining, i.e keep a copy of my servers public key/certificate so the can determine if the response came from it.
I have no idea how to do this, I am using AlamoFire in Swift to handle the networking.
Alamofire now implemented the certificate pinning.
The documentation you need is in the Readme.md
https://github.com/Alamofire/Alamofire
See their example implementation:
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"test.example.com": .PinCertificates(
certificates: ServerTrustPolicy.certificatesInBundle(),
validateCertificateChain: true,
validateHost: true
),
"insecure.expired-apis.com": .DisableEvaluation
]
let manager = Manager(
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
Alamofire 5.0 is now released. And ssl pinnig is changed. Look at the below code snipped.
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = timeoutIntervalForRequest
let trustManager = ServerTrustManager(evaluators: [
"prod.ehliyetcepte.com": PublicKeysTrustEvaluator(),
"dev.ehliyetcepte.com": DisabledEvaluator()])
self.session = Session(startRequestsImmediately: true,
configuration: configuration,
delegate: self,
serverTrustManager: trustManager)
As indicated here : https://github.com/Alamofire/Alamofire/issues/366
It is certainly something that the community is looking to support, but there's not a solid time frame around it yet. I'd say for the time being, you'll want to continue with AFNetworking, and keep a close eye on the Alamofire project for new features coming in.