Ben Copsey

Added validatesSecureCertificate property to turn off SSL cert validation for te…

…sting with self-signed certs
@@ -217,6 +217,9 @@ extern NSString* const NetworkRequestErrorDomain; @@ -217,6 +217,9 @@ extern NSString* const NetworkRequestErrorDomain;
217 217
218 // When YES, requests will automatically redirect when they get a HTTP 30x header (defaults to YES) 218 // When YES, requests will automatically redirect when they get a HTTP 30x header (defaults to YES)
219 BOOL shouldRedirect; 219 BOOL shouldRedirect;
  220 +
  221 + // When NO, requests will not check the secure certificate is valid (use for self-signed cerficates during development, DO NOT USE IN PRODUCTION) Default is YES
  222 + BOOL validatesSecureCertificate;
220 223
221 } 224 }
222 225
@@ -401,4 +404,5 @@ extern NSString* const NetworkRequestErrorDomain; @@ -401,4 +404,5 @@ extern NSString* const NetworkRequestErrorDomain;
401 @property (assign) BOOL useHTTPVersionOne; 404 @property (assign) BOOL useHTTPVersionOne;
402 @property (assign, readonly) unsigned long long partialDownloadSize; 405 @property (assign, readonly) unsigned long long partialDownloadSize;
403 @property (assign) BOOL shouldRedirect; 406 @property (assign) BOOL shouldRedirect;
  407 +@property (assign) BOOL validatesSecureCertificate;
404 @end 408 @end
@@ -97,6 +97,7 @@ static NSError *ASIUnableToCreateRequestError; @@ -97,6 +97,7 @@ static NSError *ASIUnableToCreateRequestError;
97 [self setTimeOutSeconds:10]; 97 [self setTimeOutSeconds:10];
98 [self setUseSessionPersistance:YES]; 98 [self setUseSessionPersistance:YES];
99 [self setUseCookiePersistance:YES]; 99 [self setUseCookiePersistance:YES];
  100 + [self setValidatesSecureCertificate:YES];
100 [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]]; 101 [self setRequestCookies:[[[NSMutableArray alloc] init] autorelease]];
101 [self setDidFinishSelector:@selector(requestFinished:)]; 102 [self setDidFinishSelector:@selector(requestFinished:)];
102 [self setDidFailSelector:@selector(requestFailed:)]; 103 [self setDidFailSelector:@selector(requestFailed:)];
@@ -434,6 +435,11 @@ static NSError *ASIUnableToCreateRequestError; @@ -434,6 +435,11 @@ static NSError *ASIUnableToCreateRequestError;
434 // Tell CFNetwork to automatically redirect for 30x status codes 435 // Tell CFNetwork to automatically redirect for 30x status codes
435 CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPShouldAutoredirect, [self shouldRedirect] ? kCFBooleanTrue : kCFBooleanFalse); 436 CFReadStreamSetProperty(readStream, kCFStreamPropertyHTTPShouldAutoredirect, [self shouldRedirect] ? kCFBooleanTrue : kCFBooleanFalse);
436 437
  438 + // Tell CFNetwork not to validate SSL certificates
  439 + if (!validatesSecureCertificate) {
  440 + CFReadStreamSetProperty(readStream, kCFStreamPropertySSLSettings, [NSMutableDictionary dictionaryWithObject:(NSString *)kCFBooleanFalse forKey:(NSString *)kCFStreamSSLValidatesCertificateChain]);
  441 + }
  442 +
