根据广大开发者的传闻,2017年1月1号,苹果公司要执行ATS政策了。所有app必须强制支持https(不包括一些视频流媒体的app)。当然也没说不支持的后果,不过很多人表示,app被强制下架的可能性比较小,顶多就是更新上架的时候不让过而已。不过尽管如此还是把我们吃瓜群众给吓尿了,然后上网各种适配https。
结果cocochina上又发文,表示已经问过苹果了,苹果爸爸表示会延长截止时间,给开发者更多的时间来适配HTTPS,网上那些的软文请不要相信。
不过虽然时间延长了,不过还是早晚的事啊,所以这里就给各位看官讲一讲HTTPS的那些事。
一、http和https的区别
引用度娘的一段话
HTTPS和HTTP的区别
超文本传输协议HTTP协议被用于在Web浏览器和网站服务器之间传递信息。HTTP协议以明文方式发送内容,不提供任何方式的数据加密,如果攻击者截取了Web浏览器和网站服务器之间的传输报文,就可以直接读懂其中的信息,因此HTTP协议不适合传输一些敏感信息,比如信用卡号、密码等。
为了解决HTTP协议的这一缺陷,需要使用另一种协议:安全套接字层超文本传输协议HTTPS。为了数据传输的安全,HTTPS在HTTP的基础上加入了SSL协议,SSL依靠证书来验证服务器的身份,并为浏览器和服务器之间的通信加密。
HTTPS和HTTP的区别主要为以下四点:
一、https协议需要到ca申请证书,一般免费证书很少,需要交费。
二、http是超文本传输协议,信息是明文传输,https 则是具有安全性的ssl加密传输协议。
三、http和https使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。
四、http的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比http协议安全。(引自度娘)
上面的那一坨总结出来就是,http数据为明文传输,被拦截后就直接明文可以看到数据;而https数据为加密传输,即使被拦截到了,那也是乱码。当然,这里应该有抓包过https的小伙伴表示不服,这个下面会说到的。
因为加上了这一ssl层,所以https在整个传输过程中,大部分时间都是消耗在了ssl的认证、加密中,所以相比于http,速度还是会慢一点的,所以上面说视频流媒体的app可以不用强制https,因为太耗时间了。。。
二、https之服务器简单聊一聊
因为本人并不是服务器开发,所以这个服务器这方面就提一下。
要想从http升级成https,那得先服务器认证。
就是要证明你这个服务器,是你所声明的服务器。没错,就像天朝的要证明你是你自己,你证明你妈是你吗。。。表示心疼需要证明的小伙伴。。。。
那怎么证明你妈是你妈,呸。。。不是,是证明你的服务器,就是你所声明的服务器那?你说你是百度,我还说我是那!!
所以,这里就涉及到了一个权威的机构登场CA (Certificate Authority)!!!
采用https的服务器必须从CA (Certificate Authority)申请一个用于证明服务器用途类型的证书。该证书只有用于对应的服务器的时候,客户端才信任此主机。(一般这种证书都是要花钱的买的。。。。)
举个栗子
你用浏览器访问一些网站的时候,浏览器会自动验证网站的证书,如果证书不是CA签发的,那么浏览器会提示提示你,此网站的证书无效(因为不是指定的机构签发的,有可能是自己签发的),如下图
有木有人看着眼熟,对,你登录12306的时候,有木有!!
堂堂大铁路局的12306证书竟然不是正规机构签发的!!要知道浏览器连草榴都信任,说明草榴的证书都是正规买的,12306竟然后还是自己签发的。。。就差这点钱么?
如果服务器买好了证书的话,因为要符合苹果的ATS政策
·服务器所有的连接使用TLS1.2以上版本
·HTTPS证书必须使用SHA256以上哈希算法签名
·HTTPS证书必须使用RSA 2048位或ECC 256位以上公钥算法
还有证书还要是符合苹果认同的,最近沃通的证书好像就快到期了,所以是沃通签发的赶紧去重新买。
配置完的可以用这个来查看是否符合苹果的要求,传送门SSL证书 - 腾讯云
三、https流程
其实百度https,上面就有https的流程(我是传送门https_百度百科),所以这里简单的说下,其中的各种算法、参数的交换在下面的步骤就不细说了,请自行传送门。
这里分https的单向认证和双向认证(单向还是双向这个需要服务器去配置的),所以分开来说,要不小伙伴们会蒙的
单向认证
材料:买来的服务器证书server.cer(客户端要放一个,用来验证服务端),客户端,服务器。
数据加密基本原理:RSA加密+对称加密(数据data经对称密钥key加密,然后把对称密钥key经RSA公钥加密)
1、客户端向服务器发起请求。
2、服务器响应到请求,同时把服务器的证书发给客户端。
3、客户端接收到证书,然后和客户端中的证书对比,如果证书不一致或者无效,那么断开连接。如果通过,那么进行第四部。
4、用户产生一个随机密钥,然后经服务器证书中的公钥进行加密,传给服务端。
5、服务端拿到加密数据和加密密钥,用服务器的私钥解开密钥,得到对称密钥key。
6、服务端和客户端互相通讯指定这个密钥为加密密钥。握手结束
7、客户端和服务端开始通讯,通讯数据由对称密钥加密。
双向认证
双向认证比单向认证多了一步,就是服务器要认证客户端,按照百度百科上的步骤,客户端应该有一个由CA(或正规机构)签发的p12证书,和CA根证书(签名的p12就是由这个签名的)
//上面的CA根证书和p12证书 其实都可以自签的,下面介绍的也是自签的
材料:买来的服务器证书server.cer(客户端要放一个,用来验证服务端),客户端,服务器端,CA的根证书(放到服务器中,用来验证客户端的证书),p12证书(用来放到客户端,网络请求的时候会传给服务端)。
步骤:就是在上面第4步的时候,要用p12文件来对一段数据进行签名,然后把签名和p12证书,加密的对称密钥(上面的说过的)传给服务器,然后服务器接到以后,会用CA根证书(或自签的根证书)来对证书和签名数据进行验证,如果正确,通讯继续,否则,断开连接。
其他的都是一样的。
四、自签证书的过程
p12和CA根证书,其实也可以自签的,现在说下自签的过程。
首先,先有个工具,xca
就是这个东西,然后安装以后
首先,先生生成一个datacode
然后就会生成一个这个东西
然后根据这个database生成一个CA根证书(当然,是自签的了)
直接点确定,然后就会提示你privatekey创建好了,然后直接确定到下面这个图
然后导出CA根证书
这时候,自签的CA根证书就创建好了,这个CA证书是要放到服务器上的。一般默认的CA证书时间是10年。
然后这时候,就要创建自签的p12证书了。选中这个CA证书,然后
然后subject里面和设置CA证书一样。
p12证书默认是一年的,如果感觉短的话 可以在Extensions中设置时间。
然后把P12证书放到客户端项目里。
五、iOS端的https双向适配
AFN的https的双向适配2.x和3.0不一样,先说3.0
客户端认证服务端,
shareManager = [AFHTTPSessionManager manager];
shareManager.securityPolicy = [self customSecurityPolicyWithCerName:HPWalletServerCerName];//调用方法
//客户端认证服务端的方法
+ (AFSecurityPolicy *)customSecurityPolicyWithCerName:(NSString *)cerName{
//导入证书路径
NSString *cerPath = [[NSBundle mainBundle] pathForResource:cerName ofType:@""];
//加载证书
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
NSSet *certSet = [NSSet setWithObject:cerData];
//使用AFSSLPinningModeCertificate证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate withPinnedCertificates:certSet];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = NO;
//是否需要验证域名,默认为YES
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;
return securityPolicy;
}
重写AFN的方法 来让服务器认证客户端
{//新的https
shareManager.securityPolicy = [self customSecurityPolicyWithCerName:HPWalletServerCerName];
//客户端请求验证,重写setSessionDidReceiveAuthenticationChallengeBlock方法
[shareManager setSessionDidReceiveAuthenticationChallengeBlock:^NSURLSessionAuthChallengeDisposition(NSURLSession *session, NSURLAuthenticationChallenge *challenge, NSURLCredential *__autoreleasing *_credential) {
NSURLSessionAuthChallengeDisposition disposition = NSURLSessionAuthChallengePerformDefaultHandling;
__autoreleasing NSURLCredential *credential = nil;
if([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
if([shareManager.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:@"p12文件的名字" ofType:@"p12"];
NSFileManager *fileManager =[NSFileManager defaultManager];
if(![fileManager fileExistsAtPath:p12]) {
NSLog(@"hpwallet-client.p12:not exist");
} else {
NSData *PKCS12Data = [NSData dataWithContentsOfFile:p12];
if ([[self 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;
}];
}
+ (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef *)outTrust fromPKCS12Data:(NSData *)inPKCS12Data {
OSStatus securityError = errSecSuccess;
//client certificate password
NSDictionary*optionsDictionary = [NSDictionary dictionaryWithObject:@“p12的密码“ 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;
}
AFN2.x适配
客户端认证服务器
manage.securityPolicy = [HttpServiceAFNetworkImp customSecurityPolicy];
+ (AFSecurityPolicy *)customSecurityPolicy
{
NSString *cerPath = [[NSBundle mainBundle] pathForResource:@"服务器.cer证书的名字"ofType:@"der"];
NSData *cerData = [NSData dataWithContentsOfFile:cerPath];
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
//allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
//如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = NO;
//是否需要验证域名,默认为YES
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = YES;
//放入证书
securityPolicy.pinnedCertificates = @[cerData];
return securityPolicy;
}
服务器认证客户端,这个要把AFN的AFURLConnectionOperation.m文件中 - (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge方法替换掉 替换成
- (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];
}
并添加下面的方法
- (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;
}
就好了 大功搞成,好长啊。。。