didReceiveAuthenticationChallenge called multiple times - ios

I'm doing a SSL pinning check for a website and I need to tap into the didReceiveAuthenticationChallenge in order to do so. However when I am debugging the application I noticed that the challenge is being called 3 times before finishing and afterwards I end up with NSURLErrorDomainCode=-999.
Small snippet of how my code looks:
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable))completionHandler
{
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
What I found odd is that on the third attempt the certificate on the bottom line of the snippet, is returning nil. But for the first 2 runs it is returning the same server certificate again.
Is this a normal behaviour from this method? The server only has one certificate installed that I am comparing against. I don't know if it might be relevant to add that I am using the React-Native-Webview solution for my application.

Related

How to remove NSURLCredential storage for https authentication in iOS

I need to remove the NSURLCredential storage everytime after response received for every https request. That is not happening in my case. The below method is executed only for the first time request, for next requests it is not called. When i relaunch the App, then it is called. I tried lot of suggestions in SO, but didn't help. How can i fix this?
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef trust = challenge.protectionSpace.serverTrust;
NSURLCredential *cred;
cred = [NSURLCredential credentialForTrust:trust];
[challenge.sender useCredential:cred forAuthenticationChallenge:challenge];
}
Set the credential's persistence property to NSURLCredentialPersistenceNone. That should ensure that the credential is never stored for future use (assuming you aren't still building for iOS 2).

The certificate for this server is invalid

