https双向认证

项目中要求使用https双向认证,提高数据传输安全性,之前用的单向认证只验证了服务端提供的证书和客户端内置的服务端证书是否一致,现在服务端需要验证客户端证书,客户端也验证服务端证书

  1. afnetworking中使用https双向认证;在setSessionDidReceiveAuthenticationChallengeBlockblock中,接收到服务端请求后,需要客户端提供证书

    + (void)setupSessionManager:(AFHTTPSessionManager *)manager {
        __weak typeof(manager) weakManager = manager;
        [manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession*session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing*_credential) {
            NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
            __autoreleasing NSURLCredential *credential =nil;
            if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {//验证服务器证书
                if([weakManager.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:@"client"ofType:@"p12"];
                NSFileManager *fileManager =[NSFileManager defaultManager];
                
                if(![fileManager fileExistsAtPath:p12]) {
                    NSLog(@"client.p12:not exist");
                } else {
                    NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
                    if ([self 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;
        }];
    }
    + (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;
    }
    
  2. wkwebview中使用https双向认证;wkwebview也有类似的回调方法,处理方法和上面一致;

    - (void)webView:(WKWebView *)webView didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential * _Nullable))completionHandler {
        NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
        __autoreleasing NSURLCredential *credential =nil;
        if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            if([self 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:@"client"ofType:@"p12"];
            NSFileManager *fileManager =[NSFileManager defaultManager];
            
            if(![fileManager fileExistsAtPath:p12]) {
                NSLog(@"client.p12:not exist");
            } else {
                NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
                if ([self 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;
                }
            }
        }
        completionHandler(disposition, credential);
    }
    - (BOOL)evaluateServerTrust:(SecTrustRef)serverTrust forDomain:(NSString *)domain {
        domain = nil;
        /*
        * 创建证书校验策略
        */
        NSMutableArray *policies = [NSMutableArray array];
        if (domain) {
            [policies addObject:(__bridge_transfer id) SecPolicyCreateSSL(true, (__bridge CFStringRef) domain)];
        } else {
            [policies addObject:(__bridge_transfer id) SecPolicyCreateBasicX509()];
        }
        
        // 绑定校验策略到服务端的证书上
        SecTrustSetPolicies(serverTrust, (__bridge CFArrayRef) policies);
        
        // 评估当前serverTrust是否可信任
        SecTrustResultType result;
        SecTrustEvaluate(serverTrust, &result);
        if (result == kSecTrustResultUnspecified || result == kSecTrustResultProceed) {
            return YES;
        }
        return NO;
    }
    - (BOOL)extractIdentity:(SecIdentityRef*)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
        OSStatus securityError = errSecSuccess;
        //client certificate password
        NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@"Az.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;
    }
    
  3. httpsurlconnection使用https双向认证;对httpsurlconnection进行设置,绑定客户端证书;

    private static void setCertificate(HttpsURLConnection connection) {
        InputStream ksIn = null;
        try {
            // 服务器端需要验证的客户端证书
            KeyStore trustStore = KeyStore.getInstance("BKS");
            ksIn = MyApplication.getInstance().getAssets().open("client.bks");
            keyStore.load(ksIn, "123456".toCharArray());
            SSLContext sslContext = SSLContext.getInstance("TLS");
            KeyManagerFactory keyManagerFactory = KeyManagerFactory.getInstance("X509");
            keyManagerFactory.init(keyStore, "123456".toCharArray());
            sslContext.init(keyManagerFactory.getKeyManagers(), new TrustManager[]{new TrustAllServerCerts()}, new SecureRandom());//trustManagerFactory.getTrustManagers()
            connection.setSSLSocketFactory(sslContext.getSocketFactory());
            connection.setHostnameVerifier(new HostnameVerifier() {
                @Override
                public boolean verify(String s, SSLSession sslSession) {
                    return true;
                }
            });
        } catch (IOException | KeyStoreException | NoSuchAlgorithmException | KeyManagementException | UnrecoverableKeyException | CertificateException e) {
            e.printStackTrace();
        } finally {
            try {
                ksIn.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    
    private static class TrustAllServerCerts implements X509TrustManager {
        //    默认的下面3个接口都会抛出一个异常,这里直接去掉异常,就是客户端忽略验证服务器端的验证信息直接通过
        @Override
        public void checkClientTrusted(X509Certificate[] chain, String authType) {
            Log.e("networkUtil","checkClientTrusted");
        }
    
        @Override
        public void checkServerTrusted(X509Certificate[] chain, String authType) {
            Log.e("networkUtil","checkServerTrusted");
        }
    
        @Override
        public X509Certificate[] getAcceptedIssuers() {
            Log.e("networkUtil","getAcceptedIssuers");
            return new X509Certificate[]{};
        }
    }
    
  4. webview使用https双向认证;类似httpsurlconnection处理方法,在webviewclient的onReceivedClientCertRequest回调方法中,提供bks格式的客户端证书;

    @Override
    public void onReceivedClientCertRequest(WebView webView, ClientCertRequest clientCertRequest) {
    //            super.onReceivedClientCertRequest(webView, clientCertRequest);
        InputStream certificateFileStream = null;
        try {
            PrivateKey privateKey = null;
            X509Certificate[] certificates = null;
    //                certificateFileStream = MyApplication.getInstance().getAssets().open("client.p12");//不支持p12证书???
            certificateFileStream = MyApplication.getInstance().getAssets().open("client.bks");
            KeyStore keyStore = KeyStore.getInstance("BKS");
            String password = "Az.123456";
            keyStore.load(certificateFileStream, password.toCharArray());
    
            Enumeration<String> aliases = keyStore.aliases();
            String alias = aliases.nextElement();
    
            Key key = keyStore.getKey(alias, password.toCharArray());
            if (key instanceof PrivateKey) {
                privateKey = (PrivateKey) key;
                Certificate cert = keyStore.getCertificate(alias);
                certificates = new X509Certificate[1];
                certificates[0] = (X509Certificate) cert;
            }
    
            clientCertRequest.proceed(privateKey, certificates);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                certificateFileStream.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    

iOS的实现方法比较简单,按照网上写法,替换p12证书就可以了;安卓的webviewclient里面使用的双向认证证书使用p12调试一直不行,试过在shouldInterceptRequest回调中拦截所有请求增加https证书也不行;最后试试换了bks格式的证书可以了;iOS使用p12格式,安卓使用bks格式;

posted @ 2022-05-17 15:38  yuyuyu37  阅读(491)  评论(0编辑  收藏  举报