Alamofire 4 FAILURE: Error Domain=NSURLErrorDomain Code=-999 "cancelled" - ios

Hi I am try to use Alamofire for my project but the error come out.
Here is my requesting code
//Google testing
Alamofire.request("http://google.com").responseString{
response in
debugPrint(response)
}.session.invalidateAndCancel()
Result]: FAILURE: Error Domain=NSURLErrorDomain Code=-999 "cancelled"
UserInfo={NSErrorFailingURLKey=http://google.com/,
NSLocalizedDescription=cancelled,
NSErrorFailingURLStringKey=http://google.com/}
//Own server testing
Alamofire.request("https://10.68.24.127:4533").responseString{
response in
debugPrint(response)
}.session.invalidateAndCancel()
same result
class NetworkManager {
var manager: SessionManager?
init() {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"https://10.68.24.127:4533" : .disableEvaluation
]
let configuration = URLSessionConfiguration.default
manager = Alamofire.SessionManager(
configuration: configuration,
serverTrustPolicyManager :ServerTrustPolicyManager(policies: serverTrustPolicies)
)
}
}
I set the NSAllowsArbitraryLoads to true and NSExceptionDomains.
Where is the problem?

There could be a lot of reason to why your requests "cancelled".
If you are facing that a request cancels immediately you can refer to this issue in Alamofire repository issues
jshier commented on Oct 10, 2016
An unexpected error -999 almost always means your SessionManager was
deallocated, cancelling any ongoing requests. I suggest you create a
singleton value for your custom SessionManager, or perhaps just
reevaluate if you really need one.
if you create a singleton value for your object it remains in memory and prevent from deallocate
and another thing that i avoid is to name your variables diffrent, a sessionManager is in Alamofire and your variable is also called sessionManager.
Alamofire 4.7 , Swift 4
import Alamofire
class Networking {
public static let sharedManager: SessionManager = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest=20
let manager = Alamofire.SessionManager(configuration: configuration, delegate: SessionManager.default.delegate)
return manager
}()
}
Alamofire 5.4.4 , Siwft 5.2
import Alamofire
class Networking {
static let APIManager: Session = {
let configuration = URLSessionConfiguration.default
configuration.timeoutIntervalForRequest = 20
let delegate = Session.default.delegate
let manager = Session.init(configuration: configuration,
delegate: delegate,
startRequestsImmediately: true,
cachedResponseHandler: nil)
return manager
}()
}

Check this documentation from Alamofire app-transport-security
try to add the following in your .plist file
<key>NSAppTransportSecurity</key><dict>
<key>NSExceptionDomains</key>
<dict>
<key>url.com</key>
<dict>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSIncludesSubdomains</key>
<true/>
</dict>
</dict>

Most likely you should be checking if there is an implementation of authentication challenge delegate method and check if its calling NSURLSessionAuthChallengeCancelAuthenticationChallenge.
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler

Related

SSL failing with Allow Arbitrary Loads = false

As per my requirement i should not make Allow Arbitrary Loads = true. So i set to false.
And i am allowing the trust certificate on my URLsession delegate.
My url : https://sample-app.10.names.io
code :
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
//Trust the certificate even if not valid
let urlCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, urlCredential)
}
My error :
Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?,
If I make Allow Arbitrary Loads = true, then only its working. But as per my requirement i should not change to true. Any suggestion would be helpful.
Thanks
Update:
I tried this below too :
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>https://sample-app.10.names.io</key>
<dict>
<!--Include to allow subdomains-->
<key>NSIncludesSubdomains</key>
<true/>
<!--Include to allow HTTP requests-->
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<!--Include to specify minimum TLS version-->
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
</dict>
You can use the terminal command
nscurl --ats-diagnostics --verbose https://sample-app.10.names.io
to test your server for ATS compliance.
Doing so reveals that your server only passes when perfect forward secrecy is disabled. It would seem that your server does not support ECDHE ciphers.
You can configure ATS to ignore the perfect forward secrecy requirement by specifying NSExceptionRequiresForwardSecrecy in your ATS configuration exception domains, but really you should patch your server to use newer TLS code. Otherwise it is vulnerable to replay MITM attacks.
If you are actively using Alamofire in your project, I suggest you use the built-in Session that can be configured to use your server trust certificate without much hassle. Here is some code on how it is setup in one of my projects.
class SessionManagerProvider {
// MARK: - State
let hosts: [String]
let disableEvaluation: Bool
// MARK: - Init
init(urls: [URL], disableEvaluation: Bool = false) {
hosts = urls.compactMap { $0.host }
self.disableEvaluation = disableEvaluation
}
// MARK: - Factory
func make() -> Session {
// Configure network client with SSL pinning.
let configuration = URLSessionConfiguration.af.default
configuration.timeoutIntervalForRequest = Constants.Backend.timeoutIntervalForRequest
configuration.timeoutIntervalForResource = Constants.Backend.timeoutIntervalForResource
// Allow more connections than API requests to avoid an issue, when URLSession starts to
// time-out requests when there are too many connections.
configuration.httpMaximumConnectionsPerHost = Constants.maxConcurrentApiCalls * 2
let policies = serverTrustPolicies(disableEvaluation: disableEvaluation)
let securityManager = ServerTrustManager(evaluators: policies)
let sessionManager = Session(configuration: configuration, serverTrustManager: securityManager)
return sessionManager
}
private func serverTrustPolicies(disableEvaluation: Bool) -> [String: ServerTrustEvaluating] {
var policies: [String: ServerTrustEvaluating] = [:]
for host in hosts {
if disableEvaluation {
policies[host] = DisabledTrustEvaluator()
} else {
policies[host] = PublicKeysTrustEvaluator(
performDefaultValidation: true,
validateHost: true
)
}
}
return policies
}
}

