diff --git a/SocketRocket.xcodeproj/project.pbxproj b/SocketRocket.xcodeproj/project.pbxproj index 3522975e8..59ef1bd8c 100644 --- a/SocketRocket.xcodeproj/project.pbxproj +++ b/SocketRocket.xcodeproj/project.pbxproj @@ -17,6 +17,18 @@ 3345DC871C52ACD70083CCB8 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6A12CD3145122FC00C1D980 /* Security.framework */; }; 3345DC881C52ACD70083CCB8 /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6B208301450F597009315AF /* Foundation.framework */; }; 3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */ = {isa = PBXBuildFile; fileRef = F6A12CCF145119B700C1D980 /* SRWebSocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 454A02D51D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 454A02D61D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */; }; + 454A02D71D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */; }; + 454A02D81D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */; }; + 45A5E7AF1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 45A5E7AD1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h */; }; + 45A5E7B01D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45A5E7AE1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m */; }; + 45A5E7B11D234B5800C4EE5B /* SRSecurityPolicyBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 45A5E7AD1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h */; }; + 45A5E7B21D234B5800C4EE5B /* SRSecurityPolicyBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45A5E7AE1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m */; }; + 45A5E7B31D234B5900C4EE5B /* SRSecurityPolicyBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 45A5E7AD1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h */; }; + 45A5E7B41D234B5900C4EE5B /* SRSecurityPolicyBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45A5E7AE1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m */; }; + 45A5E7B51D234B5A00C4EE5B /* SRSecurityPolicyBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = 45A5E7AD1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h */; }; + 45A5E7B61D234B5A00C4EE5B /* SRSecurityPolicyBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45A5E7AE1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m */; }; 4861E7751D022211002FAB1D /* SRProxyConnect.h in Headers */ = {isa = PBXBuildFile; fileRef = 4861E7731D022211002FAB1D /* SRProxyConnect.h */; }; 4861E7761D022211002FAB1D /* SRProxyConnect.m in Sources */ = {isa = PBXBuildFile; fileRef = 4861E7741D022211002FAB1D /* SRProxyConnect.m */; }; 555E0EB41C51E57A00E6BB92 /* SocketRocket.h in Headers */ = {isa = PBXBuildFile; fileRef = 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */; settings = {ATTRIBUTES = (Public, ); }; }; @@ -50,14 +62,6 @@ 8179958C1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; }; 8179958D1CE139700084DA37 /* SRDelegateController.m in Sources */ = {isa = PBXBuildFile; fileRef = 817995851CE139700084DA37 /* SRDelegateController.m */; }; 817996801CE184F40084DA37 /* SRAutobahnUtilities.m in Sources */ = {isa = PBXBuildFile; fileRef = 8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */; }; - 8186892F1D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */; }; - 818689301D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */; }; - 818689311D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */; }; - 818689321D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */ = {isa = PBXBuildFile; fileRef = 8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */; }; - 818689331D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */; }; - 818689341D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */; }; - 818689351D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */; }; - 818689361D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */ = {isa = PBXBuildFile; fileRef = 8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */; }; 81900A4C1D18C9CC0015A290 /* SRLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 81900A4A1D18C9CC0015A290 /* SRLog.h */; }; 81900A4D1D18C9CC0015A290 /* SRLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 81900A4A1D18C9CC0015A290 /* SRLog.h */; }; 81900A4E1D18C9CC0015A290 /* SRLog.h in Headers */ = {isa = PBXBuildFile; fileRef = 81900A4A1D18C9CC0015A290 /* SRLog.h */; }; @@ -190,6 +194,9 @@ /* Begin PBXFileReference section */ 2D4227621BB4358C000C1A6C /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 3345DC901C52ACD70083CCB8 /* SocketRocket.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = SocketRocket.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SRSecurityPolicy.h; path = SocketRocket/SRSecurityPolicy.h; sourceTree = SOURCE_ROOT; }; + 45A5E7AD1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRSecurityPolicyBuilder.h; sourceTree = ""; }; + 45A5E7AE1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRSecurityPolicyBuilder.m; sourceTree = ""; }; 4861E7731D022211002FAB1D /* SRProxyConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRProxyConnect.h; sourceTree = ""; }; 4861E7741D022211002FAB1D /* SRProxyConnect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRProxyConnect.m; sourceTree = ""; }; 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketRocket.h; sourceTree = ""; }; @@ -207,8 +214,6 @@ 817995851CE139700084DA37 /* SRDelegateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRDelegateController.m; sourceTree = ""; }; 8179967E1CE184F40084DA37 /* SRAutobahnUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRAutobahnUtilities.h; sourceTree = ""; }; 8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnUtilities.m; sourceTree = ""; }; - 8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRSecurityOptions.h; sourceTree = ""; }; - 8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRSecurityOptions.m; sourceTree = ""; }; 81900A4A1D18C9CC0015A290 /* SRLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRLog.h; sourceTree = ""; }; 81900A4B1D18C9CC0015A290 /* SRLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRLog.m; sourceTree = ""; }; 81B22EC31CE42D7E0073C636 /* SRError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRError.h; sourceTree = ""; }; @@ -401,8 +406,8 @@ 8186892C1D08EF3C004F94C8 /* Security */ = { isa = PBXGroup; children = ( - 8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */, - 8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */, + 45A5E7AD1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h */, + 45A5E7AE1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m */, ); path = Security; sourceTree = ""; @@ -554,6 +559,7 @@ children = ( 81B31C0D1CDC404100D86D43 /* Internal */, 555E0EB11C51E56D00E6BB92 /* SocketRocket.h */, + 454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */, F6A12CCF145119B700C1D980 /* SRWebSocket.h */, F6A12CD0145119B700C1D980 /* SRWebSocket.m */, 81CD05D51CEEC47300497F47 /* NSURLRequest+SRWebSocket.h */, @@ -575,11 +581,12 @@ 81B22EE51CE43ECC0073C636 /* SRURLUtilities.h in Headers */, 81B31C151CDC404100D86D43 /* SRIOConsumer.h in Headers */, 81CD05FE1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */, + 454A02D61D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */, 81CD05D81CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */, 81900A4D1D18C9CC0015A290 /* SRLog.h in Headers */, 81B31C1D1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */, 813364001D091E170062E28D /* SRProxyConnect.h in Headers */, - 818689301D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */, + 45A5E7B11D234B5800C4EE5B /* SRSecurityPolicyBuilder.h in Headers */, 2D42277F1BB4365C000C1A6C /* SRWebSocket.h in Headers */, 81B31C2E1CDC406B00D86D43 /* SRHash.h in Headers */, 811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */, @@ -599,11 +606,12 @@ 81B22EE71CE43ECC0073C636 /* SRURLUtilities.h in Headers */, 81B31C171CDC404100D86D43 /* SRIOConsumer.h in Headers */, 81CD06001CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */, + 454A02D81D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */, 81CD05DA1CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */, 81900A4F1D18C9CC0015A290 /* SRLog.h in Headers */, 81B31C1F1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */, 813364081D091E180062E28D /* SRProxyConnect.h in Headers */, - 818689321D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */, + 45A5E7B51D234B5A00C4EE5B /* SRSecurityPolicyBuilder.h in Headers */, 3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */, 81B31C301CDC406B00D86D43 /* SRHash.h in Headers */, 811934C01CDAF726003AB243 /* SocketRocket.h in Headers */, @@ -623,11 +631,12 @@ 81B22EE61CE43ECC0073C636 /* SRURLUtilities.h in Headers */, 81B31C161CDC404100D86D43 /* SRIOConsumer.h in Headers */, 81CD05FF1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */, + 454A02D71D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */, 81CD05D91CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */, 81900A4E1D18C9CC0015A290 /* SRLog.h in Headers */, 81B31C1E1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */, 813364041D091E170062E28D /* SRProxyConnect.h in Headers */, - 818689311D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */, + 45A5E7B31D234B5900C4EE5B /* SRSecurityPolicyBuilder.h in Headers */, F668C8AA153E92F90044DBAC /* SRWebSocket.h in Headers */, 81B31C2F1CDC406B00D86D43 /* SRHash.h in Headers */, 811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */, @@ -646,11 +655,12 @@ files = ( 81B22EE41CE43ECC0073C636 /* SRURLUtilities.h in Headers */, 81B31C141CDC404100D86D43 /* SRIOConsumer.h in Headers */, + 454A02D51D0FAD010060DFB2 /* SRSecurityPolicy.h in Headers */, 81CD05FD1CEEC65D00497F47 /* NSRunLoop+SRWebSocket.h in Headers */, 81CD05D71CEEC47300497F47 /* NSURLRequest+SRWebSocket.h in Headers */, 81900A4C1D18C9CC0015A290 /* SRLog.h in Headers */, - 8186892F1D08EF3C004F94C8 /* SRSecurityOptions.h in Headers */, 81B31C1C1CDC404100D86D43 /* SRIOConsumerPool.h in Headers */, + 45A5E7AF1D234B4C00C4EE5B /* SRSecurityPolicyBuilder.h in Headers */, F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */, 81B31C2D1CDC406B00D86D43 /* SRHash.h in Headers */, 4861E7751D022211002FAB1D /* SRProxyConnect.h in Headers */, @@ -852,9 +862,9 @@ buildActionMask = 2147483647; files = ( 81CD05DC1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */, + 45A5E7B21D234B5800C4EE5B /* SRSecurityPolicyBuilder.m in Sources */, 81B22ECA1CE42D7E0073C636 /* SRError.m in Sources */, 81B31C191CDC404100D86D43 /* SRIOConsumer.m in Sources */, - 818689341D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */, 81C22BC71D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */, 81CD06021CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */, 2D4227851BB43734000C1A6C /* SRWebSocket.m in Sources */, @@ -875,9 +885,9 @@ buildActionMask = 2147483647; files = ( 81CD05DE1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */, + 45A5E7B61D234B5A00C4EE5B /* SRSecurityPolicyBuilder.m in Sources */, 81B22ECC1CE42D7E0073C636 /* SRError.m in Sources */, 81B31C1B1CDC404100D86D43 /* SRIOConsumer.m in Sources */, - 818689361D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */, 81C22BC91D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */, 81CD06041CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */, 3345DC841C52ACD70083CCB8 /* SRWebSocket.m in Sources */, @@ -909,9 +919,9 @@ buildActionMask = 2147483647; files = ( 81CD05DD1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */, + 45A5E7B41D234B5900C4EE5B /* SRSecurityPolicyBuilder.m in Sources */, 81B22ECB1CE42D7E0073C636 /* SRError.m in Sources */, 81B31C1A1CDC404100D86D43 /* SRIOConsumer.m in Sources */, - 818689351D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */, 81C22BC81D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */, 81CD06031CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */, F6396B86153E67EC00345B5E /* SRWebSocket.m in Sources */, @@ -932,7 +942,7 @@ buildActionMask = 2147483647; files = ( 4861E7761D022211002FAB1D /* SRProxyConnect.m in Sources */, - 818689331D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */, + 45A5E7B01D234B4C00C4EE5B /* SRSecurityPolicyBuilder.m in Sources */, 81CD05DB1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */, 81B22EC91CE42D7E0073C636 /* SRError.m in Sources */, 81C22BC61D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */, diff --git a/SocketRocket/Internal/Security/SRSecurityOptions.h b/SocketRocket/Internal/Security/SRSecurityOptions.h deleted file mode 100644 index 573e650eb..000000000 --- a/SocketRocket/Internal/Security/SRSecurityOptions.h +++ /dev/null @@ -1,74 +0,0 @@ -// -// Copyright (c) 2016-present, Facebook, Inc. -// All rights reserved. -// -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import - -NS_ASSUME_NONNULL_BEGIN - -@interface SRSecurityOptions: NSObject - -@property (nonatomic, strong, readonly) NSURLRequest *request; - -/** - Returns `YES` if request uses SSL, otherwise - `NO`. - */ -@property (nonatomic, assign, readonly) BOOL requestRequiresSSL; - -/** - Optional array of `SecCertificateRef` SSL certificates to use for validation. - */ -@property (nullable, nonatomic, strong, readonly) NSArray *pinnedCertificates; - -/** - Set to `NO` to disable SSL certificate chain validation. - This option is not taken into account when using pinned certificates. - Default: YES. - */ -@property (nonatomic, assign, readonly) BOOL validatesCertificateChain; - -/** - Initializes an instance of a controller into it with a given request and returns it. - - @param request Request to initialize with. - */ -- (instancetype)initWithRequest:(NSURLRequest *)request - pinnedCertificates:(nullable NSArray *)pinnedCertificates - chainValidationEnabled:(BOOL)chainValidationEnabled NS_DESIGNATED_INITIALIZER; - -- (instancetype)init NS_UNAVAILABLE; -+ (instancetype)new NS_UNAVAILABLE; - -///-------------------------------------- -#pragma mark - Streams -///-------------------------------------- - -/** - Updates all the security options for the current configuration. - - @param stream Stream to update the options in. - */ -- (void)updateSecurityOptionsInStream:(NSStream *)stream; - -///-------------------------------------- -#pragma mark - Pinned Certificates -///-------------------------------------- - -/** - Validates whether a given security trust contains pinned certificates. - If no certificates are pinned - returns `YES`. - - @param trust Security trust to validate. - - @return `YES` if certificates where found, otherwise - `NO`. - */ -- (BOOL)securityTrustContainsPinnedCertificates:(SecTrustRef)trust; - -@end - -NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Security/SRSecurityOptions.m b/SocketRocket/Internal/Security/SRSecurityOptions.m deleted file mode 100644 index b5200b0c7..000000000 --- a/SocketRocket/Internal/Security/SRSecurityOptions.m +++ /dev/null @@ -1,88 +0,0 @@ -// -// Copyright (c) 2016-present, Facebook, Inc. -// All rights reserved. -// -// This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. -// - -#import "SRSecurityOptions.h" - -#import "SRURLUtilities.h" - -NS_ASSUME_NONNULL_BEGIN - -@implementation SRSecurityOptions - -///-------------------------------------- -#pragma mark - Init -///-------------------------------------- - -- (instancetype)initWithRequest:(NSURLRequest *)request - pinnedCertificates:(nullable NSArray *)pinnedCertificates - chainValidationEnabled:(BOOL)chainValidationEnabled -{ - self = [super init]; - if (!self) return self; - - _request = request; - _requestRequiresSSL = SRURLRequiresSSL(request.URL); - _pinnedCertificates = pinnedCertificates; - _validatesCertificateChain = chainValidationEnabled; - - return self; -} - -///-------------------------------------- -#pragma mark - Stream -///--------------------------------------- - -- (void)updateSecurityOptionsInStream:(NSStream *)stream -{ - // SSL not required, skip everything - if (!self.requestRequiresSSL) { - return; - } - - // Enable highest level of security (`.LevelNegotiatedSSL`) for the stream. - [stream setProperty:NSStreamSocketSecurityLevelNegotiatedSSL forKey:NSStreamSocketSecurityLevelKey]; - - // If we are not using pinned certs and if chain validation is enabled - enable it on a stream. - BOOL chainValidationEnabled = (_pinnedCertificates.count == 0 && self.validatesCertificateChain); - NSDictionary *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(chainValidationEnabled) }; - [stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings]; -} - -///-------------------------------------- -#pragma mark - Pinned Certificates -///-------------------------------------- - -- (BOOL)securityTrustContainsPinnedCertificates:(SecTrustRef)trust -{ - NSUInteger requiredCertCount = self.pinnedCertificates.count; - if (requiredCertCount == 0) { - return YES; - } - - NSUInteger validatedCertCount = 0; - CFIndex serverCertCount = SecTrustGetCertificateCount(trust); - for (CFIndex i = 0; i < serverCertCount; i++) { - SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i); - NSData *data = CFBridgingRelease(SecCertificateCopyData(cert)); - for (id ref in self.pinnedCertificates) { - 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]) { - validatedCertCount++; - break; - } - } - } - return (requiredCertCount == validatedCertCount); -} - -@end - -NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Security/SRSecurityPolicyBuilder.h b/SocketRocket/Internal/Security/SRSecurityPolicyBuilder.h new file mode 100644 index 000000000..b2b7ed5e4 --- /dev/null +++ b/SocketRocket/Internal/Security/SRSecurityPolicyBuilder.h @@ -0,0 +1,27 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +#import "SRSecurityPolicy.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SRSecurityPolicyBuilder : NSObject + ++ (id)createWithChainValidationEnabled:(BOOL)allowsUntrustedSSLCertificates + pinnedCertificates:(nullable NSArray *)pinnedCertificates; + ++ (id)defaultPolicy; ++ (id)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates; ++ (id)unencryptedPolicy; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/Internal/Security/SRSecurityPolicyBuilder.m b/SocketRocket/Internal/Security/SRSecurityPolicyBuilder.m new file mode 100644 index 000000000..871b9b052 --- /dev/null +++ b/SocketRocket/Internal/Security/SRSecurityPolicyBuilder.m @@ -0,0 +1,213 @@ +// +// Copyright (c) 2016-present, Facebook, Inc. +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import "SRSecurityPolicyBuilder.h" + +#import "SRLog.h" + +NS_ASSUME_NONNULL_BEGIN + +/** + `SRPinningPolicy` requires some level of encryption and allows pinning to + provider certificates. + */ +@interface SRPinningSecurityPolicy : NSObject + +@property (nonatomic, strong, readonly) NSArray *pinnedCertificates; + +@end + +@implementation SRPinningSecurityPolicy + +- (instancetype)initWithPinnedCertificates:(NSArray *)certificates +{ + self = [super init]; + if (!self) return self; + + if (certificates.count == 0) { + @throw [NSException exceptionWithName:@"Creating security policy failed." + reason:@"Must specify at least one certificate when creating a pinning policy." + userInfo:nil]; + } + _pinnedCertificates = [certificates copy]; + return self; +} + +- (void)updateSecurityOptionsInStream :(NSStream *)stream +{ + // Enforce TLS 1.2 + [stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // Do not validate certificate chain for the stream since we're pinning. + NSDictionary *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(NO) }; + [stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings]; +} + +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust + forDomain:(NSString*)domain +{ + SRDebugLog(@"Pinned cert count: %d", self.pinnedCertificates.count); + NSUInteger requiredCertCount = self.pinnedCertificates.count; + if (requiredCertCount == 0) { + @throw [NSException exceptionWithName:@"Evaluating security policy failed." + reason:@"There must be at least one certificate when evaluating a pinning policy." + userInfo:nil]; + } + + NSUInteger validatedCertCount = 0; + CFIndex serverCertCount = SecTrustGetCertificateCount(serverTrust); + for (CFIndex i = 0; i < serverCertCount; i++) { + SecCertificateRef cert = SecTrustGetCertificateAtIndex(serverTrust, i); + NSData *data = CFBridgingRelease(SecCertificateCopyData(cert)); + for (id ref in self.pinnedCertificates) { + 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]) { + validatedCertCount++; + break; + } + } + } + return (requiredCertCount == validatedCertCount); +} + +@end + + +/** + `SRDefaultSecurityPolicy` requires encryption and validates the certificate chain. + */ +@interface SRDefaultSecurityPolicy : NSObject + +@end + +@implementation SRDefaultSecurityPolicy + +- (void)updateSecurityOptionsInStream:(NSStream *)stream +{ + // Enforce TLS 1.2 + [stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // Validate certificate chain for the stream. + NSDictionary *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(YES) }; + [stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings]; +} + +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust + forDomain:(NSString*)domain +{ + // Certificate chain verification is configured at the stream level, but no + // further evaluation happens in this policy. + return YES; +} + +@end + + +/** + `SRWithoutSSLValidationSecurityPolicy` requires encryption, but does no verification + of certificate authenticity. If you are using certificates not signed by a + recognized certificate authority (like a self signed certificate) consider using + `SRPinningSecurityPolicy` instead. + */ +@interface SRWithoutSSLValidationSecurityPolicy : NSObject + +@end + +@implementation SRWithoutSSLValidationSecurityPolicy + +- (void)updateSecurityOptionsInStream:(NSStream *)stream +{ + // Enforce TLS 1.2 + [stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel]; + + // Do not validate certificate chain for the stream. + NSDictionary *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(NO) }; + [stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings]; +} + +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust + forDomain:(NSString*)domain +{ + // no verification + return YES; +} + +@end + + +/** + `SRNoSecurityPolicy` is not suitable for anything except local debugging. This + policy specifies that no encryption is used, and thus, no verification occurs. + */ +@interface SRUnencryptedPolicy : NSObject + +@end + +@implementation SRUnencryptedPolicy + +- (void)updateSecurityOptionsInStream:(NSStream *)stream +{ + // no-op, no encryption. +} + +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust + forDomain:(NSString*)domain +{ + // no verification + return YES; +} + +@end + + +@implementation SRSecurityPolicyBuilder + ++ (SRPinningSecurityPolicy *)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates +{ + return [[SRPinningSecurityPolicy alloc] initWithPinnedCertificates:pinnedCertificates]; +} + ++ (SRDefaultSecurityPolicy *)defaultPolicy +{ + return [SRDefaultSecurityPolicy new]; +} + ++ (SRUnencryptedPolicy *)unencryptedPolicy +{ + return [SRUnencryptedPolicy new]; +} + ++ (SRWithoutSSLValidationSecurityPolicy *)withoutSSLValidationPolicy +{ + return [SRWithoutSSLValidationSecurityPolicy new]; +} + ++ (id)createWithChainValidationEnabled:(BOOL)allowsUntrustedSSLCertificates + pinnedCertificates:(nullable NSArray *)pinnedCertificates +{ + if (pinnedCertificates) + { + return [self pinnningPolicyWithCertificates:pinnedCertificates]; + } + else if (allowsUntrustedSSLCertificates) + { + return [self withoutSSLValidationPolicy]; + } + else + { + return [self defaultPolicy]; + } + +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/SRSecurityPolicy.h b/SocketRocket/SRSecurityPolicy.h new file mode 100644 index 000000000..5b33afdef --- /dev/null +++ b/SocketRocket/SRSecurityPolicy.h @@ -0,0 +1,25 @@ +// +// Copyright 2012 Square Inc. +// Portions Copyright (c) 2016-present, Facebook, Inc. +// +// All rights reserved. +// +// This source code is licensed under the BSD-style license found in the +// LICENSE file in the root directory of this source tree. An additional grant +// of patent rights can be found in the PATENTS file in the same directory. +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@protocol SRSecurityPolicy + +- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust + forDomain:(NSString*)domain; + +- (void)updateSecurityOptionsInStream:(NSStream *)stream; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SocketRocket/SRWebSocket.h b/SocketRocket/SRWebSocket.h index b633e2f33..237c47b6f 100644 --- a/SocketRocket/SRWebSocket.h +++ b/SocketRocket/SRWebSocket.h @@ -11,6 +11,8 @@ #import +#import "SRSecurityPolicy.h" + NS_ASSUME_NONNULL_BEGIN typedef NS_ENUM(NSInteger, SRReadyState) { @@ -132,6 +134,14 @@ extern NSString *const SRHTTPResponseErrorKey; */ - (instancetype)initWithURLRequest:(NSURLRequest *)request; +/** + Initializes a web socket with a given `NSURLRequest`, specifying a transport security policy (e.g. SSL configuration). + + @param request Request to initialize with. + @param securityPolicy Policy object describing transport security behavior. + */ +- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(id)securityPolicy; + /** Initializes a web socket with a given `NSURLRequest` and list of sub-protocols. @@ -147,8 +157,16 @@ extern NSString *const SRHTTPResponseErrorKey; @param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`. @param allowsUntrustedSSLCertificates Boolean value indicating whether untrusted SSL certificates are allowed. Default: `false`. */ -- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates -NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates; + +/** + Initializes a web socket with a given `NSURLRequest`, list of sub-protocols and whether untrusted SSL certificates are allowed. + + @param request Request to initialize with. + @param protocols An array of strings that turn into `Sec-WebSocket-Protocol`. Default: `nil`. + @param securityPolicy Policy object describing transport security behavior. + */ +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray *)protocols securityPolicy:(id)securityPolicy NS_DESIGNATED_INITIALIZER; /** Initializes a web socket with a given `NSURL`. @@ -165,6 +183,14 @@ NS_DESIGNATED_INITIALIZER; */ - (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray *)protocols; +/** + Initializes a web socket with a given `NSURL`, specifying a transport security policy (e.g. SSL configuration). + + @param url URL to initialize with. + @param securityPolicy Policy object describing transport security behavior. + */ +- (instancetype)initWithURL:(NSURL *)url securityPolicy:(id)securityPolicy; + /** Initializes a web socket with a given `NSURL`, list of sub-protocols and whether untrusted SSL certificates are allowed. diff --git a/SocketRocket/SRWebSocket.m b/SocketRocket/SRWebSocket.m index 9f9f7644f..cba75e421 100644 --- a/SocketRocket/SRWebSocket.m +++ b/SocketRocket/SRWebSocket.m @@ -36,7 +36,7 @@ #import "NSURLRequest+SRWebSocket.h" #import "NSRunLoop+SRWebSocket.h" #import "SRProxyConnect.h" -#import "SRSecurityOptions.h" +#import "SRSecurityPolicyBuilder.h" #import "SRHTTPConnectMessage.h" #import "SRRandom.h" #import "SRLog.h" @@ -127,7 +127,7 @@ @implementation SRWebSocket { NSString *_secKey; - SRSecurityOptions *_securityOptions; + id _securityPolicy; BOOL _streamSecurityValidated; uint8_t _currentReadMaskKey[4]; @@ -138,6 +138,7 @@ @implementation SRWebSocket { NSURLRequest *_urlRequest; + BOOL _didConnectPreviously; BOOL _sentClose; BOOL _didFail; BOOL _cleanupScheduled; @@ -163,7 +164,7 @@ @implementation SRWebSocket { #pragma mark - Init ///-------------------------------------- -- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates +- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols securityPolicy:(id)securityPolicy { self = [super init]; if (!self) return self; @@ -171,14 +172,10 @@ - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates +{ + id securityPolicy = [SRSecurityPolicyBuilder createWithChainValidationEnabled:allowsUntrustedSSLCertificates + pinnedCertificates:request.SR_SSLPinnedCertificates]; + return [self initWithURLRequest:request protocols:protocols securityPolicy:securityPolicy]; +} + +- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(id)securityPolicy +{ + return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy]; +} + - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray *)protocols { return [self initWithURLRequest:request protocols:protocols allowsUntrustedSSLCertificates:NO]; @@ -224,6 +233,12 @@ - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protoc return [self initWithURL:url protocols:protocols allowsUntrustedSSLCertificates:NO]; } +- (instancetype)initWithURL:(NSURLRequest *)url securityPolicy:(id)securityPolicy +{ + NSURLRequest *request = [NSURLRequest requestWithURL:url]; + return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy]; +} + - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates { NSURLRequest *request = [NSURLRequest requestWithURL:url]; @@ -339,13 +354,9 @@ - (void)_connectionDoneWithError:(NSError *)error readStream:(NSInputStream *)re [self scheduleInRunLoop:[NSRunLoop SR_networkRunLoop] forMode:NSDefaultRunLoopMode]; } - // If we don't require SSL validation - consider that we connected. - // Otherwise `didConnect` is called when SSL validation finishes. - if (!_securityOptions.requestRequiresSSL) { - dispatch_async(_workQueue, ^{ - [self didConnect]; - }); - } + dispatch_async(_workQueue, ^{ + [self didConnect]; + }); } } @@ -427,33 +438,39 @@ - (void)_readHTTPHeader; - (void)didConnect; { - SRDebugLog(@"Connected"); + // Potentially called through multiple code paths. + // Make sure we only responde once. + @synchronized(self) { + if (_didConnectPreviously) { + return; + } else { + SRDebugLog(@"Connected"); + _didConnectPreviously = true; - _secKey = SRBase64EncodedStringFromData(SRRandomData(16)); - assert([_secKey length] == 24); + _secKey = SRBase64EncodedStringFromData(SRRandomData(16)); + assert([_secKey length] == 24); - CFHTTPMessageRef message = SRHTTPConnectMessageCreate(_urlRequest, - _secKey, - SRWebSocketProtocolVersion, - self.requestCookies, - _requestedProtocols); + CFHTTPMessageRef message = SRHTTPConnectMessageCreate(_urlRequest, + _secKey, + SRWebSocketProtocolVersion, + self.requestCookies, + _requestedProtocols); - NSData *messageData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); + NSData *messageData = CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message)); - CFRelease(message); + CFRelease(message); - [self _writeData:messageData]; - [self _readHTTPHeader]; + [self _writeData:messageData]; + [self _readHTTPHeader]; + } + } } - (void)_updateSecureStreamOptions { SRDebugLog(@"Setting up security for streams."); - [_securityOptions updateSecurityOptionsInStream:_inputStream]; - [_securityOptions updateSecurityOptionsInStream:_outputStream]; - - SRDebugLog(@"Allows connection any root cert: %d", _securityOptions.validatesCertificateChain); - SRDebugLog(@"Pinned cert count: %d", _securityOptions.pinnedCertificates.count); + [_securityPolicy updateSecurityOptionsInStream:_inputStream]; + [_securityPolicy updateSecurityOptionsInStream:_outputStream]; _inputStream.delegate = self; _outputStream.delegate = self; @@ -1426,12 +1443,12 @@ - (void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode { __weak typeof(self) wself = self; - if (_securityOptions.requestRequiresSSL && !_streamSecurityValidated && + if (!_streamSecurityValidated && (eventCode == NSStreamEventHasBytesAvailable || eventCode == NSStreamEventHasSpaceAvailable)) { + SecTrustRef trust = (__bridge SecTrustRef)[aStream propertyForKey:(__bridge id)kCFStreamPropertySSLPeerTrust]; - if (trust) { - _streamSecurityValidated = [_securityOptions securityTrustContainsPinnedCertificates:trust]; - } + _streamSecurityValidated = [_securityPolicy evaluateServerTrust:trust forDomain:_urlRequest.URL.host]; + if (!_streamSecurityValidated) { dispatch_async(_workQueue, ^{ NSError *error = SRErrorWithDomainCodeDescription(NSURLErrorDomain, @@ -1460,7 +1477,7 @@ - (void)safeHandleEvent:(NSStreamEvent)eventCode stream:(NSStream *)aStream } assert(_readBuffer); - if (!_securityOptions.requestRequiresSSL && self.readyState == SR_CONNECTING && aStream == _inputStream) { + if (self.readyState == SR_CONNECTING && aStream == _inputStream) { [self didConnect]; } diff --git a/SocketRocket/SocketRocket.h b/SocketRocket/SocketRocket.h index f9a2a6379..d7c305898 100644 --- a/SocketRocket/SocketRocket.h +++ b/SocketRocket/SocketRocket.h @@ -10,5 +10,6 @@ // #import +#import #import #import