NSUrlSession proxy request fails with error code 310 (kCFErrorHTTPSProxyConnectionFailure) - objective-c

I'm trying to send a POST request to a specific URL through a proxy server. To test that the code I'm writing is working, I installed squidman on my machine and started a proxy server on port 33074. I tested the proxy server by changing the network settings to use the proxy when making HTTP/HTTPS requests and it's working ok.
Now I wrote the following code:
NSDictionary* proxyConfig = #{
(NSString*) kCFNetworkProxiesHTTPSEnable: #(1),
(NSString*) kCFNetworkProxiesHTTPSProxy: #"127.0.0.1",
(NSString*) kCFNetworkProxiesHTTPSPort: #"33074",
};
NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
[sessionConfig setTimeoutIntervalForRequest:60];
[sessionConfig setConnectionProxyDictionary:proxyConfig];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:[NSURL URLWithString:#"https://the-host-where-im-posting"]];
[request setHTTPMethod:#"POST"];
[request setValue:[[NSString alloc] initWithFormat:#"%lu",length] forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:data];
NSURLSession *session = [self createHttpSession];
NSURLSessionDataTask* dataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
if(httpResponse.statusCode == 200){
//...
} else {
LOGERROR(#"Error: %#", error);
}
}
The problem is that the request is not successfull and the following error is logged:
Error: Error Domain=kCFErrorDomainCFNetwork Code=310 "(null)" UserInfo={_kCFStreamErrorCodeKey=-2096, _kCFStreamErrorDomainKey=4}
Additional Info:
1. It should be noted that the requests submits perfectly without setting the proxyConfig dictionary.
2. A similar error gets reported if I'm trying to submit the request using HTTP through the proxy (by correspondingly changing the keys in the proxyConfig dictionary) with a small diff in the Err Code and _kCFStreamErrorCodeKey: 306 instead of 310 and -2094 instead of -2096.
3. The process making the request runs as a daemon.
What exactly am I doing wrong ? What am I missing ?

I was setting the kCFNetworkProxiesHTTPSPort field from the proxyConfig dictionary to a value of type NSString. After carefully reading the documentation for it I observed the following:
Key for the port number associated with the HTTPS proxy; value is a CFNumber which is the port number.
The library was encountering an object that was not a CFNumber and was using the default port for HTTPS communication to connect to the proxy instead (443). That's bad error handling imho. It was essentially silently trying to make a request using a port that I was not aware of. Changing the proxyConfig dictionary to the following fixed the problem:
NSDictionary* proxyConfig = #{
(NSString*) kCFNetworkProxiesHTTPSEnable: #(1),
(NSString*) kCFNetworkProxiesHTTPSProxy: #"127.0.0.1",
(NSString*) kCFNetworkProxiesHTTPSPort: #(33074),
};

Related

Request NSURLSession with SSL .cert and .key files

I have to login via https in an Obj-C project. Everything is fine with all the url, the user, pass and the needed stuff. For identify, the server checks by ssl .cert and .key files. So far, so good, the files were uploaded to the server, and the connection made well by curl from terminal.
Here comes my problem.
Spend some days, read the available stuff here and there, but simply can't find any solution to send the ssl files with the request in Obj-C. (The server cannot accept p12)
Here's the curl:
curl -q -k --cert cert2048.crt --key key2048.key https://somesite.com/ -d "username=usrnm&password=psswrd"
Here's my Obj-C code so far:
-(void)connect {
NSURL *url = [NSURL URLWithString:[NSString stringWithFormat:#"https://somesite.com/"]];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestUseProtocolCachePolicy
timeoutInterval:60.0];
NSDictionary* bodyParameters = #{
#"username": #"usrnm",
#"password": #"psswrd"
};
[request setHTTPBody:[self httpBodyForParameters:bodyParameters]];
[request setHTTPMethod:#"POST"];
[request setValue:#"gzip, deflate" forHTTPHeaderField:#"Accept-Encoding"];
[request setValue:#"keep-alive" forHTTPHeaderField:#"Connection"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
NSURLSessionDataTask *postDataTask = [session dataTaskWithRequest:request completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
NSError *jsonError;
NSDictionary *jsonResponse = [NSJSONSerialization JSONObjectWithData:data options:0 error:&jsonError];
NSLog(#"%#", jsonResponse);
}];
[postDataTask resume];
}
(httpBodyForParameters simply sets up the request body)
The connection establish, everything passes, the jsonResponse holds response as expected (well, the error message about authentication failure, in regular format). But I simply can't find a way to send the ssl files as in the curl line. Sadly, https authentication is far beyond my knowledge. I'm stucked. Every help appreciated.
Thank you,
Sz
That curl command is using the cert and key as a client certificate for authentication, not sending them as files. It's actually part of the authentication handshake.
The code for importing PKCS data and using it in response to a client certificate challenge is fairly involved, so rather than try to explain it all off the top of my head, I'm going to point you to another Stack Overflow question and answer that contain pretty extensive code snippets.
Creating a SecCertificateRef for NSURLConnection Authentication Challenge
However, be aware that some of the code in that link is not quite correct. For the protection spaces that are not handled, you should use default handling, not cancel the challenge.

Network requests are failing - NSURLSession - App Sandbox - Xcode 9

I have a simple GET request that I am trying to run in my macOS application. However I keep getting the following error:
A server with the specified hostname could not be found.
The URL I am trying to download JSON data from is:
https://suggestqueries.google.com/complete/search?client=safari&q=mercedes
If I test it in my browser or in an online API tester website (such as Hurl.it), the request works fine. A JSON file is then downloaded automatically.
However running the request via my macOS app does not work. Here is my code:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"https://suggestqueries.google.com/complete/search?client=safari&q=mercedes"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
[request setHTTPMethod:#"GET"];
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
NSURLSession *session = [NSURLSession sessionWithConfiguration:configuration delegate:self delegateQueue:nil];
[[session dataTaskWithRequest:request completionHandler:^(NSData * _Nullable data, NSURLResponse * _Nullable response, NSError * _Nullable error) {
NSLog(#"Data: %#", data);
NSLog(#"Response: %#", response);
NSLog(#"Error: %#", error);
}] resume];
Here is the full error log:
dnssd_clientstub ConnectToServer: connect()-> No of tries: 1
dnssd_clientstub ConnectToServer: connect()-> No of tries: 2
dnssd_clientstub ConnectToServer: connect()-> No of tries: 3
dnssd_clientstub ConnectToServer: connect() failed
path:/var/run/mDNSResponder Socket:6 Err:-1 Errno:1 Operation not
permitted 2017-10-27 09:58:31.610493+0100 search suggestions
[] nw_resolver_create_dns_service_locked
DNSServiceCreateDelegateConnection failed: ServiceNotRunning(-65563)
TIC TCP Conn Failed [1:0x600000164080]: 10:-72000 Err(-65563)
Task <12212C3B-8606-49C2-BD72-AEBD575DB638>.<1> HTTP load failed
(error code: -1003 [10:-72000])
Task
<12212C3B-8606-49C2-BD72-AEBD575DB638>.<1> finished with error - code:
-1003
Data: (null)
Response: (null)
Error: Error Domain=NSURLErrorDomain Code=-1003 "A server with the specified
hostname could not be found." UserInfo={NSUnderlyingError=0x60400004fa20 {Error
Domain=kCFErrorDomainCFNetwork Code=-1003 "(null)"
UserInfo={_kCFStreamErrorCodeKey=-72000,
_kCFStreamErrorDomainKey=10}}, NSErrorFailingURLStringKey=https://www.suggestqueries.google.com/complete/search?client=safari&q=mercedes,
NSErrorFailingURLKey=https://www.suggestqueries.google.com/complete/search?client=safari&q=mercedes,
_kCFStreamErrorDomainKey=10, _kCFStreamErrorCodeKey=-72000, NSLocalizedDescription=A server with the specified hostname could not
be found.}
What am I doing wrong? It's just a simple GET request, I don't understand why the data won't load.
I figured out what was wrong, even though I had set Allow Arbitrary Loads to YES, this is no longer enough to enable network requests.
There is a new setting in Xcode 9 called App Sandbox that can stop incoming/outgoing network connections too! I had to turn this setting off and then all network requests started to work.
In XCode 11.5 I had to check these two flags
You have to turn off the "App Sandbox".
Go to:
xcode Project-> Capabilities -> App SandBox
Please try below code
#property (nonatomic, strong) NSURLConnection *connection;
NSMutableString *urlString = [NSMutableString stringWithString:BASE_URL];
[urlString appendFormat:#"%#",apiName]; //apiName —> Webservice Name
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:urlString]];
[request setValue:#"gF!DeEfFrjqaAaD$gH#Mn#w(z" forHTTPHeaderField:#"PC-API-KEY"]; //Optional Parameter pass if required key
[request setValue:#"application/json" forHTTPHeaderField:#"Accept"];
[request setHTTPMethod:#"POST"]; //if your required Get than [request setHTTPMethod:#"GET"];
[request setValue:#"application/json" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:[parameterDict toJSON]];
[request setTimeoutInterval:45.0];
self.connection =[[NSURLConnection alloc] initWithRequest:request delegate:self];
if (self.connection) {
[[UIApplication sharedApplication] setNetworkActivityIndicatorVisible:YES];
self.downLoadedData = [NSMutableData data];
}

overwrite JSON data on server

My app is pulling down a JSON file from my server (GoDaddy) and parsing it into an array. The user is then able to add a new record to the existing JSON. I'm running into issues when I try to post all of the data back to server.
To be clear, I'm trying to overwrite the existing file that's on the server with the new data. I know FTP isn't the direction I need to be going, but I'm not using a database of any kind. Just files on the server.
I've made sure the JSON is valid and I've called GoDaddy to make sure I don't need to change any permissions. I've used this directory for FTP before.
Could it be that my REMOTE_PATH_TO_ROOT_APP_FOLDER uses FTP credentials? If so, what URL do I need to use?
- (void)postExamplesDictionaryToServer:(nonnull NSString *)dName
:(nonnull NSString *)rName
:(nonnull NSData *)jsonData
{
NSString *userFileURLString = [NSString stringWithFormat:#"%#/%#/%#TESTER.json", REMOTE_PATH_TO_ROOT_APP_FOLDER, dName, rName];
NSURL *userFileURL = [NSURL URLWithString:userFileURLString];
NSString *postLength = [NSString stringWithFormat:#"%lu", (unsigned long)[jsonData length]];
NSMutableURLRequest *request = [[NSMutableURLRequest alloc] init];
[request setURL:userFileURL];
[request setHTTPMethod:#"POST"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/json; charset=UTF-8" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:jsonData];
[[self.session uploadTaskWithRequest:request fromData:jsonData completionHandler:^(NSData *data, NSURLResponse *response, NSError *error)
{
NSString *requestReply = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"Error: %#", error);
}] resume];
}
EDIT: I logged the NSERROR passed to the completion handler and got this:
Error: (null)
BUT...If I change the URL to a file that doesn't already exist on the server, I get this:
Error: Error Domain=NSURLErrorDomain Code=-1008 "resource unavailable"
UserInfo={NSUnderlyingError=0x7fca8ad44280 {Error Domain=kCFErrorDomainCFNetwork Code=-1008 "(null)"},
NSErrorFailingURLStringKey=ftp://*****:*****#ftp.*****.com/public_html/app/******/File.json,
NSErrorFailingURLStringKey=ftp://*****:*****#ftp.*****.com/public_html/app/******/File.json, NSLocalizedDescription=resource unavailable}
Does this mean it's not attempting to do an upload task, only a request somehow?

AFNetworking upload task with credentials (HTTP Basic Auth)

I need to upload video files from my app to a server. I tried doing so via [AFHTTPRequestOperationManager post:parameters:success:failure] but unfortunately kept getting request timeouts. I'm now trying something similar to Creating an Upload Task from the AF Docs.
I read on SO and the AF Docs about setSessionDidReceiveAuthenticationChallengeBlock: and tried to implement the whole upload malarky as follows:
__block ApiManager *myself = self;
// Construct the URL
NSString *strUrl = [NSString stringWithFormat:#"%#/%#", defaultUrl, [self getPathForEndpoint:endpoint]];
NSURL *URL = [NSURL URLWithString:strUrl];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
[request setHTTPMethod:#"POST"];
// Build a session manager
NSURLSessionConfiguration *configuration = [NSURLSessionConfiguration defaultSessionConfiguration];
AFURLSessionManager *manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:configuration];
// Set authentication handler
[manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *credential) {
*credential = myself.credentials;
return NSURLSessionAuthChallengeUseCredential;
}];
// Create the upload task
NSURLSessionUploadTask *uploadTask = [manager uploadTaskWithRequest:request fromFile:filePath progress:nil completionHandler:^(NSURLResponse *response, id responseObject, NSError *error) {
if (error) {
[myself endpoint:endpoint returnedFailure:error];
} else {
[myself endpoint:endpoint returnedSuccess:responseObject];
}
}];
// and run with it
[uploadTask resume];
The myself.credentials object has been set previously to have the correct username and password. Whenever this request fires, I get 401 unauthorised as a response. I tried putting NSLog(#"CHALLENGE") inside the challenge block above, but it never seems to get called, so AFNetworking isn't giving me a way to supply credentials. I know that this works perfectly well on the server side because I've tested it with Postman.
How can I get AFNetworking to let me supply credentials for HTTP Basic Auth with this upload task?
I'm not sure about AFNetworking's setSessionDidReceiveAuthenticationChallengeBlock, but as long as you have an NSMutableURLRequest you may set the Authorization HTTP Header directly on the request object:
[request setValue:base64AuthorizationString forHTTPHeaderField:#"Authorization"];
Keep in mind that the value must be base64 representation of the username:password string.
Otherwise, for GET, POST, etc. requests on a session manager, you may set the credentials on the request serializer used by the session manager. See:
[AFHTTPRequestSerializer setAuthorizationHeaderFieldWithUsername:password:]
[AFHTTPRequestSerializer clearAuthorizationHeader]

Error in GET on https ?? Can't set NSLocalizedRecoverySuggestion

NSURL *url = [NSURL URLWithString:#"https://myUrlString.com"];
NSURLRequest *request = [NSURLRequest requestWithURL:url];
NSURLResponse *responseurl;
NSError *err;
NSData *data = [NSURLConnection sendSynchronousRequest:request returningResponse:&responseurl error:&err];
NSLog(#"data length:%u", data.length);
NSLog(#"response:%# , error:%#", responseurl, err);
And the response i got is :-
data length:0
response:(null) ,
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 “myUrlString.com” which could put your confidential information at risk." UserInfo=0x8742d70 {NSErrorFailingURLStringKey=https:https://myUrlString.com,
NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?,
NSErrorFailingURLKey=https://myUrlString.com,
NSLocalizedDescription=The certificate for this server is invalid. You might be connecting to a server that is pretending to be “myUrlString.com” which could put your confidential information at risk., NSUnderlyingError=0x8745bf0 "The certificate for this server is invalid. You might be connecting to a server that is pretending to be “myUrlString.com” which could put your confidential information at risk.", NSURLErrorFailingURLPeerTrustErrorKey=}
You are using Https request so you should use ASIHTTPRequest or you may try this code
NSMutableURLRequest *request = [[[NSMutableURLRequest alloc] init] autorelease];
[request setURL:[NSURL URLWithString:#"https:yoururl"]];
[request setHTTPMethod:#"GET"];
[request setValue:postLength forHTTPHeaderField:#"Content-Length"];
[request setValue:#"application/x-www-form-urlencoded" forHTTPHeaderField:#"Content-Type"];
[request setHTTPBody:getData];
See Technical Note TN2232 for a discussion of what to do to properly resolve HTTPS Server Trust issues, and more importantly, what not to do. The bottom line is that they encourage you to fix the server to resolve the trust issue, rather than working around it.
If you want to temporarily work around it (for example you're just doing some testing with some tool like Charles for which you're temporarily intercepting requests in order to perform diagnostics), you can use the private method to turn off this validation (note, if this code is in app submitted to App Store, they may reject it for using a private API) or you can respond to the NSURLConnection delegate methods.
Again, be very careful about shipping code that bypasses this important warning, but if you have to during the testing of your app, then one of the above methods can be useful.