什么是HTTPS?
在说HTTPS之前先说说什么是HTTP,HTTP就是我们平时浏览网页时候使用的一种协议。HTTP协议传输的数据都是未加密的,也就是明文的,因此使用HTTP协议传输隐私信息非常不安全。为了保证这些隐私数据能加密传输,于是网景公司设计了SSL(Secure Sockets Layer)协议用于对HTTP协议传输的数据进行加密,从而就诞生了HTTPS。
如果从最终的数据解析角度去看HTTPS,HTTPS与HTTP没有任何区别,HTTPS就是将HTTP协议数据包放到SSL/TSL层加密后,在TCP/IP层组成IP数据报去传输,以此保证传输数据的安全;在接受端,SSL/TSL将接收的数据包解密之后,将数据传给HTTP协议层,获得普通的HTTP数据,简单地概括为 HTTPS = HTTP + SSL/TLS + TCP。
在WWDC 2016开发者大会上,苹果宣布在2016年底前所有iOS应用都必须启用App Transport Security安全功能,对于未启用App Transport Security安全功能的app苹果都会进行更严格的审核,如果没有充分的理由,很可能你的app会被拒绝上架。
此处引用苹果官方文档内容:
App Store Review for ATS
此处省略一大段苹果官方文档内容.....
When submitting your app to the App Store, provide sufficient information for the App Store to determine why your app cannot make secure connections by default.
一:公司使用的是知名CA颁发的证书,服务器配置符合苹果ATS要求:
App Transport Security 要求 TLS 1.2,而且它要求站点使用支持forward secrecy协议的密码。证书也要求是符合ATS规格的,ATS只信任知名CA颁发的证书,小公司所使用的 self signed certificate,还是会被ATS拦截。因此慎重检查与你的应用交互的服务器是不是符合ATS的要求非常重要。
官方文档App Transport Security Technote对CA颁发的证书要求:
The leaf server certificate must be signed with one of the following types of keys:
1.Rivest-Shamir-Adleman (RSA) key with a length of at least 2048 bits
2.Elliptic-Curve Cryptography (ECC) key with a size of at least 256 bits
In addition, the leaf server certificate hashing algorithm must be Secure Hash Algorithm 2 (SHA-2) with a digest length, sometimes called a “fingerprint,” of at least 256 (that is, SHA-256 or greater).
这种情况不需要在Bundle中引入CA文件,可以交给系统去判断服务器端的证书是不是SSL证书,验证过程也不需要我们去具体实现。
二:基于AFNetWorking的SSL特定服务器证书信任处理,重写AFNetWorking的customSecurityPolicy方法,这里我创建了一个HttpRequest工具类,分别对GET和POST方法进行了封装,以GET方法为例:
+ (void)getWithUrl:(NSString *)url
params:(NSDictionary *)params
success:(void (^)(id))success
fail:(void (^)(NSError *))fail
{
//初始化AFHTTPSessionManager
AFHTTPSessionManager *manager = [self manager];
// 发出GET请求
[manager GET:url parameters:params success:^(AFHTTPRequestOperation *operation, id response){
if (success) {
success(response);
}
} fail:^(AFHTTPRequestOperation *operation, NSError *error) {
if (error) {
fail(error);
}
}];
}
+ (AFHTTPSessionManager *)manager {
//初始化AFHTTPSessionManager
AFHTTPSessionManager *manager = [AFHTTPSessionManager manager];
switch (sg_requestType) {
case kZHRequestTypeJSON: {
manager.requestSerializer = [AFJSONRequestSerializer serializer];
break;
}
case kZHRequestTypePlainText: {
manager.requestSerializer = [AFHTTPRequestSerializer serializer];
break;
}
}
// https ssl 验证 KOpenHttpsSSL为证书名称的宏
if (KOpenHttpsSSL) {
[manager setSecurityPolicy:[self customSecurityPolicy]];
}
return manager;
}
+ (AFSecurityPolicy*)customSecurityPolicy {
// /先导入证书
NSString *cerFilePath = [[NSBundle mainBundle] pathForResource:KCertificateName ofType:@"cer"];//证书的路径
NSData *certFileData = [NSData dataWithContentsOfFile:cerFilePath];
// AFSSLPinningModeCertificate 证书验证模式
AFSecurityPolicy *securityPolicy = [AFSecurityPolicy policyWithPinningMode:AFSSLPinningModeCertificate];
// allowInvalidCertificates 是否允许 self signed certificate
// 验证 self signed certificate要设置为YES
securityPolicy.allowInvalidCertificates = YES;
//是否需要验证域名
securityPolicy.validatesDomainName = NO;
securityPolicy.pinnedCertificates = [NSSet setWithArray:@[certFileData]];
return securityPolicy;
}
这样,就能够在AFNetWorking的基础上使用HTTPS协议访问特定服务器,但是不能信任根证书的CA文件,因此这种方式存在风险,读取pinnedCertificates中的证书数组的时候有可能失败,如果证书不符合,certFileData就会为nil。
三:更改系统方法,发送异步NSURLConnection请求。
//发送https网络请求
- (void)sendHttpsRequest
{
NSString *urlStr = @"https://api.weibo.com/";
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);
}
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信任证书的问题得以解决。