diff --git a/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h index 6ab5e1e96..2f784377b 100644 --- a/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h +++ b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.h @@ -15,7 +15,7 @@ NS_ASSUME_NONNULL_BEGIN @interface SRPinningSecurityPolicy : SRSecurityPolicy -- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates; +- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys; @end diff --git a/SocketRocket/Internal/Security/SRPinningSecurityPolicy.m b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.m index 0eecd2bed..f94bc6955 100644 --- a/SocketRocket/Internal/Security/SRPinningSecurityPolicy.m +++ b/SocketRocket/Internal/Security/SRPinningSecurityPolicy.m @@ -8,6 +8,7 @@ // #import "SRPinningSecurityPolicy.h" +#import #import @@ -18,12 +19,13 @@ @interface SRPinningSecurityPolicy () @property (nonatomic, copy, readonly) NSArray *pinnedCertificates; +@property (nonatomic, assign, readonly) BOOL comparesPublicKeys; @end @implementation SRPinningSecurityPolicy -- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates +- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys { // Do not validate certificate chain since we're pinning to specific certificates. self = [super initWithCertificateChainValidationEnabled:NO]; @@ -35,6 +37,7 @@ - (instancetype)initWithCertificates:(NSArray *)pinnedCertificates userInfo:nil]; } _pinnedCertificates = [pinnedCertificates copy]; + _comparesPublicKeys = comparesPublicKeys; return self; } @@ -53,7 +56,7 @@ - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domai SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref; // TODO: (nlutsenko) Add caching, so we don't copy the data for every pinned cert all the time. NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert)); - if ([trustedCertData isEqualToData:data]) { + if ([self isServerCertificateDataValid:data trustedCertData:trustedCertData]) { validatedCertCount++; break; } @@ -62,6 +65,33 @@ - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domai return (requiredCertCount == validatedCertCount); } +- (BOOL)isServerCertificateDataValid:(NSData *)serverCertData trustedCertData:(NSData *)trustedCertData +{ + if (_comparesPublicKeys) { + return [[self getPublicKeyStringFromData:trustedCertData] isEqualToString:[self getPublicKeyStringFromData:serverCertData]]; + } else { + return [trustedCertData isEqualToData:serverCertData]; + } +} + +- (NSString *)getPublicKeyStringFromData:(NSData *)data +{ + const unsigned char *certificateDataBytes = (const unsigned char *)[data bytes]; + X509 *certificateX509 = d2i_X509(NULL, &certificateDataBytes, [data length]); + ASN1_BIT_STRING *pubKey2 = X509_get0_pubkey_bitstr(certificateX509); + + NSString *publicKeyString = [[NSString alloc] init]; + + for (int i = 0; i < pubKey2->length; i++) { + NSString *aString = [NSString stringWithFormat:@"%02x", pubKey2->data[i]]; + publicKeyString = [publicKeyString stringByAppendingString:aString]; + } + + X509_free(certificateX509); + + return publicKeyString; +} + @end NS_ASSUME_NONNULL_END diff --git a/SocketRocket/NSURLRequest+SRWebSocket.h b/SocketRocket/NSURLRequest+SRWebSocket.h index 32d78da36..e756c0e2a 100644 --- a/SocketRocket/NSURLRequest+SRWebSocket.h +++ b/SocketRocket/NSURLRequest+SRWebSocket.h @@ -20,6 +20,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, nonatomic, copy, readonly) NSArray *SR_SSLPinnedCertificates; +/** + A boolean value indicating whether equivalent public keys will be sufficient for validating SSL certificates. + */ +@property (nonatomic, assign, readonly) BOOL SR_comparesPublicKeys; + @end @interface NSMutableURLRequest (SRWebSocket) @@ -29,6 +34,11 @@ NS_ASSUME_NONNULL_BEGIN */ @property (nullable, nonatomic, copy) NSArray *SR_SSLPinnedCertificates; +/** + A boolean value indicating whether equivalent public keys will be sufficient for validating SSL certificates. + */ +@property (nonatomic, assign) BOOL SR_comparesPublicKeys; + @end NS_ASSUME_NONNULL_END diff --git a/SocketRocket/NSURLRequest+SRWebSocket.m b/SocketRocket/NSURLRequest+SRWebSocket.m index 27f87434e..f92150013 100644 --- a/SocketRocket/NSURLRequest+SRWebSocket.m +++ b/SocketRocket/NSURLRequest+SRWebSocket.m @@ -18,6 +18,7 @@ void import_NSURLRequest_SRWebSocket() { } NS_ASSUME_NONNULL_BEGIN static NSString *const SRSSLPinnnedCertificatesKey = @"SocketRocket_SSLPinnedCertificates"; +static NSString *const SRSSLComparesPublicKeysKey = @"SocketRocket_SSLComparesPublicKeys"; @implementation NSURLRequest (SRWebSocket) @@ -26,6 +27,11 @@ - (nullable NSArray *)SR_SSLPinnedCertificates return [NSURLProtocol propertyForKey:SRSSLPinnnedCertificatesKey inRequest:self]; } +- (BOOL)SR_comparesPublicKeys +{ + return [[NSURLProtocol propertyForKey:SRSSLComparesPublicKeysKey inRequest:self] boolValue]; +} + @end @implementation NSMutableURLRequest (SRWebSocket) @@ -40,6 +46,16 @@ - (void)setSR_SSLPinnedCertificates:(nullable NSArray *)SR_SSLPinnedCertificates [NSURLProtocol setProperty:[SR_SSLPinnedCertificates copy] forKey:SRSSLPinnnedCertificatesKey inRequest:self]; } +- (BOOL)SR_comparesPublicKeys +{ + return [[NSURLProtocol propertyForKey:SRSSLComparesPublicKeysKey inRequest:self] boolValue]; +} + +- (void)setSR_comparesPublicKeys:(BOOL)SR_comparesPublicKeys +{ + [NSURLProtocol setProperty:[NSNumber numberWithBool:SR_comparesPublicKeys] forKey:SRSSLComparesPublicKeysKey inRequest:self]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SocketRocket/SRSecurityPolicy.h b/SocketRocket/SRSecurityPolicy.h index 85e41c549..7cc65b76c 100644 --- a/SocketRocket/SRSecurityPolicy.h +++ b/SocketRocket/SRSecurityPolicy.h @@ -27,8 +27,9 @@ NS_ASSUME_NONNULL_BEGIN chain validation. @param pinnedCertificates Array of `SecCertificateRef` SSL certificates to use for validation. + @param comparesPublicKeys Boolean to indicate whether equivalent public keys will be sufficient for validating SSL certificates. */ -+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates; ++ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys; /** Specifies socket security and optional certificate chain validation. diff --git a/SocketRocket/SRSecurityPolicy.m b/SocketRocket/SRSecurityPolicy.m index 3997c14fc..8b93a08d5 100644 --- a/SocketRocket/SRSecurityPolicy.m +++ b/SocketRocket/SRSecurityPolicy.m @@ -25,9 +25,9 @@ + (instancetype)defaultPolicy return [self new]; } -+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates ++ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates comparesPublicKeys:(BOOL)comparesPublicKeys { - return [[SRPinningSecurityPolicy alloc] initWithCertificates:pinnedCertificates]; + return [[SRPinningSecurityPolicy alloc] initWithCertificates:pinnedCertificates comparesPublicKeys:comparesPublicKeys]; } - (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 43c51aedd..8a6530db5 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -187,8 +187,9 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray