Skip to content

Commit

Permalink
Pluggable, more flexible, security policies. (#429)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
michaelkirk authored and nlutsenko committed Jul 1, 2016
1 parent b4e7932 commit 8096fef
Show file tree
Hide file tree
Showing 10 changed files with 329 additions and 203 deletions.
60 changes: 40 additions & 20 deletions SocketRocket.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

21 changes: 21 additions & 0 deletions SocketRocket/Internal/Security/SRPinningSecurityPolicy.h
Original file line number Diff line number Diff line change
@@ -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 changes: 67 additions & 0 deletions SocketRocket/Internal/Security/SRPinningSecurityPolicy.m
Original file line number Diff line number Diff line change
@@ -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
74 changes: 0 additions & 74 deletions SocketRocket/Internal/Security/SRSecurityOptions.h

This file was deleted.

88 changes: 0 additions & 88 deletions SocketRocket/Internal/Security/SRSecurityOptions.m

This file was deleted.

67 changes: 67 additions & 0 deletions SocketRocket/SRSecurityPolicy.h
Original file line number Diff line number Diff line change
@@ -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 changes: 66 additions & 0 deletions SocketRocket/SRSecurityPolicy.m
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit 8096fef

Please sign in to comment.