I have just started learning ios development, and I am trying to get an image from website which uses ssl, when i connect to the site through a browser(laptop) there is a warning which says that the root certificate is not trusted, I am not the owner of the website, however I can fully trust it.
My first attempt:
self.eventImage.image = [UIImage imageWithData:
[NSData dataWithContentsOfURL:
[NSURL URLWithString:imageUrl]]];
so I get this error
NSURLConnection/CFURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9807)
I have tried to send users to the picture link by starting ios web browser, when they do that, they would get a message asking them if they could trust it or not, if they hit yes the image will appear, however i want the image to appear inside the application.
I have also tried to use web view but it didn't work.
Most of the similar questions in here suggested using this
- (BOOL)connection:(NSURLConnection *)
connection canAuthenticateAgainstProtectionSpace:
(NSURLProtectionSpace *)protectionSpace {
return NO;
//return [protectionSpace.authenticationMethod isEqualToString:
// NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)
connection didReceiveAuthenticationChallenge:
(NSURLAuthenticationChallenge *)challenge {
NSString *imageUri =[self.detailItem objectForKey: #"image"];
NSArray *trustedHosts = [[NSArray alloc]initWithObjects:imageUri, nil];
if ([challenge.protectionSpace.authenticationMethod
isEqualToString:NSURLAuthenticationMethodServerTrust])
if ([trustedHosts containsObject:challenge.protectionSpace.host])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust] forAuthenticationChallenge:
challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
but these two methods were never called when I add them.
Try adding these two methods
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Change your code. Instead of using NSData dataWithContentsOfURL: you need to use your own explicit NSURLConnection. Then you can make use of the appropriate NSURLConnectionDelegate methods.
Another option is to use the popular AFNetworking library.
rmaddy, user2179059, and Anindya Sengupta answers helped in resolving this issue.
first, i used NSURLConnection explicitly, and for the secure connection i used this approach ( got it from this blog post )
-(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]) {
// instead of XXX.XXX.XXX, add the host URL,
// if this didn't work, print out the error you receive.
if ([challenge.protectionSpace.host isEqualToString:#"XXX.XXX.XXX"]) {
NSLog(#"Allowing bypass...");
NSURLCredential *credential = [NSURLCredential credentialForTrust:
challenge.protectionSpace.serverTrust];
[challenge.sender useCredential:credential
forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
this differ from user2179059 by limiting unsecure connection to that host only.
I hope you have included the NSURLConnectionDelegate protocol in the interface in .h file?
#interface ConnectionExampleViewController : UIViewController <NSURLConnectionDelegate>
Related
I have a Web Service, i hosted in IIS7 and generated a self signed certificate for it.
And also i have iPad application as front-end. When i try to connect them using NSURLConnection it is working with this code.
- (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:#"IP/HostName"])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
!!! The problem is when i check over "SSL Required" on the IIS , i am getting an error
<?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"><soap:Body><soap:Fault><faultcode>soap:Server</faultcode><faultstring>Server was unable to process request. ---> The request failed with HTTP status 403: Forbidden.</faultstring><detail /></soap:Fault></soap:Body></soap:Envelope>
How can i generate and send certificate from client (application) side.
Dose anybody have an idea about this?
Thanks in advance.
Here's an issue: I need to implement both HTTP basic authorization and MS NTLM authorization. I use asynchronous NSURLConnection so I receive
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
callback. The full code of this method looks like that:
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSString* authMethod = [[challenge protectionSpace] authenticationMethod];
NSLog(#"Authentication method: %#", authMethod);
NSString *userName = self.login;
if ([authMethod isEqualToString:NSURLAuthenticationMethodNTLM]) {
userName = [userName lastElementAfterSlash];
}
else if ([authMethod isEqualToString:NSURLAuthenticationMethodNegotiate]) {
userName = [NSString stringWithFormat:#"%##%#", userName, self.url.host];
}
if ([challenge previousFailureCount] <= 1) {
NSURLCredential *credential = [NSURLCredential credentialWithUser:userName password:self.password persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:[self.url host]
port:0
protocol:self.url.scheme
realm:[self.url host]
authenticationMethod:authMethod];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
forProtectionSpace:protectionSpace];
}
else {
NSLog(#"Authentication error");
NSLog(#"Failed login with status code: %d", [(NSHTTPURLResponse*)[challenge failureResponse]statusCode]);
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
The two global issues I've met so far using this method:
1) As per Charles sniffer, all the requests to the server are doubled (as per Apple Docs, it's an expected behaviour). However, it leads to lack of performance (two requests instead of just one) comparing to setting header directly, using setValue:forHTTPHeader:.
2) It's being called not for every request. For example, when I'm trying to grab an image from server, it returns 302 (redirect to login web page) HTTP code instead of 401 HTTP code, so this does not work at all.
What I want to accomplish, is to grab the correct Authorization header once and then put it manually in every subsequent NSMutableURLRequest I make. I can compose HTTP Basic authorization manually, it's pretty simple, but I can't compose NTLM header in the same manner, that's why I was relying on didReceiveAuthenticationChallenge: method.
Could you please point me out, how can I receive the Authorization header, that is set automatically by NSURLProtectionSpace, so each request will INITIALLY go authorized?
P.S. I tried to receive it from
[connection.currentRequest allHTTPHeaderFields];
but it returns an empty array. I'm fighting with that for more than two days, any help will be kindly appreciated!
I want to access some web ressources using HTTPS in a custom iOS 6 app. Some target servers are using a certificate which is signed by a CA that is not by default included in iOS, but was manually added to the keychain of the device. Therefore all URLs can be opened in Safari without any warning or error.
What I want to achieve is the same behavior as Safari: I want to load the websites if Safari would have trusted them, or deny to load them in case of any error. As the installed certificates can change from case to case I do not want to manually include any certificates in the application ressources, which is what many questions here at SO are about.
My problem is that I do not get SecTrustEvaluate to return kSecTrustResultProceed. Do you have any idea what I can do?
If my canAuthenticateAgainstProtectionSpace returns NO, iOS handles the server certificate check on itself, but it does not seem to check for additionally installed certificates (as Safari does).
Here is some code to try and understand what I got so far:
- (void)viewDidLoad
{
[super viewDidLoad];
[self loadURLWithString:#"https://myserver.com"];
}
+ (BOOL) isChallenge: (NSURLAuthenticationChallenge*) challenge validForConnection: (NSURLConnection*)conn{
SecTrustRef serverTrust=[[challenge protectionSpace] serverTrust];
//Some magic here?
// Check Server Certificate
SecTrustResultType evalResult;
if(SecTrustEvaluate(serverTrust,&evalResult) != errSecSuccess){
NSLog(#"Call to SecTrustEvaluate failed");
return NO;
}
if(evalResult != kSecTrustResultProceed){
NSLog(#"Server certificate invalid");
return NO;
}
NSLog(#"Server certificate valid");
return YES;
}
- (void)loadURLWithString: (NSString*)str{
NSURLConnection *conn =
[NSURLConnection connectionWithRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:str]] delegate:self];
[conn start];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge{
if([[self class] isChallenge:challenge validForConnection:connection])
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
else
[challenge.sender cancelAuthenticationChallenge:challenge];
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace{
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
NSLog(#"Failed with error: %#",error);
}
- (void)connectionDidFinishLoading:(NSURLConnection *)connection{
NSLog(#"loading complete");
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response{
}
- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data{
}
What you're trying to do is not permitted. For more info, see this thread in the Apple developer forums:
https://devforums.apple.com/message/660579#660579
I'm trying to get the three above working properly, and something is not clicking. Specifically, the authentication request is not being triggered when it appears it should be. According to what I've read here, the relevant pieces are:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
NSLog(#"protection space");
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"auth challenge");
NSInteger count = [challenge previousFailureCount];
if (count > 0)
{
NSLog(#"count > 0");
NSObject<ServiceDelegate> *delegate = [currentRequest delegate];
[[challenge sender] cancelAuthenticationChallenge:challenge];
if ([delegate respondsToSelector:#selector(wrapperHasBadCredentials:)])
{
[delegate rbService:self wrapperHasBadCredentials:self];
}
return;
}
NSArray *trustedHosts = [[NSArray alloc] initWithObjects:#"myserver", nil];
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
NSLog(#"server trust");
if ([trustedHosts containsObject:challenge.protectionSpace.host])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
else
{
NSLog(#"credentials");
NSURLCredential* credential = [[[NSURLCredential alloc] initWithUser:#"xxx" password:#"xxx" persistence:NSURLCredentialPersistenceForSession] autorelease];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
}
The target server is set up for two URLS, one HTTPS and one HTTP, both of which prompt for username/password using basic authentication. I've checked the target server using firefox and everything seems to work as advertised. The target server uses an untrusted cert, but I thought I'd taken care of that in the code. Maybe not.
The specific behavior in the application:
When using HTTP the log reads:
log - protection space
(then returns 401 code)
When using HTTPS:
log - protection space
log - auth challenge
log - server trust
log - protection space
(then returns a 401 code)
In the first instance, it gets to the canAuthenticate... section, returns, but then doesn't challenge for the authentication, and returns a 401 response.
In the second instance, it does all that, actually does challenge, then goes to the canAuthenticate... section again, returns, and returns a 401 response.
Keep in mind that the request is a POST request, complete with headers and an HTTPBody. The authentication is not included in the headers (I would rather not do that), but if there is no other solution I will try things that way.
As always, thank you very much for the help. It's priceless.
It looks like the answer here has to be sending the authentication along with the POST request. I was thinking that the challenge/response process would somehow add those headers to the request by itself, but apparently not.
can we use following methods for ultra cassini ? as i know it is very well with IIS
- (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 ([trustedHosts containsObject:challenge.protectionSpace.host])
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
thank you
I'm not sure if Cassini has the level of authentication built into it, but I'm a bit doubtful. I'm pretty sure you should be able to use IIS Express, which will be replacing Cassini.
Check out Scott Guthrie's blog post about IIS Express.