iOS HTTPS 双向认证

iOS HTTPS 双向认证

搞了半天,记录一下,坑很多。
双向认证,就是在访问网络的时候进行证书认证,首先本地需要一个服务器证书,一个客户端证书。客户端发送请求,服务器返回服务器证书和本地服务器证书比对,然后客户端发送客户端证书到服务器。如果全部匹配就返回加密算法,然后可以访问网络,否则就不能访问。

流程

  • 1.需要服务端提供认证证书.crt文件,然后自己导出成.cer文件
  • 2.将导出的cer证书加入到项目中,注意勾选相应的target不然可能获取证书路径为nil
  • 3.通过cer证书生成证书校验的安全策略
  • 4.在AFNetworking的网络请求中设置安全策略:[_manager setSecurityPolicy:[CertificatehttpsTools customSecurityPolicy]];
  • 5.通过抓包工具Charles检验请求和返回的内容是否加密

证书

  • 需要server.cer 和client.p12两个文件,用的自签名证书
  • 配置项目info.plist,增加下面几个属性
  • 注意域名和服务器配置有关
	<key>NSAppTransportSecurity</key>
	<dict>
		<key>NSExceptionDomains</key>
		<dict>
			<key>xxxx.com</key>
			<dict>
				<key>NSExceptionAllowsInsecureHTTPLoads</key>
				<true/>
				<key>NSExceptionMinimumTLSVersion</key>
				<string>TLSv1.0</string>
				<key>NSExceptionRequiresForwardSecrecy</key>
				<false/>
				<key>NSIncludesSubdomains</key>
				<true/>
			</dict>
		</dict>
	</dict>

网络

  • AFN3.0
  • 然后就是设置AFN的安全参数 AFSecurityPolicy
  • 在初始化网络工具类是指定这个参数
  • 需要注意这个baseUrl问题,指定类似这种就行 https://192.168.0.22" ,不然就会报错 'Invalid Security Policy', reason: 'A security policy configured with AFSSLPinningModeCertificate can only be applied on a manager with a secure base URL (i.e. https)'
- (void)setupAFNetwork{
    AFHTTPSessionManager *manager = [[AFHTTPSessionManager manager] initWithBaseURL:[NSURL URLWithString:BaseURL]];
    manager.securityPolicy = [self getCustomHttpsPolicy:manager];
    manager.securityPolicy.allowInvalidCertificates = YES;
    manager.responseSerializer.acceptableContentTypes = [manager.responseSerializer.acceptableContentTypes setByAddingObject:@"text/html"];
    
    self.httpSessionManager = manager;
    
}
// 配置安全参数
-(AFSecurityPolicy*) getCustomHttpsPolicy:(AFHTTPSessionManager*)manager{
    
    //https 公钥证书配置
    
    NSString *certFilePath = [[NSBundle mainBundle] pathForResource:@"serverapple" ofType:@"cer"];
    
    NSData *certData = [NSData dataWithContentsOfFile:certFilePath];
    
    NSSet *certSet = [NSSet setWithObject:certData];
    
    AFSecurityPolicy *policy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certSet];
    
    policy.allowInvalidCertificates = YES;
    
    policy.validatesDomainName = NO;//是否校验证书上域名与请求域名一致
   
    //https回调 客户端验证
    
    [manager setSessionDidBecomeInvalidBlock:^(NSURLSession * _Nonnull session, NSError * _Nonnull error) {
        
        NSLog(@"setSessionDidBecomeInvalidBlock");
        
    }];
    
    __weak typeof(manager)weakManger = manager;
    
    __weak typeof(self)weakSelf = self;
    
    //客户端请求验证 重写 setSessionDidReceiveAuthenticationChallengeBlock 方法
    
    [manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) {
        
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        
        __autoreleasing NSURLCredential *credential =nil;
        
        if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            
            if([weakManger.securityPolicy evaluateServerTrust:challenge.protectionSpace.serverTrust forDomain:challenge.protectionSpace.host]) {
                
                credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
                
                if(credential) {
                    
                    disposition =NSURLSessionAuthChallengeUseCredential;
                    
                } else {
                    
                    disposition =NSURLSessionAuthChallengePerformDefaultHandling;
                    
                }
                
            } else {
                
                disposition = NSURLSessionAuthChallengeCancelAuthenticationChallenge;
                
            }
            
        } else {
            
            // client authentication
            
            SecIdentityRef identity = NULL;
            
            SecTrustRef trust = NULL;
            
            NSString *p12 = [[NSBundle mainBundle] pathForResource:@"clientapple"ofType:@"p12"];
            
            NSFileManager *fileManager =[NSFileManager defaultManager];
            
            if(![fileManager fileExistsAtPath:p12])
                
            {
                
                NSLog(@"client.p12:not exist");
                
            }
            
            else
                
            {
                
                NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
                
                if ([[weakSelf class]extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data])
                    
                {
                    
                    SecCertificateRef certificate = NULL;
                    
                    SecIdentityCopyCertificate(identity, &certificate);
                    
                    const void*certs[] = {certificate};
                    
                    CFArrayRef certArray =CFArrayCreate(kCFAllocatorDefault, certs,1,NULL);
                    
                    credential =[NSURLCredential credentialWithIdentity:identity certificates:(__bridge  NSArray*)certArray persistence:NSURLCredentialPersistencePermanent];
                    
                    disposition =NSURLSessionAuthChallengeUseCredential;
                    
                }
                
            }
            
        }
        
        *_credential = credential;
        
        return disposition;
        
    }];
    
    return policy;
    
}


+ (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
    
    OSStatus securityError = errSecSuccess;
    
    //client certificate password
    
    NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"123456"
                                      
                                                                 forKey:(__bridge id)kSecImportExportPassphrase];
    
    CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
    
    securityError = SecPKCS12Import((__bridge CFDataRef)inPKCS12Data,(__bridge CFDictionaryRef)optionsDictionary,&items);
    
    if(securityError == 0) {
        
        CFDictionaryRef myIdentityAndTrust =CFArrayGetValueAtIndex(items,0);
        
        const void*tempIdentity =NULL;
        
        tempIdentity= CFDictionaryGetValue (myIdentityAndTrust,kSecImportItemIdentity);
        
        *outIdentity = (SecIdentityRef)tempIdentity;
        
        const void*tempTrust =NULL;
        
        tempTrust = CFDictionaryGetValue(myIdentityAndTrust,kSecImportItemTrust);
        
        *outTrust = (SecTrustRef)tempTrust;
        
    } else {
        
        NSLog(@"Failedwith error code %d",(int)securityError);
        
        return NO;
        
    }
    
    return YES;
    
}

参考文章
参考文章

原文地址:https://www.cnblogs.com/songliquan/p/12784559.html