zoukankan      html  css  js  c++  java
  • 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;
        
    }
    

    参考文章
    参考文章

  • 相关阅读:
    iOS
    关于HTTP协议学习(三)
    关于HTTP协议学习(二)
    关于HTTP协议学习(一)
    Swift之Swift编码规范
    老罗学习MVC之旅:MVC组件分析
    android 左右翻页
    android 检测网络是否可用
    android 在线升级借助开源中国App源码
    android 渐变展示启动屏
  • 原文地址:https://www.cnblogs.com/songliquan/p/12784559.html
Copyright © 2011-2022 走看看