NSURLConnectionDelegate willSendRequestForAuthenticationChallenge won't get called on iOS 8 - ios

My server provide several authentication methods: NTLM and digest.
My iOS client won't handle the NTLM authentication, so I implement the connection:willSendRequestForAuthenticationChallenge: delegate to reject the NTLM, then use correct credential only for the digest authentication challenge.
Everything works fine on iOS 7 so far.
But on iOS 8, I found a weird behavior:
the connection:willSendRequestForAuthenticationChallenge: delegate won't be called at most time (95%)!!
I got this error instead:
Error: Error Domain=NSPOSIXErrorDomain Code=54 "The operation couldn’t be completed. Connection reset by peer"
UserInfo=0x16520fb0 {_kCFStreamErrorCodeKey=54,
NSErrorPeerAddressKey=<CFData 0x16682e40 [0x2f752440]>{length = 16, capacity = 16, bytes = 0x10020d7eac12780b0000000000000000},
NSErrorFailingURLKey=http://SERVER_IP:SERVER_PORT/Tunnel/Message.aspx,
NSErrorFailingURLStringKey=http://SERVER_IP:SERVER_PORT/Tunnel/Message.aspx,
_kCFStreamErrorDomainKey=1}
Only 5% time the delegate is correctly called and work as usual.
Below shows how I send my request to server and handle the authentication challenge:
- (void)postRequest
{
NSString *IP = SERVER_IP;
int port = SERVER_PORT;
NSString *url = [NSString stringWithFormat:#"http://%#:%d/Tunnel/Message.aspx", IP, port];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]];
[request setHTTPMethod:#"POST"];
[request setValue:#"application/xml" forHTTPHeaderField:#"Content-Type"];
NSString *xml = [NSString stringWithFormat:#"<?xml version=\"1.0\" encoding=\"UTF-8\"?><GetServerInfo></GetServerInfo>"];
[request setHTTPBody: [xml dataUsingEncoding:NSUTF8StringEncoding]];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
}
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"%#", challenge.protectionSpace);
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPDigest])
{
if ([challenge previousFailureCount] == 0)
{
[[challenge sender] useCredential:[NSURLCredential credentialWithUser:USERNAME
password:PASSWORD
persistence:NSURLCredentialPersistenceNone]
forAuthenticationChallenge:challenge];
}
else
{
[[challenge sender] continueWithoutCredentialForAuthenticationChallenge:challenge];
}
}
else
{
[[challenge sender] rejectProtectionSpaceAndContinueWithChallenge:challenge];
}
}
Those code work on iOS 7, willSendRequestForAuthenticationChallenge get called several times during the authentication challenge, but not even called any once on iOS 8!
Could this be a bug of iOS 8 or something changed since iOS 8?

