On Apr 16, 2008, at 12:03 AM, Stephane Huaulme <[EMAIL PROTECTED]> wrote:

when I try to fetch a web page on an internal server using:

[NSString stringWithContentsOfURL:[NSURL URLWithString: theURLString]
encoding:NSASCIIStringEncoding error:&theError];

I get the following error:

<CFString 0xa00daec8 [0xa01d71a0]>{contents = "NSUnderlyingError"} =
Error Domain=NSURLErrorDomain Code=-1203 UserInfo=0x15579210 "bad
server certificate

what can I do about this?

Don't know if it's related, but after installing the release version of Safari 3.1 and some security update or other, I'm seeing this a lot.

Amusingly, one the sites 3.1 has complained about is developer.apple.com...

In any case, I often have to grab many files off a certain bastardized download site used by a client - a site which forces you to click on every single URL. That got old fast, so I'd hacked together something with NSURLDownload to automate it all. Suddenly on 3/18, not only was developer.apple.com getting certificate errors, so was this particular site, in both Safari and my app.

The archives turned up a little info about the private method
[NSURLRequest setAllowsAnyHTTPSCertificate:YES forHost:hostName].

Which I guess is not a great thing to use in production code, but if you have to get around the problem somehow, it does work.

However I didn't want to just blow past the certificates without taking a look at them. Google turned up some code (from Mike Ash I believe?) showing how to use the Security framework to put up a confirmation dialog with details for the bad certificates. I fixed some typos and switched to a sheet rather than a modal dialog. Ripped directly out of the app, Mail is going to mangle it for you below...

Posted with the usual caveats about using private API. (In this case it was an app for my private use, so who cares; sounds like you may be in a similar situation.)


