Skip to content

Commit 8096fef

Browse files
michaelkirknlutsenko
authored andcommitted
Pluggable, more flexible, security policies. (facebookincubator#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
1 parent b4e7932 commit 8096fef

File tree

10 files changed

+329
-203
lines changed

10 files changed

+329
-203
lines changed

SocketRocket.xcodeproj/project.pbxproj

Lines changed: 40 additions & 20 deletions
Large diffs are not rendered by default.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
//
2+
// Copyright (c) 2016-present, Facebook, Inc.
3+
// All rights reserved.
4+
//
5+
// This source code is licensed under the BSD-style license found in the
6+
// LICENSE file in the root directory of this source tree. An additional grant
7+
// of patent rights can be found in the PATENTS file in the same directory.
8+
//
9+
10+
#import <Foundation/Foundation.h>
11+
#import "SRSecurityPolicy.h"
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
@interface SRPinningSecurityPolicy : SRSecurityPolicy
16+
17+
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates;
18+
19+
@end
20+
21+
NS_ASSUME_NONNULL_END
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Copyright (c) 2016-present, Facebook, Inc.
3+
// All rights reserved.
4+
//
5+
// This source code is licensed under the BSD-style license found in the
6+
// LICENSE file in the root directory of this source tree. An additional grant
7+
// of patent rights can be found in the PATENTS file in the same directory.
8+
//
9+
10+
#import "SRPinningSecurityPolicy.h"
11+
12+
#import <Foundation/Foundation.h>
13+
14+
#import "SRLog.h"
15+
16+
NS_ASSUME_NONNULL_BEGIN
17+
18+
@interface SRPinningSecurityPolicy ()
19+
20+
@property (nonatomic, copy, readonly) NSArray *pinnedCertificates;
21+
22+
@end
23+
24+
@implementation SRPinningSecurityPolicy
25+
26+
- (instancetype)initWithCertificates:(NSArray *)pinnedCertificates
27+
{
28+
// Do not validate certificate chain since we're pinning to specific certificates.
29+
self = [super initWithCertificateChainValidationEnabled:NO];
30+
if (!self) { return self; }
31+
32+
if (pinnedCertificates.count == 0) {
33+
@throw [NSException exceptionWithName:@"Creating security policy failed."
34+
reason:@"Must specify at least one certificate when creating a pinning policy."
35+
userInfo:nil];
36+
}
37+
_pinnedCertificates = [pinnedCertificates copy];
38+
39+
return self;
40+
}
41+
42+
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
43+
{
44+
SRDebugLog(@"Pinned cert count: %d", self.pinnedCertificates.count);
45+
NSUInteger requiredCertCount = self.pinnedCertificates.count;
46+
47+
NSUInteger validatedCertCount = 0;
48+
CFIndex serverCertCount = SecTrustGetCertificateCount(serverTrust);
49+
for (CFIndex i = 0; i < serverCertCount; i++) {
50+
SecCertificateRef cert = SecTrustGetCertificateAtIndex(serverTrust, i);
51+
NSData *data = CFBridgingRelease(SecCertificateCopyData(cert));
52+
for (id ref in self.pinnedCertificates) {
53+
SecCertificateRef trustedCert = (__bridge SecCertificateRef)ref;
54+
// TODO: (nlutsenko) Add caching, so we don't copy the data for every pinned cert all the time.
55+
NSData *trustedCertData = CFBridgingRelease(SecCertificateCopyData(trustedCert));
56+
if ([trustedCertData isEqualToData:data]) {
57+
validatedCertCount++;
58+
break;
59+
}
60+
}
61+
}
62+
return (requiredCertCount == validatedCertCount);
63+
}
64+
65+
@end
66+
67+
NS_ASSUME_NONNULL_END

SocketRocket/Internal/Security/SRSecurityOptions.h

Lines changed: 0 additions & 74 deletions
This file was deleted.

SocketRocket/Internal/Security/SRSecurityOptions.m

Lines changed: 0 additions & 88 deletions
This file was deleted.

SocketRocket/SRSecurityPolicy.h

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
//
2+
// Copyright (c) 2016-present, Facebook, Inc.
3+
// All rights reserved.
4+
//
5+
// This source code is licensed under the BSD-style license found in the
6+
// LICENSE file in the root directory of this source tree. An additional grant
7+
// of patent rights can be found in the PATENTS file in the same directory.
8+
//
9+
10+
#import <Foundation/Foundation.h>
11+
#import <Security/Security.h>
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
@interface SRSecurityPolicy : NSObject
16+
17+
/**
18+
A default `SRSecurityPolicy` implementation specifies socket security and
19+
validates the certificate chain.
20+
21+
Use a subclass of `SRSecurityPolicy` for more fine grained customization.
22+
*/
23+
+ (instancetype)defaultPolicy;
24+
25+
/**
26+
Specifies socket security and provider certificate pinning, disregarding certificate
27+
chain validation.
28+
29+
@param pinnedCertificates Array of `SecCertificateRef` SSL certificates to use for validation.
30+
*/
31+
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates;
32+
33+
/**
34+
Specifies socket security and optional certificate chain validation.
35+
36+
@param enabled Whether or not to validate the SSL certificate chain. If you
37+
are considering using this method because your certificate was not issued by a
38+
recognized certificate authority, consider using `pinningPolicyWithCertificates` instead.
39+
*/
40+
- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled NS_DESIGNATED_INITIALIZER;
41+
42+
/**
43+
Updates all the security options for input and output streams, for example you
44+
can set your socket security level here.
45+
46+
@param stream Stream to update the options in.
47+
*/
48+
- (void)updateSecurityOptionsInStream:(NSStream *)stream;
49+
50+
/**
51+
Whether or not the specified server trust should be accepted, based on the security policy.
52+
53+
This method should be used when responding to an authentication challenge from
54+
a server. In the default implemenation, no further validation is done here, but
55+
you're free to override it in a subclass. See `SRPinningSecurityPolicy.h` for
56+
an example.
57+
58+
@param serverTrust The X.509 certificate trust of the server.
59+
@param domain The domain of serverTrust.
60+
61+
@return Whether or not to trust the server.
62+
*/
63+
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain;
64+
65+
@end
66+
67+
NS_ASSUME_NONNULL_END

SocketRocket/SRSecurityPolicy.m

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//
2+
// Copyright (c) 2016-present, Facebook, Inc.
3+
// All rights reserved.
4+
//
5+
// This source code is licensed under the BSD-style license found in the
6+
// LICENSE file in the root directory of this source tree. An additional grant
7+
// of patent rights can be found in the PATENTS file in the same directory.
8+
//
9+
10+
#import "SRSecurityPolicy.h"
11+
#import "SRPinningSecurityPolicy.h"
12+
13+
NS_ASSUME_NONNULL_BEGIN
14+
15+
@interface SRSecurityPolicy ()
16+
17+
@property (nonatomic, assign, readonly) BOOL certificateChainValidationEnabled;
18+
19+
@end
20+
21+
@implementation SRSecurityPolicy
22+
23+
+ (instancetype)defaultPolicy
24+
{
25+
return [self new];
26+
}
27+
28+
+ (instancetype)pinnningPolicyWithCertificates:(NSArray *)pinnedCertificates
29+
{
30+
return [[SRPinningSecurityPolicy alloc] initWithCertificates:pinnedCertificates];
31+
}
32+
33+
- (instancetype)initWithCertificateChainValidationEnabled:(BOOL)enabled
34+
{
35+
self = [super init];
36+
if (!self) { return self; }
37+
38+
_certificateChainValidationEnabled = enabled;
39+
40+
return self;
41+
}
42+
43+
- (instancetype)init
44+
{
45+
return [self initWithCertificateChainValidationEnabled:YES];
46+
}
47+
48+
- (void)updateSecurityOptionsInStream:(NSStream *)stream
49+
{
50+
// Enforce TLS 1.2
51+
[stream setProperty:(__bridge id)CFSTR("kCFStreamSocketSecurityLevelTLSv1_2") forKey:(__bridge id)kCFStreamPropertySocketSecurityLevel];
52+
53+
// Validate certificate chain for this stream if enabled.
54+
NSDictionary<NSString *, id> *sslOptions = @{ (__bridge NSString *)kCFStreamSSLValidatesCertificateChain : @(self.certificateChainValidationEnabled) };
55+
[stream setProperty:sslOptions forKey:(__bridge NSString *)kCFStreamPropertySSLSettings];
56+
}
57+
58+
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain
59+
{
60+
// No further evaluation happens in the default policy.
61+
return YES;
62+
}
63+
64+
@end
65+
66+
NS_ASSUME_NONNULL_END

0 commit comments

Comments
 (0)