Compare commits

...

1 Commits

Author SHA1 Message Date
Michael Kirk
cb2cf164c0 Pluggable, more flexible, security policies.
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
2016-06-30 18:18:30 -07:00
10 changed files with 329 additions and 203 deletions

View File

@ -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 */,

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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.

View File

@ -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];
}

View File

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