Jelajahi Sumber

Pluggable, more flexible, security policies. (#429)

Extract @fredericjacobs' CertificateVerifier concept with @nlutsenko's
SRSecurityOptions into a pluggable SRSecurityPolicy model

This retains existing SSL configuration code paths, while allowing users
more flexibility to specify their own security policy.

If you are alread using AFNetworking and an `AFSecurityPolicy`, it's
intended that you can share domain trust logic by delegating
`SRSecurityPolicy evaluateTrust:ForDomain` to your AFSecurityPolicy
instance.

Inspired by original "Require TLS 1.2 & enable pinning" pull request by
Frederic Jacobs (@fredericjacobs) at:

https://github.com/facebook/SocketRocket/pull/274/files
Michael Kirk 9 tahun lalu
induk
melakukan
8096fef47d

+ 40 - 20
SocketRocket.xcodeproj/project.pbxproj

@@ -17,6 +17,22 @@
 		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 */; };
+		454FEA7B1D2570D400073768 /* SRPinningSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */; };
+		454FEA7C1D2570D400073768 /* SRPinningSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */; };
+		454FEA7D1D2570F600073768 /* SRPinningSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */; };
+		454FEA7E1D2570F600073768 /* SRPinningSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */; };
+		454FEA7F1D2570F800073768 /* SRPinningSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */; };
+		454FEA801D2570F800073768 /* SRPinningSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */; };
+		454FEA811D2570F900073768 /* SRPinningSecurityPolicy.h in Headers */ = {isa = PBXBuildFile; fileRef = 454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */; };
+		454FEA821D2570F900073768 /* SRPinningSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */; };
+		454FEA841D25717C00073768 /* SRSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA831D25717C00073768 /* SRSecurityPolicy.m */; };
+		454FEA851D25719900073768 /* SRSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA831D25717C00073768 /* SRSecurityPolicy.m */; };
+		454FEA861D25719A00073768 /* SRSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA831D25717C00073768 /* SRSecurityPolicy.m */; };
+		454FEA871D25719A00073768 /* SRSecurityPolicy.m in Sources */ = {isa = PBXBuildFile; fileRef = 454FEA831D25717C00073768 /* SRSecurityPolicy.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 +66,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 +198,10 @@
 /* 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; };
+		454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRPinningSecurityPolicy.h; sourceTree = "<group>"; };
+		454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRPinningSecurityPolicy.m; sourceTree = "<group>"; };
+		454FEA831D25717C00073768 /* SRSecurityPolicy.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRSecurityPolicy.m; sourceTree = "<group>"; };
 		4861E7731D022211002FAB1D /* SRProxyConnect.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRProxyConnect.h; sourceTree = "<group>"; };
 		4861E7741D022211002FAB1D /* SRProxyConnect.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRProxyConnect.m; sourceTree = "<group>"; };
 		555E0EB11C51E56D00E6BB92 /* SocketRocket.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SocketRocket.h; sourceTree = "<group>"; };
@@ -207,8 +219,6 @@
 		817995851CE139700084DA37 /* SRDelegateController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRDelegateController.m; sourceTree = "<group>"; };
 		8179967E1CE184F40084DA37 /* SRAutobahnUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRAutobahnUtilities.h; sourceTree = "<group>"; };
 		8179967F1CE184F40084DA37 /* SRAutobahnUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRAutobahnUtilities.m; sourceTree = "<group>"; };
-		8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRSecurityOptions.h; sourceTree = "<group>"; };
-		8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRSecurityOptions.m; sourceTree = "<group>"; };
 		81900A4A1D18C9CC0015A290 /* SRLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRLog.h; sourceTree = "<group>"; };
 		81900A4B1D18C9CC0015A290 /* SRLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SRLog.m; sourceTree = "<group>"; };
 		81B22EC31CE42D7E0073C636 /* SRError.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SRError.h; sourceTree = "<group>"; };
@@ -401,8 +411,8 @@
 		8186892C1D08EF3C004F94C8 /* Security */ = {
 			isa = PBXGroup;
 			children = (
-				8186892D1D08EF3C004F94C8 /* SRSecurityOptions.h */,
-				8186892E1D08EF3C004F94C8 /* SRSecurityOptions.m */,
+				454FEA791D2570D400073768 /* SRPinningSecurityPolicy.h */,
+				454FEA7A1D2570D400073768 /* SRPinningSecurityPolicy.m */,
 			);
 			path = Security;
 			sourceTree = "<group>";
@@ -554,6 +564,8 @@
 			children = (
 				81B31C0D1CDC404100D86D43 /* Internal */,
 				555E0EB11C51E56D00E6BB92 /* SocketRocket.h */,
+				454A02D41D0FAD010060DFB2 /* SRSecurityPolicy.h */,
+				454FEA831D25717C00073768 /* SRSecurityPolicy.m */,
 				F6A12CCF145119B700C1D980 /* SRWebSocket.h */,
 				F6A12CD0145119B700C1D980 /* SRWebSocket.m */,
 				81CD05D51CEEC47300497F47 /* NSURLRequest+SRWebSocket.h */,
@@ -573,13 +585,14 @@
 			buildActionMask = 2147483647;
 			files = (
 				81B22EE51CE43ECC0073C636 /* SRURLUtilities.h in Headers */,
+				454FEA7F1D2570F800073768 /* SRPinningSecurityPolicy.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 */,
 				2D42277F1BB4365C000C1A6C /* SRWebSocket.h in Headers */,
 				81B31C2E1CDC406B00D86D43 /* SRHash.h in Headers */,
 				811934BE1CDAF725003AB243 /* SocketRocket.h in Headers */,
@@ -597,13 +610,14 @@
 			buildActionMask = 2147483647;
 			files = (
 				81B22EE71CE43ECC0073C636 /* SRURLUtilities.h in Headers */,
+				454FEA811D2570F900073768 /* SRPinningSecurityPolicy.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 */,
 				3345DC8A1C52ACD70083CCB8 /* SRWebSocket.h in Headers */,
 				81B31C301CDC406B00D86D43 /* SRHash.h in Headers */,
 				811934C01CDAF726003AB243 /* SocketRocket.h in Headers */,
@@ -621,13 +635,14 @@
 			buildActionMask = 2147483647;
 			files = (
 				81B22EE61CE43ECC0073C636 /* SRURLUtilities.h in Headers */,
+				454FEA7D1D2570F600073768 /* SRPinningSecurityPolicy.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 */,
 				F668C8AA153E92F90044DBAC /* SRWebSocket.h in Headers */,
 				81B31C2F1CDC406B00D86D43 /* SRHash.h in Headers */,
 				811934BC1CDAF725003AB243 /* SocketRocket.h in Headers */,
@@ -645,11 +660,12 @@
 			buildActionMask = 2147483647;
 			files = (
 				81B22EE41CE43ECC0073C636 /* SRURLUtilities.h in Headers */,
+				454FEA7B1D2570D400073768 /* SRPinningSecurityPolicy.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 */,
 				F6A12CD1145119B700C1D980 /* SRWebSocket.h in Headers */,
 				81B31C2D1CDC406B00D86D43 /* SRHash.h in Headers */,
@@ -854,8 +870,9 @@
 				81CD05DC1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */,
 				81B22ECA1CE42D7E0073C636 /* SRError.m in Sources */,
 				81B31C191CDC404100D86D43 /* SRIOConsumer.m in Sources */,
-				818689341D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */,
 				81C22BC71D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */,
+				454FEA801D2570F800073768 /* SRPinningSecurityPolicy.m in Sources */,
+				454FEA851D25719900073768 /* SRSecurityPolicy.m in Sources */,
 				81CD06021CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */,
 				2D4227851BB43734000C1A6C /* SRWebSocket.m in Sources */,
 				81C22BFD1D1256E1007BFDDF /* SRRandom.m in Sources */,
@@ -877,8 +894,9 @@
 				81CD05DE1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */,
 				81B22ECC1CE42D7E0073C636 /* SRError.m in Sources */,
 				81B31C1B1CDC404100D86D43 /* SRIOConsumer.m in Sources */,
-				818689361D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */,
 				81C22BC91D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */,
+				454FEA821D2570F900073768 /* SRPinningSecurityPolicy.m in Sources */,
+				454FEA871D25719A00073768 /* SRSecurityPolicy.m in Sources */,
 				81CD06041CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */,
 				3345DC841C52ACD70083CCB8 /* SRWebSocket.m in Sources */,
 				81C22BFF1D1256E1007BFDDF /* SRRandom.m in Sources */,
@@ -911,8 +929,9 @@
 				81CD05DD1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */,
 				81B22ECB1CE42D7E0073C636 /* SRError.m in Sources */,
 				81B31C1A1CDC404100D86D43 /* SRIOConsumer.m in Sources */,
-				818689351D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */,
 				81C22BC81D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */,
+				454FEA7E1D2570F600073768 /* SRPinningSecurityPolicy.m in Sources */,
+				454FEA861D25719A00073768 /* SRSecurityPolicy.m in Sources */,
 				81CD06031CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */,
 				F6396B86153E67EC00345B5E /* SRWebSocket.m in Sources */,
 				81C22BFE1D1256E1007BFDDF /* SRRandom.m in Sources */,
@@ -932,10 +951,11 @@
 			buildActionMask = 2147483647;
 			files = (
 				4861E7761D022211002FAB1D /* SRProxyConnect.m in Sources */,
-				818689331D08EF3C004F94C8 /* SRSecurityOptions.m in Sources */,
 				81CD05DB1CEEC47300497F47 /* NSURLRequest+SRWebSocket.m in Sources */,
 				81B22EC91CE42D7E0073C636 /* SRError.m in Sources */,
 				81C22BC61D124168007BFDDF /* SRHTTPConnectMessage.m in Sources */,
+				454FEA7C1D2570D400073768 /* SRPinningSecurityPolicy.m in Sources */,
+				454FEA841D25717C00073768 /* SRSecurityPolicy.m in Sources */,
 				81B31C181CDC404100D86D43 /* SRIOConsumer.m in Sources */,
 				81CD06011CEEC65D00497F47 /* NSRunLoop+SRWebSocket.m in Sources */,
 				81C22BFC1D1256E1007BFDDF /* SRRandom.m in Sources */,

+ 21 - 0
SocketRocket/Internal/Security/SRPinningSecurityPolicy.h

@@ -0,0 +1,21 @@
+//
+// 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 <Foundation/Foundation.h>
+#import "SRSecurityPolicy.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SRPinningSecurityPolicy : SRSecurityPolicy
+
+- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 67 - 0
SocketRocket/Internal/Security/SRPinningSecurityPolicy.m

@@ -0,0 +1,67 @@
+//
+// 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 "SRPinningSecurityPolicy.h"
+
+#import <Foundation/Foundation.h>
+
+#import "SRLog.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SRPinningSecurityPolicy ()
+
+@property (nonatomic, copy, readonly) NSArray *pinnedCertificates;
+
+@end
+
+@implementation SRPinningSecurityPolicy
+
+- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates
+{
+    // Do not validate certificate chain since we're pinning to specific certificates.
+    self = [super initWithCertificateChainValidationEnabled:NO];
+    if (!self) { return self; }
+
+    if (pinnedCertificates.count == 0) {
+        @throw [NSException exceptionWithName:@"Creating security policy failed."
+                                       reason:@"Must specify at least one certificate when creating a pinning policy."
+                                     userInfo:nil];
+    }
+    _pinnedCertificates = [pinnedCertificates copy];
+
+    return self;
+}
+
+- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
+{
+    SRDebugLog(@"Pinned cert count: %d", self.pinnedCertificates.count);
+    NSUInteger requiredCertCount = self.pinnedCertificates.count;
+
+    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
+
+NS_ASSUME_NONNULL_END

+ 0 - 74
SocketRocket/Internal/Security/SRSecurityOptions.h

@@ -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 <Foundation/Foundation.h>
-
-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

+ 0 - 88
SocketRocket/Internal/Security/SRSecurityOptions.m

@@ -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<NSString *, id> *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

+ 67 - 0
SocketRocket/SRSecurityPolicy.h

@@ -0,0 +1,67 @@
+//
+// 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 <Foundation/Foundation.h>
+#import <Security/Security.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SRSecurityPolicy : NSObject
+
+/**
+ A default `SRSecurityPolicy` implementation specifies socket security and
+ validates the certificate chain.
+
+ Use a subclass of `SRSecurityPolicy` for more fine grained customization.
+ */
++ (instancetype)defaultPolicy;
+
+/**
+ Specifies socket security and provider certificate pinning, disregarding certificate
+ chain validation.
+
+ @param pinnedCertificates Array of `SecCertificateRef` SSL certificates to use for validation.
+ */
++ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates;
+
+/**
+ Specifies socket security and optional certificate chain validation.
+
+ @param enabled Whether or not to validate the SSL certificate chain. If you
+ are considering using this method because your certificate was not issued by a
+ recognized certificate authority, consider using `pinningPolicyWithCertificates` instead.
+ */
+- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled NS_DESIGNATED_INITIALIZER;
+
+/**
+ Updates all the security options for input and output streams, for example you
+ can set your socket security level here.
+
+ @param stream Stream to update the options in.
+ */
+- (void)updateSecurityOptionsInStream:(NSStream *)stream;
+
+/**
+ Whether or not the specified server trust should be accepted, based on the security policy.
+
+ This method should be used when responding to an authentication challenge from
+ a server. In the default implemenation, no further validation is done here, but
+ you're free to override it in a subclass. See `SRPinningSecurityPolicy.h` for
+ an example.
+
+ @param serverTrust The X.509 certificate trust of the server.
+ @param domain The domain of serverTrust.
+
+ @return Whether or not to trust the server.
+ */
+- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 66 - 0
SocketRocket/SRSecurityPolicy.m

@@ -0,0 +1,66 @@
+//
+// 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 "SRSecurityPolicy.h"
+#import "SRPinningSecurityPolicy.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SRSecurityPolicy ()
+
+@property (nonatomic, assign, readonly) BOOL certificateChainValidationEnabled;
+
+@end
+
+@implementation SRSecurityPolicy
+
++ (instancetype)defaultPolicy
+{
+    return [self new];
+}
+
++ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates
+{
+    return [[SRPinningSecurityPolicy alloc] initWithCertificates:pinnedCertificates];
+}
+
+- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled
+{
+    self = [super init];
+    if (!self) { return self; }
+
+    _certificateChainValidationEnabled = enabled;
+
+    return self;
+}
+
+- (instancetype)init
+{
+    return [self initWithCertificateChainValidationEnabled:YES];
+}
+
+- (void)updateSecurityOptionsInStream:(NSStream *)stream
+{
+    // Enforce TLS 1.2
+    [stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
+
+    // Validate certificate chain for this stream if enabled.
+    NSDictionary<NSString *, id> *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(self.certificateChainValidationEnabled) };
+    [stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
+}
+
+- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
+{
+    // No further evaluation happens in the default policy.
+    return YES;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 27 - 2
SocketRocket/SRWebSocket.h

@@ -45,6 +45,7 @@ typedef NS_ENUM(NSInteger, SRStatusCode) {
 };
 
 @class SRWebSocket;
+@class SRSecurityPolicy;
 
 /**
  Error domain used for errors reported by SRWebSocket.
@@ -132,6 +133,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:(SRSecurityPolicy *)securityPolicy;
+
 /**
  Initializes a web socket with a given `NSURLRequest` and list of sub-protocols.
 
@@ -147,8 +156,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<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
-NS_DESIGNATED_INITIALIZER;
+- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(nullable NSArray<NSString *> *)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<NSString *> *)protocols securityPolicy:(SRSecurityPolicy *)securityPolicy NS_DESIGNATED_INITIALIZER;
 
 /**
  Initializes a web socket with a given `NSURL`.
@@ -165,6 +182,14 @@ NS_DESIGNATED_INITIALIZER;
  */
 - (instancetype)initWithURL:(NSURL *)url protocols:(nullable NSArray<NSString *> *)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:(SRSecurityPolicy *)securityPolicy;
+
 /**
  Initializes a web socket with a given `NSURL`, list of sub-protocols and whether untrusted SSL certificates are allowed.
 

+ 40 - 19
SocketRocket/SRWebSocket.m

@@ -36,7 +36,7 @@
 #import "NSURLRequest+SRWebSocket.h"
 #import "NSRunLoop+SRWebSocket.h"
 #import "SRProxyConnect.h"
-#import "SRSecurityOptions.h"
+#import "SRSecurityPolicy.h"
 #import "SRHTTPConnectMessage.h"
 #import "SRRandom.h"
 #import "SRLog.h"
@@ -127,7 +127,8 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
     NSString *_secKey;
 
-    SRSecurityOptions *_securityOptions;
+    SRSecurityPolicy *_securityPolicy;
+    BOOL _requestRequiresSSL;
     BOOL _streamSecurityValidated;
 
     uint8_t _currentReadMaskKey[4];
@@ -163,7 +164,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 #pragma mark - Init
 ///--------------------------------------
 
-- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
+- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols securityPolicy:(SRSecurityPolicy *)securityPolicy
 {
     self = [super init];
     if (!self) return self;
@@ -171,13 +172,9 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
     assert(request.URL);
     _url = request.URL;
     _urlRequest = request;
-    _allowsUntrustedSSLCertificates = allowsUntrustedSSLCertificates;
-
     _requestedProtocols = [protocols copy];
-
-    _securityOptions = [[SRSecurityOptions alloc] initWithRequest:request
-                                               pinnedCertificates:request.SR_SSLPinnedCertificates
-                                           chainValidationEnabled:allowsUntrustedSSLCertificates];
+    _securityPolicy = securityPolicy;
+    _requestRequiresSSL = SRURLRequiresSSL(_url);
 
     _readyState = SR_CONNECTING;
 
@@ -204,6 +201,25 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
     return self;
 }
 
+- (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
+{
+    SRSecurityPolicy *securityPolicy;
+    NSArray *pinnedCertificates = request.SR_SSLPinnedCertificates;
+    if (pinnedCertificates) {
+        securityPolicy = [SRSecurityPolicy pinnningPolicyWithCertificates:pinnedCertificates];
+    } else {
+        BOOL certificateChainValidationEnabled = !allowsUntrustedSSLCertificates;
+        securityPolicy = [[SRSecurityPolicy alloc] initWithCertificateChainValidationEnabled:certificateChainValidationEnabled];
+    }
+
+    return [self initWithURLRequest:request protocols:protocols securityPolicy:securityPolicy];
+}
+
+- (instancetype)initWithURLRequest:(NSURLRequest *)request securityPolicy:(SRSecurityPolicy *)securityPolicy
+{
+    return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy];
+}
+
 - (instancetype)initWithURLRequest:(NSURLRequest *)request protocols:(NSArray<NSString *> *)protocols
 {
     return [self initWithURLRequest:request protocols:protocols allowsUntrustedSSLCertificates:NO];
@@ -224,6 +240,12 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
     return [self initWithURL:url protocols:protocols allowsUntrustedSSLCertificates:NO];
 }
 
+- (instancetype)initWithURL:(NSURL *)url securityPolicy:(SRSecurityPolicy *)securityPolicy
+{
+    NSURLRequest *request = [NSURLRequest requestWithURL:url];
+    return [self initWithURLRequest:request protocols:nil securityPolicy:securityPolicy];
+}
+
 - (instancetype)initWithURL:(NSURL *)url protocols:(NSArray<NSString *> *)protocols allowsUntrustedSSLCertificates:(BOOL)allowsUntrustedSSLCertificates
 {
     NSURLRequest *request = [NSURLRequest requestWithURL:url];
@@ -341,7 +363,7 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
         // If we don't require SSL validation - consider that we connected.
         // Otherwise `didConnect` is called when SSL validation finishes.
-        if (!_securityOptions.requestRequiresSSL) {
+        if (!_requestRequiresSSL) {
             dispatch_async(_workQueue, ^{
                 [self didConnect];
             });
@@ -448,12 +470,11 @@ NSString *const SRHTTPResponseErrorKey = @"HTTPResponseStatusCode";
 
 - (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);
+    if (_requestRequiresSSL) {
+        SRDebugLog(@"Setting up security for streams.");
+        [_securityPolicy updateSecurityOptionsInStream:_inputStream];
+        [_securityPolicy updateSecurityOptionsInStream:_outputStream];
+    }
 
     _inputStream.delegate = self;
     _outputStream.delegate = self;
@@ -1426,11 +1447,11 @@ static const size_t SRFrameHeaderOverhead = 32;
 {
     __weak typeof(self) wself = self;
 
-    if (_securityOptions.requestRequiresSSL && !_streamSecurityValidated &&
+    if (_requestRequiresSSL && !_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, ^{
@@ -1460,7 +1481,7 @@ static const size_t SRFrameHeaderOverhead = 32;
             }
             assert(_readBuffer);
 
-            if (!_securityOptions.requestRequiresSSL && self.readyState == SR_CONNECTING && aStream == _inputStream) {
+            if (!_requestRequiresSSL && self.readyState == SR_CONNECTING && aStream == _inputStream) {
                 [self didConnect];
             }
 

+ 1 - 0
SocketRocket/SocketRocket.h

@@ -10,5 +10,6 @@
 //
 
 #import <SocketRocket/SRWebSocket.h>
+#import <SocketRocket/SRSecurityPolicy.h>
 #import <SocketRocket/NSRunLoop+SRWebSocket.h>
 #import <SocketRocket/NSURLRequest+SRWebSocket.h>