问题
I have the following simple code to connect to a SSL webpage
NSMutableURLRequest *urlRequest=[NSMutableURLRequest requestWithURL:url];
[ NSURLConnection sendSynchronousRequest: urlRequest returningResponse: nil error: &error ];
Except it gives an error if the cert is a self signed one Error Domain=NSURLErrorDomain Code=-1202 UserInfo=0xd29930 \"untrusted server certificate\".
Is there a way to set it to accept connections anyway (just like in a browser you can press accept) or a way to bypass it?
回答1:
There is a supported API for accomplishing this! Add something like this to your NSURLConnection
delegate:
- (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];
}
Note that connection:didReceiveAuthenticationChallenge:
can send its message to challenge.sender (much) later, after presenting a dialog box to the user if necessary, etc.
回答2:
If you're unwilling (or unable) to use private APIs, there's an open source (BSD license) library called ASIHTTPRequest that provides a wrapper around the lower-level CFNetwork APIs
. They recently introduced the ability to allow HTTPS connections
using self-signed or untrusted certificates with the -setValidatesSecureCertificate:
API. If you don't want to pull in the whole library, you could use the source as a reference for implementing the same functionality yourself.
回答3:
Ideally, there should only be two scenarios of when an iOS application would need to accept an un-trusted certificate.
Scenario A: You are connected to a test environment which is using a self-signed certificate.
Scenario B: You are Proxying HTTPS
traffic using a MITM Proxy like Burp Suite, Fiddler, OWASP ZAP, etc.
The Proxies will return a certificate signed by a self-signed CA so that the proxy is able to capture HTTPS
traffic.
Production hosts should never use un-trusted certificates for obvious reasons.
If you need to have the iOS simulator accept an un-trusted certificate for testing purposes it is highly recommended that you do not change application logic in order disable the built in certificate validation provided by the NSURLConnection
APIs. If the application is released to the public without removing this logic, it will be susceptible to man-in-the-middle attacks.
The recommended way to accept un-trusted certificates for testing purposes is to import the Certificate Authority(CA) certificate which signed the certificate onto your iOS Simulator or iOS device. I wrote up a quick blog post which demonstrates how to do this which an iOS Simulator at:
accepting untrusted certificates using the ios simulator
回答4:
NSURLRequest
has a private method called setAllowsAnyHTTPSCertificate:forHost:
, which will do exactly what you'd like. You could define the allowsAnyHTTPSCertificateForHost:
method on NSURLRequest
via a category, and set it to return YES
for the host that you'd like to override.
回答5:
To complement the accepted answer, for much better security, you could add your server certificate or your own root CA certificate to keychain( https://stackoverflow.com/a/9941559/1432048), however doing this alone won't make NSURLConnection authenticate your self-signed server automatically. You still need to add the below code to your NSURLConnection delegate, it's copied from Apple sample code AdvancedURLConnections, and you need to add two files(Credentials.h, Credentials.m) from apple sample code to your projects.
- (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])
OSStatus err;
NSURLProtectionSpace * protectionSpace;
SecTrustRef trust;
SecTrustResultType trustResult;
BOOL trusted;
protectionSpace = [challenge protectionSpace];
assert(protectionSpace != nil);
trust = [protectionSpace serverTrust];
assert(trust != NULL);
err = SecTrustEvaluate(trust, &trustResult);
trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));
// If that fails, apply our certificates as anchors and see if that helps.
//
// It's perfectly acceptable to apply all of our certificates to the SecTrust
// object, and let the SecTrust object sort out the mess. Of course, this assumes
// that the user trusts all certificates equally in all situations, which is implicit
// in our user interface; you could provide a more sophisticated user interface
// to allow the user to trust certain certificates for certain sites and so on).
if ( ! trusted ) {
err = SecTrustSetAnchorCertificates(trust, (CFArrayRef) [Credentials sharedCredentials].certificates);
if (err == noErr) {
err = SecTrustEvaluate(trust, &trustResult);
}
trusted = (err == noErr) && ((trustResult == kSecTrustResultProceed) || (trustResult == kSecTrustResultUnspecified));
}
if(trusted)
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
回答6:
I can't take any credit for this, but this one I found worked really well for my needs. shouldAllowSelfSignedCert
is my BOOL
variable. Just add to your NSURLConnection
delegate and you should be rockin for a quick bypass on a per connection basis.
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)space {
if([[space authenticationMethod] isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if(shouldAllowSelfSignedCert) {
return YES; // Self-signed cert will be accepted
} else {
return NO; // Self-signed cert will be rejected
}
// Note: it doesn't seem to matter what you return for a proper SSL cert
// only self-signed certs
}
// If no other authentication is required, return NO for everything else
// Otherwise maybe YES for NSURLAuthenticationMethodDefault and etc.
return NO;
}
回答7:
In iOS 9, SSL connections will fail for all invalid or self-signed certificates. This is the default behavior of the new App Transport Security feature in iOS 9.0 or later, and on OS X 10.11 and later.
You can override this behavior in the Info.plist
, by setting NSAllowsArbitraryLoads
to YES
in the NSAppTransportSecurity
dictionary. However, I recommend overriding this setting for testing purposes only.
For information see App Transport Technote here.
回答8:
The category workaround posted by Nathan de Vries will pass the AppStore private API checks, and is useful in cases where you do not have control of the NSUrlConnection
object.
One example is NSXMLParser
which will open the URL you supply, but does not expose the NSURLRequest
or NSURLConnection
.
In iOS 4 the workaround still seems to work, but only on the device, the Simulator does not invoke the allowsAnyHTTPSCertificateForHost:
method anymore.
回答9:
You have to use NSURLConnectionDelegate
to allow HTTPS connections and there are new callbacks with iOS8.
Deprecated:
connection:canAuthenticateAgainstProtectionSpace:
connection:didCancelAuthenticationChallenge:
connection:didReceiveAuthenticationChallenge:
Instead those, you need to declare:
connectionShouldUseCredentialStorage:
- Sent to determine whether the URL loader should use the credential storage for authenticating the connection.
connection:willSendRequestForAuthenticationChallenge:
- Tells the delegate that the connection will send a request for an authentication challenge.
With willSendRequestForAuthenticationChallenge
you can use challenge
like you did with the deprecated methods, for example:
// Trusting and not trusting connection to host: Self-signed certificate
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
回答10:
I posted some gist code (based on someone else's work which I note) that lets you properly authenticate against a self generated certificate (and how to get a free certificate - see comments bottom of Cocoanetics)
My code is here github
回答11:
If you want to keep using sendSynchronousRequest i work in this solution:
FailCertificateDelegate *fcd=[[FailCertificateDelegate alloc] init];
NSURLConnection *c=[[NSURLConnection alloc] initWithRequest:request delegate:fcd startImmediately:NO];
[c setDelegateQueue:[[NSOperationQueue alloc] init]];
[c start];
NSData *d=[fcd getData];
you can see it here: Objective-C SSL Synchronous Connection
回答12:
With AFNetworking I have successfully consumed https webservice with below code,
NSString *aStrServerUrl = WS_URL;
// Initialize AFHTTPRequestOperationManager...
AFHTTPRequestOperationManager *manager = [AFHTTPRequestOperationManager manager];
manager.requestSerializer = [AFJSONRequestSerializer serializer];
manager.responseSerializer = [AFJSONResponseSerializer serializer];
[manager.requestSerializer setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
manager.securityPolicy.allowInvalidCertificates = YES;
[manager POST:aStrServerUrl parameters:parameters success:^(AFHTTPRequestOperation *operation, id responseObject)
{
successBlock(operation, responseObject);
} failure:^(AFHTTPRequestOperation *operation, NSError *error)
{
errorBlock(operation, error);
}];
回答13:
You can use this Code
-(void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodServerTrust)
{
[[challenge sender] useCredential:[NSURLCredential credentialForTrust:[[challenge protectionSpace] serverTrust]] forAuthenticationChallenge:challenge];
}
}
Use -connection:willSendRequestForAuthenticationChallenge:
instead of these Deprecated Methods
Deprecated:
-(BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
-(void)connection:(NSURLConnection *)connection didCancelAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
来源:https://stackoverflow.com/questions/933331/how-to-use-nsurlconnection-to-connect-with-ssl-for-an-untrusted-cert