Last active
December 27, 2015 00:59
-
-
Save jorgenpt/7241694 to your computer and use it in GitHub Desktop.
Utility class to retrieve HTTPS certificates from a server.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
@protocol HTTPSCertificateRetrieverDelegate <NSObject> | |
/** | |
* Called when we succeed (or fail) to retrieve the certificate. | |
* | |
* @param certificate nil if we fail, otherwise the data of the certificate. | |
*/ | |
- (void)certificateRetrieved:(NSData*)certificate; | |
@end | |
@interface HTTPSCertificateRetriever : NSObject | |
/// Server's HTTPS certificate, nil before delegate has received certificateRetrieved. | |
@property (readonly) NSData *certificate; | |
@property (weak) id<HTTPSCertificateRetrieverDelegate> delegate; | |
/// Instantiate a certificate retriever for the given HTTPS URL. | |
- (instancetype)initWithURL:(NSURL*)url | |
delegate:(id<HTTPSCertificateRetrieverDelegate>)delegate; | |
/// Asynchronously start the HTTPS request on the current CFRunLoop. | |
- (void)startWithTimeout:(NSTimeInterval)timeout | |
retries:(NSInteger)retries; | |
/// Stop the certificate attempt. | |
- (void)stop; | |
/// YES if the certificate retrieving request has completed. | |
- (BOOL)hasCompleted; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "HTTPSCertificateRetriever.h" | |
#import "NSTimer+Blocks.h" | |
@interface HTTPSCertificateRetriever () <NSURLConnectionDelegate> | |
@property (strong) NSData *certificate; | |
@property (strong) NSInputStream *readStream; | |
@property (strong) NSTimer *timeoutTimer; | |
@property (assign) BOOL hasCompleted; | |
@property (strong) id request; | |
- (void)handleStreamEvent:(CFStreamEventType)event; | |
@end | |
static void bridgeStreamEvent(CFReadStreamRef stream, CFStreamEventType type, void *clientCallBackInfo) | |
{ | |
HTTPSCertificateRetriever *self = (__bridge HTTPSCertificateRetriever *)clientCallBackInfo; | |
[self handleStreamEvent:type]; | |
} | |
@implementation HTTPSCertificateRetriever | |
- (instancetype)initWithURL:(NSURL*)url | |
delegate:(id<HTTPSCertificateRetrieverDelegate>)delegate | |
{ | |
self = [super init]; | |
if (self) | |
{ | |
if (![[[url scheme] lowercaseString] isEqualToString:@"https"]) | |
return nil; | |
self.delegate = delegate; | |
self.request = CFBridgingRelease(CFHTTPMessageCreateRequest(NULL, CFSTR("GET"), (__bridge CFURLRef)url, kCFHTTPVersion1_0)); | |
} | |
return self; | |
} | |
- (void)dealloc | |
{ | |
[self closeStream]; | |
} | |
- (CFReadStreamRef)cfReadStream | |
{ | |
return (__bridge CFReadStreamRef)self.readStream; | |
} | |
- (void)startWithTimeout:(NSTimeInterval)timeout | |
retries:(NSInteger)retries | |
{ | |
if (self.readStream) | |
[self stop]; | |
self.hasCompleted = NO; | |
self.certificate = nil; | |
if (timeout > 0.0) | |
{ | |
self.timeoutTimer = [NSTimer timerWithTimeInterval:timeout | |
block:^{ | |
[self closeStream]; | |
if (retries > 0) | |
{ | |
[self startWithTimeout:timeout | |
retries:(retries - 1)]; | |
} | |
else | |
{ | |
self.hasCompleted = YES; | |
[self.delegate certificateRetrieved:nil]; | |
} | |
} | |
repeats:NO]; | |
[[NSRunLoop mainRunLoop] addTimer:self.timeoutTimer | |
forMode:NSRunLoopCommonModes]; | |
} | |
else | |
{ | |
if ([self.timeoutTimer isValid]) | |
{ | |
[self.timeoutTimer invalidate]; | |
self.timeoutTimer = nil; | |
} | |
} | |
self.readStream = CFBridgingRelease(CFReadStreamCreateForHTTPRequest(NULL, (__bridge CFHTTPMessageRef)self.request)); | |
CFStreamClientContext context = { 0, (__bridge void *)(self), NULL, NULL, NULL }; | |
CFOptionFlags streamEvents = kCFStreamEventHasBytesAvailable | kCFStreamEventEndEncountered | kCFStreamEventErrorOccurred; | |
CFReadStreamSetClient(self.cfReadStream, streamEvents, bridgeStreamEvent, &context); | |
/* We set these settings so we'll accept self-signed certs as well as expired certificates. */ | |
NSDictionary *sslSettings = @{ | |
(NSString*)kCFStreamSSLAllowsAnyRoot: @YES, | |
(NSString*)kCFStreamSSLValidatesCertificateChain: @NO, | |
(NSString*)kCFStreamSSLAllowsExpiredCertificates: @YES, | |
(NSString*)kCFStreamSSLAllowsExpiredRoots: @YES, | |
}; | |
CFReadStreamSetProperty(self.cfReadStream, kCFStreamPropertySSLSettings, (__bridge CFTypeRef)(sslSettings)); | |
CFReadStreamScheduleWithRunLoop(self.cfReadStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); | |
CFReadStreamOpen(self.cfReadStream); | |
} | |
- (void)stop | |
{ | |
[self closeStream]; | |
} | |
- (void)closeStream | |
{ | |
if ([self.timeoutTimer isValid]) | |
{ | |
[self.timeoutTimer invalidate]; | |
self.timeoutTimer = nil; | |
} | |
if (self.readStream) | |
{ | |
CFReadStreamClose(self.cfReadStream); | |
CFReadStreamSetClient(self.cfReadStream, kCFStreamEventNone, NULL, NULL); | |
CFReadStreamUnscheduleFromRunLoop(self.cfReadStream, CFRunLoopGetCurrent(), kCFRunLoopCommonModes); | |
self.readStream = nil; | |
} | |
} | |
- (void)handleStreamEvent:(CFStreamEventType)event | |
{ | |
NSArray *certs = [self.readStream propertyForKey:(NSString*)kCFStreamPropertySSLPeerCertificates]; | |
SecCertificateRef certificateRef = NULL; | |
if ([certs count] > 0) | |
certificateRef = (__bridge SecCertificateRef)([certs objectAtIndex:0]); | |
if (certificateRef) | |
self.certificate = CFBridgingRelease(SecCertificateCopyData(certificateRef)); | |
else | |
self.certificate = nil; | |
[self closeStream]; | |
self.hasCompleted = YES; | |
[self.delegate certificateRetrieved:self.certificate]; | |
} | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import <Foundation/Foundation.h> | |
@interface NSTimer (Blocks) | |
+ (id)scheduledTimerWithTimeInterval:(NSTimeInterval)aTimeInterval | |
block:(void (^)())aBlock | |
repeats:(BOOL)repeats; | |
+ (id)timerWithTimeInterval:(NSTimeInterval)aTimeInterval | |
block:(void (^)())aBlock | |
repeats:(BOOL)repeats; | |
@end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#import "NSTimer+Blocks.h" | |
@implementation NSTimer (Blocks) | |
+ (id)scheduledTimerWithTimeInterval:(NSTimeInterval)aTimeInterval | |
block:(void (^)())aBlock | |
repeats:(BOOL)repeats | |
{ | |
return [self scheduledTimerWithTimeInterval:aTimeInterval | |
target:self | |
selector:@selector(executeBlock:) | |
userInfo:[aBlock copy] | |
repeats:repeats]; | |
} | |
+ (id)timerWithTimeInterval:(NSTimeInterval)aTimeInterval | |
block:(void (^)())aBlock | |
repeats:(BOOL)repeats | |
{ | |
return [self timerWithTimeInterval:aTimeInterval | |
target:self | |
selector:@selector(executeBlock:) | |
userInfo:[aBlock copy] | |
repeats:repeats]; | |
} | |
+ (void)executeBlock:(NSTimer *)aTimer; | |
{ | |
if ([aTimer userInfo]) | |
{ | |
void (^block)() = (void (^)())[aTimer userInfo]; | |
block(); | |
} | |
} | |
@end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment