iOS: HTTPS 与自签名证书

不是每个公司都会以数百美金一年的代价向CA购买SSL证书。在企业应用中,付费的SSL证书经常被自签名证书所替代。当然,对于自签名证书iOS是没有能力验证的。Safari遇到这种无法验证的自签名证书的唯一处理方法,就是将问题扔给用户,让用户决定是否应该相信此类证书。它提供了两个按钮,一个“继续”按钮和一个“取消”按钮。当你点击“取消”按钮,则你将无法访问所请求的资源。 当你点击“继续”按钮,则Safari会认为用户授权它放弃对该服务器的验证,所产生的风险由用户自己承担。 当然,HTTPS传输仍然是加密的。

一、配置HTTPS服务器

我们将使用Tomcat来配置HTTPS服务器。关于Tomcat在mac下的安装,请参考《 安装Tomcat到Mac OSX 》一文。如果你安装了Eclipse,则很可能Eclipse IDE中已经配置过Tomcat。带开Eclipse的“偏好设置”,在Server->RuntimeEnvironments中可以看到已安装好的Tomcat服务器:


点击打开链接

点击“Edit…”按钮,你可以找到Tomcat安装路径:


点击打开链接

打开“终端”,进入Tomcat安装目录:

cd /Library/Tomcat/apache-tomcat-7.0.14

运行以下命令:

keytool -genkey -v -alias tomcat-keyalg RSA -storetype JKS -keystore tomcat.keystore -dname"CN=www.handtimes.com,OU=ipcc,O=云电同方,L=昆明,ST=云南,C=中国" -storepass 123456 -keypass 123456

这将在Tomcat安装路径下生成服务器证书及密钥库(库名:tomcat,文件名:tomcat.keystore),证书是自签名的,证书机构采用域名。密钥库密码和私钥密码都是123456。默认过期时间为3个月(90天)。

注意:-storetype JKS指定了密钥库的类型为java key store。这很重要,如果你指定为其他类型如PKCS12,则Tomcat会报"Invalidkeystore format"错误。

打开tomcat目录下的server.xml,你可以直接从Finder中打开它,或者在Eclipse的Servers项目中编辑这个文件 。

查找 <Connector port="8443",将此段代码的注释取消:

<Connectorport="8443" protocol="HTTP/1.1" SSLEnabled="true"

              maxThreads="150" scheme="https"secure="true"

              clientAuth="false" sslProtocol="TLS" />

在其中加入两个属性:

keystoreFile="/Library/Tomcat/apache-tomcat-7.0.14/tomcat.keystore"

keystorePass="123456"

注意,本文中的keytool工具使用的是sun/oracle JDK的版本。如果你的机器上安装的是GNU jvm,则keytool应该是GNU的版本,则上述server.xml代码中还应该加入以下属性:

keystoreType="gkr"algorithm="JessieX509"

在Eclipse中编辑完server.xml,然后使用“Run As->Run Configurations…->Run”使修改生效,Eclipse会自动重启Tomcat,但控制台输出如下内容时,表明https服务已经启动:

信息: StartingProtocolHandler ["http-bio-8443"]

2012-7-9 11:29:01org.apache.coyote.AbstractProtocolHandler start

此时,使用HTTPS端口8443随便访问一个页面,例如: https://localhost:8443/AnyMail/table_css_test.html

此时safari会弹出一个窗口提示用户,接收服务器的证书:


点击打开链接

只有点击“继续”,用户才可以访问该页面。

如果你使用FireFox,则需要将此页面添加到例外。

二、iOS 访问HTTPS

新建Single View Application。在ViewController.xib上拖入一个按钮,一个UILabel和一个UIWebView,并连接到源代码。


点击打开链接

打开ViewController.h,声明如下成员:

NSURLRequest* _request;

    NSURLConnectionconnection;

    NSString* filePath;

    NSOutputStreamfileStream;

    NSURL* url,*baseUrl;

    NSStringEncoding enc;

    NSURLAuthenticationChallenge *_challenge;

_request和connection对象不用多说,我们准备使用URLRequest和URLConnection来请求HTTPS。

程序中将向HTTPS服务器请求一个html文件,这个文件会以临时文件的形式保存到filePath的路径。fileStream则是文件输出流。

url和baseUrl分别指定这个html页面的url地址和base url地址。

enc是服务器页面编码,本例中将使用GBK编码。

由于HTTPS服务器采用了自签名的证书,iOS无法验证此类证书, 所以客户端会向用户索要一个凭据(即Credential,用户对此证书采取什么样的信任策略)。在Safari中,是通过前图所示的那个“服务器证书”窗口来进行的。而在我们的程序中,这个窗口会用一个AlertView替代,服务器询问时的相关内容会封装在一个NSURLAuthenticationChallenge对象中(包括服务器证书),我们以_challenge成员retain它。

使用NSURLConnection请求一个网页资源很简单,这个过程在按钮的触摸事件中触发:

- (IBAction)goAction:(id)sender {

    _challenge=nil;

    filePath = [[[AppDelegatesharedAppDelegate] pathForTemporaryFileWithPrefix:@"Get"]retain];

    NSLog(@"filePath=%@",filePath);

    fileStream = [[NSOutputStreamalloc]initToFileAtPath:filePathappend:NO];

    assert(fileStream != nil);

   

    [fileStreamopen];

    _request = [NSURLRequestrequestWithURL:url];

    assert(_request != nil);

   

    connection = [NSURLConnectionconnectionWithRequest:_requestdelegate:self];

}

在方法中我先打开了NSOutputStream 对象,以便将网页写入到临时文件中。这里本来想实现一种缓存机制,不过由于时间原因,我没有再深究下去,导致多次请求后在tmp文件夹中留下了一堆的临时文件。临时文件的文件名是以Get+UUID命名的,我把命名方法写在了AppDelegate里,希望你能找到并解决这个我遗留下来的问题:


点击打开链接

重要的是[NSURLConnectionconnectionWithRequest:delegate:]方法的调用,我们获取了一个NSURLConnection对象并将它的delegate设置为self。

self在实现NSULConnectionDelegate方法时,特别实现了connection:canAuthenticateAgainstProtectionSpace:方法,以及connection:didReceiveAuthenticationChallenge:方法:

 

- (BOOL)connection:(NSURLConnection *)conncanAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace

{

    NSLog(@"authenticatemethod:%@",protectionSpace.authenticationMethod);

    return [protectionSpace.authenticationMethodisEqualToString:

            NSURLAuthenticationMethodServerTrust];

}

- (void)connection:(NSURLConnection *)conndidReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

{

    _challenge=[challenge retain];

    UIAlertView* alertView = [[[UIAlertViewalloc] initWithTitle:@"服务器证书"

                                                        message:@"这个网站有一个服务器证书,点击接受,继续访问该网站,如果你不确定,请点击取消"

                                                       delegate:self

                                              cancelButtonTitle:@"接受"

                                              otherButtonTitles:@"取消", nil] autorelease];

   

    [alertView show];

 

}

canAuthenticateAgainstProtectionSpace:方法在连接到一些有安全限制的网站时调用,例如:服务器信任、客户端证书、HTTP表单验证等。但URLConnection不知道也没有强制程序员必需处理哪些安全问题,因此它把一个NSURLProtectionSpace对象作为参数传递,如果程序员想响应某一类安全问题,那么在这个方法最后就返回YES。你要明白程序员可以处理哪些安全问题,你可以查看NSURLProtectionSpace的authenticationMethod属性。这是一个NSString属性,可能取值包括以下常量:

NSString *NSURLAuthenticationMethodDefault;

NSString*NSURLAuthenticationMethodHTTPBasic;

NSString*NSURLAuthenticationMethodHTTPDigest;

NSString*NSURLAuthenticationMethodHTMLForm;

NSString*NSURLAuthenticationMethodNegotiate;

NSString*NSURLAuthenticationMethodNTLM;

NSString*NSURLAuthenticationMethodClientCertificate;

NSString*NSURLAuthenticationMethodServerTrust;

 

当然在这里,我们只处理“服务器信任”的安全问题。

didReceiveAuthenticationChallenge方法则紧接第一个方法之后调用。如果第一个方法中返回true,那么URLConnection接下来就调用delegate的第二个方法(NO则跳过第二个方法)。

在这里,我们弹出了一个UIAlertView,提示用户进行处理。

接下来实现UIAlertView的delegate方法:

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex

{

     // Accept=0,Cancel=1;

    if(buttonIndex==0){

        NSURLCredential *   credential;

       

        NSURLProtectionSpaceprotectionSpace;

        SecTrustRef            trust;

        NSString *              host;

        SecCertificateRef      serverCert;

        assert(_challenge !=nil);

        protectionSpace = [_challengeprotectionSpace];

        assert(protectionSpace != nil);

       

        trust = [protectionSpaceserverTrust];

        assert(trust != NULL);

       

        credential = [NSURLCredentialcredentialForTrust:trust];

        assert(credential != nil);

        host = [[_challengeprotectionSpace] host];

        if (SecTrustGetCertificateCount(trust) > 0) {

           serverCert = SecTrustGetCertificateAtIndex(trust, 0);

        } else {

           serverCert = NULL;

        }

        [[_challengesender] useCredential:credential forAuthenticationChallenge:_challenge];

    }

}

这个方法中,如果用户选择“接受”,则我们从NSURLAuthenticationChallenge对象中获取服务器证书,并将该证书应用于URLConnection,接下来会继续调用URLConnection的其他delegate方法。如果用户选择“取消”,则会导致服务器返回一个错误,这会调用connection:didFailedWithError:方法。

其它方法请自行参考源代码:资源下载



  • 1
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值