How to trust an endpoint iOS swift

I'm calling an endpoint that has a self-signed ssl certificate i have tried adding this in my info.plist
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
But i am still not able to access the endpoint i keep getting this
NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “endpoint” which could put your confidential information at risk.
You need to create a session manager and tell it to disable evaluation of the ssl in that server.
Something like this
static var manager: Alamofire.SessionManager = {
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"https://example.com": .disableEvaluation
]
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = Alamofire.SessionManager.defaultHTTPHeaders
let manager = Alamofire.SessionManager(
configuration: URLSessionConfiguration.default,
serverTrustPolicyManager: ServerTrustPolicyManager(policies: serverTrustPolicies)
)
return manager
}()
An then, instead of calling request in Alamofire, like this
Alamofire.request("https://example.com", method: .get…
you call it in your manager
manager.request("https://example.com"…

iOS App Extension: Requesting permissions

I'm writing an integration app harness that will test the functionality of our SDK in the real world and I'm planning to do so using iOS Action App extensions as subprocesses to initiate the Action Extensions as test cases. I followed the article below to accomplish that
https://ianmcdowell.net/blog/nsextension/
And so I created one action extension so far, which is to test starting the library manager of the SDK. It works on the simulator but when tested on the device the test fails, because it requires the Location Services to run the SDK.
I could override that service, but Xcode will generate this logs that complains about not accessing the NSUserDefaults. This adds 30 seconds of testing so I rather avoid that
2017-12-07 10:38:55.907542-0500 LibraryManagerTestExtension[2619:13235133] [User Defaults] Couldn't read values in CFPrefsPlistSource<0x1017041d0> (Domain: com.apple.Accessibility, User: kCFPreferencesCurrentUser, ByHost: No, Container: kCFPreferencesNoContainer, Contents Need Refresh: Yes): accessing preferences outside an application's container requires user-preference-read or file-read-data sandbox access, detaching from cfprefsd
2017-12-07 10:39:25.932663-0500 LibraryManagerTestExtension[2619:13235133] failed to open connection to AppleKeyStore
2017-12-07 10:39:25.933117-0500 LibraryManagerTestExtension[2619:13235133] Unexpected AppleKeyStore error: -536870212
2017-12-07 10:39:25.933951-0500 LibraryManagerTestExtension[2619:13235133] MKBDeviceUnlockedSinceBoot: MKBDeviceUnlockedSinceBoot fails with error(-1) for handle(0) with AKS error(-536870212)
2017-12-07 10:39:25.934237-0500 LibraryManagerTestExtension[2619:13235133] Attempting to create a background session before first device unlock!
2017-12-07 10:39:25.938302-0500 LibraryManagerTestExtension[2619:13235250] An error occurred on the xpc connection: Error Domain=NSCocoaErrorDomain Code=4099 "The connection to service named com.apple.nsurlsessiond was invalidated." UserInfo={NSDebugDescription=The connection to service named com.apple.nsurlsessiond was invalidated.}
Here are my codes. Hope you can provide me some guidance on what things I'm missing and needed to be added to get the permissions.
import Foundation
import IntegrationExtension
import VSTB
public class LibraryManagerTestExtension : NSObject, NSExtensionRequestHandling {
var extensionContext : NSExtensionContext!
var libraryManager : QPLibraryManager!
public func beginRequest(with context: NSExtensionContext) {
print("Beginning request with context: %#", context.description);
extensionContext = context
guard let configContent = getConfigContent() else {
extensionContext.cancelRequest(withError: QPError(code: -1, description: "ConfigContent is empty"))
return
}
startLibraryManager(configContent)
}
func getConfigContent() -> [String: Any]? {
//Get the config path, which is the 1st first of the input items
let inputItems = extensionContext.inputItems
print("Input Items: \(inputItems)")
assert(inputItems.count == 1)
let inputItem = inputItems.first as? NSExtensionItem
assert(inputItem?.attachments?.count == 1)
return inputItem?.attachments?.first as? [String: Any]
}
fileprivate func startLibraryManager(_ contentConfig: [String: Any]) {
let foo = isOpenAccessGranted()
print("Access: \(foo)")
let configuration = QPLibraryConfiguration(dictionary: contentConfig)
//Disable location services by overriding it with custom values. This adds 30 seconds of testing though, which needs to be avoided
configuration?.setStartupLibraryConfigurationValue(0, for: .IOS_LOCATION_MANAGER_MODE)
let userLocation : [String : Any] = ["country" : "IN",
"territory" : "",
"city" : "Chennai",
"latitude" : 0,
"longitude" : 0]
configuration?.setRuntimeLibraryConfigurationValue(userLocation, for: .USER_LOCATION)
libraryManager = QPLibraryManager()
libraryManager.delegate = self
libraryManager.start(with: configuration)
}
}
extension LibraryManagerTestExtension : QPLibraryManagerDelegate {
public func libraryManager(_ libraryManager: QPLibraryManager!, didStartWith association: QPLibraryManagerAssociation!) {
//Handle Library Start
let item = NSExtensionItem()
extensionContext.completeRequest(returningItems: [item]) { (expired : Bool) in
print("Completed Request")
}
}
public func libraryManager(_ libraryManager: QPLibraryManager!, didFailToStartWith association: QPLibraryManagerAssociation!, error: QPError!) {
//Handle Library failed
extensionContext.cancelRequest(withError: error)
}
}
Here's the .plist file. Note that the action extension is a non-ui extensions that are manually triggered by the app. App groups are also enabled on both app and extension
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>RequestOpenAccess</key>
<true/>
<key>NSExtensionActivationRule</key>
<string>FALSEPREDICATE</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.app.non-ui-extension</string>
<key>NSExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).LibraryManagerTestExtension</string>
</dict>

Certificate Invalid Issue with Alamofire 4.0

I am trying to consume web services for my iOS app over https. The web server uses a self signed certificate.
When consuming the web service, I get the error “certificate is Invalid”.
FAILURE: Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “portal” which could put your confidential information at risk."
I know the best practise is to fix this at the server side to enable a trusted root CA. But as this is a temporary development environment, we are using a self signed certificate.
Since this is ATS issue, I have edited ATS in my info.plist as below.
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>devportal</key>
<dict>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
</plist>
As the NSException domains doesn’t work with IP and port number, I have created a host entry in my etc/hosts file for the web server IP and consuming it like https://devportal:8443/rest/login instead of consuming it as https://192.22.xx.xxx:8443/rest/login
I have followed alamofire documentation on server trust policies, edited ATS to allow exception domains but nothing worked out for me. I have spent over 3 days on this issue. Am I missing something? Does anybody faced a similar issue? Is there any solution for this? Thanks in advance
I am using almofire 4.0, Xcode 8.0. Below is my code.
class LoginService{
private static var Manager: Alamofire.SessionManager = {
let pathToCert = Bundle.main.path(forResource: "192.22.xx.xxx", ofType: "crt") // Downloaded this certificate and have added to my bundle
let localCertificate:NSData = NSData(contentsOfFile: pathToCert!)!
// Create the server trust policies
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"192.22.xx.xxx": .pinCertificates(
certificates: [SecCertificateCreateWithData(nil, localCertificate)!],
validateCertificateChain: true,
validateHost: true
),
"devportal:8443": .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
}()
/**
Calls the Login Web Service to authenticate the user
*/
public func login(username:String, password: String){
let parameters = [
"username": "TEST",
"password": "PASSWORD",
]
let header: HTTPHeaders = ["Accept": "application/json"]
LoginService.Manager.request("https://devportal:8443/rest/login", method: .post, parameters: parameters, encoding: JSONEncoding(options: []),headers :header).responseJSON { response in
debugPrint(response)
if let json = response.result.value {
print("JSON: \(json)")
}
}
}
}
I modified my code like below and it worked. I referred Swift: How to Make Https Request Using Server SSL Certificate for fixing this issue.
class LoginService{
private static var Manager: Alamofire.SessionManager = {
// Create the server trust policies
let serverTrustPolicies: [String: ServerTrustPolicy] = [
"devportal:8443": .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
}()
/**
Calls the Login Web Service to authenticate the user
*/
public func login(username:String, password: String){
// Handle Authentication challenge
let delegate: Alamofire.SessionDelegate = LoginService.Manager.delegate
delegate.sessionDidReceiveChallenge = { session, challenge in
var disposition: URLSession.AuthChallengeDisposition = .performDefaultHandling
var credential: URLCredential?
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
disposition = URLSession.AuthChallengeDisposition.useCredential
credential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
} else {
if challenge.previousFailureCount > 0 {
disposition = .cancelAuthenticationChallenge
} else {
credential = LoginService.Manager.session.configuration.urlCredentialStorage?.defaultCredential(for: challenge.protectionSpace)
if credential != nil {
disposition = .useCredential
}
}
}
return (disposition, credential)
}
//Web service Request
let parameters = [
"username": "TEST",
"password": "PASSWORD",
]
let header: HTTPHeaders = ["Accept": "application/json"]
LoginService.Manager.request("https://devportal:8443/rest/login", method: .post, parameters: parameters, encoding: JSONEncoding(options: []),headers :header).responseJSON { response in
debugPrint(response)
if let json = response.result.value {
print("JSON: \(json)")
}
}
}
}
You should also configure your plist as below
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSExceptionDomains</key>
<dict>
<key>devportal</key>
<dict>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.2</string>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSExceptionRequiresForwardSecrecy</key>
<false/>
<key>NSExceptionAllowsInsecureHTTPLoads</key>
<true/>
</dict>
</dict>
<key>NSAllowsArbitraryLoads</key>
<false/>
</dict>
</plist>
Do not enter IP or port numbers in your NSExceptiondomains. It won't
work. If you are trying to connect to a web server with IP address,
map the IP address to a domain by adding a host entry in etc/hosts
file in your mac and then use the domain name in NSExceptionDomains
IMPORTANT: Do not use this code in production as this puts your users
information at risk, by bypassing auth challenge.
Not suggesting for production use-cases
//Use this manager class
class APIManager {
static var Manager: Alamofire.Session = {
let manager = ServerTrustManager(evaluators: ["your endpoint": DisabledTrustEvaluator()])
let session = Session(serverTrustManager: manager)
return session
}()
}
//Call APIs using this manager
APIManager.Manager.request("API")

IOS 9 Sending Post Request, Blocked by ATS, Bypass is not working

I'm trying to bypass Application Transport Security(ATS), its a new feature of IOS 9 and Xcode 7. However, I tried the info.plist bypass and I am still having problems. I tried the exact same code in Xcode 6 and the request does get sent successfully, so the request should be correct. This could just be a bug on the new Xcode but I was wondering if anyone else ran into the same issue. I'm pretty sure I'm following proper documentation: https://developer.apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/index.html#//apple_ref/doc/uid/TP40016240
Info.plist(not complete, just part on ATS)
<key>NSAppTransportSecurity</key>
<dict>
<key>NSExceptionDomains</key>
<dict/>
<key>NSAllowsArbitraryLoads</key>
<true/>
<key>http://127.0.0.1:5000</key>
<dict>
<key>NSIncludesSubdomains</key>
<true/>
<key>NSTemporaryExceptionAllowsInsecureHTTPLoads</key>
<true/>
<key>NSTemporaryExceptionMinimumTLSVersion</key>
<string>TLSv1.1</string>
</dict>
</dict>
Request:
let postData = NSMutableData(data: "username=bobbyz".dataUsingEncoding(NSUTF8StringEncoding)!)
postData.appendData("&password=form".dataUsingEncoding(NSUTF8StringEncoding)!)
let request = NSMutableURLRequest(URL: NSURL(string: "http://127.0.0.1:5000/register")!,
cachePolicy: .UseProtocolCachePolicy,
timeoutInterval: 10.0)
request.HTTPMethod = "POST"
request.HTTPBody = postData
let session = NSURLSession.sharedSession()
let dataTask = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
if (error != nil) {
print(error)
} else {
let httpResponse = response as? NSHTTPURLResponse
print(httpResponse)
}
})
dataTask.resume()
This just happened to me. Turns out I accidentally added the bypass information to my Unit Test Info.plist. Putting it in the correct Info.plist fixed the issue, as expected. I also used "localhost" instead of "127.0.0.1" and did not provide the port.
Using Xcode 7 Beta 4.

Resources