自苹果宣布2017年1月1日开始强制使用https以来,htpps慢慢成为大家讨论的对象之一,不是说此前https没有出现,只是这一决策让得开发者始料未及,博主在15年的时候就做过https的接口,深知此坑之深,原因就是自身对这方面知识不了解加上网上的资料少,除此外还有博客不知对错就互相转载,导致当时网上几乎找不到能用的代码,这一点,博主说的毫不夸张。
鉴于此,博主一直想填一下这个坑,多增加一些正确的代码,来供广大开发者使用,后来一直被搁置,经过尝试后,博主现将整理好的代码发布在这里,希望能帮到焦急寻找的开发者。
1.先来说说老的afnetworking2.x怎么来实现的
博主在网上看过几篇帖子,其中说的一些方法是正确的,但是却并不全对,由于那几篇博客几乎一样,博主不能确定最早的那篇是谁写的,所以就重新在下面说明下方法:
1)倒入client.p12证书;
2)在plist文件做如图配置:
3)在afnetworking中修改一个类:
找到这个文件,在里面增加一个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
- (osstatus)extractidentity:(cfdataref)inp12data toidentity:(secidentityref*)identity { osstatus securityerror = errsecsuccess; cfstringref password = cfstr( "证书密码" ); const void *keys[] = { ksecimportexportpassphrase }; const void *values[] = { password }; cfdictionaryref options = cfdictionarycreate(null, keys, values, 1, null, null); cfarrayref items = cfarraycreate(null, 0, 0, null); securityerror = secpkcs12import(inp12data, options, &items); if (securityerror == 0) { cfdictionaryref ident = cfarraygetvalueatindex(items,0); const void *tempidentity = null; tempidentity = cfdictionarygetvalue(ident, ksecimportitemidentity); *identity = (secidentityref)tempidentity; } if (options) { cfrelease(options); } return securityerror; } |
再修改一个方法:
用下面的这段代码替换nsurlconnectiondelegate中的同名代码,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
- ( void )connection:(nsurlconnection *)connection willsendrequestforauthenticationchallenge:(nsurlauthenticationchallenge *)challenge { nsstring *thepath = [[nsbundle mainbundle] pathforresource:@ "client" oftype:@ "p12" ]; //倒入证书 nslog(@"thepath===========%@",thepath); nsdata *pkcs12data = [[nsdata alloc] initwithcontentsoffile:thepath]; cfdataref inpkcs12data = (__bridge cfdataref)pkcs12data; secidentityref identity = null; // extract the ideneity from the certificate [self extractidentity :inpkcs12data toidentity:&identity]; seccertificateref certificate = null; secidentitycopycertificate (identity, &certificate); const void *certs[] = {certificate}; // cfarrayref certarray = cfarraycreate(kcfallocatordefault, certs, 1, null); // create a credential from the certificate and ideneity, then reply to the challenge with the credential //nslog(@"identity=========%@",identity); nsurlcredential *credential = [nsurlcredential credentialwithidentity:identity certificates:nil persistence:nsurlcredentialpersistencepermanent]; // credential = [nsurlcredential credentialwithidentity:identity certificates:(__bridge nsarray*)certarray persistence:nsurlcredentialpersistencepermanent]; [challenge.sender usecredential:credential forauthenticationchallenge:challenge]; } |
4)发起请求
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
nsstring *url = @ "xxxxxxxxxx" ; // 1.获得请求管理者 afhttprequestoperationmanager *mgr = [afhttprequestoperationmanager manager]; //2设置https 请求 afsecuritypolicy *securitypolicy = [afsecuritypolicy policywithpinningmode:afsslpinningmodecertificate]; securitypolicy.allowinvalidcertificates = yes; mgr.securitypolicy = securitypolicy; // 3.发送post请求 [mgr post:url parameters:nil success:^(afhttprequestoperation * _nonnull operation, id _nonnull responseobject) { nslog(@ "responseobject: %@" , responseobject); } failure:^(afhttprequestoperation * _nonnull operation, nserror * _nonnull error) { nslog(@ "error: %@" , error); }]; |
到此,老版的afnetworking请求https接口的双向验证就做完了,但是有一个问题,这里需要改动afnetworking的代码,何况新的afnetworking已经有了,为了保持代码的活力,老的应该摒弃的,而且更新pods后肯定替换的代码就没了,也是一个问题,不要急,下面来说说怎么用新的afnetworking,并解决被pods更新替换代码的问题。
最后再说一点,使用老的af来请求,只用到了client.p12文件,并没有用到server.cer,在新的里面是有用到的,猜测可能是客户端选择信任任何证书导致的,就变成了单向的验证。
demo放在最后
2.来说说新的afnetworking3.x怎么来实现的
1)倒入client.p12和server.cer文件
2)plist内的设置,这是和上面一样的:
3)这里可不需要修改类里面的代码,但是这里需要重写一个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
nsstring *url = @ "https://test.niuniuhaoguanjia.com/3.0.0/?service=city.getcitylist" ; nsstring *certfilepath = [[nsbundle mainbundle] pathforresource:@ "server" 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; _manager = [afhttpsessionmanager manager]; _manager.securitypolicy = policy; _manager.requestserializer = [afhttprequestserializer serializer]; _manager.responseserializer = [afhttpresponseserializer serializer]; _manager.responseserializer.acceptablecontenttypes = [nsset setwithobjects:@ "application/json" , @ "text/json" , @ "text/javascript" ,@ "text/plain" , nil]; //关闭缓存避免干扰测试r _manager.requestserializer.cachepolicy = nsurlrequestreloadignoringlocalcachedata; [_manager setsessiondidbecomeinvalidblock:^(nsurlsession * _nonnull session, nserror * _nonnull error) { nslog(@ "setsessiondidbecomeinvalidblock" ); }]; //客户端请求验证 重写 setsessiondidreceiveauthenticationchallengeblock 方法 __weak typeof(self)weakself = self; [_manager setsessiondidreceiveauthenticationchallengeblock:^nsurlsessionauthchallengedisposition(nsurlsession*session, nsurlauthenticationchallenge *challenge, nsurlcredential *__autoreleasing*_credential) { 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 { // 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 ([[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; }]; |
4)发起请求
1
2
3
4
5
6
7
8
9
10
11
12
13
|
//第三步和这一步代码是放在一起的,请注意哦 [_manager get:url parameters:nil progress:^(nsprogress * _nonnull downloadprogress) { } success:^(nsurlsessiondatatask * _nonnull task, id _nullable responseobject) { nsdictionary *dic = [nsjsonserialization jsonobjectwithdata:responseobject options:nsjsonreadingmutablecontainers error:nil]; nslog(@ "json: %@" , dic); } failure:^(nsurlsessiondatatask * _nullable task, nserror * _nonnull error) { nslog(@ "error: %@" , error); nsdata *data = [error.userinfo objectforkey:@ "com.alamofire.serialization.response.error.data" ]; nsstring *str = [[nsstring alloc]initwithdata:data encoding:nsutf8stringencoding]; nslog(@ "%@" ,str); }]; |
另外还要加上一个方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
+( bool )extractidentity:(secidentityref*)outidentity andtrust:(sectrustref *)outtrust frompkcs12data:(nsdata *)inpkcs12data { osstatus securityerror = errsecsuccess; //client certificate password nsdictionary*optionsdictionary = [nsdictionary dictionarywithobject:@ "证书密码" 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; } |
没错,我们是要封装一下,可是要怎么封装呢?博主尝试了集中都失败了,真是百思不得解,相信主动去封装的开发者也会碰到封装后请求失败的问题,也许你成功了,但是这里需要注意一个在block内使用变量的问题,具体的可以去看博主怎么封装的。
到这里,新的af请求https就已经结束了,想看封装的,demo放在最后。
3.单向验证
说到这个,不得不说一下网上的很多方法,都把单向验证当作双向的,其实也是并不理解其原理,关于原理,请看这里
代码实现af都是一样的:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
//af加上这句和下面的方法 _manager.securitypolicy = [self customsecuritypolicy]; /**** ssl pinning ****/ - (afsecuritypolicy*)customsecuritypolicy { nsstring *cerpath = [[nsbundle mainbundle] pathforresource:@ "server" oftype:@ "cer" ]; nsdata *certdata = [nsdata datawithcontentsoffile:cerpath]; afsecuritypolicy *securitypolicy = [afsecuritypolicy policywithpinningmode:afsslpinningmodecertificate]; [securitypolicy setallowinvalidcertificates:yes]; nsset *set = [nsset setwithobjects:certdata, nil]; [securitypolicy setpinnedcertificates:@[certdata]]; /**** ssl pinning ****/ return securitypolicy; } |
4.demo下载福利
因为证书安全问题,demo 里的证书博主删除了,请见谅,请大家放入自己的证书。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持服务器之家。
原文链接:http://blog.csdn.net/codingfire/article/details/53419521