NSURLSessionAuthChallengeRejectProtectionSpace fails - custom certificate validation on OS X iOS - objective-c

I have application with custom certificate validation. Code is quite simple:
- (void) URLSession:(NSURLSession *)session
task:(NSURLSessionTask *)task
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler
{
NSString * authenticationMethod = challenge.protectionSpace.authenticationMethod;
CSHTTPDownoadTaskProxy *proxyTask = [self streamedDataTaskForHttpTask: task]; // get custom information about task
if ([authenticationMethod isEqualToString: NSURLAuthenticationMethodServerTrust]) {
if (!proxyTask) {
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
LOG_UNEXPECTED_TASK(task);
return;
}
[self validateCertificate: challenge.protectionSpace withCompletionHandler: completionHandler];
} else if ([authenticationMethod isEqualToString: NSURLAuthenticationMethodHTTPBasic] {
… … …
}
}
- (void) validateCertificate: (NSURLProtectionSpace *)protectionSpace
withCompletionHandler: (void (^)(NSURLSessionAuthChallengeDisposition disposition,
NSURLCredential *credential))completionHandler {
// some logic
… … …
completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil);
}
Now when certificate is rejected usually everything works as expected, certificate is rejected and error is reported for HTTP request.
For some servers even though completionHandler(NSURLSessionAuthChallengeRejectProtectionSpace, nil); is invoked request is processed and completed successfully.
NSURLSessionAuthChallengeRejectProtectionSpace documentation says:
Reject this challenge, and call the authentication delegate method
again with the next authentication protection space. The provided
credential parameter is ignored.
This is a bit confusing, what does it mean "next authentication protection space"?
This API covers also password protection space and I understand that that if service has multiple ways of authentication, for example: Negotiate, Diget, Basic, NSURLSession prepossess best one first it it is rejected it notifies about alternative possibility or fails request.
But my delegate is invoked only once for this authentication method NSURLAuthenticationMethodServerTrust!
For most cases when I'm using this value expected error is reported (invalid certificate):
Error Domain=NSURLErrorDomain Code=-1202 "The certificate for this
server is invalid. You might be connecting to a server that is
pretending to be “10.133.32.55” which could put your confidential
information at risk."
For other cases (logs show that completion handler was called with proper values) HTTP request is processed and completes successfully (it happens for some servers on different client machines).
Did anyone else experience similar problems?
I could do nasty workaround with NSURLSessionAuthChallengeCancelAuthenticationChallenge. This value reports that request was "Canceled" so I have to generate my own error.
I don't like this approach and prefer find reason why NSURLSessionAuthChallengeRejectProtectionSpace doesn't work sometimes.
Update
Ok I have some extra clue. Problem appears only on servers which are demanding client identity certificate.
When client identity is not needed certificate validation is accepted (certificate is rejected when requested), if client identity is demanded and available, server certificate validation result is ignored.
After finding precise answer it looks like whoever configured test servers did that more properly for servers with client identity leading to this side effects.

The purpose of NSURLSessionAuthChallengeRejectProtectionSpace is to tell the OS that you aren't able to do anything with a particular authentication type. For example, if the server says that it is willing to accept either client certificates, basic auth (username/password) or digest auth (essentially signing the request using the username/password as a key) then NSURLSession will ask you for one of those methods at a time.
If the session asks you for client certificates and all you have are a username and password, you would reject the client certificate authentication method by calling the continuation block with NSURLSessionAuthChallengeRejectProtectionSpace, and the session will then ask you for a username and password.
IMO, it doesn't make much sense to use NSURLSessionAuthChallengeRejectProtectionSpace in combination with server trust authentication, because there is no alternative to TLS for evaluating whether the connection is trusted. In practice, I think that this is equivalent to calling NSURLSessionAuthChallengePerformDefaultHandling, which means that if the cert is trusted, it will be accepted, otherwise it will be rejected. But you should be using NSURLSessionAuthChallengePerformDefaultHandling explicitly if that is the desired behavior (or NSURLSessionAuthChallengeCancelAuthenticationChallenge if you want to cancel the request).
It may be that rejecting the request in the presence of a client certificate causes the server's cert to be ignored, in which case this is probably a bug, and you should file it. But the reason for doing so is presumably because accepting the server's cert is required when sending a client cert, and you told it to skip the server validation and send you the next method (client cert). So I'm pretty sure that's the reason things are behaving strangely.
Also, you need a final "else" case there to handle unknown protection spaces. Chances are, the else case should use NSURLSessionAuthChallengePerformDefaultHandling. If you don't do this, then when you get an unknown protection space, the connection will just stay open forever and will never move forward or get canceled, because nothing ever calls the continuation block.