This happened to me when I was updating my iOS 7 app to iOS 8. We were using Oracle SOA as the middle ware and sunddenly it stopped calling the delegate methods. Below worked for me in both iOS8 and iOS7. (With Xcode 6.1)
- (void) addBasicHTTPAuthenticationHeaders
{
NSString * wsUserName = #"userNameForWebService";
NSString * wsPassword = #"passwordForWebService";
NSString *authStr = [NSString stringWithFormat:#"%#:%#", wsUserName, wsPassword];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", [authData base64EncodedStringWithOptions:NSDataBase64EncodingEndLineWithCarriageReturn]];
[urlRequest setValue:authValue forHTTPHeaderField:#"Authorization"];
}

Related

iOS 9 ATS blocking HTTPS request to server with Self-Signed Certficate

This issue is all over Stack Overflow and I have spent the past 2 days trying countless combinations of ATP configurations and getting my app to work. I'm going to be thorough with my problem as it appears the tiniest thing can affect how to resolve this.
I have just recently set up an Ubuntu 14 server with SSL and TLS 1.2 enabled. On my server are my server-side scripts which my app depends on. In my app I use a NSMutableURLRequest to request my API from the server like so:
NSString * noteDataString = [NSString stringWithFormat:#"email=%#&password=%#&type=%#", companyEmail, companyPassword, #"login"];
NSData * postData = [noteDataString dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString * postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[postData length]];
NSMutableURLRequest * request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"https://mydomain.me:443/path/to/file.php"]];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:postData];
NSHTTPURLResponse * urlResponse = nil;
NSError * error = nil;
NSData * responseData = [NSURLConnection sendSynchronousRequest:request returningResponse:&urlResponse error:&error];
NSString * result = [[NSString alloc] initWithData:responseData encoding:NSUTF8StringEncoding];
NSLog(#"Response Code: %ld", (long)[urlResponse statusCode]);
When I copy the url into Chrome the correct PHP return is shown. When requesting from Xcode 7 on iOS 9 I get this error:
NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
I have used countless info.plist settings as seen across similar issues. I have tried disabling the need for forward secrecy, I have tried enabling arbitrary loads, I have tried using the exception domain mydomain.me and its subdomains. The only progress I achieve is an error code -9813 switching to -9802.
I am aware of adding delegate methods for NSURLRequest but these are never called and assumed redundant for solving this problem.
I built a lot of the app off a MAMP server using localhost and http and the requests worked when I enabled arbitrary loads then, so nothing wrong with my plist.
It's mind-boggling onto why I have a special case, and I knew Stack Overflow was the place for such situations!
Thanks, and I hope that this helps many more developers beyond me when solved.
Solution. Thank you everybody in the comments for pointing me in the right direction. The solution was to create an NSObject that handled NSURLRequests for this special case.
Credit to: https://www.cocoanetics.com/2010/12/nsurlconnection-with-self-signed-certificates/
The following is almost a direct copy from the tutorial in the link, but I figured it'd be easier to stay here.
So,
I had to create a new NSObject class with the following code:
BWWebService.h
#import <Foundation/Foundation.h>
#interface BWWebService : NSObject{
NSMutableData *receivedData;
NSURLConnection *connection;
NSStringEncoding encoding;
}
- (id)initWithURL:(NSURL *)url;
#end
BWWebService.m
#import "BWWebService.h"
#implementation BWWebService
- (id)initWithURL:(NSURL *)url{
if (self = [super init]){
NSURLRequest * request = [NSURLRequest requestWithURL:url];
connection = [NSURLConnection connectionWithRequest:request delegate:self];
[connection start];
}
return self;
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
// Every response could mean a redirect
receivedData = nil;
CFStringEncoding cfEncoding = CFStringConvertIANACharSetNameToEncoding((CFStringRef)
[response textEncodingName]);
encoding = CFStringConvertEncodingToNSStringEncoding(cfEncoding);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
if (!receivedData){
// No store yet, make one
receivedData = [[NSMutableData alloc] initWithData:data];
}else{
// Append to previous chunks
[receivedData appendData:data];
}
}
// All worked
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSString * xml = [[NSString alloc] initWithData:receivedData encoding:encoding];
NSLog(#"%#", xml);
}
// And error occurred
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Error retrieving data, %#", [error localizedDescription]);
}
// To deal with self-signed certificates
- (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]){
// we only trust our own domain
if ([challenge.protectionSpace.host isEqualToString:#"myuntrusteddomain.me"]){
NSURLCredential * credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
#end
And if that wasn't enough, to make the request I used the following in replacement of my original request:
NSURL * url = [NSURL URLWithString:#"https://myuntrusteddomain.me:443/path/to/script.php"];
BWWebService * webService;
webService = [[BWWebService alloc] initWithURL:url];
I know that this does not POST data like the original, but that comes later. I am sure it will be a matter of handling the POST in initWithURL.
Thanks everyone.
Edit: It appears this application of the solution only works with Allows Arbitrary Loads set to YES.
Open Xcode, Command-Shift-2, enter 9813, and you immediately find -9813 = errSSLNoRootCert, which was to be expected since your self signed certificate has no root certificate, while -9802 = errSSLFatalAlert (you really buggered it up).
The problem seems to be that for security reasons, some software doesn't like self signed certificates. This can often be fixed by creating and installing your own root certificate, and having a certificate signed by your own root certificate.

NSURL POST with XSS vulnerability for iOS

I ran into a problem with my code after the company did a code scan.
The report showed that my code, where I try to do a web service POST request, has a vulnerability for XSS attacks.
I'm not very familiar with issues on security.
Can anyone point me at the right direction as to how to fix this security vulnerability?
Thanks alot.
My web service calls are to a CA trusted server, so I used:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
SecTrustResultType result;
SecTrustEvaluate(challenge.protectionSpace.serverTrust, & result);
if(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified) {
[challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge];
return;
to secure the connection.
The following code is where I compose my URL request.
Again, I don't have a lot of knowledge on the topic of security,
so any help would be appreciated!
- (BOOL)httpPostWithUrl:(NSString *)url headersAndValues:(NSDictionary *)headersAndValues delegate:(id)delegate
{
NSMutableString *bodyString = [[NSMutableString alloc] initWithString:#""];
for (NSString *key in [headersAndValues allKeys])
{
[bodyString appendString:[NSString stringWithFormat:#"%#=%#&", key, headersAndValues[key]]];
}
NSMutableURLRequest *urlRequest = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:url]
cachePolicy:NSURLRequestReloadIgnoringLocalCacheData
timeoutInterval:20.0f];
[urlRequest setHTTPMethod:#"POST"];
[urlRequest setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"content-type"];
if (bodyString.length)
{
NSString *requestBody = [bodyString substringToIndex:bodyString.length-1];
NSData *requestData = [requestBody dataUsingEncoding:NSUTF8StringEncoding allowLossyConversion:YES];
[urlRequest setHTTPBody:requestData];
if (!_connectionRunning)
{
NSURLConnection *connection =[[NSURLConnection alloc] initWithRequest:request delegate:self];
return YES;
}
else
{
// error
}
}
}
return NO;
}
I think that xss in this line:
[bodyString appendString:[NSString stringWithFormat:#"%#=%#&", key, headersAndValues[key]]];
You need to check key and headersAndValues[key] on invalid characters.
NSString *checkedKey = [self alphanumericStringFromString:checkedKey];
+ (NSString *)alphanumericString { // NSString category
NSCharacterSet *charactersToRemove = [[NSCharacterSet alphanumericCharacterSet] invertedSet];
NSString *trimmedReplacement = [[self componentsSeparatedByCharactersInSet:charactersToRemove] componentsJoinedByString:#""];
return trimmedReplacement;
}
Hope it helps you.

detect if website login failed objective-c?

Just a quick one how do i detect if a login failed here is my code:
- (IBAction)btnTimetable:(id)sender {
NSString *user = _txtUsername.text;
NSString *pass = _txtPassword.text;
NSString *content = [NSString stringWithFormat:#"username=%#&password=%#", user, pass];
NSData *data =[content dataUsingEncoding:NSASCIIStringEncoding allowLossyConversion:YES];
NSString *postlenght=[NSString stringWithFormat:#"%lu",(unsigned long)[data length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://moodle.thomroth.ac.uk/login/index.php?mode=login"]];
[request setHTTPMethod:#"POST"];
[request setValue:postlenght forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Length"];
[request setHTTPBody:data];
//NSError *error=nil;
//NSURLResponse *response=nil;
//NSData *result=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
[_webView loadRequest:request];
[self performSelector:#selector(parseTimetable) withObject:nil afterDelay:3.0];
}
i dont even know where to start on this one is there a delegate method to detect such actions ?
As stated in the official Developer forums, UIWebView does not support authentication challenges in iOS. Please read here (requires developer account): UIWebView does not directly support authentication challenges
sendSynchronousRequest:returningResponse:error: should return nil and the status code of the returned response should equal 401 (Unauthorized):
[(NSHTTPURLRequest*)response statusCode] == 401
I would guess, the error parameter should be set to a corresponding error (please check this through printing it to the console).
If you use the delegate approach of NSURLConnection the situation is different:
When NSURLConnection receives a 401 (for example, the connection requires credentials for authentication, or a previous authentication attempt failed), it does invoke the delegate
- (void)connection:(NSURLConnection *)connection
willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
if implemented, otherwise it invokes these (now considered obsolete methods):
- (BOOL)connection:(NSURLConnection *)connection
canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
if implemented.
The official documentation provided more information: NSURLConnectionDelegate Protocol Reference.
You can cancel the authentication request, if you decide to do so. As a result, the connection can fail.
If the connection fails, connection:didFailWithError will be invoked.
If you really want to do it using UIWebView you could use it's delegate method and parse code answer of your website.
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSString *webSource = [_web stringByEvaluatingJavaScriptFromString:#"document.body.innerHTML"];
}

Basic auth iOS 6 - Not working

I am trying create a login screen which sends login info sharepoint server and i expect to be able to successfully login.
There are plenty of old examples and libraries which I am not able to use. But after spending hours I found this link to have the crux of all
http://transoceanic.blogspot.com/2011/10/objective-c-basic-http-authorization.html
My code looks like this now:
- (void) startLogin {
NSURL *url = [NSURL URLWithString:#"http://site-url.com"];
NSString *loginString =(NSMutableString*)[NSString stringWithFormat:#"%#:%#",usernameTextField.text,passwordTextField.text];
NSData *encodedLoginData=[loginString dataUsingEncoding:NSASCIIStringEncoding];
NSString *authHeader=[NSString stringWithFormat:#"Basic %#", [encodedLoginData base64Encoding]];
NSURLRequest *request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:3.0];
// [request setValue:authHeader forKey:#"Authorization"];
[request setValue:authHeader forHTTPHeaderField:#"Authorization"];
[request setHTTPMethod:#"POST"];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
There are three issues:
the commented out line-code doesn't give any error but it crashes on that line(while debugging)
on [request setValue:authHeader forHTTPHeaderField:#"Authorization"]; i am getting error "No visible interface for NSURLRequest declares selector setHTTPHeaderField"
Also, I am getting warning - unused variable "connection" in last line. I am not sure how this whole thing works and any simple example or correction is appreciated.
I would also like to know if there are any other simple methods for basic auth.
UPDATE: Delegate methods
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
// Access has failed two times...
if ([challenge previousFailureCount] > 1)
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Authentication Error"
message:#"Too many unsuccessul login attempts."
delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
else
{
// Answer the challenge
NSURLCredential *cred = [[NSURLCredential alloc] initWithUser:#"admin" password:#"password"
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
}
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"Connection success.");
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
{
NSLog(#"Connection failure.");
}
Change NSURLRequest to NSMutableURLRequest to access it's setValue:forHTTPHeaderField method, and add a HOST header as well if it's a shared web host.
At the end, you have to start the connection:
[connection start];
Also, make sure you've set up your NSURLConnectionDelegate delegate methods for the call backs.

NSURLConnection only authenticates on second attempt

I am trying to load a secure website in a UIWebView my basic approach is to create a NSURL, the n a NSURLRequest, then a NSURLConnection, then to load the NSURLRequest in the UIWebView. When the website is loaded I receive
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
I respond to the challenge sender with
- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
But after that I get nothing... it just hangs. I put in break points so I know that
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
is being called. If I wait till I am sure that the NSURLConnection is not going to complete then reload the view no authentication challenge is sent but the view will load. I do not have any control over the server. I am open to using AFNetworking, but only if necessary.
The full listing of source code is provided below:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge
{
NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults];
if ([challenge previousFailureCount] == 0)
{
NSString *username = #"username";
NSString *password = #"passsword";
NSURLCredential * cred = [NSURLCredential credentialWithUser:username
password:password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:cred forAuthenticationChallenge:challenge];
}
else
{
}
}
-(void)updateCard
{
NSURL * url = [NSURL URLWithString:#"https://ssl.letu.edu/applications/chapelattendance/attendance.html"];
NSURLRequest * request = [NSURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:50.0];
self.webView =[[UIWebView alloc] initWithFrame:self.bounds];
self.webView.delegate = self;
[self.webView loadRequest:request];
self.connection = [[ NSURLConnection alloc]initWithRequest:request delegate:self];
[self.connection start];
}
Where did I go wrong?
You need to first retrieve the "authentication method" the server is requesting for:
[[challenge protectionSpace] authenticationMethod]
These are the authentication methods (which are string constants) which the expression above returns:
NSURLAuthenticationMethodDefault
NSURLAuthenticationMethodHTTPBasic
NSURLAuthenticationMethodHTTPDigest
NSURLAuthenticationMethodHTMLForm
NSURLAuthenticationMethodNegotiate
NSURLAuthenticationMethodNTLM
NSURLAuthenticationMethodClientCertificate
NSURLAuthenticationMethodServerTrust
Then, you have these options:
If you want to provide the credentials for the given authentication method, you invoke
useCredential:forAuthenticationChallenge:
If you don't want to handle that authentication method yourself and want the system try
to authenticate, you may invoke performDefaultHandlingForAuthenticationChallenge:
which may then fail or not, depending whether the system is capable to handle that type
of authentication and whether it can find credentials in well known storages.
If you cant handle that authentication method -- say authentication method
NSURLAuthenticationMethodNTLM for example -- you can skip this protection
space and try another protection space if another one
exists in this authentication challenge. Then you may possibly get an
authentication method NSURLAuthenticationMethodHTTPBasic which you
are capable to handle.
In order to reject the current protection space you send method
rejectProtectionSpaceAndContinueWithChallenge: to the
authentication challenge sender. Then, NSURLConnection will send
once again willSendRequestForAuthenticationChallenge: to your
delegate with another protection space if any further exists.
You may try to continue without providing credentials at all.
Likely, the authentication will fail. You can try it through
sending message continueWithoutCredentialForAuthenticationChallenge:
to the authentication challenge sender.
And finally, you can cancel the request through canceling the
authentication challenge: send cancelAuthenticationChallenge: to
the authentication challenge sender.
Note: NSURLAuthenticationMethodHTTPBasic and NSURLAuthenticationMethodHTTPDigest authentication methods can be handled with the same NSURLCredential object created with +credentialWithUser:password:persistence:
If anyone comes along and has the same problem be sure I want to share the solution I found. Use AFNetworking.
Here is the revised code:
-(void)updateCard
{
if(!self.webView)
{
self.webView =[[UIWebView alloc] initWithFrame:self.bounds];
self.webView.delegate = self;
}
NSUserDefaults * userDefaults = [NSUserDefaults standardUserDefaults];
NSString *username = #"username";
NSString *password = #"password";
NSURL *url = [NSURL URLWithString:#"https://ssl.letu.edu/"];
AFHTTPClient *client = [[AFHTTPClient alloc] initWithBaseURL: url];
[client setAuthorizationHeaderWithUsername:username password:password];
NSMutableURLRequest *request = [client requestWithMethod:#"GET" path:#"applications/chapelattendance/attendance.html"
parameters:nil];
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request];
[operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject)
{
[self.webView loadRequest:request];
}
failure: ^(AFHTTPRequestOperation *operation, NSError *error)
{
NSLog(#"Could not load chapel attendance");
}];
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
[queue addOperation:operation];
}
AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:urlRequest];
operation.securityPolicy.allowInvalidCertificates = YES;
You need to send the username and password combination with the http header to authenticate the request while sending the same.
NSData *authData = [#"username:password" dataUsingEncoding:NSASCIIStringEncoding];
NSString *authorization = [NSString stringWithFormat:#"Basic %#", [authData base64Encoding]];
[mutableRequest addValue:authorization forHTTPHeaderField:#"Authorization"];

Resources