I know that if I use following nsurlconnectiondelegate it will be fixed
– connection:willSendRequestForAuthenticationChallenge: –
connection:canAuthenticateAgainstProtectionSpace
But I am trying to use
sendAsynchronousRequest:queue:completionHandler:
So you don't get the callback. I looked into apple docs it say following
If authentication is required in order to download the request, the required credentials must be specified as part of the URL. If authentication fails, or credentials are missing, the connection will attempt to continue without credentials.
I could not figure out how to do that. When I looked up all I got is this private call
+(void)setAllowsAnyHTTPSCertificate:(BOOL)inAllow forHost:(NSString *)inHost;
Any idea how to do this?
Following is the error I get
The certificate for this server is invalid. You might be connecting to
a server that is pretending to be “example.com=0x8b34da0
{NSErrorFailingURLStringKey=https://example.com/test/,
NSLocalizedRecoverySuggestion=Would you like to connect to the server
anyway?, NSErrorFailingURLKey=https://example.com/test/,
NSLocalizedDescription=The certificate for this server is invalid. You
might be connecting to a server that is pretending to be “example.com”
which could put your confidential information at risk.,
NSUnderlyingError=0xa26c1c0 "The certificate for this server is
invalid. You might be connecting to a server that is pretending to be
“example.com” which could put your confidential information at risk.",
NSURLErrorFailingURLPeerTrustErrorKey=
The webserver which you are using is asking for Server Trust Authentication, you need to properly respond with the appropriate action. You need to implement connection:willSendRequestForAuthenticationChallenge: delegate method and use SecTrustRef to authenticate it.
More information can be found here:-
https://developer.apple.com/library/ios/technotes/tn2232/_index.html
This was my code to fix error:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
id<NSURLAuthenticationChallengeSender> sender = [challenge sender];
if ([[protectionSpace authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust])
{
SecTrustRef trust = [[challenge protectionSpace] serverTrust];
NSURLCredential *credential = [[NSURLCredential alloc] initWithTrust:trust];
[sender useCredential:credential forAuthenticationChallenge:challenge];
}
else
{
[sender performDefaultHandlingForAuthenticationChallenge:challenge];
}
}
Try this.
Initiate your session using custom session config as shown below:
let session = URLSession(configuration: URLSessionConfiguration.ephemeral,
delegate: self,
delegateQueue: nil)
Implement the following delegate callback method:
public func urlSession(_: URLSession, task _: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: #escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
guard let serverTrust = challenge.protectionSpace.serverTrust else {
return completionHandler(URLSession.AuthChallengeDisposition.useCredential, nil)
}
return completionHandler(URLSession.AuthChallengeDisposition.useCredential, URLCredential(trust: serverTrust))
}
If you are using AFNetworking, you can use this code:
(Just as a temp client-side solution!)
AFHTTPSessionManager * apiManager = [AFHTTPSessionManager initWithBaseURL:[NSURL URLWithString:baseURL];
AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeNone];
policy.allowInvalidCertificates = YES;
apiManager.securityPolicy = policy;
you can't fix it with the way you are trying
either drop to CFNetworking to allow bad certs
use NSConnection with a delegate and an undoc'd method
use the private API you found
all not good. CFNetwork would have to be OK for apple for now but the other 2 methods aren't even appstore-safe
Better get the server fixed. Thats the easiest and CLEANEST
This issue cannot be fixed with the way you are trying with blocks. you need to set delegates and implement the authentication challenge delegates to bypass the certificate validation.
Best solution is to either create a right certificate (make sure it is not self-signed) or change the protocol to HTTP if you are fine with it.
In my case, this error occurred due to my firewall blocked the required url. it's worked fine after removing firewall restrictions
That is a certificate error. you need to change your settings so that your program/os ignores the certificate, or add the url/certificate to a trusted list.
Sorry that is authentication, certificate is authentication. I took a look, and I found this article.
Not sure if it will resolve your issue, but it basically states, that they don't cover the case of connecting to a site with how a certificate in the documentation.
http://www.cocoanetics.com/2009/11/ignoring-certificate-errors-on-nsurlrequest/
In my case, this error occurred due to my system date. It was set as an old date, and the certificate is not effective from that old date. After correct the date, it works.

SSL Pinning on iOS

To improve my app's security and protect the user from MITM attacks I'm trying to do SSL pinning with my self-signed certificate following the content of this post.
So I'm using the following code to compare the certificate that I get from the server with the one that bundled in the app.
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
SecTrustRef serverTrust = challenge.protectionSpace.serverTrust;
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
NSData *remoteCertificateData = CFBridgingRelease(SecCertificateCopyData(certificate));
NSLog(#"Remote Certificate Data Length: %d",[remoteCertificateData length]);
NSString *cerPath = [[NSBundle mainBundle] pathForResource:#"apache" ofType:#"crt"];
NSData *localCertData = [NSData dataWithContentsOfFile:cerPath];
NSLog(#"Local Certificate Data Length: %d",[localCertData length]);
if ([remoteCertificateData isEqualToData:localCertData]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
The only things that are different between my code and the one in the blog post I linked are the name and the extension (.cer to .crt) for the resource representing my certificate and the two NSLogs I added that will come handy later to show what the problem is.
In fact when this code is executed I get this output:
2013-05-22 16:08:53.331 HTTPS Test[5379:c07] Remote Certificate Data Length: 880
2013-05-22 16:09:01.346 HTTPS Test[5379:c07] Local Certificate Data Length: 1249
Obviously the comparison between the Local and the Remote certificates fails because the length of the data is different and so it also fails the pinning.
Why does this happen and how could I solve this problem?
I had the same issue. The problem is probably because you have not converted your .crt file to the correct format. iOS & OSX are looking for your certificate to be in .der format. You need to use openssl to convert it. Here is a very helpful article on this topic. My public certificate came from an Apache server (I am assuming that yours did as well). After looking over openssl documentation I was able to figure out how to get this to work.
1) Open Terminal and change directory to the location of your .crt.
2) Execute this command:
openssl x509 -in your_cert.crt -outform der -out your_output_name.der
This will create an output file named 'your_output_file.der'. You must import this into your xCode project and reference it in the
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
method of your NSURLConnectionDelegate implementation.
I hope this helps!

Ignore ssl certification MKNetworkKit

So it seems that ASIHTTPRequest allows you to ignore the certificates on https:// endpoints. I'm currently using MKNetworkKit and have implemented all my calls. Unfortunately, our testing server is on https but does not have a SSL certificate.
I'm able to connect fine using a curl with the -k command. I've tried various things in MKNetworkKit to ignore the NSURLAuthenticationChallenge, but to no avail. The latest thing I tried was the following:
op.authHandler = ^(NSURLAuthenticationChallenge *challenge)
{
NSURLCredential *credential = [NSURLCredential credentialWithUser:_userName password:password persistence:NSURLCredentialPersistenceNone];
[challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
};
This allowed me to actually get a 401 error returned (instead of being blank). Looking at the curl string, MKNetworkKit strips my username/password when the above block hits. I'm not sure if that's progress or not.
Anyone know how to simply ignore the SSL certificate?
Edit:
I had to update MKNetwork kit to get the new method ShouldContinueWithInvalidCertificate on MKNetworkOperation and my testing server got the certError fixed.
However, now I'm having a weird error happening. I'm still unable to get any return from two specific endpoints on the server. I look at the request, copy it into the command as a curl, and it immediately returns results. I'm not getting an error either from the operation.
What's happening here?
The MKNetworkOperation class has a property called shouldContinueWithInvalidCertificate which is defaulted to NO. All you have to do is set it to YES, and it will ignore the certs.
The comments:
/*!
* #abstract Boolean variable that states whether the operation should continue if the certificate is invalid.
* #property shouldContinueWithInvalidCertificate
*
* #discussion
* If you set this property to YES, the operation will continue as if the certificate was valid (if you use Server Trust Auth)
* The default value is NO. MKNetworkKit will not run an operation with a server that is not trusted.
*/

iOS canAuthenticateAgainstProtectionSpace method not called every time

I am trying to perform SSL certificate validation and have implemented the delegate canAuthenticateAgainstProtectionSpace
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace: (NSURLProtectionSpace*)protectionSpace
{
OSStatus status = SecTrustEvaluate(protectionSpace.serverTrust, &trustResult);
if(status == errSecSuccess)
{
}
else
{
}
}
However, I notice that this delegate gets called the first time for a given URL, but not for subsequent attempts for the same URL. I thought this had to do with the cached response , so I created the NSURLRequest like the following:
NSURLRequest *request = [[NSURLRequest alloc]
initWithURL: [NSURL URLWithString:_urlString]
cachePolicy: NSURLRequestReloadIgnoringLocalAndRemoteCacheData
timeoutInterval: 10
];
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
This doesn't help either. Any ideas, how I can get canAuthenticateAgainstProtectionSpace method to get called every time?
The answer above doesn't solve the actual problem. The actual problem here is that an authentication challenge is only being presented the first time a connection is established for that URL while the app is open.
As explained here
A TLS session is processor intensive and Apple doesn't want you to create a new one every time a connection is made to that URL, so they cache one for you. In this case, it's working against you, but you should be able to work around the issue by including a "." character at the end of your host.
In our case, we were trying to establish a connection to a web server containing a certificate issued by an in-house CA. Since we knew the CA wouldn't be trusted on the first connection, we allowed the connection to continue so that the CA could be downloaded. During that connection, we add the "." character to the end of the host. All subsequent connections use the regular URL without the "." character at the end of the host. This ensures that the CA cert we downloaded is validated the first time a "real" connection is made.
I solved the problem by adding the following code:
- (void)connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
The above cancels the authentication challenge and so the delegate canAuthenticateAgainstProtectionSpace gets called every time

Resources