Related

Why doesn't CORS preflight request prompt for (or reuse a connection where) client certificates (are sent)?

Given a situation where
a server requires client certificates,
the consumer is on another origin, and
wants to do preflight-requiring requests (e.g PUT, POST with content-type json, etc)
is there no way to allow this -- without dropping the client certificate requirement and making them optional?
Background
You might say "but it does work", but apparently this is considered a bug in Chrome[1].
On Firefox, preflight requiring requests don't go through at all because the preflight doesn't succeed (because "a strictly non-client-certified" TLS connection is made to the server)[2][3]. Yet, when e.g loading an image from the certificate-protected server, it does prompt for a client certificate and succeeds if provided.
In both [2] and [3] the "confused deputy problem" is cited as the reason, same issue is mentioned in the Fetch specification[4] (ctrl-f to find it everywhere). What I don't get is how is this a confused deputy problem? If a preflight is made, it prompts the user to choose a certificate, so the user knows about it being sent. And the final say still goes to the reply for the preflight -- doing a credentialed preflight doesn't skip the preflight sequence.
Secondly, the OPTIONS method (used in the preflight) shouldn't be side-effecting, so there is no deputy to be confused there either.
How come one has to make certificates optional to make this work? Optional certificates seem strictly worse security wise, and also adds a configuration burden to stop non-client-certified and non-OPTION requests from going forward.
https://bugs.chromium.org/p/chromium/issues/detail?id=775438
https://bugzilla.mozilla.org/show_bug.cgi?id=1019603
https://bugzilla.mozilla.org/show_bug.cgi?id=1511151
https://fetch.spec.whatwg.org/#cors-protocol-and-credentials

MFP Objective C - WLAFSSLPinningModeCertificate can only be applied on a manager with a secure base URL

Please help with this, I'm using the latest IBMMobileFirstPlatformFoundation.framework
When try to login to https server.
[[WLAuthorizationManager sharedInstance] login:#"AuthenticateUser" withCredentials:params withCompletionHandler:^(NSError *error){
if (error != nil) {
errorHandler(error);
}else{
successHandler(nil);
}
}];
Below error message is shown.
Thread 1: "A security policy configured with
WLAFSSLPinningModeCertificate can only be applied on a manager with
a secure base URL (i.e. https)"
I suppose you are using certificate pinning with a http URL. If so, the behaviour is indeed expected.
What is the wlProtocol in your mfpclient.plist ?
If it is http, then the whole point of using certificate pinning is moot since there is no certificate involved in the communication.

Decent routine to check server certificate

I am writing a client app that need talk to Active Directory server and one of requirements is to support LDAPS/StartTLS.
I already figure out there is one option need to set:
if (ldap_set_option(pLdap, LDAP_OPT_SERVER_CERTIFICATE, &my_cert_check_func) != LDAP_SUCCESS) {
std::cerr << "ldap set cert check callback failed" <<std::endl;
return NULL;
}
and my_cert_check_func is over-naive and not safe at all:
static BOOLEAN my_cert_check_func(PLDAP connection, PCCERT_CONTEXT server_cert)
{
return TRUE;
}
And I also did a lot of googling and read quite a lot msdn, but still no clue. I have never handle such security-related coding before so any thing related to cert check are welcome.
And because I write this app using Winldap API, so the code should use Windows specific APIs.
And I am also thinking do such check using openssl api (this api is a dependency of my app, so it is fine to use that).
Could you show me some sample code for doing real checking of server certs against client security store or what ever client has?
Thank you very much!
You don't need to verify the entire certificate chain etc. for validity. LDAPS should already have done that. You only need to check the subjectDN of the certificate against what you think it should be when talking to that server.

ASIHTTPRequest retrieve server certificate

