HTTP(S) POST : connectionDidFinishLoading gets called with empty data, possible trust issue - ios

I am trying to communicate with TSYS payment gateway which has an https server from my iOS application. My original code is given below.
-(void)postRequest:(NSString *)jsonBody{
NSData *requestData = [NSData dataWithBytes:[jsonBody UTF8String]
length:[jsonBody length]];
NSURL *url = [NSURL URLWithString:TSYS_URL];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc]initWithURL:url];
[request setHTTPMethod:#"POST"];
[request setValue:[NSString stringWithFormat:#"%d",[requestData length]]
forHTTPHeaderField:#"Content-Length"];
[request setValue:#"*/*" forHTTPHeaderField:#"Accept"];
[request setHTTPBody:requestData];
[[NSURLConnection alloc]initCustomURLWithRequest:request delegate:self ];
}
- (void)connection:(NSURLConnection *)connection
didReceiveResponse:(NSURLResponse *)response{
NSHTTPURLResponse* httpResponse = (NSHTTPURLResponse*)response;
int code = [httpResponse statusCode];
NSLog(#"http error code : %d", code);
[self.receivedData setLength:0];
}
- (void)connection:(NSURLConnection *)connection
didReceiveData:(NSData *)data {
[self.receivedData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(#"receivedData.length : %d", [self.receivedData length]);
connection = nil;
}
- (void)connection:(NSURLConnection *)connection
didFailWithError:(NSError *)error {
connection = nil;
}
The payment gateway url is
#define TSYS_URL #"https://stagegw.transnox.com/servlets/TransNox_API_Server"
And I get this in console
http error code : 404
receivedData.length : 0
When I saw the log, the first port of call was about HTTPS SSL authentication, so I wrote a android project that do POST by trusting all certificate (not ideal, I know), and hurray I was getting correct response. But when I removed the part that enables HttpClient to trust all certificates, I got empty response(in Android).
To make iOS trust all certificate, I added below code
- (BOOL)connection:(NSURLConnection *)connection
canAuthenticateAgainstProtectionSpace:
(NSURLProtectionSpace *)protectionSpace {
//This always returns true
return [protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge {
if([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust]){
NSURLCredential *credential = [NSURLCredential
credentialForTrust:challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential
forAuthenticationChallenge:challenge];
}
[challenge.sender
continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Still I get the same error.
Ok my questions are
First and foremost, why is this not working? I need to get this
working , so that I can move forward with project. Since my project
is in beginning state, I don't mind if the solution is to trust all
certificates. I somehow need to get server talking.
The type of authentication challenge I am getting is
NSURLAuthenticationMethodServerTrust. Ideally I should get the
server issued certificate and make my NSURLConnection object trust
it. How could I get the certificate file from server?

I got it working at last. In the end, it was not an issue with authentication. Along with the all the code above, I had to pass a header field "User-Agent" with the request. When I passed it I started to get response from the server.

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.

How to call https url in uiwebview?

I have an HTTPS url. It is loading in iPhone Safari, but didn't work in UIWebView.
Error is:
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9813)
How can I fix this problem?
I have explained below how to access https url in UIWebview it will help you clear the problem because it works for me.
Calling http is same as https url.
If, however, you're using a self-signed certificate, it's necessary to add some additional code.Because by default iOS will reject all untrusted https connections.
Restricting untrusted connections is very good default behaviour and any disabling of this is highly risky.So we will show an alert as we're going to bypass the default behaviour.
-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace;
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
These two above methods allows us to provide our own authentication mechanism for trusted connections
#import "ClassCon.h"
// For now, I've hard coded the IP address of my trusted server.
#define TRUSTED_HOST #"192.168.1.2"
#implementation ClassCon {
NSMutableData *contentData;
NSURLConnection *conn;
}
-(void) loadContent {
contentData = [NSMutableData data];
NSString *contentURL = #"our url";
conn = [[NSURLConnection alloc] initWithRequest:
[NSURLRequest requestWithURL:[NSURL URLWithString:contentURL]] delegate:self];
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
[contentData appendData:data];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(#"Bad: %#", [error description]);
ContentResponse *response = [[ContentResponse alloc]initWithRc:-999 andDescr:#"error" andAction:0];
conn = nil;
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSString *loadedContent = [[NSString alloc] initWithData:
contentData encoding:NSUTF8StringEncoding];
NSLog(#"Loaded content: %#",loadedContent);
}
// ------------ ByPass ssl starts ----------
-(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])) {
if ([challenge.protectionSpace.host isEqualToString:TRUSTED_HOST]) {
NSLog(#"Allowing bypass...");
NSURLCredential *credential = [NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential
forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
// -------------------ByPass ssl ends
#end
Hope this helps

how to wait the response from a http request in ios

Anybody knows how to wait the response of a http request? In my code, I am doing a http request to an url and then what i need to do, it is to check the http response in order to decide different treatment. I have something like this:
-(void)check{
[self fetchURL:#"http://something"];
if(response != nil || [response length] != 0){
do something....
}
else{
do something else....
}
}
-(void)fetchURL:(NSString *)urlWeb{
NSURL *url = [NSURL URLWithString:urlWeb];
NSURLRequest *request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
NSLog(#"INSIDE OF didReceiveResponse");
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"INSIDE OF didFailWithError");
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"INSIDE OF connectionDidFinishLoading");
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
// Append the new data to receivedData.
// receivedData is an instance variable declared elsewhere.
NSLog(#"inside of didReceiveData");
response = [NSString stringWithUTF8String:[data bytes]];
NSLog(#"response: %#", response);
}
I have been trying different options that I have seen around here, but i cant stop the execution of my code and wait for that answer...that means when I check the response of my http request, it always appears empty or with a nil reference...
any help how to figure out??
thanks
You can't evaluate the response value right after your 'fetchUrl' call, because your request is asynchronous, and your code goes on with the execution without waiting for the answer. You will receive the response value only in one of the delegate method, so there's the place where you should check the result.
If you really want to make a synchronous request you can use sendSynchronousRequest:returningResponse:error: like this
NSError *error;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error];
if(data){
//use data
}
else{
//check error domain and code
}
(See the Apple NSURLConnection Reference)
But keep in mind that your program will be stuck on this call till it receives a response or goes timeout.
Why don't you write this code:
if(response != nil || [response length] != 0){
do something....
}
else{
do something else....
}
In - (void)connectionDidFinishLoading:(NSURLConnection *)connection; method it wouldn't execute unless you have your complete proper response.
And Just for ado: Right to way to get data properly should be:
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
[_responseData appendData:data];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSString *string = [[NSString alloc] initWithData:_responseData encoding:NSUTF8StringEncoding];
if (string)
NSLog(#"string = %#", string);
}
Did you try checking the respons witin connectionDidFinishLoading: ?
That is the very delegate method which is called when the data was transferred successfully. Before that point in time, you should not expect any meaningful data.
Besides - didReceiveData should provide you with portions of data received in the meantime. Apparently you do not seem to process it nor just to store it for later evaluation (witin connectionDidFinishLoading)

HTTPS post request in IOS

Im trying to make https post request by using following code.
NSURL *url = [NSURL URLWithString:#"https://portkey.formspring.me/login/"];
//initialize a request from url
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[url standardizedURL]];
//set http method
[request setHTTPMethod:#"POST"];
//initialize a post data
NSDictionary *postDict = [NSDictionary dictionaryWithObjectsAndKeys:#"username", #"username",
#"password", #"password", nil];
NSError *error=nil;
NSData* jsonData = [NSJSONSerialization dataWithJSONObject:postDict
options:NSJSONWritingPrettyPrinted error:&error];
[request setValue:#"application/x-www-form-urlencoded; charset=utf-8" forHTTPHeaderField:#"Content-Type"];
//set post data of request
[request setHTTPBody:jsonData];
//initialize a connection from request
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
//start the connection
[connection start];
But im getting following response.
error Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “portkey.formspring.me” which could put your confidential information at risk." UserInfo=0x7564180 {NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, NSErrorFailingURLKey=https://portkey.formspring.me/login/, NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending >to be “portkey.formspring.me” which could put your confidential information at risk., NSUnderlyingError=0x7168a20 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “portkey.formspring.me” which could put your confidential information at risk.", NSURLErrorFailingURLPeerTrustErrorKey=}
Can anybody tell me how to make post request using NSURLConnection??
Thanks in advance.
The certificate for the site you're trying to connect to is untrusted (try visiting the link you posted in Chrome).
By default, iOS won't let you connect to site that presents an untrusted certificate. You can bypass this check if absolutely necessary - see this question: How to use NSURLConnection to connect with SSL for an untrusted cert?
However, it's going to much, much, much better to actually fix the certificate in question.
These Delegate Methods of NSURLConnection are Deprecated
-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
You can use -connection:willSendRequestForAuthenticationChallenge: instead of these 3 Deprecated Methods
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
{
[[challenge sender] useCredential:[NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]] forAuthenticationChallenge:challenge];
}
}

NSURLConnection - didReceiveData didn't get called - iOS & ARC

I'm currently experimenting with the twitter streaming api and i'm trying to get a stream with NSURLConnection. As it doesn't work with twitter, i simplified everything and tried to get some source-code out of google's website, but this doesn't work neither.
The connection starts and ends, but without calling the didReceiveData delegate. I'm sure i'm missing something. Hope you guy's can help me!
In the header: #interface ViewController : UIViewController <NSURLConnectionDataDelegate, NSURLConnectionDelegate, NSURLConnectionDownloadDelegate, NSURLAuthenticationChallengeSender>
And in the body:
- (void)viewDidLoad
{
[super viewDidLoad];
NSURLConnection *connection;
NSMutableURLRequest *request;
// Do any additional setup after loading the view, typically from a nib.
request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[request setHTTPMethod:#"GET"];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
[connection start];
}
- (void)connectionDidFinishDownloading:(NSURLConnection *)connection destinationURL:(NSURL *)destinationURL {
NSLog(#"Stream finished.");
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%#", dataString);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Connection failed!");
}
A couple of thoughts.
Your declaration of connectionDidFinishLoading doesn't look right. The standard NSURLConnectionDataDelegate method does not have a destinationURL parameter:
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"%s", __FUNCTION__);
}
Given the presence of NSURLAuthenticationChallengeSender, if you're expecting a challenge (which you won't get with Google web site) then you'd obviously handle it accordingly:
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
// For example, if you have a userid/password to use, see if this is the first
// challenge, and then tell NSURLConnection to try using those credentials, and if
// it failed a second time, you might just cancel the authentication challenge.
if (challenge.previousFailureCount == 0) {
NSURLCredential *credential = [NSURLCredential credentialWithUser:kUserID password:kPassword persistence:NSURLCredentialPersistenceForSession];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
} else {
[challenge.sender cancelAuthenticationChallenge:challenge];
}
}
By the way, you do not want to call start method when you use initWithRequest or connectionWithRequest. It's only needed if you do a simple initWithRequest:delegate:startImmediately: and instruct it to not startImmediately.
Anyway, I used the following and it works fine:
- (void)viewDidLoad
{
[super viewDidLoad];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:#"http://www.google.com"]];
[NSURLConnection connectionWithRequest:request delegate:self];
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection
{
NSLog(#"%s", __FUNCTION__);
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {
NSString *dataString = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
NSLog(#"%s: %#", __FUNCTION__, dataString);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"%s error=%#", __FUNCTION__, error);
}
Someone said here that it was their SOAP format
NSURLConnection delegate method: didReceiveData not called ...Why ?? (iPhone SDK)
This also may be what you are looking for
NSURLConnection didReceiveData not called
Your NSURLConnection local variable connection is going out of scope at the end of viewDidLoad. Add a property to your ViewController to hold the NSURLConnection variable in scope.

Resources