// NSURLDownload delegate method
- (void)download:(NSURLDownload *)download didFailWithError:(NSError *)error
{
        NSURL    *url = [[download request] URL];
        NSString *path = [url absoluteString];
        NSString *description = [error localizedDescription];
        NSString *reason = [error localizedFailureReason];
        
        int errorCode = [error code];
        
        // handle sudden onset certificate errors
        //
// first saw NSURLErrorServerCertificateHasUnknownRoot == -1203 on 3/18/08
        // after update to Safari 3.1 and a security update.
if ( (errorCode <= NSURLErrorServerCertificateHasBadDate) && (errorCode >= NSURLErrorClientCertificateRejected) )
        {
NSURL *failingURL = [[error userInfo] objectForKey:@"NSErrorFailingURLKey"]; NSArray *badCerts = [[error userInfo] objectForKey:@"NSErrorPeerCertificateChainKey"];
                
                SecPolicySearchRef policySearch = NULL;
                
if (SecPolicySearchCreate(CSSM_CERT_X_509v3, &CSSMOID_APPLE_TP_SSL, NULL, &policySearch) == noErr)
                {
                        SecPolicyRef policy = NULL;
                        OSStatus status;
                        
// original code says this while-loop will only loop once, but on my problem // site it looped twice per failed URL - evidently the site had - two- bad // certificates per URL. (If it's even their fault and not Safari's...) while ((status = SecPolicySearchCopyNext(policySearch, &policy)) == noErr)
                        {
                                SecTrustRef trust = NULL;
if (SecTrustCreateWithCertificates((CFArrayRef)badCerts, policy, &trust) == noErr)
                                {
SFCertificateTrustPanel *panel = [SFCertificateTrustPanel sharedCertificateTrustPanel];
                                        NSString *host = [failingURL host];
                                        //NSString *host = [failingURL 
_web_hostString];
                                        
                                        [panel 
setDefaultButtonTitle:@"Continue"];
                                        [panel 
setAlternateButtonTitle:@"Cancel"];
                                        
if ([panel respondsToSelector:@selector (setInformativeText:)]) // this method is in Tiger but is undocumented
                                        {
//NSString *informativeText = [NSString stringWithFormat:@"Security certificate is invalid for item %d, host % @, failed host [EMAIL PROTECTED]", (downloadIndex + 1), [url host], host]; // the problem with this is that once you set it, subsequent attempts to change it do nothing; the originally-set text remains unchanged. Must be doing something wrong. For now, just hardcoded to the problem site. //[panel performSelector:@selector(setInformativeText:) withObject:informativeText];
                                                
[panel performSelector:@selector(setInformativeText:) withObject:@"DownloadYouSendItItems"];
                                        }
                                        [panel setShowsHelp:YES];
                                        
// docs say we won't receive any further delegate messages for this failed download. // But note this method's "download" param and our currentDownload instance // var are actually the same object, which we want to release - but only once.
                                        [self setCurrentDownload:NULL];

                                        NSString *sheetText;
sheetText = [NSString stringWithFormat:@"Security certificate is invalid for item %d, host %@, failed host [EMAIL PROTECTED]",
                                                                                
                                        (downloadIndex + 1),
                                                                                
                                        [url host],
                                                                                
                                        host];                                  
                                        [panel beginSheetForWindow:mainWindow
                                                                
modalDelegate:self
didEndSelector:@selector (certificateTrustSheetDidEnd:returnCode:contextInfo:)
                                                                  
contextInfo:[host retain] // didEndSelector will release
                                                                                
trust:trust
                                                                          
message:sheetText];
                                                                                
        
                                        CFRelease(trust);
                                        trust = NULL;
                                }
                                
                                CFRelease(policy);
                                policy = NULL;
                        } // end while
                        
                        CFRelease(policySearch);
                }
        }
        else // Handle other "normal" download errors
        {
                
NSLog( @"Download of '%@' failed with error:%d, [EMAIL PROTECTED]", path, errorCode, description );

// docs say we won't receive any further delegate messages for this failed download. // Note this method's "download" param and the app's currentDownload instance // var are actually the same object, which we want to release, but only once.
                [self setCurrentDownload:NULL];
                
NSString *failedReason = [NSString stringWithFormat:@" failed with error:%d, %@ %@", [error code], description, reason];
                
                // cause bindings to update filename status display in window
                if ( [self currentDownloadName] )
[self setCurrentDownloadName:[[self currentDownloadName] stringByAppendingString:failedReason]];
                else
[self setCurrentDownloadName:[path stringByAppendingString:failedReason]];
                
                // continue next download
[self performSelector:@selector(initiateNextDownload) withObject:NULL afterDelay:0.];

        }
        
}

- (void)certificateTrustSheetDidEnd:(NSWindow *)sheet returnCode:(int) returnCode contextInfo:(void *)contextInfo
{
// we set the contextInfo tp the retained host name of the failing URL, obtained
        // from [[error userInfo] objectForKey:@"NSErrorFailingURLKey"].
        // we'll need to release this before returning; done below
        NSString *hostName = (NSString *)contextInfo;
        
        // note to self: SecTrustGetResult() useful?
        
        if (returnCode == NSOKButton)
        {
                NSLog( @"Allowing invalid certificate for host:%@", hostName );
                
// unfortunately we have to use a private API to get past this certificate
                [NSURLRequest setAllowsAnyHTTPSCertificate:YES 
forHost:hostName];       
                
                // having set the certificate to be ignored, start this 
download again
[self performSelector:@selector(downloadItem:) withObject:[NSNumber numberWithInt:downloadIndex] afterDelay:0.0];
        }
        else // user clicked Cancel
        {
                NSLog( @"Disallowing invalid certificate for host:%@", hostName 
);
                // skip to next download
[self performSelector:@selector(initiateNextDownload) withObject:NULL afterDelay:0.0];
        }
        
        [hostName release];
}


_______________________________________________

Cocoa-dev mailing list (Cocoa-dev@lists.apple.com)

Please do not post admin requests or moderator comments to the list.
Contact the moderators at cocoa-dev-admins(at)lists.apple.com

Help/Unsubscribe/Update your Subscription:
http://lists.apple.com/mailman/options/cocoa-dev/archive%40mail-archive.com

This email sent to [EMAIL PROTECTED]

Reply via email to