437 // Set the client 443 // Set the client
438 CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL}; 444 CFStreamClientContext ctxt = {0, self, NULL, NULL, NULL};
439 if (!CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt)) { 445 if (!CFReadStreamSetClient(readStream, kNetworkEvents, ReadStreamClientCallBack, &ctxt)) {
@@ -1350,12 +1356,28 @@ static NSError *ASIUnableToCreateRequestError; @@ -1350,12 +1356,28 @@ static NSError *ASIUnableToCreateRequestError;
1350 { 1356 {
1351 NSError *underlyingError = [(NSError *)CFReadStreamCopyError(readStream) autorelease]; 1357 NSError *underlyingError = [(NSError *)CFReadStreamCopyError(readStream) autorelease];
1352 1358
  1359 +
  1360 +
1353 [self cancelLoad]; 1361 [self cancelLoad];
1354 [self setComplete:YES]; 1362 [self setComplete:YES];
1355 1363
1356 if (![self error]) { // We may already have handled this error 1364 if (![self error]) { // We may already have handled this error
1357 1365
1358 - [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:@"A connection failure occurred",NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]]; 1366 +
  1367 + NSString *reason = @"A connection failure occurred";
  1368 +
  1369 + // We'll use a custom error message for common SSL errors, but you should always check underlying error if you want more details
  1370 + if ([[underlyingError domain] isEqualToString:NSOSStatusErrorDomain]) {
  1371 + if ([underlyingError code] == errSSLUnknownRootCert) {
  1372 + reason = [NSString stringWithFormat:@"%@: Secure certificate had an untrusted root",reason];
  1373 + } else if ([underlyingError code] == errSSLCertExpired) {
  1374 + reason = [NSString stringWithFormat:@"%@: Secure certificate expired",reason];
  1375 + } else if ([underlyingError code] >= -9807 || [underlyingError code] <= -9818) {
  1376 + reason = [NSString stringWithFormat:@"%@: SSL problem (probably a bad certificate)",reason];
  1377 + }
  1378 + }
  1379 +
  1380 + [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain code:ASIConnectionFailureErrorType userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,underlyingError,NSUnderlyingErrorKey,nil]]];
1359 } 1381 }
1360 [super cancel]; 1382 [super cancel];
1361 } 1383 }
@@ -1640,4 +1662,5 @@ static NSError *ASIUnableToCreateRequestError; @@ -1640,4 +1662,5 @@ static NSError *ASIUnableToCreateRequestError;
1640 @synthesize authenticationRetryCount; 1662 @synthesize authenticationRetryCount;
1641 @synthesize updatedProgress; 1663 @synthesize updatedProgress;
1642 @synthesize shouldRedirect; 1664 @synthesize shouldRedirect;
  1665 +@synthesize validatesSecureCertificate;
1643 @end 1666 @end
@@ -32,5 +32,5 @@ @@ -32,5 +32,5 @@
32 - (void)testCharacterEncoding; 32 - (void)testCharacterEncoding;
33 - (void)testCompressedResponse; 33 - (void)testCompressedResponse;
34 - (void)testCompressedResponseDownloadToFile; 34 - (void)testCompressedResponseDownloadToFile;
35 - 35 +- (void)testSSL;
36 @end 36 @end
@@ -616,4 +616,24 @@ @@ -616,4 +616,24 @@
616 GHAssertTrue(success,@"Failed to correctly display increment progress for a partial download"); 616 GHAssertTrue(success,@"Failed to correctly display increment progress for a partial download");
617 } 617 }
618 618
  619 +- (void)testSSL
  620 +{
  621 + NSURL *url = [NSURL URLWithString:@"https://selfsigned.allseeing-i.com"];
  622 + ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
  623 + [request start];
  624 +
  625 + GHAssertNotNil([request error],@"Failed to generate an error for a self-signed certificate");
  626 +
  627 + // Just for testing the request generated a custom error description - don't do this! You should look at the domain / code of the underlyingError in your own programs.
  628 + BOOL success = ([[[request error] localizedDescription] isEqualToString:@"A connection failure occurred: Secure certificate had an untrusted root"]);
  629 + GHAssertTrue(success,@"Basic synchronous request failed");
  630 +
  631 + // Turn off certificate validation, and try again
  632 + request = [ASIHTTPRequest requestWithURL:url];
  633 + [request setValidatesSecureCertificate:NO];
  634 + [request start];
  635 +
  636 + GHAssertNil([request error],@"Failed to accept a self-signed certificate");
  637 +}
  638 +
619 @end 639 @end