Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

Fix for self-signed client certificates on iOS 5 (alternate) #314

Open
wants to merge 1 commit into from

2 participants

@james-chalfant

1.) Restructure handling of ssl options (i.e., kCFStreamPropertySSLSettings) so that disabling SSL certificate verification and adding an SSL client certificate are not mutually exclusive. Also removed several settings which appear unneeded to successfully use a self-signed certificate / a certificate using a self generated CA based on my testing; further context on the necessity of these options (kCFStreamSSLAllowsExpiredCertificates, kCFStreamSSLAllowsAnyRoot, setting kCFStreamSSLPeerName to kCFNull) would be useful.

2.) Add option to specify an SSL CA Certificate to use instead of the normal root certificates for verifying a server certificate. Note that this isn't handled in an ideal fashion - certificate verification is completely disabled for the initial handshake, then manually checked on the first read event (i.e., when handleNetworkEvent gets a kCFStreamEventHasBytesAvailable event). I was unable to succeed at other methods of adding a root CA certificate (adding it to the keychain, altering the ssl context)

FYI, this is my first time using github/submitting, so feedback is welcome.

James Chalfant 1.) Restructing handling of ssl options (i.e., kCFStreamPropertySSLSe…
…ttings) so that disabling SSL certificate verification and adding an SSL client certificate are not mutually exclusive

2.) Add option to specify an SSL CA Certificate to use instead of the normal root certificates for verifying a server certificate. Note that this isn't handled in an ideal fashion - certificate verification is completely disabled for the initial handshake, then manually checked on the first read event (i.e., when handleNetworkEvent gets a kCFStreamEventHasBytesAvailable event). I was unable to succeed at other methods of adding a root CA certificate (adding it to the keychain, altering the ssl context).
14a587c
@sebbu

Works great. Thanks

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 10, 2012
  1. 1.) Restructing handling of ssl options (i.e., kCFStreamPropertySSLSe…

    James Chalfant authored
    …ttings) so that disabling SSL certificate verification and adding an SSL client certificate are not mutually exclusive
    
    2.) Add option to specify an SSL CA Certificate to use instead of the normal root certificates for verifying a server certificate. Note that this isn't handled in an ideal fashion - certificate verification is completely disabled for the initial handshake, then manually checked on the first read event (i.e., when handleNetworkEvent gets a kCFStreamEventHasBytesAvailable event). I was unable to succeed at other methods of adding a root CA certificate (adding it to the keychain, altering the ssl context).
This page is out of date. Refresh to see the latest.
Showing with 111 additions and 37 deletions.
  1. +11 −0 Classes/ASIHTTPRequest.h
  2. +100 −37 Classes/ASIHTTPRequest.m
