iOS开发HTTPS实现之信任SSL证书和自签名证书

转自:http://www.jianshu.com/p/6b9c8bd5005a/comments/5539345

(收录一下供自己学习用的)

字数1566 阅读5025 评论76 喜欢30

首先来分析一下什么是HTTPS以及了解HTTPS对于iOS开发者的意义

HTTPS 以及SSL/TSL

  • 什么是SSL?

SSL(Secure Sockets Layer, 安全套接字层),因为原先互联网上使用的 HTTP 协议是明文的,存在很多缺点,比如传输内容会被偷窥(嗅探)和篡改。 SSL 协议的作用就是在传输层对网络连接进行加密。

  • 何为TLS?

到了1999年,SSL 因为应用广泛,已经成为互联网上的事实标准。IETF 就在那年把 SSL 标准化。标准化之后的名称改为 TLS(Transport Layer Security,传输层安全协议)。SSL与TLS可以视作同一个东西的不同阶段

  • HTTPS

简单来说,HTTPS = HTTP + SSL/TLS, 也就是 HTTP over SSL 或 HTTP over TLS,这是后面加 S 的由来 。

HTTPS和HTTP异同:HTTP和HTTPS使用的是完全不同的连接方式,用的端口也不一样,前者是80,后者是443。HTTP的连接很简单,是无状态的;HTTPS协议是由SSL+HTTP协议构建的可进行加密传输、身份认证的网络协议,比HTTP协议安全。

在WWDC 2016开发者大会上,苹果宣布了一个最后期限:到2017年1月1日 App Store中的所有应用都必须启用 App Transport Security安全功能。App Transport Security(ATS)是苹果在iOS 9中引入的一项隐私保护功能,屏蔽明文HTTP资源加载,连接必须经过更安全的HTTPS。苹果目前允许开发者暂时关闭ATS,可以继续使用HTTP连接,但到年底所有官方商店的应用都必须强制性使用ATS。

所以对于iOS开发者来说,需要尽早解决HTTPS请求的问题。

发送HTTPS请求信任SSL证书和自签名证书,分为三种情况

1.如果你的app服务端安装的是SLL颁发的CA,可以使用系统方法直接实现信任SSL证书,关于Apple对SSL证书的要求请参考:苹果官方文档CertKeyTrustProgGuide

这种方式不需要在Bundle中引入CA文件,可以交给系统去判断服务器端的证书是不是SSL证书,验证过程也不需要我们去具体实现。

示例代码:

NSURL *URL = [NSURL URLWithString:URLString];
NSURLRequest *request = [[NSURLRequest alloc] initWithURL:URL cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:10];
//创建同步连接
NSError *error = nil;
NSData *receivedData = [NSURLConnection sendSynchronousRequest:request returningResponse:nil error:&error];
NSString *receivedInfo = [[NSString alloc] initWithData:receivedData encoding:NSUTF8StringEncoding];

当然,如果你需要同时信任SSL证书和自签名证书的话还是需要在代码中实现CA的验证,这种情况在后面会提到。

2.基于AFNetWorking的SSL特定服务器证书信任处理,重写AFNetWorking的customSecurityPolicy方法,这里我创建了一个HttpRequest类,分别对GET和POST方法进行了封装,以GET方法为例:

+ (void)get:(NSString *)url params:(NSDictionary *)params success:(void (^)(id))success failure:(void (^)(NSError *))failure {
// 1.获得请求管理者
AFHTTPRequestOperationManager *mgr = [AFHTTPRequestOperationManager manager];
// 2.申明返回的结果是text/html类型
mgr.responseSerializer = [AFHTTPResponseSerializer serializer];
// 3.设置超时时间为10s
mgr.requestSerializer.timeoutInterval = 10; // 加上这行代码,https ssl 验证。
if(openHttpsSSL) {
[mgr setSecurityPolicy:[self customSecurityPolicy]];
} // 4.发送GET请求
[mgr GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id responseObj){
if (success) {
success(responseObj);
}
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
if (error) {
failure(error);
}
}];
}
+ (AFSecurityPolicy*)customSecurityPolicy {
// /先导入证书
NSString *cerPath = [[NSBundle mainBundle] pathForResource:certificate ofType:@"cer"];//证书的路径
NSData *certData = [NSData dataWithContentsOfFile:cerPath]; // AFSSLPinningModeCertificate 使用证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate]; // allowInvalidCertificates 是否允许无效证书(也就是自建的证书),默认为NO
// 如果是需要验证自建证书,需要设置为YES
securityPolicy.allowInvalidCertificates = YES; //validatesDomainName 是否需要验证域名,默认为YES;
//假如证书的域名与你请求的域名不一致,需把该项设置为NO;如设成NO的话,即服务器使用其他可信任机构颁发的证书,也可以建立连接,这个非常危险,建议打开。
//置为NO,主要用于这种情况:客户端请求的是子域名,而证书上的是另外一个域名。因为SSL证书上的域名是独立的,假如证书上注册的域名是www.google.com,那么mail.google.com是无法验证通过的;当然,有钱可以注册通配符的域名*.google.com,但这个还是比较贵的。
//如置为NO,建议自己添加对应域名的校验逻辑。
securityPolicy.validatesDomainName = NO; securityPolicy.pinnedCertificates = @[certData]; return securityPolicy;
}

其中的cerPath就是app bundle中证书路径,certificate为证书名称的宏,仅支持cer格式,securityPolicy的相关配置尤为重要,请仔细阅读customSecurityPolicy方法并根据实际情况设置其属性。

这样,就能够在AFNetWorking的基础上使用HTTPS协议访问特定服务器,但是不能信任根证书的CA文件,因此这种方式存在风险,读取pinnedCertificates中的证书数组的时候有可能失败,如果证书不符合,certData就会为nil。

3.更改系统方法,发送异步NSURLConnection请求。

- (void)getDataWithURLRequest {
//connection
NSString *urlStr = @"https://developer.apple.com/cn/";
NSURL *url = [NSURL URLWithString:urlStr];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:10];
NSURLConnection *connection = [[NSURLConnection alloc]initWithRequest:request delegate:self];
[connection start];
}

重点在于处理NSURLConnection的didReceiveAuthenticationChallenge代理方法,对CA文件进行验证,并建立信任连接。

- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {

    return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
} - (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge { /*
//直接验证服务器是否被认证(serverTrust),这种方式直接忽略证书验证,直接建立连接,但不能过滤其它URL连接,可以理解为一种折衷的处理方式,实际上并不安全,因此不推荐。
SecTrustRef serverTrust = [[challenge protectionSpace] serverTrust];
return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
forAuthenticationChallenge: challenge];
*/
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:@"cloudwin" ofType:@"cer"];//自签名证书
NSData* caCert = [NSData dataWithContentsOfFile:cerPath]; NSString *cerPath2 = [[NSBundle mainBundle] pathForResource:@"apple" ofType:@"cer"];//SSL证书
NSData * caCert2 = [NSData dataWithContentsOfFile:cerPath2]; NSCAssert(caCert != nil, @"caCert is nil");
if(nil == caCert)
break; /* failed */ NSCAssert(caCert2 != nil, @"caCert2 is nil");
if (nil == caCert2) {
break;
} SecCertificateRef caRef = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert);
NSCAssert(caRef != nil, @"caRef is nil");
if(nil == caRef)
break; /* failed */ SecCertificateRef caRef2 = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)caCert2);
NSCAssert(caRef2 != nil, @"caRef2 is nil");
if(nil == caRef2)
break; /* failed */ NSArray *caArray = @[(__bridge id)(caRef),(__bridge id)(caRef2)]; 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");
}
/* https://developer.apple.com/library/ios/technotes/tn2232/_index.html */
/* https://developer.apple.com/library/mac/qa/qa1360/_index.html */
/* 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
return [[challenge sender] useCredential: [NSURLCredential credentialForTrust: serverTrust]
forAuthenticationChallenge: challenge]; } while(0);
} // Bad dog
return [[challenge sender] cancelAuthenticationChallenge: challenge]; }

这里的关键在于result参数的值,根据官方文档的说明,判断(result == kSecTrustResultUnspecified) || (result == kSecTrustResultProceed)的值,若为1,则该网站的CA被app信任成功,可以建立数据连接,这意味着所有由该CA签发的各个服务器证书都被信任,而访问其它没有被信任的任何网站都会连接失败。该CA文件既可以是SLL也可以是自签名。

NSURLConnection的其它代理方法实现

#pragma mark -- connect的异步代理方法
-(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {
NSLog(@"请求被响应");
_mData = [[NSMutableData alloc]init];
} -(void)connection:(NSURLConnection *)connection didReceiveData:(nonnull NSData *)data {
NSLog(@"开始返回数据片段"); [_mData appendData:data];
} -(void)connectionDidFinishLoading:(NSURLConnection *)connection {
NSLog(@"链接完成");
//可以在此解析数据
NSString *receiveInfo = [NSJSONSerialization JSONObjectWithData:self.mData options:NSJSONReadingAllowFragments error:nil];
NSLog(@"received data:\\\\n%@",self.mData);
NSLog(@"received info:\\\\n%@",receiveInfo);
} //链接出错
-(void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {
NSLog(@"error - %@",error);
}

至此,HTTPS信任证书的问题得以解决,这不仅是为了响应Apple强制性使用ATS的要求,也是为了实际生产环境安全性的考虑,HTTPS是未来的趋势,建议尽早支持。

iOS开发HTTPS实现之信任SSL证书和自签名证书的更多相关文章

  1. 沃通SSL证书、代码签名证书应用于机器人安全防护

    近两年,扫地机器人.智能音箱等消费级机器人产品逐渐走入大众生活的中.随着人工智能技术的迅猛发展,预计2023年全球消费级机器人市场规模将达到150亿美元.然而,产业的迅猛发展却伴随着安全防护的缺失,安 ...

  2. CA证书、自颁发证书、自签名证书联系

    一.理论基础 ssl:secure socket layer(安全套接层协议)的缩写,通过此协议可以保证两个应用通信的可靠性和保密性.openssl:是ssl协议的实现.提供了对称加密算法.非对称加密 ...

  3. 自签名证书说明——自签名证书的Issuer和Subject是一样的。不安全的原因是:没有得到专业SSL证书颁发的机构的技术支持?比如使用不安全的1024位非对称密钥对,有效期设置很长等

    一般的数字证书产品的主题通常含有如下字段:公用名称 (Common Name) 简称:CN 字段,对于 SSL 证书,一般为网站域名:而对于代码签名证书则为申请单位名称:而对于客户端证书则为证书申请者 ...

  4. [https][openssl] OpenSSL 公钥、私钥以及自签名证书

    转自:https://www.zybuluo.com/muyanfeixiang/note/392079 简介 公钥私钥用来互相加解密的一对密钥,一般是采用RSA非对称算法.公钥加密的私钥能解密,私钥 ...

  5. iOS开发——https+证书

    由于公司项目的需要,经过调研后,已成功. 同事已把此问题写成博客<[加密解密]HTTPS>,我就直接转用了 附带链接如下: http://www.jianshu.com/p/8351917 ...

  6. 开发微信小程序中SSL协议的申请、证书绑定、TLS 版本处理等

    在上篇随笔<基于微信小程序的系统开发准备工作>介绍了开发微信小程序的一些前期的架构设计.技术路线 .工具准备等方面内容,本篇随笔继续这个步骤,逐步介绍我们实际开发过程中对SSL协议的申请及 ...

  7. iOS开发--in house发布和安装(ipa重新签名)

    in house从字面意思理解就是‘内部的’,in house版本的ipa就是一个用于公司内部使用或测试的一个苹果应用程序安装包. 作为一个app应用程序开发者,在app应用程序在苹果商店上架前总需要 ...

  8. iOS开发,让数据更安全的几个加密方式

    任何应用的开发中安全都是重中之重,在信息交互异常活跃的现在,信息加密技术显得尤为重要.在app应用开发中,我们需要对应用中的多项数据进行加密处理,从而来保证应用上线后的安全性,给用户一个安全保障.这篇 ...

  9. iOS开发支付篇-内购(IAP)

    一,前言 经典文章参考: . http://yimouleng.com/2015/12/17/ios-AppStore/ 内购流程 . http://www.jianshu.com/p/b199a46 ...

随机推荐

  1. .net HttpClient类(System.Net.Http) c#并发使用测试、压测

    .net HttpClient类(System.Net.Http) c#并发使用测试.压测

  2. 解决Github访问超慢问题[自己留档]

    解决Github访问超慢问题 Github is so slowly. 这段时间访问 github 都非常慢,google了一下发现是github某个CDN被伟大的墙屏蔽所致. 出问题的应该是这个CD ...

  3. DEDECMS如何修改数据库密码以及忘记了后台密码怎么办

    忘记后台密码1.登录PHPmyadmin登录phpmyadmin,找到忘记密码网站所在的数据库并打开2.打开dede_admin数据表:找到dede_admin这项如图,pwd下的值就是你的密码,织梦 ...

  4. hadoop-集群管理(2)——内存设置

    http://www.cnblogs.com/yuechaotian/archive/2013/03/08/2949607.html 1. 内存 hadoop为各个守护进程(namenode,seco ...

  5. 可自定义导航条功能案例ios项目源码

    可定制的navigationitem ,当我们使用系统的方法设置navigationItem的leftItem或者rightItem时,我们会 发现item位置会有偏差(左边的偏右,右边的偏左).当设 ...

  6. css3 文本效果

    CSS3 文本效果   1 CSS3 文本阴影在 CSS3 中,text-shadow 可向文本应用阴影,能够规定水平阴影.垂直阴影.模糊距离,以及阴影的颜色.text-shadow: 5px 5px ...

  7. 线程池(C#)

    转自:http://blog.sina.com.cn/s/blog_494305f30100ryw7.html 在这里你可以学到Microsoft研究CLR实现线程池的原理机制,从而更灵活的处理CLR ...

  8. AWS学习之EC2

    1.AWS简介 AWS(Amazon Web Services )提供了一整套云计算服务,让您能够构建复杂.可扩展的应用程序.如今,成千上万各种规模的客户都在使用这些云计算服务,它们涉及各个行业,包括 ...

  9. Java垃圾回收基础

  10. 将数组之中的省份市区地区ID改成对用中文字符

    数据表数据源的省市区联动: 原始数据: //获取所有学校信息 $school=D('school'); $info=$school->getList(); 数据如下: 1 => array ...