How to Connect localhost (with invalid certificate) using Alamofire? - swift

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.

Related

Swift Switching from HTTPS to HTTP

This one's a weird one.
I have a little API server that does some stuff that I want my mobile app to talk to. It has DNS and SSL correct configured, I can reach it from my browser or postman no problem. Additionally I have set up a 301 redirect to HTTPS if anyone tries to approach it via HTTP.
I have a viewModel in swift thats calling out to this API with a pretty standard set up.
func getEndpointData() {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "mycoolApi.com"
urlComponents.path = "/v1/endpoint/\(model.endpoint.param)"
let url = urlComponents.url!
let session = URLSession(configuration: .default)
session.dataTaskPublisher(for: url)
.tryMap(){ element -> Data in
guard let httpResponse = element.response as? HTTPURLResponse,
httpResponse.statusCode == 200 else {
throw URLError(.badServerResponse)
}
return element.data
}
.decode(type: EndpointData.self, decoder: JSONDecoder())
.receive(on: RunLoop.main)
.sink(receiveCompletion: { print("recieved completion \($0)")},
receiveValue: { endpointData in
self.rawData = endpointData.myCoolProperty
})
.store(in: &cancellables)
}
Should be calling via HTTPS right?
Wrong.
This is the error I get:
recieved completion failure(Foundation.URLError(_nsError: Error Domain=NSURLErrorDomain
Code=-1022 "The resource could not be loaded because the App Transport Security policy
requires the use of a secure connection." UserInfo={NSLocalizedDescription=The resource could
not be loaded because the App Transport Security policy requires the use of a secure
connection., NSErrorFailingURLStringKey=http://mycoolApi.com/v1/endpoint/<Path_Param_ID>/,
NSErrorFailingURLKey=http://mycoolApi.com/v1/endpoint/<Path_Param_ID>/,
_NSURLErrorRelatedURLSessionTaskErrorKey=(
I feel like Im taking crazy pills.. Why is the simulator forcing something thats explicitly https to http?
I am not sure if this is the right way but I recently faced this issue on my live app there are two possible solution one you add "App Transport Security Settings" & "Allow Arbitrary Loads" in your info.plist
check out the image for info.plist
Second thing which worked for me https://www.mycoolApi.com so you need "www" after https://

cannot authenticate user for aws appsync with swift SDK

I am trying to connect to my AWS AppSync service from my Swift mobile app using the AWS Swift SDK but keep getting the following error:
Error occurred: (401 unauthorized) Did not receive a successful HTTP code.
I am using User Pools and have set everything up following the tutorial for swift. My question is, how do I incorporate the AppSync.json config file generated in the console in my request? That is not mentioned in the tutorial and may be the reason I cannot connect.
The json file looks like this:
{
"graphqlEndpoint": "my_endpoint_url",
"region": "us-east-1",
"authenticationType": "AMAZON_COGNITO_USER_POOLS",
"apiKey": null
}
At the moment I am using the following configuration:
// Set up Amazon Cognito credentials
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: CognitoIdentityRegion,
identityPoolId: CognitoIdentityPoolId, identityProviderManager: pool)
// You can choose your database location, accessible by SDK
let databaseURL = URL(fileURLWithPath:NSTemporaryDirectory()).appendingPathComponent(database_name)
do {
// Initialize the AWS AppSync configuration
let appSyncConfig = try AWSAppSyncClientConfiguration(url: AppSyncEndpointURL,
serviceRegion: AppSyncRegion,
credentialsProvider: credentialsProvider,
databaseURL:databaseURL)
appSyncClient = try AWSAppSyncClient(appSyncConfig: appSyncConfig)
// Set id as the cache key for objects
appSyncClient?.apolloClient?.cacheKeyForObject = { $0["id"] }
} catch {
print("Error initializing appsync client. \(error)")
}
EDIT #1
It turns out the example is using the API key method rather than user pools. So now I have changed the config to:
let appSyncConfig = try AWSAppSyncClientConfiguration(url: AppSyncEndpointURL, serviceRegion: AppSyncRegion, userPoolsAuthProvider: CognitoUserPoolsAuthProvider(pool: pool))
The problem is the message now is:
Use of unresolved identifier 'CognitoUserPoolsAuthProvider'
if I try:
let appSyncConfig = try AWSAppSyncClientConfiguration(url: AppSyncEndpointURL, serviceRegion: AppSyncRegion, userPoolsAuthProvider: AWSCognitoUserPoolsAuthProvider(pool: pool))
the error is:
'AWSCognitoUserPoolsAuthProvider' cannot be constructed because it has no accessible initializers
Not sure how to satisfy the userPoolsAuthProvider: argument in the config.
To address your specific issue, userPoolsAuthProvider needs to accept a class that extends the AWSCognitoUserPoolsAuthProvider protocol. So your instantiation would look something like this:
let appSyncConfig = try AWSAppSyncClientConfiguration(url: AppSyncEndpointURL,
serviceRegion: AppSyncRegion,
userPoolsAuthProvider: self,
databaseURL:databaseURL)
And then in the class in which you are creating the AppSyncClient, you would extend like this:
extension YourClass: AWSCognitoUserPoolsAuthProvider {
func getLatestAuthToken() -> String {
var token: String = ""
// pool is an instance of AWSCognitoIdentityUserPool
self.pool?.currentUser()?.getSession().continueOnSuccessWith(block: { (task) -> Any? in
token = task.result!.idToken!.tokenString
return nil
}).waitUnitFinished()
}
return token
}
Also I think the AppSync configuration by default looks for an awsconfiguration.json file in your project. You posted this a while back so possibly things have changed with AWS services and AppSync.
Hope this helps

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

About Alamofire version for use manager

I use this code.
var apiPath : String = "/api/list/"
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
configuration.timeoutIntervalForRequest = 60
let manager = Alamofire.Manager(configuration: configuration)
manager.session.configuration.HTTPAdditionalHeaders = ["_token" : self._token]
manager.request(.GET, self._host + self._url + apiPath, parameters: nil)
.responseSwiftyJSON ({ (request, response, resultJson, error) in
if (resultJson["Success"]) {
//get list success
} else {
println("request : \(request)")
println("response : \(response)")
println("resultJson : \(resultJson)")
println("error : \(error)")
}
})
And I got some problem
Alamofire version 1.2.1 : No Problem
Alamofire version 1.2.2 & 1.2.3 :
request : { URL: https://test.com/api/list/ }
response : nil
resultJson : null
error : Optional(Error Domain=NSURLErrorDomain Code=-999 "cancelled"
UserInfo=0x7feb92c434f0 {NSErrorFailingURLKey=https
://test.com/api/list/, NSLocalizedDescription=cancelled,
NSErrorFailingURLStringKey=http s://test.com/api/list/})
why response was nil and resultJson was null on version 1.2.2 and 1.2.3
Please help me what problem in this code..
I just encountered the same problem as you today after updating Alamofire from 1.2.1 to 1.2.3.
I discovered by adding "manager.session.invalidateAndCancel()" at the end and inside the responseJSON block fixed this issue. But what I cannot get my head around is that how can this line of code INSIDE the responseJSON block affects the responseJSON results.
Anyway I will just run with this fix until the Alamofire team fixes it or someone explains to me why this is happening.
I noticed that your API endpoint indicates to a secure connection:
httpS://test.com/api/list/
Just try it just in case, maybe it repeats your situation.
In my case, this was a typo in the API manager code. Which from the part can be said is connected with App Transport Security Settings.
Just changed the protected protocol from httpS:// to http:// and the error:
NSURLErrorDomain Code = -999 "cancelled"
was gone and it all worked!
+And also if you had a similar problem. Be sure to discuss this with a backend specialist who deals with the server or API configuration for your application. This means that the server does not have valid security certificates. Perhaps you still need a secure connection. Or this specialist can again configure everything back from http:// to httpS://, and I'm not sure (did not check) whether this will work again when in the code you are already using a non-secure http:// connection.

How do I accept a self-signed SSL certificate using iOS 7's NSURLSession and its family of delegate methods for development purposes?

I am developing an iPhone app. During development, I need to connect to a server that's using a self-signed SSL certificate. I'm pretty certain - (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler is my opportunity to write some exception code to allow this. However, I can't find any resources that tell me how to do this. I can see the following error in the log:
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
In addition to this, when I NSLog(#"error = %#", error); from within the above delegate method I get:
Error Domain=NSURLErrorDomain Code=-1202 "The certificate for
this server is invalid. You might be connecting to a server that is
pretending to be api.mydevelopmenturl.example which could put your
confidential information at risk." UserInfo=0x10cbdbcf0
{NSUnderlyingError=0x112ec9730 "The certificate for this server is
invalid. You might be connecting to a server that is pretending to be
api.mydevelopmenturl.example which could put your confidential information
at risk.", NSErrorFailingURLStringKey=https://api.mydevelopmenturl.example/posts,
NSErrorFailingURLKey=https://api.mydevelopmenturl.example/posts,
NSLocalizedRecoverySuggestion=Would you like to connect to the
server anyway?, NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x112e5a020>,
NSLocalizedDescription=The certificate for this server is invalid.
You might be connecting to a server that is pretending to be
api.mydevelopmenturl.example which could put your confidential
information at risk.}
Any ideas on how to resolve this issue? Please post code as I've read the conceptual docs and I don't understand them. Here's an example of one that's beyond me: https://developer.apple.com/library/content/technotes/tn2232/_index.html
This works for me:
NSURLSessionConfiguration *sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:sessionConfiguration delegate:self delegateQueue:Nil];
...
...
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler{
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]){
if([challenge.protectionSpace.host isEqualToString:#"mydomain.example"]){
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
}
}
Apple has a Technical Note 2232 which is quite informative and explains in detail HTTPS server trust evaluation.
In this case error -1202 in the NSURLErrorDomain domain is NSURLErrorServerCertificateUntrusted, which means that server trust evaluation has failed. You might also receive a variety of other errors; Appendix A: Common Server Trust Evaluation Errors lists the most common ones.
From the Technical Note:
In most cases the best way to resolve a server trust evaluation
failure is to fix the server. This has two benefits: it offers the
best security and it reduces the amount of code you have to write. The
remainder of this technote describes how you can diagnose server trust
evaluation failures and, if it's not possible to fix the server, how
you can customize server trust evaluation to allow your connection to
proceed without completely undermining the user's security.
The particular bit that is germane to this question is the section on NSURLSession server trust evaluation:
NSURLSession allows you to customize HTTPS server trust evaluation by
implementing the -URLSession:didReceiveChallenge:completionHandler:
delegate method. To customize HTTPS server trust evaluation, look for
a challenge whose protection space has an authentication method of
NSURLAuthenticationMethodServerTrust. For those challenges, resolve
them as described below. For other challenges, the ones that you don't
care about, call the completion handler block with the
NSURLSessionAuthChallengePerformDefaultHandling disposition and a NULL
credential.
When dealing with the NSURLAuthenticationMethodServerTrust
authentication challenge, you can get the trust object from the
challenge's protection space by calling the -serverTrust method. After
using the trust object to do your own custom HTTPS server trust
evaluation, you must resolve the challenge in one of two ways:
If you want to deny the connection, call the completion handler block with
the NSURLSessionAuthChallengeCancelAuthenticationChallenge disposition
and a NULL credential.
If you want to allow the connection, create a credential from your
trust object (using +[NSURLCredential credentialForTrust:]) and
call the completion handler block with that credential and the
NSURLSessionAuthChallengeUseCredential disposition.
The upshot of all this is that if you implement the following delegate method, you can override server trust for a particular server:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
if([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust])
{
if([challenge.protectionSpace.host
isEqualToString:#"domaintooverride.example"])
{
NSURLCredential *credential =
[NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}
else
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
}
}
Note that you have to handle both the case of the host matching the one you want to override and all other cases. If you don't handle the "all other cases" part, the behavior result is undefined.
Find a trusted SSL certificate authority online that's offering a free 90 day trial for new certificates. Install the certificate on your server. You now have 90 days to develop your app to a point where you can make a decision as to whether or not it's worth it to pay money to "renew" the certificate. This is the best answer for me since my decision to use the self-signed certificate was financially motivated and 90 days gives me enough time develop my app to a point where I can decide if it's worth it to spend money on an SSL certificate or not. This approach avoids having to deal with the security implications of running a codebase that is tweaked to accept self-signed certificates. Sweet! Yay for bootstrapping!
Do yourself a huge favour and don't.
Start by reading the paper The most dangerous code in the world: validating SSL certificates in non-browser software, especially section 10, "Breaking or disabling certificate validation". It specifically calls out a Cocoa-related blog that specifically describes how to do what you ask.
But don't. Disabling SSL certificate checking is like introducing a ticking time bomb into your app. Sometime, someday, it will accidentally be left enabled, and a build will get into the wild. And on that day, your users will be put at serious risk.
Instead you should use a certificate, signed with an intermediate cert that you can install and trust on that specific device, which will allow the SSL validation to succeed without endangering any other device than your own (and only then, temporarily).
For Swift 3.0 / 4
If you would just like to allow any kind of self-signed certificates, you could use the following approach, to implement an URLSessionDelegate.
Apple provides additional information of how to use the URLSessionDelegate for all kinds of authentication methods: https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/URLLoadingSystem/Articles/AuthenticationChallenges.html
At first implement the delegate method and assign an according delegate:
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
let task = urlSession.dataTask(with: urlRequest).resume()
Now implement the delegate's method
https://developer.apple.com/documentation/foundation/nsurlsessiondelegate/1409308-urlsession?language=objc
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.previousFailureCount == 0 else {
challenge.sender?.cancel(challenge)
// Inform the user that the user name and password are incorrect
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
// Within your authentication handler delegate method, you should check to see if the challenge protection space has an authentication type of NSURLAuthenticationMethodServerTrust
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
// and if so, obtain the serverTrust information from that protection space.
&& challenge.protectionSpace.serverTrust != nil
&& challenge.protectionSpace.host == "yourdomain.com" {
let proposedCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, proposedCredential)
}
}
Still, you could adapt the acceptance of any self-signed cert for your provided domain to match to a very specific one. Make sure you added this certificate before to your build targets bundle. I named it here "cert.cer"
func urlSession(_ session: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.previousFailureCount == 0 else {
challenge.sender?.cancel(challenge)
// Inform the user that the user name and password are incorrect
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
&& challenge.protectionSpace.serverTrust != nil
&& challenge.protectionSpace.host == "yourdomain.com" {
if let trust = challenge.protectionSpace.serverTrust,
let pem = Bundle.main.url(forResource:"cert", withExtension: "cer"),
let data = NSData(contentsOf: pem),
let cert = SecCertificateCreateWithData(nil, data) {
let certs = [cert]
SecTrustSetAnchorCertificates(trust, certs as CFArray)
var result=SecTrustResultType.invalid
if SecTrustEvaluate(trust,&result)==errSecSuccess {
if result==SecTrustResultType.proceed || result==SecTrustResultType.unspecified {
let proposedCredential = URLCredential(trust: trust)
completionHandler(.useCredential,proposedCredential)
return
}
}
}
}
completionHandler(.performDefaultHandling, nil)
}
Same as friherd's solution but in swift:
func URLSession(session: NSURLSession, task: NSURLSessionTask, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust{
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
}
}
just need add .cer to SecTrust and it pass on ATS
class NSURLSessionPinningDelegate: NSObject, URLSessionDelegate {
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Swift.Void) {
if (challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust) {
if let trust = challenge.protectionSpace.serverTrust,
let pem = Bundle.main.path(forResource: "https", ofType: "cer"),
let data = NSData(contentsOfFile: pem),
let cert = SecCertificateCreateWithData(nil, data) {
let certs = [cert]
SecTrustSetAnchorCertificates(trust, certs as CFArray)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: trust))
return
}
}
// Pinning failed
completionHandler(URLSession.AuthChallengeDisposition.cancelAuthenticationChallenge, nil)
}
}
update xcode 9
var result:(message:String, data:Data?) = (message: "Fail", data: nil)
var request = URLRequest(url: url)
let sessionDelegate = SessionDelegate()
let session = URLSession(configuration: .default, delegate: sessionDelegate, delegateQueue: nil)
let task = session.dataTask(with: request){(data, response, error) in
}
task.resume()
the delegate task
class SessionDelegate:NSObject, URLSessionDelegate
{
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
{
print(challenge.protectionSpace.host)
if(challenge.protectionSpace.host == "111.11.11.11")
{
let credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(URLSession.AuthChallengeDisposition.useCredential, credential)
}
}
}
}
Here is the solution that worked for me.
You need to accept the connection in through the connection's delegate including both messages:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Please note that with doing this, you're not checking the trustability of the certificate, so only the SSL encryption of the HTTPS connection is interesting, but the signing authority is not taking into consideration here, which can decrease security.
This Works fine for me to by pass self-signed :
Delegate : NSURLSessionDelegate
- (void)URLSession:(NSURLSession *)session **task**:(NSURLSessionTask *)task didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential *credential))completionHandler
{
completionHandler(NSURLSessionAuthChallengeUseCredential, [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust]);
}
Perhaps a better way is to provide the user with the opportunity to accept the certificate confirming (visually) that the URL is accurate for the service being accessed. For example, if the host is entered into some app setting, test at the user's entry and let the user decide right there.
Consider that this "user confirm" tactic is used by Safari, thus condoned by Apple, it would make sense that it would be employed logically for other apps.
Suggest digging into NSErrorRecoveryAttempting (am doing no myself)
http://apple.co/22Au1GR
Get the host confirmed, then take the individual URL exclusion route mentioned herewithin. Depending upon the implementation it may also make sense to store the host as an exclusion for future reference.
This seems like something Apple would have implemented by nature in Cocoa but as of yet, I have not found an 'easy button'. Would have liked a "kLetUserDecide" flag on something in NSURL or NSURLSession instead of everyone having to implement the delegate method as well as the NSErrorRecoveryAttempting protocol.