Recently I've been trying to connect to a test server of mine using a self-signed SSL certificate using NSURLSession.sharedSession().dataTaskWithRequest()
Now I get this error:
The certificate for this server is invalid. You might be connecting to a server that is pretending to be “...” which could put your confidential information at risk.
I've been searching the web for how to solve it.
All of them advised for using one of these:
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool
func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential!) -> Void)
Now, when I ran my app I noticed that only
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
ran.
This is my code:
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge)
{
// Trusting and not trusting connection to host: Self-signed certificate
challenge.sender.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust), forAuthenticationChallenge: challenge)
challenge.sender.continueWithoutCredentialForAuthenticationChallenge(challenge)
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
{
let trust = challenge.protectionSpace.serverTrust
let cred = NSURLCredential(forTrust: trust)
challenge.sender.useCredential(cred, forAuthenticationChallenge: challenge)
}
}
Yet I keep getting NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813).
Aha! Apparently I've had a let urlConnection = NSURLConnection(request: request, delegate: self) a bit before that I haven't noticed...
And I changed NSURLSession.sharedSession().dataTaskWithRequest()
to
let task = NSURLSession(configuration: NSURLSessionConfiguration.defaultSessionConfiguration(), delegate: self, delegateQueue: NSOperationQueue.mainQueue()).dataTaskWithRequest(request)
And left in place the delegate methods... that did it :)
Related
UPDATE
I've found that if I run the server and then the macOS app and leave it for 40 seconds (so the server has sent 40 "a" characters, one each second) then eventually the didReceive response delegate is called, and the didReceive data delegate then starts getting called with every new bit of data. This leads to logging like this in the console of the macOS app:
URLAuthenticationChallenge
Got response: <NSHTTPURLResponse: 0x6080000385c0> { URL: https://localhost:10443/sub } { status code: 200, headers {
"Content-Type" = "text/plain; charset=utf-8";
Date = "Thu, 03 Nov 2016 16:51:28 GMT";
Vary = "Accept-Encoding";
} }
Received data: Optional("{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n{\"Data\":\"a\"}\n")
Received data: Optional("{\"Data\":\"a\"}\n")
Received data: Optional("{\"Data\":\"a\"}\n")
Received data: Optional("{\"Data\":\"a\"}\n")
Received data: Optional("{\"Data\":\"a\"}\n")
Received data: Optional("{\"Data\":\"a\"}\n")
Received data: Optional("{\"Data\":\"a\"}\n")
...
which suggests that there's some buffering going on somewhere.
I've been testing out how URLSession works with HTTP/2 connections and I've run into some issues.
I've got an incredibly simple macOS app here: https://github.com/hamchapman/http2-barebones-mac-app although the whole code for it is basically just this:
class ViewController: NSViewController, URLSessionDelegate, URLSessionDataDelegate {
override func viewDidLoad() {
var urlComponents = URLComponents()
urlComponents.scheme = "https"
urlComponents.host = "localhost"
urlComponents.port = 10443
guard let url = urlComponents.url else {
print("Bad URL, try again")
return
}
var request = URLRequest(url: url.appendingPathComponent("/sub"))
request.httpMethod = "SUB"
request.timeoutInterval = REALLY_LONG_TIME
let sessionConfiguration = URLSessionConfiguration.ephemeral
sessionConfiguration.timeoutIntervalForResource = REALLY_LONG_TIME
sessionConfiguration.timeoutIntervalForRequest = REALLY_LONG_TIME
let session = Foundation.URLSession(configuration: sessionConfiguration, delegate: self, delegateQueue: nil)
let task: URLSessionDataTask = session.dataTask(with: request)
task.resume()
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: #escaping (URLSession.ResponseDisposition) -> Void) {
print("Got response: \(response)")
completionHandler(.allow)
}
public func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
let dataString = String(data: data, encoding: .utf8)
print("Received data: \(dataString)")
}
public func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
print("Error: \(error)")
}
// So it works with self-signed certs (we don't care about TLS etc in this example)
public func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard challenge.previousFailureCount == 0 else {
challenge.sender?.cancel(challenge)
completionHandler(.cancelAuthenticationChallenge, nil)
return
}
let allowAllCredential = URLCredential(trust: challenge.protectionSpace.serverTrust!)
completionHandler(.useCredential, allowAllCredential)
}
}
You can see that the http method being used is SUB. This is designed to be a method that you use if you want to subscribe to a given resource, which in my simple example is at path /sub. This should in theory be able to make use of HTTP/2 streaming to send new data over to the macOS app's connection, when the server has new data to send.
Here is the very basic (Go) app that I've been using as the server: https://github.com/hamchapman/http2-barebones-server (the readme has instructions on how to run it).
It's basically setup to accept a SUB request at /sub and send back a 200 OK immediately, and then every second it sends "a" as a bit of data.
The problem I'm facing is that as far as the Go server is concerned, the connection is being made fine. However, the URLSessionDelegate gets called with the expected URLAuthenticationChallenge (the server only allows encrypted connections), but the URLSessionDataDelegate methods that get called when a response is received and when data is received are never called.
You can verify that the server is working as expected by running it and then using the following curl command:
curl --http2 -k -v -X SUB https://localhost:10443/sub
(you might need to download the latest version of curl - see here for info: https://simonecarletti.com/blog/2016/01/http2-curl-macosx/)
I've also verified that the data is actually being received by the connection made in the macOS app (using Wireshark), but the delegate never gets called.
Does anyone know why this might be happening? Is data getting buffered somewhere? Is HTTP/2 support not fully there in URLSession?
It's because the first 512 bytes are buffered: https://forums.developer.apple.com/thread/64875
I'm new to Swift and NSURLConnection & NSURLSession. I have this code, to load a webpage and this works. But I got this warning that NSURLConnection was deprecated in iOS 9.0, and I have to use NSURLSession.
This is my code:
var authenticated:Bool = false
var urlConnection:NSURLConnection!
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
self.authenticated = false
self.urlConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
return true
}
// We use this method is to accept an untrusted site which unfortunately we need to do, as our PVM servers are self signed.
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool {
return (protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust)
}
func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.previousFailureCount == 0 {
self.authenticated = true
let credential: NSURLCredential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!
)
challenge.sender!.useCredential(credential, forAuthenticationChallenge: challenge)
}
else {
challenge.sender!.cancelAuthenticationChallenge(challenge)
}
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
// remake a webview call now that authentication has passed ok.
self.authenticated = true
web.loadRequest(request)
// Cancel the URL connection otherwise we double up (webview + url connection, same url = no good!)
urlConnection.cancel()
}
This works. Now I want to 'convert' it to NSURLSession, but I can't seem to manage. Can somebody help me with this? I'm quite sure it's not so difficult for someone who can code very well.
I've tried several times to change to NSURLSession, but every time I've got this error: NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813). And with NSURLConnection the problem is solved.
This is one of my attempts while using NSURLSession:
var authenticated:Bool = false
var urlSession:NSURLSession!
var urlSessionConfig:NSURLSessionConfiguration!
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
self.authenticated = false
self.urlSessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
self.urlSession = NSURLSession(configuration: self.urlSessionConfig, delegate: self, delegateQueue:NSOperationQueue.mainQueue())
let task = self.urlSession.dataTaskWithRequest(request){
(myData, myResponse, myError) -> Void in
if(myError == nil){
if(self.authenticated){
self.web.loadRequest(request)
}
}
}
task.resume()
return false
}
return true
}
func URLSession(session: NSURLSession, didReceiveChallenge challenge: NSURLAuthenticationChallenge, completionHandler: (NSURLSessionAuthChallengeDisposition, NSURLCredential?) -> Void) {
if(challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust){
if(challenge.protectionSpace.host == "mydomain.org"){
self.authenticated = true
let credential = NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!)
completionHandler(NSURLSessionAuthChallengeDisposition.UseCredential,credential);
}
}
}
You don't have to use NSURLSession. Odds are, NSURLConnection will be in a semi-supported state until the end of time, given how broadly it is used.
With that said, I'm only going to say this once, so listen very carefully. Do not, under any circumstances, publicly ship either version of this code as written. Self-signed certificates are okay, as long as you check them properly. This code is not doing that, which makes them no better than HTTP.
If the data has to be kept secret, either use a real certificate or add code to validate the self-signed certificates properly.
If it isn't even slightly important to keep it secret, just use HTTP.
This document explains how to properly implement modified TLS chain validation by adding trust for a specific certificate:
https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/OverridingSSLChainValidationCorrectly.html
BTW, you're not doing anything with challenges in other protection spaces or for other hostnames. If you never call the completion handler, then tasks that request any other type of authentication will just hang around forever, in limbo, waiting for you to decide how to handle the authentication request. This is probably not what you want, though I doubt it is causing your problem unless you're behind a proxy. Your NSURLConnection code accepted challenges within a single protection space, so it probably didn't experience that problem (as much).
With that said, I don't see why this is failing with that error code unless there's something else wrong with the cert beyond being self-signed. Oh, and there's a missing underscore in the method declaration. I don't think that is important anymore, but I'm not certain. Be sure your delegate method is actually getting called.
You might also try setting the CFNETWORK_DIAGNOSTICS environment variable to 1 (or more) and see if that provides any further insight.
I know this question is asked often, but I have implemented the solution found in the answers and I'm not having any luck. I am not a Swift developer, so I'm guessing I'm missing something.
Here is my NSURLConnectionDelegate methods:
func connection(connection: NSURLConnection, canAuthenticateAgainstProtectionSpace protectionSpace: NSURLProtectionSpace) -> Bool {
return protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
print("Second attempt resulted in authentication challenge")
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
print("We got a response.....")
}
func connection(connection: NSURLConnection, didFailWithError error: NSError) {
print("============ second attempt failed ==============")
print("\(error)")
}
The connection goes through a VPN that doesn't allow for DNS. We have to use the IP, which results in an invalid cert error. The Cert is correct, just not using the IP. I am trying to ignore the cert error but I am still getting "An SSL error has occurred and a secure connection to the server cannot be made"
My understanding is that
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
Should resolve this, but it doesn't seem to be working. The print line does run, so the delegate method is being called.
Thanks!
As Breek suggested, NSAppTransportSecurity in info.plist did the trick for iOS 9.
I have a self signed certificate in the VM I use to test my service. Using answers found in UIWebView to view self signed websites (No private api, not NSURLConnection) - is it possible? I was able to write functioning swift 2.0 code. Xcode 7 tells me that NSURLConnection is deprecated and I should use NSURLSession instead. None of my attempts to migrate this code succeeded, and none of the usual conversion scenarios described in other answers seem to apply.
If I create a new NSURLSession in order to handle the authentication challenge with my delegate methods, the other loads still happen on the sharedSession, and therefore fail.
var authRequest : NSURLRequest? = nil
var authenticated = false
var trustedDomains = [:] // set up as necessary
func webView(webView: UIWebView, shouldStartLoadWithRequest request: NSURLRequest, navigationType: UIWebViewNavigationType) -> Bool {
if !authenticated {
authRequest = request
let urlConnection: NSURLConnection = NSURLConnection(request: request, delegate: self)!
urlConnection.start()
return false
}
else if isWebContent(request.URL!) { // write your method for this
return true
}
return processData(request) // write your method for this
}
func connection(connection: NSURLConnection, willSendRequestForAuthenticationChallenge challenge: NSURLAuthenticationChallenge) {
if challenge.protectionSpace.authenticationMethod == NSURLAuthenticationMethodServerTrust {
let challengeHost = challenge.protectionSpace.host
if let _ = trustedDomains[challengeHost] {
challenge.sender!.useCredential(NSURLCredential(forTrust: challenge.protectionSpace.serverTrust!), forAuthenticationChallenge: challenge)
}
}
challenge.sender!.continueWithoutCredentialForAuthenticationChallenge(challenge)
}
func connection(connection: NSURLConnection, didReceiveResponse response: NSURLResponse) {
authenticated = true
connection.cancel()
webview!.loadRequest(authRequest!)
}
I was able to suppress the deprecation warning by adding this line before the first method. I would prefer a solution that replaces NSURLConnection but in the absence of that, this will have to do.
// hide NSURLConnection deprecation
#available(iOS, deprecated=9.0)
I am trying a sample project with NSURLConnection.
import Foundation
import UIKit
class loginVC: ViewController, UITextFieldDelegate, NSURLConnectionDelegate, NSURLConnectionDataDelegate{
var webData: NSMutableData!
override func viewDidLoad() {
super.viewDidLoad()
webData = NSMutableData()
callWebService("testdentist#gmail.com", Password:"1")
}
func callWebService(userName:NSString, Password:NSString){
var strURl: NSURL = NSURL .URLWithString("")
var request: NSMutableURLRequest = NSMutableURLRequest(URL: strURl, cachePolicy:NSURLRequestCachePolicy.ReloadIgnoringLocalCacheData, timeoutInterval:60.0)
var postString: NSString = ""
postString = postString.stringByAppendingFormat("username=%#&password=%#", userName,Password)
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
request.HTTPMethod = ""
var connection: NSURLConnection = NSURLConnection(request: request, delegate:self)
connection.start()
}
//NSURLConnection webservice
func connection(connection: NSURLConnection!, didReceiveResponse response: NSURLResponse!){
// webData.length = 0
println("response")
}
func connection(connection: NSURLConnection!, didReceiveData data: NSData!){
println(data.length)
webData .appendData(data)
}
func connection(connection: NSURLConnection, didFailWithError error: NSError!){
println("error in connection")
}
func connectionDidFinishLoading(connection: NSURLConnection!){
var response: NSString = NSString(data: webData, encoding: NSUTF8StringEncoding)
println("response:\(response)")
if response != ""{
}
}
func connection(connection: NSURLConnection, didReceiveAuthenticationChallenge challenge: NSURLAuthenticationChallenge!){
var authentication: NSURLCredential = NSURLCredential.credentialWithUser("", password:"", persistence: NSURLCredentialPersistence.ForSession)
}
}
It seems all delegates are getting called except didReceiveAuthenticationChallenge delegate method.What i am missing.any help will be appreciated.thanks in advance
For iOS 8 and above, you must implement connection(_:willSendRequestForAuthenticationChallenge:). connection:didReceiveAuthenticationChallenge: is not called in iOS8, only in older operating systems.
So, to provide authentication in iOS8 and above, implement the method above and in there you must invoke one of the challenge-responder methods (NSURLAuthenticationChallengeSender protocol):
useCredential:forAuthenticationChallenge:
continueWithoutCredentialForAuthenticationChallenge:
cancelAuthenticationChallenge:
performDefaultHandlingForAuthenticationChallenge:
rejectProtectionSpaceAndContinueWithChallenge:
didReceiveAuthenticationChallenge is a method on NSURLConnectionDelegate, whilst the rest of your methods (except didFailWithError) are all NSURLConnectionDataDelegate methods. Are you implementing both protocols in your controller? It would perhaps help if you posted all your class' code.
i don't think the question is in those terms for new Swift 3.0..
I have a similar problem, a code regularly working under xcode7/ios7-8-9/Swift 2.2
Migration have produced:
func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: (Foundation.URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
...
}
but does NOT work.
rewrite by hand:
func urlSession(_: URLSession,
didReceive challenge: URLAuthenticationChallenge,
completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
}
now it DOES work.
my two cents.