`
huobengluantiao8
  • 浏览: 1022113 次
文章分类
社区版块
存档分类
最新评论

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;

NSURLConnection * connection;

NSString* filePath;

NSOutputStream * fileStream;

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;

NSURLProtectionSpace * protectionSpace;

SecTrustReftrust;

NSString * host;

SecCertificateRefserverCert;

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:方法。

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



分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics