236 lines
8.5 KiB
Objective-C
236 lines
8.5 KiB
Objective-C
// AFSecurity.m
|
|
//
|
|
// Copyright (c) 2013 AFNetworking (http://afnetworking.com)
|
|
//
|
|
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
// of this software and associated documentation files (the "Software"), to deal
|
|
// in the Software without restriction, including without limitation the rights
|
|
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
// copies of the Software, and to permit persons to whom the Software is
|
|
// furnished to do so, subject to the following conditions:
|
|
//
|
|
// The above copyright notice and this permission notice shall be included in
|
|
// all copies or substantial portions of the Software.
|
|
//
|
|
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
// THE SOFTWARE.
|
|
|
|
#import "AFSecurityPolicy.h"
|
|
|
|
#if !defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
|
static NSData * AFSecKeyGetData(SecKeyRef key) {
|
|
CFDataRef data = NULL;
|
|
|
|
#if defined(NS_BLOCK_ASSERTIONS)
|
|
SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data);
|
|
#else
|
|
OSStatus status = SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data);
|
|
NSCAssert(status == errSecSuccess, @"SecItemExport error: %ld", (long int)status);
|
|
#endif
|
|
|
|
NSCParameterAssert(data);
|
|
|
|
return (__bridge_transfer NSData *)data;
|
|
}
|
|
#endif
|
|
|
|
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
|
|
#if defined(__IPHONE_OS_VERSION_MIN_REQUIRED)
|
|
return [(__bridge id)key1 isEqual:(__bridge id)key2];
|
|
#else
|
|
return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
|
|
#endif
|
|
}
|
|
|
|
static id AFPublicKeyForCertificate(NSData *certificate) {
|
|
SecCertificateRef allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate);
|
|
NSCParameterAssert(allowedCertificate);
|
|
|
|
SecCertificateRef allowedCertificates[] = {allowedCertificate};
|
|
CFArrayRef tempCertificates = CFArrayCreate(NULL, (const void **)allowedCertificates, 1, NULL);
|
|
|
|
SecPolicyRef policy = SecPolicyCreateBasicX509();
|
|
SecTrustRef allowedTrust = NULL;
|
|
#if defined(NS_BLOCK_ASSERTIONS)
|
|
SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust);
|
|
#else
|
|
OSStatus status = SecTrustCreateWithCertificates(tempCertificates, policy, &allowedTrust);
|
|
NSCAssert(status == errSecSuccess, @"SecTrustCreateWithCertificates error: %ld", (long int)status);
|
|
#endif
|
|
|
|
SecTrustResultType result = 0;
|
|
|
|
#if defined(NS_BLOCK_ASSERTIONS)
|
|
SecTrustEvaluate(allowedTrust, &result);
|
|
#else
|
|
status = SecTrustEvaluate(allowedTrust, &result);
|
|
NSCAssert(status == errSecSuccess, @"SecTrustEvaluate error: %ld", (long int)status);
|
|
#endif
|
|
|
|
SecKeyRef allowedPublicKey = SecTrustCopyPublicKey(allowedTrust);
|
|
NSCParameterAssert(allowedPublicKey);
|
|
|
|
CFRelease(allowedTrust);
|
|
CFRelease(policy);
|
|
CFRelease(tempCertificates);
|
|
CFRelease(allowedCertificate);
|
|
|
|
return (__bridge_transfer id)allowedPublicKey;
|
|
}
|
|
|
|
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
|
|
SecTrustResultType result = 0;
|
|
|
|
#if defined(NS_BLOCK_ASSERTIONS)
|
|
SecTrustEvaluate(serverTrust, &result);
|
|
#else
|
|
OSStatus status = SecTrustEvaluate(serverTrust, &result);
|
|
NSCAssert(status == errSecSuccess, @"SecTrustEvaluate error: %ld", (long int)status);
|
|
#endif
|
|
|
|
return (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
|
|
}
|
|
|
|
static NSArray * AFCertificateTrustChainForServerTrust(SecTrustRef serverTrust) {
|
|
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
|
|
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
|
|
|
|
for (CFIndex i = 0; i < certificateCount; i++) {
|
|
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
|
|
[trustChain addObject:(__bridge_transfer NSData *)SecCertificateCopyData(certificate)];
|
|
}
|
|
|
|
return [NSArray arrayWithArray:trustChain];
|
|
}
|
|
|
|
static NSArray * AFPublicKeyTrustChainForServerTrust(SecTrustRef serverTrust) {
|
|
SecPolicyRef policy = SecPolicyCreateBasicX509();
|
|
CFIndex certificateCount = SecTrustGetCertificateCount(serverTrust);
|
|
NSMutableArray *trustChain = [NSMutableArray arrayWithCapacity:(NSUInteger)certificateCount];
|
|
for (CFIndex i = 0; i < certificateCount; i++) {
|
|
SecCertificateRef certificate = SecTrustGetCertificateAtIndex(serverTrust, i);
|
|
|
|
SecCertificateRef someCertificates[] = {certificate};
|
|
CFArrayRef certificates = CFArrayCreate(NULL, (const void **)someCertificates, 1, NULL);
|
|
|
|
SecTrustRef trust = NULL;
|
|
|
|
OSStatus status = SecTrustCreateWithCertificates(certificates, policy, &trust);
|
|
NSCAssert(status == errSecSuccess, @"SecTrustCreateWithCertificates error: %ld", (long int)status);
|
|
|
|
SecTrustResultType result;
|
|
status = SecTrustEvaluate(trust, &result);
|
|
NSCAssert(status == errSecSuccess, @"SecTrustEvaluate error: %ld", (long int)status);
|
|
|
|
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
|
|
|
|
CFRelease(trust);
|
|
CFRelease(certificates);
|
|
}
|
|
CFRelease(policy);
|
|
|
|
return [NSArray arrayWithArray:trustChain];
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
@interface AFSecurityPolicy()
|
|
@property (readwrite, nonatomic, strong) NSArray *pinnedPublicKeys;
|
|
@end
|
|
|
|
@implementation AFSecurityPolicy
|
|
|
|
+ (NSArray *)defaultPinnedCertificates {
|
|
static NSArray *_defaultPinnedCertificates = nil;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
NSBundle *bundle = [NSBundle bundleForClass:[self class]];
|
|
NSArray *paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."];
|
|
|
|
NSMutableArray *certificates = [NSMutableArray arrayWithCapacity:[paths count]];
|
|
for (NSString *path in paths) {
|
|
NSData *certificateData = [NSData dataWithContentsOfFile:path];
|
|
[certificates addObject:certificateData];
|
|
}
|
|
|
|
_defaultPinnedCertificates = [[NSArray alloc] initWithArray:certificates];
|
|
});
|
|
|
|
return _defaultPinnedCertificates;
|
|
}
|
|
|
|
+ (instancetype)defaultPolicy {
|
|
AFSecurityPolicy *securityPolicy = [[self alloc] init];
|
|
securityPolicy.SSLPinningMode = AFSSLPinningModeNone;
|
|
|
|
return securityPolicy;
|
|
}
|
|
|
|
+ (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {
|
|
AFSecurityPolicy *securityPolicy = [[self alloc] init];
|
|
securityPolicy.SSLPinningMode = pinningMode;
|
|
[securityPolicy setPinnedCertificates:[self defaultPinnedCertificates]];
|
|
|
|
return securityPolicy;
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (void)setPinnedCertificates:(NSArray *)pinnedCertificates {
|
|
_pinnedCertificates = pinnedCertificates;
|
|
|
|
if (self.pinnedCertificates) {
|
|
NSMutableArray *mutablePinnedPublicKeys = [NSMutableArray arrayWithCapacity:[self.pinnedCertificates count]];
|
|
for (NSData *certificate in self.pinnedCertificates) {
|
|
[mutablePinnedPublicKeys addObject:AFPublicKeyForCertificate(certificate)];
|
|
}
|
|
self.pinnedPublicKeys = [NSArray arrayWithArray:mutablePinnedPublicKeys];
|
|
} else {
|
|
self.pinnedPublicKeys = nil;
|
|
}
|
|
}
|
|
|
|
#pragma mark -
|
|
|
|
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust {
|
|
switch (self.SSLPinningMode) {
|
|
case AFSSLPinningModeNone:
|
|
return (self.allowInvalidCertificates || AFServerTrustIsValid(serverTrust));
|
|
case AFSSLPinningModeCertificate: {
|
|
for (NSData *trustChainCertificate in AFCertificateTrustChainForServerTrust(serverTrust)) {
|
|
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
|
|
return YES;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case AFSSLPinningModePublicKey: {
|
|
for (id trustChainPublicKey in AFPublicKeyTrustChainForServerTrust(serverTrust)) {
|
|
for (id pinnedPublicKey in self.pinnedPublicKeys) {
|
|
if (AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) {
|
|
return YES;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
#pragma mark - NSKeyValueObserving
|
|
|
|
+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
|
|
return [NSSet setWithObject:@"pinnedCertificates"];
|
|
}
|
|
|
|
@end
|