iOS开发总结

iOS中SNI 实现

2017-11-13  本文已影响0人  Auditore

1.SNI的概念与原理

SNI(Server Name Indication)是为了解决一个服务器使用多个域名和证书的SSL/TLS扩展。它的工作原理如下:

上述过程中,当客户端直接用这个IP替换URL中域名发起请求,请求URL中头部的host字段会被替换成HttpDns解析出来的IP,导致服务器获取到的域名为解析后的IP。也就导致服务器不知道你想请求哪个域名,因为服务器里有多个域名,也就无法找到匹配的证书,只能返回默认的证书或者不返回,所以会出现SSL/TLS握手不成功的错误。而且如果你想强制把host头部字段改成域名,那也是无效的,请求发出去后,iOS会自动把host改成ip。

由于iOS上层网络库NSURLConnection/NSURLSession没有提供接口进行SNI字段的配置,因此可以考虑使用NSURLProtocol拦截网络请求,用更底层的网络库去修改,才能实现。

2.用libcurl实现 iOS SNI

之所以用libcurl,是因为它有一个CURLOPT_RESOLVE函数可以解决SNI问题,下面就是我用libcurl写的发起http请求的函数,需要传入两个参数,一个是URL字符串,一个是形如Domin:port:IP的一个字符串,解释下这个字符串的几个参数

下面是我写的一个发起请求的范例,你也可以直接参照官方文档里的Example,更简洁些,会看官方文档很关键呐.

我自己写的代码如下:

//这是发起请求的函数
CURLcode sendRequestWithResolve(const char *resolve,const char *url,const char *postFile, const char * cerPath){
    CURL *curl;
    CURLcode res = CURLE_SEND_ERROR; //默认返回值为发送请求错误
    struct curl_slist *headers = NULL;
    struct curl_slist *host = NULL;
    //增加resolve映射表
    host = curl_slist_append(host, resolve);
    //增加HTTP header
    headers = curl_slist_append(headers, "Accept:application/json");
    headers = curl_slist_append(headers, "Content-Type:application/json");
    headers = curl_slist_append(headers, "charset:utf-8");
    curl = curl_easy_init();    // 初始化
    if (curl)
    {
        curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);// 改协议头
        curl_easy_setopt(curl, CURLOPT_RESOLVE, host);
        if (postFile) {
            //如果是POST请求的话,需要设置参数,具体如何发起POST请求自行Google吧
            curl_easy_setopt(curl, CURLOPT_POSTFIELDS, postFile);
        }
        if(res != CURLE_OK){
            fprintf(stderr, "curl_easy_perform() failed: %s\n",curl_easy_strerror(res));
        }
        curl_easy_setopt(curl, CURLOPT_URL,url);
        //检查自签名证书路径是否存在,是否要进行自签名校验
        if(cerPath) {
            curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, true);
            curl_easy_setopt(curl, CURLOPT_CAINFO, cerPath);
        }
        res = curl_easy_perform(curl);   // 执行curl请求
        curl_slist_free_all(headers);
        curl_easy_cleanup(curl);
    }
    curl_slist_free_all(host);
    return res;
}


//发起请求  因为这个函数也可以支持POST请求和自签名证书,如果不需要就传NULL好了
CURLcode code = sendRequestWithResolve("www.baidu.com:443:111.13.100.92", "https://www.baidu.com/",NULL,NULL);
NSLog(@"libCurl request code %i",code);

双向验证
我之前理解“双向验证”,就是自签名证书的验证并手动继续而已。但其实不是,双向认证和单向认证原理基本差不多,只是除了客户端需要认证服务端以外,增加了服务端对客户端的认证。
HTTPS 是支持双向认证的,不过那指的是客户端(浏览器或 APP)也像服务端一样,在发送请求给服务端的时候带上证书,再由服务端使用对应的私钥进行验证。一般 APP 不需要这么做。所以代码里我也注释掉了,如果有需要再具体研究。

https的请求用libcurl发起,就能解决SNI问题了。当然,最好是结合NSURLProtocol来统一处理请求,具体实现请自己探索。

3.使用libcurl过程中遇到的一些问题

3.1 如何获取libcurl

libcurl官网并没有直接可供iOS使用的源码或者库,需要我们手动编译。我们要把openssl源码交叉编译进libcurl中,这样才能支持https请求,这也就要求我们先编译openssl,同样,openssl官网也是没有iOS可用的源码或者库的。看到这里你已经知道编译是件多么头痛的事情了吧,直接去网上下载支持https的libcurl库是一种方式,但基本上这些库里面的openssl或者libcurl版本都比较旧了,可能存在一些历史遗留问题。所以最好的方式就是自己去手动编译支持https的libcurl!网络上大部分都是用脚本编译的,但很遗憾很多脚本都是无效的,推荐看我自己写的这篇文章,编译支持iOS的libcurl+OpenSSL库(支持https IPv6),里面详细地介绍了如何编译libcurl的过程,我已经测试过libcurl 7.56.1 混编openssl 1.1.0g,可以顺利编译,并且能正常发起http/https请求。

3.2 libcurl的线程安全问题

浅析libcurl多线程安全问题这篇文章里面比较详细地探究了libcurl的线程安全问题,值得一看,文章提到在使用多线程libcurl发送请求,在未设置超时或长超时的情况下程序运行良好。但只要设置了较短超时(小于180s),程序就会出现随机的coredump。并且栈里面找不到任何有用的信息。解决方式是用easy_setopt(curl, CURLOPT_NOSIGNAL, (long)1);来避免。还有curl_global_init这个函数要放在程序入口执行,我的做法是放到APPDelegate.m文件的didFinishLaunch函数中执行。防止多次重复创建。

4.结语

这次SNI调研花了不少时间在熟悉libcurl上,但是结果还是令人满意的。

上一篇 下一篇

猜你喜欢

热点阅读