AFNetworking, as read in this article, is version 3.2.0.
The AFSecurityPolicy class is used to verify that the certificate for HTTPS requests is correct.
1. The interface
1.1. The enumeration
typedef NS_ENUM(NSUInteger, AFSSLPinningMode) {
AFSSLPinningModeNone,
AFSSLPinningModePublicKey,
AFSSLPinningModeCertificate,
};
Copy the code
This enumeration defines the server certificate validation mode:
AFSSLPinningModeNone does not use a certificate authentication server
AFSSLPinningModePublicKey using the public key certificate authentication server
AFSSLPinningModeCertificate use certificate authentication server
1.2. The attribute
/** Server certificate authentication mode, default is not authentication */ @property (readonly, nonatomic, assign) AFSSLPinningMode SSLPinningMode; By default, AFNetworking searches for all. Cer certificate files in the project, but does not use a single certificate as the default. To create an AFSecurityPolicy object, load the certificate by calling the certificatesInBundle method. Then call policyWithPinningMode: withPinnedCertificates method to create object * / @ property (nonatomic, strong, nullable) NSSet <NSData *> *pinnedCertificates; /** Whether to trust invalid or expired certificates, default is no */ @property (nonatomic, assign) BOOL allowInvalidCertificates; /** Whether to validate the domain name in the certificate. The default value is */ @property (nonatomic, assign) BOOL validatesDomainName;Copy the code
Method of 1.3.
/** use this method to find all certificates contained in the specified package */ + (NSSet <NSData *> *)certificatesInBundle:(NSBundle *)bundle; /** The default configuration includes disallowing invalid or expired certificates, validating domain names, not validating certificates, and public keys in certificates */ + (instanceType)defaultPolicy; */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode; /** specify the certificate validation mode and instance of the certificate chemical method */ + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet <NSData *> *)pinnedCertificates; EvaluateServerTrust :(SecTrustRef)serverTrustforDomain:(nullable NSString *)domain;
Copy the code
2. Implement
2.1. Private static methods
The method is to convert the key to NSData type
#if ! TARGET_OS_IOS && ! TARGET_OS_WATCH && ! TARGET_OS_TV
static NSData * AFSecKeyGetData(SecKeyRef key) {
CFDataRef data = NULL;
__Require_noErr_Quiet(SecItemExport(key, kSecFormatUnknown, kSecItemPemArmour, NULL, &data), _out);
return (__bridge_transfer NSData *)data;
_out:
if (data) {
CFRelease(data);
}
return nil;
}
#endif
Copy the code
This method is to determine whether two keys are equal
static BOOL AFSecKeyIsEqualToKey(SecKeyRef key1, SecKeyRef key2) {
#if TARGET_OS_IOS || TARGET_OS_WATCH || TARGET_OS_TV
return [(__bridge id)key1 isEqual:(__bridge id)key2];
#else
return [AFSecKeyGetData(key1) isEqual:AFSecKeyGetData(key2)];
#endif
}
Copy the code
This method is used to retrieve the public key from the certificate
static id AFPublicKeyForCertificate(NSData *certificate) { id allowedPublicKey = nil; SecCertificateRef allowedCertificate; SecPolicyRef policy = nil; SecTrustRef allowedTrust = nil; SecTrustResultType result; allowedCertificate = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificate); __Require_Quiet(allowedCertificate ! = NULL, _out); policy = SecPolicyCreateBasicX509(); __Require_noErr_Quiet(SecTrustCreateWithCertificates(allowedCertificate, policy, &allowedTrust), _out); __Require_noErr_Quiet(SecTrustEvaluate(allowedTrust, &result), _out); allowedPublicKey = (__bridge_transfer id)SecTrustCopyPublicKey(allowedTrust); _out:if (allowedTrust) {
CFRelease(allowedTrust);
}
if (policy) {
CFRelease(policy);
}
if (allowedCertificate) {
CFRelease(allowedCertificate);
}
return allowedPublicKey;
}
Copy the code
This method is to determine whether the server can be trusted
static BOOL AFServerTrustIsValid(SecTrustRef serverTrust) {
BOOL isValid = NO;
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(serverTrust, &result), _out);
isValid = (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed);
_out:
return isValid;
}
Copy the code
This method is used to get all the certificates returned by the server
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];
}
Copy the code
This method is used to obtain the public key in all certificates returned by the server
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;
__Require_noErr_Quiet(SecTrustCreateWithCertificates(certificates, policy, &trust), _out);
SecTrustResultType result;
__Require_noErr_Quiet(SecTrustEvaluate(trust, &result), _out);
[trustChain addObject:(__bridge_transfer id)SecTrustCopyPublicKey(trust)];
_out:
if (trust) {
CFRelease(trust);
}
if (certificates) {
CFRelease(certificates);
}
continue;
}
CFRelease(policy);
return [NSArray arrayWithArray:trustChain];
}
Copy the code
There are two macros used frequently: __Require_Quiet and __Require_noErr_Quiet. Let’s look at their definitions:
#ifndef __Require_Quiet
#define __Require_Quiet(assertion, exceptionLabel) \
do\ {\if( __builtin_expect(! (assertion), 0) ) \ { \ goto exceptionLabel; \} \}while(0)#endif
Copy the code
You can see that the __Require_Quiet macro is used to jump and execute the code after the tag when the assertion is false
#ifndef __Require_noErr_Quiet
#define __Require_noErr_Quiet(errorCode, exceptionLabel) \
do\ {\if( __builtin_expect(0 ! = (errorCode), 0) ) \ { \ goto exceptionLabel; \} \}while(0)#endif
Copy the code
As you can see, the __Require_noErr_Quiet macro is used to jump and execute the code after the tag when the assertion is true
2.2 class extensions
/** Save server certificate authentication mode */ @property (readWrite, Nonatomic, assign) AFSSLPinningMode SSLPinningMode; /** Save the set of public keys in the certificate */ @property (readwrite, nonatomic, strong) NSSet *pinnedPublicKeys;Copy the code
2.3. To achieve
+ (NSSet *)certificatesInBundle:(NSBundle *)bundle {// obtain all. Cer file paths = [bundle pathsForResourcesOfType:@"cer" inDirectory:@"."]; NSMutableSet *certificates = [NSMutableSet] // Walk through the file path and convert the corresponding files to NSDatasetWithCapacity:[paths count]];
for (NSString *path inpaths) { NSData *certificateData = [NSData dataWithContentsOfFile:path]; [certificates addObject:certificateData]; } // Returns a collection of certificates converted to binaryreturn [NSSet setWithSet:certificates]; DefaultPinnedCertificates {} + (NSSet *) / / get the default certificate list static NSSet * _defaultPinnedCertificates = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ NSBundle *bundle = [NSBundle bundleForClass:[self class]]; _defaultPinnedCertificates = [self certificatesInBundle:bundle]; });return_defaultPinnedCertificates; } + (instanceType)defaultPolicy {// Instantiate the object by default AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = AFSSLPinningModeNone;returnsecurityPolicy; } + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode {// call the omnipowerful instantiation methodreturn[self policyWithPinningMode:pinningMode withPinnedCertificates:[self defaultPinnedCertificates]]; } + (instancetype)policyWithPinningMode:(AFSSLPinningMode)pinningMode withPinnedCertificates:(NSSet *)pinnedCertificates {// instantiate the object and set the property AFSecurityPolicy *securityPolicy = [[self alloc] init]; securityPolicy.SSLPinningMode = pinningMode; [securityPolicysetPinnedCertificates:pinnedCertificates];
return securityPolicy;
}
- (instancetype)init {
self = [super init];
if(! self) {returnnil; } self.validatesDomainName = YES;return self;
}
- (void)setPinnedCertificates:(NSSet *)pinnedCertificates { _pinnedCertificates = pinnedCertificates; // If a certificate is set, fetch the public key in the certificate and save itif (self.pinnedCertificates) {
NSMutableSet *mutablePinnedPublicKeys = [NSMutableSet setWithCapacity:[self.pinnedCertificates count]];
for (NSData *certificate in self.pinnedCertificates) {
id publicKey = AFPublicKeyForCertificate(certificate);
if(! publicKey) {continue;
}
[mutablePinnedPublicKeys addObject:publicKey];
}
self.pinnedPublicKeys = [NSSet setWithSet:mutablePinnedPublicKeys];
} else {
self.pinnedPublicKeys = nil;
}
}
- (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust
forDomain: Domain nsstrings *) {/ / when using self-built certificate authentication Domain, must use AFSSLPinningModePublicKey or AFSSLPinningModeCertificate for validationif(domain && self.allowInvalidCertificates && self.validatesDomainName && (self.SSLPinningMode == AFSSLPinningModeNone || [self.pinnedCertificates count] == 0)) { // https://developer.apple.com/library/mac/documentation/NetworkingInternet/Conceptual/NetworkingTopics/Articles/Overriding SSLChainValidationCorrectly.html // According to the docs, you should only trust your provided certsfor evaluation.
// Pinned certificates are added to the trust. Without pinned certificates,
// there is nothing to evaluate against.
//
// From Apple Docs:
// "Do not implicitly trust self-signed certificates as anchors (kSecTrustOptionImplicitAnchors). // Instead, add your own (self-signed) CA certificate to the list of trusted anchors."
NSLog(@"In order to validate a domain name for self signed certificates, you MUST use pinning.");
returnNO; } // Add a policy to verify domain names if necessary NSMutableArray *policies = [NSMutableArray array];if (self.validatesDomainName) {
[policies addObject:(__bridge_transfer id)SecPolicyCreateSSL(true, (__bridge CFStringRef)domain)];
} else{ [policies addObject:(__bridge_transfer id)SecPolicyCreateBasicX509()]; ServerTrust SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef)policies); // If the authentication policy is no authenticationif(self.sslPinningMode == AFSSLPinningModeNone) {// If the trust certificate is invalid or expired, the certificate is directly authenticated, otherwise the certificate is returnedreturnself.allowInvalidCertificates || AFServerTrustIsValid(serverTrust); // If the validation policy is validation but failed and you do not trust invalid or expired certificates, return failed}else if(! AFServerTrustIsValid(serverTrust) && ! self.allowInvalidCertificates) {returnNO; } /** When the code goes here it means: 1. There is validation policy 2. */ switch (self.sslpinningMode) {// If there is no authentication policy, the certificate fails to pass the authenticationcase AFSSLPinningModeNone:
default:
returnNO; // If the validation policy is to validate the entire certificatecaseAFSSLPinningModeCertificate: {/ / iterates over to verify certificate NSMutableArray * pinnedCertificates = [NSMutableArray array];for (NSData *certificateData inself.pinnedCertificates) { [pinnedCertificates addObject:(__bridge_transfer id)SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certificateData)]; } SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)pinnedCertificates); // Return if validation failsif(! AFServerTrustIsValid(serverTrust)) {returnNO; } // Obtain the chain after being validated,which *should* contain the pinned certificate in the last position (if it is the Root CA)
NSArray *serverCertificates = AFCertificateTrustChainForServerTrust(serverTrust);
for (NSData *trustChainCertificate in [serverCertificates reverseObjectEnumerator]) {
if ([self.pinnedCertificates containsObject:trustChainCertificate]) {
returnYES; }}returnNO; } // If the authentication policy is to verify the public key of the certificatecaseAFSSLPinningModePublicKey: {/ / get the server returns and comparison between the public and local public key as long as have a same authenticated NSUInteger trustedPublicKeyCount = 0; NSArray *publicKeys = AFPublicKeyTrustChainForServerTrust(serverTrust);for (id trustChainPublicKey in publicKeys) {
for (id pinnedPublicKey in self.pinnedPublicKeys) {
if(AFSecKeyIsEqualToKey((__bridge SecKeyRef)trustChainPublicKey, (__bridge SecKeyRef)pinnedPublicKey)) { trustedPublicKeyCount += 1; }}}returntrustedPublicKeyCount > 0; }}return NO;
}
Copy the code
2.4. KVO
+ (NSSet *)keyPathsForValuesAffectingPinnedPublicKeys {
return [NSSet setWithObject:@"pinnedCertificates"];
}
Copy the code
Add a dependency key to make the pinnedPublicKeys property dependent on the pinnedCertificates property so that observers of the pinnedPublicKeys can be notified when the Value of the pinnedCertificates property changes
2.5. Implementation of NSSecureCoding protocol
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [self init];
if(! self) {return nil;
}
self.SSLPinningMode = [[decoder decodeObjectOfClass:[NSNumber class] forKey:NSStringFromSelector(@selector(SSLPinningMode))] unsignedIntegerValue];
self.allowInvalidCertificates = [decoder decodeBoolForKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
self.validatesDomainName = [decoder decodeBoolForKey:NSStringFromSelector(@selector(validatesDomainName))];
self.pinnedCertificates = [decoder decodeObjectOfClass:[NSArray class] forKey:NSStringFromSelector(@selector(pinnedCertificates))];
return self;
}
- (void)encodeWithCoder:(NSCoder *)coder {
[coder encodeObject:[NSNumber numberWithUnsignedInteger:self.SSLPinningMode] forKey:NSStringFromSelector(@selector(SSLPinningMode))];
[coder encodeBool:self.allowInvalidCertificates forKey:NSStringFromSelector(@selector(allowInvalidCertificates))];
[coder encodeBool:self.validatesDomainName forKey:NSStringFromSelector(@selector(validatesDomainName))];
[coder encodeObject:self.pinnedCertificates forKey:NSStringFromSelector(@selector(pinnedCertificates))];
}
Copy the code
About NSSecureCoding AFNetworking source reading (2), from the AFURLRequestSerialization “4.1.2.4.7 NSSecureCoding implementation methods of agreement” in has been explained, will not repeat
2.6. Implementation of NSCopying protocol
- (instancetype)copyWithZone:(NSZone *)zone {
AFSecurityPolicy *securityPolicy = [[[self class] allocWithZone:zone] init];
securityPolicy.SSLPinningMode = self.SSLPinningMode;
securityPolicy.allowInvalidCertificates = self.allowInvalidCertificates;
securityPolicy.validatesDomainName = self.validatesDomainName;
securityPolicy.pinnedCertificates = [self.pinnedCertificates copyWithZone:zone];
return securityPolicy;
}
Copy the code
3. Summary
Since I don’t know much about HTTP and SSL, I have only read the general features. To learn more about HTTP and SSL, learn more about HTTP and SSL.
AFNetworking
AFNetworking (A) — Start using it
Source: reading AFNetworking (2) — AFURLRequestSerialization
Source: reading AFNetworking (3) — AFURLResponseSerialization
AFNetworking (4) — AFSecurityPolicy
Source: reading AFNetworking (5) — AFNetworkReachabilityManager
AFNetworking (6) — Afurlssession Manager
AFNetworking (7) — Afhttpssession Manager
AFNetworking (8) – AFAutoPurgingImageCache
AFNetworking (9) — AFImageDownloader
The source code to read: AFNetworking (10) — AFNetworkActivityIndicatorManager
AFNetworking — UIActivityIndicatorView+AFNetworking
AFNetworking (12) — UIButton+AFNetworking
AFNetworking (13) — UIImageView+AFNetworking
UIProgressView+AFNetworking
AFNetworking — UIRefreshControl+AFNetworking
UIWebView+AFNetworking