iOS使用自簽名證書實現(xiàn)HTTPS請求

由于蘋果規(guī)定2017年1月1日以后弊仪,所有APP都要使用HTTPS進行網(wǎng)絡請求励饵,否則無法上架,因此研究了一下在iOS中使用HTTPS請求的實現(xiàn)役听。相信大家對HTTPS都或多或少有些了解,這里我就不再介紹了典予,主要功能就是將傳輸?shù)膱笪倪M行加密,提高安全性。

1艳吠、證書準備

證書分為兩種,一種是花錢向認證的機構購買的證書昭娩,服務端如果使用的是這類證書的話,那一般客戶端不需要做什么栏渺,用HTTPS進行請求就行了,蘋果內(nèi)置了那些受信任的根證書的磕诊。另一種是自己制作的證書纹腌,使用這類證書的話是不受信任的(當然也不用花錢買)滞磺,因此需要我們在代碼中將該證書設置為信任證書。

我這邊使用的是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改為你創(chuàng)建證書的密碼。

然后在文件夾空白處按住ctrl+shift點擊右鍵徒河,選擇在此處打開命令窗口,在命令窗口中輸入“start.bat ip/域名”來執(zhí)行批處理文件顽照,其中start.bat是添加了上述命令的批處理文件,ip/域名即你服務器的ip或者域名代兵。執(zhí)行成功后會生成一個.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或域名蒸殿。注意蘋果要求協(xié)議要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];

需要實現(xiàn)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)坷剧,請?zhí)鎿Q掉你的證書名稱
???????????? */
??????????? 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 */
???????????
??????????? //將讀取的證書設置為服務端幀數(shù)的根證書
??????????? 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];
}

此時即可成功請求到服務端惫企。

注:調(diào)用SecTrustSetAnchorCertificates設置可信任證書列表后就只會在設置的列表中進行驗證,會屏蔽掉系統(tǒng)原本的信任列表狞尔,要使系統(tǒng)的繼續(xù)起作用只要調(diào)用SecTrustSetAnchorCertificates方法,第二個參數(shù)設置成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重新設置證書驗證回調(diào)來自己驗證證書逸月,然后將自己的證書加入到可信任的證書列表中遍膜,即可通過證書的校驗瓤湘。

由于服務端使用.jks是一個證書庫,客戶端獲取到的證書可能不止一本恩尾,我這邊獲取到了兩本,具體獲取到基本可通過SecTrustGetCertificateCount方法獲取證書個數(shù)翰意,AFNetworking在evaluateServerTrust:forDomain:方法中,AFSSLPinningMode的類型為AFSSLPinningModeCertificate和AFSSLPinningModePublicKey的時候都有校驗服務端的證書個數(shù)與客戶端信任的證書數(shù)量是否一樣醒第,如果不一樣的話無法請求成功,所以這邊我就修改他的源碼稠曼,當有一個校驗成功時即算成功。

當類型為AFSSLPinningModeCertificate時

return trustedCertificateCount == [serverCertificates count] - 1;

為AFSSLPinningModePublicKey時

return trustedPublicKeyCount > 0 && ((self.validatesCertificateChain) || (!self.validatesCertificateChain && trustedPublicKeyCount >= 1));

去掉了第二塊中的trustedPublicKeyCount == [serverCertificates count]的條件霞幅。

這邊使用的AFNetworking的版本為2.5.3,如果其他版本有不同之處請自行根據(jù)實際情況修改量瓜。

demo地址:https://github.com/fengling2300/networkTest

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市抵赢,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌铅鲤,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件邢享,死亡現(xiàn)場離奇詭異淡诗,居然都是意外死亡骇塘,警方通過查閱死者的電腦和手機款违,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來插爹,“玉大人,你說我怎么就攤上這事赠尾。” “怎么了气嫁?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長寸宵。 經(jīng)常有香客問我,道長梯影,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任光酣,我火速辦了婚禮,結果婚禮上救军,老公的妹妹穿的比我還像新娘。我一直安慰自己唱遭,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布拷泽。 她就那樣靜靜地躺著,像睡著了一般司致。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上脂矫,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音庭再,去河邊找鬼。 笑死拄轻,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的恨搓。 我是一名探鬼主播,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了夺姑?” 一聲冷哼從身側(cè)響起墩邀,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤眉睹,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后废膘,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡斋配,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了艰争。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡甩卓,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出逾柿,到底是詐尸還是另有隱情,我是刑警寧澤宅此,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站父腕,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏侣诵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一杜顺、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧躬络,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽茴扁。三九已至,卻和暖如春峭火,著一層夾襖步出監(jiān)牢的瞬間毁习,已是汗流浹背纺且。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留载碌,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓衅枫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親为鳄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容