Merge branch 'experimental-ssl-pinning'

This commit is contained in:
Mattt Thompson 2013-03-24 00:57:16 -04:00
commit 9b2a20c0a1
8 changed files with 156 additions and 9 deletions

View file

@ -10,13 +10,16 @@ Pod::Spec.new do |s|
s.requires_arc = true
s.ios.deployment_target = '5.0'
s.ios.frameworks = 'MobileCoreServices', 'SystemConfiguration'
s.ios.frameworks = 'MobileCoreServices', 'SystemConfiguration', 'Security'
s.osx.deployment_target = '10.7'
s.osx.frameworks = 'CoreServices', 'SystemConfiguration'
s.osx.frameworks = 'CoreServices', 'SystemConfiguration', 'Security'
s.prefix_header_contents = <<-EOS
#import <Availability.h>
#define _AFNETWORKING_PIN_SSL_CERTIFICATES_
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#import <SystemConfiguration/SystemConfiguration.h>
#import <MobileCoreServices/MobileCoreServices.h>

View file

@ -21,6 +21,7 @@
// THE SOFTWARE.
#import <Foundation/Foundation.h>
#import "AFURLConnectionOperation.h"
#import <Availability.h>
@ -137,6 +138,13 @@ typedef enum {
@property (readonly, nonatomic, assign) AFNetworkReachabilityStatus networkReachabilityStatus;
#endif
/**
Default SSL pinning mode for each `AFHTTPRequestOperation` which will be enqueued with `enqueueHTTPRequestOperation:`.
*/
#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_
@property (nonatomic, assign) AFURLConnectionOperationSSLPinningMode defaultSSLPinMode;
#endif
///---------------------------------------------
/// @name Creating and Initializing HTTP Clients
///---------------------------------------------
@ -440,7 +448,7 @@ typedef enum {
///----------------
/**
### Network Reachability
## Network Reachability
The following constants are provided by `AFHTTPClient` as possible network reachability statuses.
@ -471,7 +479,7 @@ typedef enum {
A key in the userInfo dictionary in a `AFNetworkingReachabilityDidChangeNotification` notification.
The corresponding value is an `NSNumber` object representing the `AFNetworkReachabilityStatus` value for the current reachability status.
### Parameter Encoding
## Parameter Encoding
The following constants are provided by `AFHTTPClient` as possible methods for serializing parameters into query string or message body values.

View file

@ -540,6 +540,10 @@ static void AFNetworkReachabilityReleaseCallback(const void *info) {
#pragma mark -
- (void)enqueueHTTPRequestOperation:(AFHTTPRequestOperation *)operation {
#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_
operation.SSLPinningMode = self.defaultSSLPinMode;
#endif
[self.operationQueue addOperation:operation];
}

View file

@ -60,6 +60,14 @@
Subclasses are strongly discouraged from overriding `setCompletionBlock:`, as `AFURLConnectionOperation`'s implementation includes a workaround to mitigate retain cycles, and what Apple rather ominously refers to as ["The Deallocation Problem"](http://developer.apple.com/library/ios/#technotes/tn2109/).
## SSL Pinning
Relying on the CA trust model to validate SSL certificates exposes your app to security vulnerabilities, such as man-in-the-middle attacks. For applications that connect to known servers, SSL certificate pinning provides an increased level of security, by checking server certificate validity against those specified in the app bundle.
SSL with certificate pinning is strongly recommended for any application that transmits sensitive information to an external webservice.
When `_AFNETWORKING_PIN_SSL_CERTIFICATES_` is defined and the Security framework is linked, connections will be validated on all matching certificates with a `.cer` extension in the bundle root.
## NSCoding & NSCopying Conformance
`AFURLConnectionOperation` conforms to the `NSCoding` and `NSCopying` protocols, allowing operations to be archived to disk, and copied in memory, respectively. However, because of the intrinsic limitations of capturing the exact state of an operation at a particular moment, there are some important caveats to keep in mind:
@ -75,6 +83,15 @@
- A copy of an operation will not include the `outputStream` of the original.
- Operation copies do not include `completionBlock`. `completionBlock` often strongly captures a reference to `self`, which would otherwise have the unintuitive side-effect of pointing to the _original_ operation when copied.
*/
#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_
typedef enum {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
} AFURLConnectionOperationSSLPinningMode;
#endif
@interface AFURLConnectionOperation : NSOperation <NSURLConnectionDelegate,
#if (defined(__IPHONE_OS_VERSION_MIN_REQUIRED) && __IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_5_0) || \
(defined(__MAC_OS_X_VERSION_MIN_REQUIRED) && __MAC_OS_X_VERSION_MIN_REQUIRED >= __MAC_10_8)
@ -131,7 +148,6 @@ NSCoding, NSCopying>
*/
@property (readonly, nonatomic, assign) NSStringEncoding responseStringEncoding;
///-------------------------------
/// @name Managing URL Credentials
///-------------------------------
@ -150,6 +166,15 @@ NSCoding, NSCopying>
*/
@property (nonatomic, strong) NSURLCredential *credential;
/**
The pinning mode which will be used for SSL connections. `AFSSLPinningModePublicKey` by default.
@discussion To enable SSL Pinning, `#define _AFNETWORKING_PIN_SSL_CERTIFICATES_` in `Prefix.pch`. Also, make sure that the Security framework is linked with the binary. See the "SSL Pinning" section in the `AFURLConnectionOperation`" header for more information.
*/
#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_
@property (nonatomic, assign) AFURLConnectionOperationSSLPinningMode SSLPinningMode;
#endif
///------------------------
/// @name Accessing Streams
///------------------------
@ -290,6 +315,21 @@ NSCoding, NSCopying>
///----------------
/**
## Network Reachability
The following constants are provided by `AFURLConnectionOperation` as possible SSL Pinning options.
enum {
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
}
`AFSSLPinningModePublicKey`
Pin SSL connections to certificate public key (SPKI).
`AFSSLPinningModeCertificate`
Pin SSL connections to exact certificate. This may cause problems when your certificate expires and needs re-issuance.
## User info dictionary keys
These keys may exist in the user info dictionary, in addition to those defined for NSError.

View file

@ -145,6 +145,9 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
@dynamic inputStream;
@synthesize outputStream = _outputStream;
@synthesize credential = _credential;
#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_
@synthesize SSLPinningMode = _SSLPinningMode;
#endif
@synthesize shouldUseCredentialStorage = _shouldUseCredentialStorage;
@synthesize userInfo = _userInfo;
@synthesize backgroundTaskIdentifier = _backgroundTaskIdentifier;
@ -167,7 +170,6 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
+ (NSThread *)networkRequestThread {
static NSThread *_networkRequestThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
_networkRequestThread = [[NSThread alloc] initWithTarget:self selector:@selector(networkRequestThreadEntryPoint:) object:nil];
[_networkRequestThread start];
@ -176,24 +178,61 @@ static inline BOOL AFStateTransitionIsValid(AFOperationState fromState, AFOperat
return _networkRequestThread;
}
#ifdef _AFNETWORKING_PIN_SSL_CERTIFICATES_
+ (NSArray *)pinnedCertificates {
static NSArray *_pinnedCertificates = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
NSMutableArray *certificates = [NSMutableArray array];
NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]];
for (NSString *path in paths) {
NSData *certificateData = [NSData dataWithContentsOfFile:path];
[certificates addObject:certificateData];
}
_pinnedCertificates = [[NSArray alloc] initWithArray:certificates];
});
return _pinnedCertificates;
}
+ (NSArray *)pinnedPublicKeys {
static NSArray *_pinnedPublicKeys = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSArray *pinnedCertificates = [self pinnedCertificates];
NSMutableArray *publicKeys = [NSMutableArray arrayWithCapacity:[pinnedCertificates count]];
for (NSData *data in pinnedCertificates) {
SecCertificateRef allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)data);
NSCParameterAssert(allowedCertificate);
SecCertificateRef allowedCertificates[] = {allowedCertificate};
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
SecPolicyRef policy = SecPolicyCreateBasicX509();
SecTrustRef allowedTrust = NULL;
OSStatus status = SecTrustCreateWithCertificates(certificates, policy, &allowedTrust);
NSAssert(status == noErr, @"SecTrustCreateWithCertificates error: %ld", (long int)status);
SecKeyRef allowedPublicKey = SecTrustCopyPublicKey(allowedTrust);
[publicKeys addObject:(__bridge_transfer id)allowedPublicKey];
CFRelease(allowedTrust);
CFRelease(policy);
CFRelease(certificates);
CFRelease(allowedCertificate);
}
_pinnedPublicKeys = [[NSArray alloc] initWithArray:publicKeys];
});
return _pinnedPublicKeys;
}
#endif
- (id)initWithRequest:(NSURLRequest *)urlRequest {
self = [super init];
if (!self) {
@ -509,13 +548,58 @@ willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challe
if ([[[self class] pinnedCertificates] containsObject:certificateData]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
switch (self.SSLPinningMode) {
case AFSSLPinningModePublicKey: {
id publicKey = (__bridge_transfer id)SecTrustCopyPublicKey(serverTrust);
if ([[self.class pinnedPublicKeys] containsObject:publicKey]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
break;
}
case AFSSLPinningModeCertificate: {
SecCertificateRef serverCertificate = SecTrustGetCertificateAtIndex(serverTrust, 0);
NSData *serverCertificateData = (__bridge_transfer NSData *)SecCertificateCopyData(serverCertificate);
if ([[[self class] pinnedCertificates] containsObject:serverCertificateData]) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
break;
}
case AFSSLPinningModeNone: {
#ifdef _AFNETWORKING_ALLOW_INVALID_SSL_CERTIFICATES_
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
#else
SecTrustResultType result = 0;
OSStatus status = SecTrustEvaluate(serverTrust, &result);
NSAssert(status == noErr, @"SecTrustEvaluate error: %ld", (long int)status);
if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) {
NSURLCredential *credential = [NSURLCredential credentialForTrust:serverTrust];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
} else {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
#endif
break;
}
}
}
}
}
#endif
- (BOOL)connection:(NSURLConnection *)connection
canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{

View file

@ -21,6 +21,7 @@
F82EB080159A172000B10B56 /* AFPropertyListRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F82EB077159A172000B10B56 /* AFPropertyListRequestOperation.m */; };
F82EB081159A172000B10B56 /* AFURLConnectionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F82EB079159A172000B10B56 /* AFURLConnectionOperation.m */; };
F82EB082159A172000B10B56 /* AFXMLRequestOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = F82EB07B159A172000B10B56 /* AFXMLRequestOperation.m */; };
F88812F216C533E9003C8B8C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F88812F116C533E9003C8B8C /* Security.framework */; };
F8A847CF161F55A500940F39 /* CoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8A847CE161F55A500940F39 /* CoreServices.framework */; };
F8A847D2161F55AC00940F39 /* SystemConfiguration.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8A847D1161F55AC00940F39 /* SystemConfiguration.framework */; };
/* End PBXBuildFile section */
@ -57,6 +58,7 @@
F82EB07A159A172000B10B56 /* AFXMLRequestOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AFXMLRequestOperation.h; path = ../AFNetworking/AFXMLRequestOperation.h; sourceTree = "<group>"; };
F82EB07B159A172000B10B56 /* AFXMLRequestOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = AFXMLRequestOperation.m; path = ../AFNetworking/AFXMLRequestOperation.m; sourceTree = "<group>"; };
F877018B159A1CE700B45C0D /* AFNetworking Example.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = "AFNetworking Example.entitlements"; sourceTree = "<group>"; };
F88812F116C533E9003C8B8C /* Security.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Security.framework; path = System/Library/Frameworks/Security.framework; sourceTree = SDKROOT; };
F8A847CE161F55A500940F39 /* CoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreServices.framework; path = System/Library/Frameworks/CoreServices.framework; sourceTree = SDKROOT; };
F8A847D1161F55AC00940F39 /* SystemConfiguration.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SystemConfiguration.framework; path = System/Library/Frameworks/SystemConfiguration.framework; sourceTree = SDKROOT; };
/* End PBXFileReference section */
@ -66,6 +68,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F88812F216C533E9003C8B8C /* Security.framework in Frameworks */,
F8A847D2161F55AC00940F39 /* SystemConfiguration.framework in Frameworks */,
F8A847CF161F55A500940F39 /* CoreServices.framework in Frameworks */,
F8129C001591061B009BFE23 /* Cocoa.framework in Frameworks */,
@ -78,6 +81,7 @@
F8129BF01591061B009BFE23 = {
isa = PBXGroup;
children = (
F88812F116C533E9003C8B8C /* Security.framework */,
F8A847D1161F55AC00940F39 /* SystemConfiguration.framework */,
F8A847CE161F55A500940F39 /* CoreServices.framework */,
F877018B159A1CE700B45C0D /* AFNetworking Example.entitlements */,

View file

@ -9,6 +9,7 @@
/* Begin PBXBuildFile section */
F8129C7415910C37009BFE23 /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = F8129C7215910C37009BFE23 /* AppDelegate.m */; };
F818101615E6A0C600EF93C2 /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ABD6EC159FC2CE001BE42C /* MobileCoreServices.framework */; };
F88812F016C533D6003C8B8C /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F8E469E013957DF100DB05C8 /* Security.framework */; };
F8A847C1161F51A300940F39 /* Default-568h@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F8A847C0161F51A300940F39 /* Default-568h@2x.png */; };
F8A847C3161F523E00940F39 /* Default.png in Resources */ = {isa = PBXBuildFile; fileRef = F8A847C2161F523E00940F39 /* Default.png */; };
F8A847C5161F524200940F39 /* Default@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = F8A847C4161F524200940F39 /* Default@2x.png */; };
@ -95,6 +96,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
F88812F016C533D6003C8B8C /* Security.framework in Frameworks */,
F8E469651395739D00DB05C8 /* UIKit.framework in Frameworks */,
F8E469671395739D00DB05C8 /* Foundation.framework in Frameworks */,
F8E469691395739D00DB05C8 /* CoreGraphics.framework in Frameworks */,

View file

@ -1,5 +1,7 @@
#import <Availability.h>
#define _AFNETWORKING_PIN_SSL_CERTIFICATES_
#if __IPHONE_OS_VERSION_MIN_REQUIRED
#ifndef __IPHONE_3_0
#warning "This project uses features only available in iPhone SDK 3.0 and later."