View
11 Classes/ASIHTTPRequest.h
@@ -368,8 +368,12 @@ typedef void (^ASIDataBlock)(NSData *data);
// If not nil and the URL scheme is https, CFNetwork configured to supply a client certificate
SecIdentityRef clientCertificateIdentity;
+
NSArray *clientCertificates;
+ // If not nil and the URL scheme is https, CFNetwork configured to check server certificates with ONLY this CA certificate
+ SecCertificateRef caCertificate;
+
// Details on the proxy to use - you could set these yourself, but it's probably best to let ASIHTTPRequest detect the system proxy settings
NSString *proxyHost;
int proxyPort;
@@ -440,6 +444,9 @@ typedef void (^ASIDataBlock)(NSData *data);
// Used internally to record when a request has finished downloading data
BOOL downloadComplete;
+ // Used internally to record when a request has been checked against a ca certificate
+ BOOL caCertificateCheckComplete;
+
// An ID that uniquely identifies this request - primarily used for debugging persistent connections
NSNumber *requestID;
@@ -755,6 +762,10 @@ typedef void (^ASIDataBlock)(NSData *data);
- (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity;
+#pragma mark ca certificate
+
+- (void)setCaCertificate:(SecCertificateRef)aCaCertificate;
+
#pragma mark session credentials
+ (NSMutableArray *)sessionProxyCredentialsStore;
View
137 Classes/ASIHTTPRequest.m
@@ -180,6 +180,8 @@ - (void)timeOutPACRead;
- (void)useDataFromCache;
+- (BOOL)checkCaCertificate;
+
// Called to update the size of a partial download when starting a request, or retrying after a timeout
- (void)updatePartialDownloadSize;
@@ -234,6 +236,7 @@ - (void)callBlock:(ASIBasicBlock)block;
@property (assign) ASIAuthenticationState authenticationNeeded;
@property (assign, nonatomic) BOOL readStreamIsScheduled;
@property (assign, nonatomic) BOOL downloadComplete;
+@property (assign, nonatomic) BOOL caCertificateCheckComplete;
@property (retain) NSNumber *requestID;
@property (assign, nonatomic) NSString *runLoopMode;
@property (retain, nonatomic) NSTimer *statusTimer;
@@ -344,6 +347,9 @@ - (void)dealloc
if (clientCertificateIdentity) {
CFRelease(clientCertificateIdentity);
}
+ if (caCertificate) {
+ CFRelease(caCertificate);
+ }
[self cancelLoad];
[redirectURL release];
[statusTimer invalidate];
@@ -1205,44 +1211,41 @@ - (void)startRequest
// Handle SSL certificate settings
//
- if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) {
-
- // Tell CFNetwork not to validate SSL certificates
- if (![self validatesSecureCertificate]) {
- // see: http://iphonedevelopment.blogspot.com/2010/05/nsstream-tcp-and-ssl.html
-
- NSDictionary *sslProperties = [[NSDictionary alloc] initWithObjectsAndKeys:
- [NSNumber numberWithBool:YES], kCFStreamSSLAllowsExpiredCertificates,
- [NSNumber numberWithBool:YES], kCFStreamSSLAllowsAnyRoot,
- [NSNumber numberWithBool:NO], kCFStreamSSLValidatesCertificateChain,
- kCFNull,kCFStreamSSLPeerName,
- nil];
-
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream],
- kCFStreamPropertySSLSettings,
- (CFTypeRef)sslProperties);
- [sslProperties release];
- }
-
- // Tell CFNetwork to use a client certificate
- if (clientCertificateIdentity) {
- NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
-
- NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
-
- // The first object in the array is our SecIdentityRef
- [certificates addObject:(id)clientCertificateIdentity];
-
- // If we've added any additional certificates, add them too
- for (id cert in clientCertificates) {
- [certificates addObject:cert];
+ if([[[[self url] scheme] lowercaseString] isEqualToString:@"https"])
+ {
+ NSMutableDictionary *sslProperties = [NSMutableDictionary dictionaryWithCapacity:1];
+
+ // Tell CFNetwork not to validate SSL certificates
+ if ((!validatesSecureCertificate) || (caCertificate))
+ {
+ [sslProperties setObject:[NSNumber numberWithBool:NO] forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
+ if (caCertificate)
+ {
+ [self setCaCertificateCheckComplete:NO];
+ }
}
-
- [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
-
- CFReadStreamSetProperty((CFReadStreamRef)[self readStream], kCFStreamPropertySSLSettings, sslProperties);
- }
-
+ else
+ {
+ [sslProperties setObject:[NSNumber numberWithBool:YES] forKey:(NSString *)kCFStreamSSLValidatesCertificateChain];
+ }
+
+ // Tell CFNetwork to use a client certificate
+ if (clientCertificateIdentity)
+ {
+ NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[clientCertificates count]+1];
+
+ // The first object in the array is our SecIdentityRef
+ [certificates addObject:(id)clientCertificateIdentity];
+
+ // If we've added any additional certificates, add them too
+ for (id cert in clientCertificates)
+ {
+ [certificates addObject:cert];
+ }
+ [sslProperties setObject:certificates forKey:(NSString *)kCFStreamSSLCertificates];
+ }
+
+ CFReadStreamSetProperty((CFReadStreamRef)readStream, kCFStreamPropertySSLSettings, sslProperties);
}
//
@@ -1640,6 +1643,7 @@ - (ASIHTTPRequest *)HEADRequest
[headRequest setUseHTTPVersionOne:[self useHTTPVersionOne]];
[headRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
[headRequest setClientCertificateIdentity:clientCertificateIdentity];
+ [headRequest setCaCertificate:caCertificate];
[headRequest setClientCertificates:[[clientCertificates copy] autorelease]];
[headRequest setPACurl:[self PACurl]];
[headRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
@@ -3242,6 +3246,11 @@ - (BOOL)willAskDelegateToConfirmRedirect
- (void)handleBytesAvailable
{
+ if (![self checkCaCertificate])
+ {
+ return;
+ }
+
if (![self responseHeaders]) {
[self readResponseHeaders];
}
@@ -3647,6 +3656,43 @@ - (BOOL)retryUsingNewConnection
return NO;
}
+- (BOOL)checkCaCertificate
+{
+ BOOL success = YES;
+ if (([[[[self url] scheme] lowercaseString] isEqualToString:@"https"]) &&
+ (caCertificate) &&
+ (![self caCertificateCheckComplete]))
+ {
+ SecPolicyRef policy = SecPolicyCreateSSL(NO, (CFStringRef)[[self url] host]);
+ SecTrustRef trust = NULL;
+ CFArrayRef streamCertificates =
+ (CFArrayRef)[readStream propertyForKey:(NSString*)kCFStreamPropertySSLPeerCertificates];
+ SecTrustCreateWithCertificates(streamCertificates,
+ policy,
+ &trust);
+ SecTrustSetAnchorCertificates(trust, (CFArrayRef)[NSArray arrayWithObject:(id) caCertificate]);
+ SecTrustResultType trustResultType = kSecTrustResultInvalid;
+ OSStatus status = SecTrustEvaluate(trust, &trustResultType);
+ if ((status != errSecSuccess) || (trustResultType != kSecTrustResultUnspecified))
+ {
+ NSString *reason = @"A connection failure occurred: SSL problem (certificate failed check against user assigned ca)";
+ [self cancelLoad];
+ [self failWithError:[NSError errorWithDomain:NetworkRequestErrorDomain
+ code:ASIConnectionFailureErrorType
+ userInfo:[NSDictionary dictionaryWithObjectsAndKeys:reason,NSLocalizedDescriptionKey,nil]]];
+ success = NO;
+ }
+ if (trust) {
+ CFRelease(trust);
+ }
+ if (policy) {
+ CFRelease(policy);
+ }
+ [self setCaCertificateCheckComplete:YES];
+ }
+ return success;
+}
+
- (void)handleStreamError
{
@@ -4091,6 +4137,7 @@ - (id)copyWithZone:(NSZone *)zone
[newRequest setShouldRedirect:[self shouldRedirect]];
[newRequest setValidatesSecureCertificate:[self validatesSecureCertificate]];
[newRequest setClientCertificateIdentity:clientCertificateIdentity];
+ [newRequest setCaCertificate:caCertificate];
[newRequest setClientCertificates:[[clientCertificates copy] autorelease]];
[newRequest setPACurl:[self PACurl]];
[newRequest setShouldPresentCredentialsBeforeChallenge:[self shouldPresentCredentialsBeforeChallenge]];
@@ -4130,6 +4177,21 @@ - (void)setClientCertificateIdentity:(SecIdentityRef)anIdentity {
}
+#pragma mark ca certificate
+
+- (void)setCaCertificate:(SecCertificateRef)aCaCertificate {
+ if(caCertificate) {
+ CFRelease(caCertificate);
+ }
+
+ caCertificate = aCaCertificate;
+
+ if (caCertificate) {
+ CFRetain(caCertificate);
+ }
+}
+
+
#pragma mark session credentials
+ (NSMutableArray *)sessionProxyCredentialsStore
@@ -5100,6 +5162,7 @@ - (void)setRequestRedirectedBlock:(ASIBasicBlock)aRedirectBlock
@synthesize readStreamIsScheduled;
@synthesize shouldUseRFC2616RedirectBehaviour;
@synthesize downloadComplete;
+@synthesize caCertificateCheckComplete;
@synthesize requestID;
@synthesize runLoopMode;
@synthesize statusTimer;
Something went wrong with that request. Please try again.