For some legacy and internal reasons, I need to retrieve the certificate of the server (and ones from the chain as well), and read its fingerprint.
I understand this is easy to implement using AFNetworking or NSURLConnection since I only need to implement it in - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
But is it possible to do the same thing when using ASIHTTPRequest, which is just just basically a wrapper of CFNetworking?
Once again, I don't have a client certificate and my goal is to get the certificate from the server to read its fingerprint of issuer.
I will need to validate the fingerprint of server certificate in client and continue the request if it matches the one I expected, or cancel the request / throw error otherwise. Is this possible using ASIHTTPRequest?
I verified that there's no way to do this using CFNetworking (what ASIHTTPRequest is using), see https://devforums.apple.com/thread/87346. For those who don't have ios Developers account (I am not sure why you are reading this though), "No. After thinking about this in depth I don't think it's possible. NSURLConnection, which does this, use a private hook into CFHTTPStream, and you won't be able to use that."
Btw, we rewrote our network layer using NSURLConnection and solved this problem

Objective-C/Cocoa: How do I accept a bad server certificate?

Using NSURLRequest, I am trying to access a web site that has an expired certificate. When I send the request, my connection:didFailWithError delegate method is invoked with the following info:
-1203, NSURLErrorDomain, bad server certificate
My searches have only turned up one solution: a hidden class method in NSURLRequest:
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:myHost];
However, I don't want to use private APIs in a production app for obvious reasons.
Any suggestions on what to do? Do I need to use CFNetwork APIs, and if so, two questions:
Any sample code I can use to get started? I haven't found any online.
If I use CFNetwork for this, do I have to ditch NSURL entirely?
EDIT:
iPhone OS 3.0 introduced a supported method for doing this. More details here: How to use NSURLConnection to connect with SSL for an untrusted cert?
The supported way of doing this requires using CFNetwork. You have to do is attach a kCFStreamPropertySSLSettings to the stream that specifies kCFStreamSSLValidatesCertificateChain == kCFBooleanFalse. Below is some quick code that does it, minus checking for valid results add cleaning up. Once you have done this You can use CFReadStreamRead() to get the data.
CFURLRef myURL = CFURLCreateWithString(kCFAllocatorDefault, CFSTR("http://www.apple.com"), NULL);
CFHTTPMessageRef myRequest = CFHTTPMessageCreateRequest(kCFAllocatorDefault, CFSTR("GET"), myURL, kCFHTTPVersion1_1);
CFReadStreamRef myStream = CFReadStreamCreateForHTTPRequest(kCFAllocatorDefault, myRequest);
CFMutableDictionaryRef myDict = CFDictionaryCreateMutable(kCFAllocatorDefault, 0, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks);
CFDictionarySetValue(myDict, kCFStreamSSLValidatesCertificateChain, kCFBooleanFalse);
CFReadStreamSetProperty(myStream, kCFStreamPropertySSLSettings, myDict);
CFReadStreamOpen(myStream);
If it's for an internal server for testing purposes, why not just import the test server's certificate into the KeyChain and set custom trust settings?
iPhone OS 3.0 introduced a supported way of doing this that doesn't require the lower-level CFNetwork APIs. More details here:
How to use NSURLConnection to connect with SSL for an untrusted cert?
I've hit the same issue - I was developing a SOAP client, and the dev server has a "homegrown" certificate. I wasn't able to solve the issue even using that method, since I wasn't using NSURL, but the (poorly documented and apparently abandoned) WS methods, and decided for the time being to (internally) just use a non-SSL connection.
Having said that, however, the question that springs to mind is, if you aren't willing to use a private API in a production app, should you be allowing access to a site with a dodgy certificate?
I'll quote Jens Alfke:
That's not just a theoretical security problem. Something
like 25% of public DNS servers have been compromised, according to
recent reports, and can direct users to phishing/malware/ad sites even
if they enter the domain name properly. The only thing protecting you
from that is SSL certificate checking.
Can you create a self signed certificate and add your custom certificate authority to the trusted CAs? I'm not quite sure how this would work on the iPhone, but I'd assume on Mac OS X you would add these to the Keychain.
You may also be interested in this post Re: How to handle bad certificate error in NSURLDownload
Another option would be to use an alternate connection library.
I am a huge fan of AsyncSocket and it has support for self signed certs
http://code.google.com/p/cocoaasyncsocket/
Take a look, I think it is way more robust then the standard NSURLRequests.