標籤:time erro epo 參數 xxx 根憑證 是你 屏蔽 nil
由於蘋果規定2017年1月1日以後,所有APP都要使用HTTPS進行網路請求,否則無法上架,因此研究了一下在iOS中使用HTTPS請求的實現。相信大家對HTTPS都或多或少有些瞭解,這裡我就不再介紹了,主要功能就是將傳輸的報文進行加密,提高安全性。
1、認證準備
認證分為兩種,一種是花錢向認證的機構購買的認證,服務端如果使用的是這類認證的話,那一般用戶端不需要做什麼,用HTTPS進行請求就行了,蘋果內建了那些受信任的根憑證的。另一種是自己製作的認證,使用這類認證的話是不受信任的(當然也不用花錢買),因此需要我們在代碼中將該認證設定為信任認證。
我這邊使用的是xca來製作了根憑證,製作流程請參考http://www.2cto.com/Article/201411/347512.html,由於xca無法匯出.jsk的尾碼,因此我們只要製作完根憑證後以.p12的格式匯出就行了,之後的認證製作由命令列來完成。自製一個批次檔,添加如下命令:
set ip=%1%
md %ip%
keytool -importkeystore -srckeystore ca.p12 -srcstoretype PKCS12 -srcstorepass 123456 -destkeystore ca.jks -deststoretype JKS -deststorepass 123456
keytool -genkeypair -alias server-%ip% -keyalg RSA -keystore ca.jks -storepass 123456 -keypass 123456 -validity 3650 -dname "CN=%ip%, OU=ly, O=hik, L=hz, ST=zj, C=cn"
keytool -certreq -alias server-%ip% -storepass 123456 -file %ip%\server-%ip%.certreq -keystore ca.jks
keytool -gencert -alias ca -storepass 123456 -infile %ip%\server-%ip%.certreq -outfile %ip%\server-%ip%.cer -validity 3650 -keystore ca.jks
keytool -importcert -trustcacerts -storepass 123456 -alias server-%ip% -file %ip%\server-%ip%.cer -keystore ca.jks
keytool -delete -keystore ca.jks -alias ca -storepass 123456
將上面加粗的ca.p12改成你匯出的.p12檔案的名稱,123456改為你建立認證的密碼。
然後在檔案夾空白處按住ctrl+shift點擊右鍵,選擇在此處開啟命令視窗,在命令視窗中輸入“start.bat ip/網域名稱”來執行批次檔,其中start.bat是添加了上述命令的批次檔,ip/網域名稱即你伺服器的ip或者網域名稱。執行成功後會產生一個.jks檔案和一個以你的ip或網域名稱命名的檔案夾,檔案夾中有一個.cer的認證,這邊的.jks檔案將在服務端使用.cer檔案將在用戶端使用,到這裡認證的準備工作就完成了。
2、服務端配置
由於我不做服務端好多年,只會使用Tomcat,所以這邊只講下Tomcat的配置方法,使用其他伺服器的同學請自行尋找設定方法。
開啟tomcat/conf目錄下的server.xml檔案將HTTPS的配置開啟,並進行如下配置:
<Connector URIEncoding="UTF-8" protocol="org.apache.coyote.http11.Http11NioProtocol" port="8443" maxThreads="200" scheme="https" secure="true" SSLEnabled="true" sslProtocol="TLSv1.2" sslEnabledProtocols="TLSv1.2" keystoreFile="${catalina.base}/ca/ca.jks" keystorePass="123456" clientAuth="false" SSLVerifyClient="off" netZone="你的ip或網域名稱"/>
keystoreFile是你.jks檔案放置的目錄,keystorePass是你製作認證時設定的密碼,netZone填寫你的ip或網域名稱。注意蘋果要求協議要TLSv1.2以上。
3、iOS端配置
首先把前面產生的.cer檔案添加到項目中,注意在添加的時候選擇要添加的targets。
1.使用NSURLSession進行請求
代碼如下:
NSString *urlString = @"https://xxxxxxx";
NSURL *url = [NSURL URLWithString:urlString];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestReloadIgnoringCacheData timeoutInterval:10.0f];
NSURLSession *session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration] delegate:self delegateQueue:[NSOperationQueue mainQueue]];
NSURLSessionDataTask *task = [session dataTaskWithRequest:request];
[task resume];
需要實現NSURLSessionDataDelegate中的URLSession:didReceiveChallenge:completionHandler:方法來進行認證的校正,代碼如下:
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition, NSURLCredential * _Nullable credential))completionHandler {
NSLog(@"認證認證");
if ([[[challenge protectionSpace] authenticationMethod] isEqualToString: NSURLAuthenticationMethodServerTrust]) {
do
{
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
NSCAssert(serverTrust != nil, @"serverTrust is nil");
if(nil == serverTrust)
break; /* failed */
/**
* 匯入多張CA認證(Certification Authority,支援SSL認證以及自簽名的CA),請替換掉你的認證名稱
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自我簽署憑證
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
NSCAssert(caCert != nil, @"caCert is nil");
if(nil == caCert)
break; /* failed */
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, @"caRef is nil");
if(nil == caRef)
break; /* failed */
//可以添加多張認證
NSArray *caArray = @[(__bridge id)(caRef)];
NSCAssert(caArray != nil, @"caArray is nil");
if(nil == caArray)
break; /* failed */
//將讀取的認證設定為服務端幀數的根憑證
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
if(!(errSecSuccess == status))
break; /* failed */
SecTrustResultType result = -1;
//通過本地匯入的認證來驗證伺服器的認證是否可信
status = SecTrustEvaluate(serverTrust, &result);
if(!(errSecSuccess == status))
break; /* failed */
NSLog(@"stutas:%d",(int)status);
NSLog(@"Result: %d", result);
BOOL allowConnect = (result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed);
if (allowConnect) {
NSLog(@"success");
}else {
NSLog(@"error");
}
/* kSecTrustResultUnspecified and kSecTrustResultProceed are success */
if(! allowConnect)
{
break; /* failed */
}
#if 0
/* Treat kSecTrustResultConfirm and kSecTrustResultRecoverableTrustFailure as success */
/* since the user will likely tap-through to see the dancing bunnies */
if(result == kSecTrustResultDeny || result == kSecTrustResultFatalTrustFailure || result == kSecTrustResultOtherError)
break; /* failed to trust cert (good in this case) */
#endif
// The only good exit point
NSLog(@"信任該認證");
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
return [[challenge sender] useCredential: credential
forAuthenticationChallenge: challenge];
}
while(0);
}
// Bad dog
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge,credential);
return [[challenge sender] cancelAuthenticationChallenge: challenge];
}
此時即可成功請求到服務端。
註:調用SecTrustSetAnchorCertificates設定可信任認證列表後就只會在設定的列表中進行驗證,會屏蔽掉系統原本的信任清單,要使系統的繼續起作用只要調用SecTrustSetAnchorCertificates方法,第二個參數設定成NO即可。
2.使用AFNetworking進行請求
AFNetworking首先需要配置AFSecurityPolicy類,AFSecurityPolicy類封裝了認證校正的過程。
/**
AFSecurityPolicy分三種驗證模式:
AFSSLPinningModeNone:只是驗證認證是否在信任清單中
AFSSLPinningModeCertificate:該模式會驗證認證是否在信任清單中,然後再對比服務端認證和用戶端認證是否一致
AFSSLPinningModePublicKey:只驗證服務端認證與用戶端認證的公開金鑰是否一致
*/
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
securityPolicy.allowInvalidCertificates = YES;//是否允許使用自我簽署憑證
securityPolicy.validatesDomainName = NO;//是否需要驗證網域名稱,預設YES
AFHTTPSessionManager *_manager = [AFHTTPSessionManager manager];
_manager.responseSerializer = [AFHTTPResponseSerializer serializer];
_manager.securityPolicy = securityPolicy;
//設定逾時
[_manager.requestSerializer willChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.timeoutInterval = 20.f;
[_manager.requestSerializer didChangeValueForKey:@"timeoutinterval"];
_manager.requestSerializer.cachePolicy = NSURLRequestReloadIgnoringCacheData;
_manager.responseSerializer.acceptableContentTypes = [NSSet setWithObjects:@"application/xml",@"text/xml",@"text/plain",@"application/json",nil];
__weak typeof(self) weakSelf = self;
[_manager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
/**
* 匯入多張CA認證
*/
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"ca" ofType:@"cer"];//自我簽署憑證
NSData* caCert = [NSData dataWithContentsOfFile:cerPath];
NSArray *cerArray = @[caCert];
weakSelf.manager.securityPolicy.pinnedCertificates = cerArray;
SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, @"caRef is nil");
NSArray *caArray = @[(__bridge id)(caRef)];
NSCAssert(caArray != nil, @"caArray is nil");
OSStatus status = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)caArray);
SecTrustSetAnchorCertificatesOnly(serverTrust,NO);
NSCAssert(errSecSuccess == status, @"SecTrustSetAnchorCertificates failed");
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__autoreleasing NSURLCredential *credential = nil;
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if ([weakSelf.manager.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 {
disposition = NSURLSessionAuthChallengePerformDefaultHandling;
}
return disposition;
}];
上述代碼通過給AFHTTPSessionManager重新設定認證驗證回調來自己驗證認證,然後將自己的認證加入到可信任的認證列表中,即可通過認證的校正。
由於服務端使用.jks是一個認證庫,用戶端擷取到的認證可能不止一本,我這邊擷取到了兩本,具體擷取到基本可通過SecTrustGetCertificateCount方法擷取認證個數,AFNetworking在evaluateServerTrust:forDomain:方法中,AFSSLPinningMode的類型為AFSSLPinningModeCertificate和AFSSLPinningModePublicKey的時候都有校正服務端的認證個數與用戶端信任的認證數量是否一樣,如果不一樣的話無法請求成功,所以這邊我就修改他的源碼,當有一個校正成功時即算成功。
當類型為AFSSLPinningModeCertificate時
return trustedCertificateCount == [serverCertificates count] - 1;
為AFSSLPinningModePublicKey時
return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1));
去掉了第二塊中的trustedPublicKeyCount == [serverCertificates count]的條件。
這邊使用的AFNetworking的版本為2.5.3,如果其他版本有不同之處請自行根據實際情況修改。
demo地址:https://github.com/fengling2300/networkTest
浪投王
連結:http://www.jianshu.com/p/e6a26ecd84aa
來源:簡書
著作權歸作者所有。商業轉載請聯絡作者獲得授權,非商業轉載請註明出處。
iOS使用自我簽署憑證實現HTTPS請求