UIWebView iOS 中的客户端证书身份验证

4

我在Objective-C方面还比较新手,但我正在开发一个应用程序,其中包含一个UIWebView,加载一些Web内容。所有的网页都需要客户端证书进行身份验证,我已经为此苦恼了几天。

有人知道如何在UIWebView中实现它的流程吗?

谢谢!


现在可以使用SFSafariViewController(Safari Services框架)与iOS身份验证。 - smat88dd
1个回答

8
为了避免UIWebView出现任何问题,在进行Web视图请求之前,您需要使用客户端证书向您的网站根目录发出请求。您可以使用UIWebViewDelegate方法:
-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType

接下来,UIWebView将能够毫无问题地加载所有内容。

如果你刚开始学习Objective-C,我想你也是Foundation框架的新手,所以这里提供一点帮助。

为了解决这个问题,我使用了ASIHTTPRequest,因为它已经嵌入到我们的项目中。但是你也可以使用NSURLConnection,并在NSURLConnectionDelegate方法中实现逻辑:

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

因此,这是我提供一个客户端证书给ASIHTTPRequest以前向UIWebView请求的代码:

-(BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{

  SecIdentityRef identity = NULL;
  SecTrustRef trust       = NULL;  
  NSData *PKCS12Data      = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"test.cert" ofType:@"pfx"]];

  [self extractIdentity:&identity andTrust:&trust fromPKCS12Data:PKCS12Data];

  NSURL *serverUrl              = [NSURL URLWithString:URL_SECURE_SERVER];
  ASIHTTPRequest *firstRequest  = [ASIHTTPRequest requestWithURL:serverUrl];

  [firstRequest setValidatesSecureCertificate:NO];
  [firstRequest setClientCertificateIdentity:identity];
  [firstRequest startSynchronous];

  return YES;
}

我正在同步发送请求,以确保在让UIWebView开始加载之前完成请求。

我使用了一种方法从证书中检索身份,该方法是:

- (BOOL)extractIdentity:(SecIdentityRef *)outIdentity andTrust:(SecTrustRef*)outTrust fromPKCS12Data:(NSData *)inPKCS12Data
{
  OSStatus securityError          = errSecSuccess;
  NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"mobigate" forKey:(id)kSecImportExportPassphrase];

  CFArrayRef items  = CFArrayCreate(NULL, 0, 0, NULL);
  securityError     = SecPKCS12Import((CFDataRef)inPKCS12Data,(CFDictionaryRef)optionsDictionary,&items);

  if (securityError == 0) { 
    CFDictionaryRef myIdentityAndTrust  = CFArrayGetValueAtIndex (items, 0);
    const void *tempIdentity            = NULL;

    tempIdentity = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);
    *outIdentity = (SecIdentityRef)tempIdentity;

    const void *tempTrust = NULL;

    tempTrust = CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemTrust);
    *outTrust = (SecTrustRef)tempTrust;

  } 
  else {
    NSLog(@"Failed with error code %d",(int)securityError);
    return NO;
  }

  return YES;
}

这里使用 NSURLConnection 而不是 ASIHTTPRequest 来实现同样的技术。

  • 获取您的 SecIdentityRef 和 SecCertificateRef
  • 使用这些信息创建 NSURLCredential
  • 将此 NSURLCredential 发送回连接中的 [challenge sender] 以在 connection:didReceiveAuthenticationChallenge: 方法中进行身份验证

要使用 NSURLConnection 进行证书验证,您需要实现 NSURLConnectionDelegate 方法:

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

在这种方法中,NSURLConnection会告诉您它收到了一个挑战。您需要创建一个NSURLCredential并发送回[challenge sender]。
因此,您可以创建您的NSURLCredential:
+ (NSURLCredential *)credentialWithIdentity:(SecIdentityRef)identity certificates:(NSArray *)certArray persistence:(NSURLCredentialPersistence)persistence
{

  NSString *certPath = [[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"cer"];
  NSData *certData   = [[NSData alloc] initWithContentsOfFile:certPath];

  SecIdentityRef myIdentity;  // ???

  SecCertificateRef myCert = SecCertificateCreateWithData(NULL, (CFDataRef)certData);
  [certData release];
  SecCertificateRef certArray[1] = { myCert };
  CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
  CFRelease(myCert);
  NSURLCredential *credential = [NSURLCredential credentialWithIdentity:myIdentity
                              certificates:(NSArray *)myCerts
                               persistence:NSURLCredentialPersistencePermanent];
  CFRelease(myCerts);
}

最后,与之一起使用

- (void)useCredential:(NSURLCredential *)credential forAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge

在 [挑战发送者] 上

你应该拥有所需的一切。祝你好运。


你在哪个部分遇到了困难? - teriiehina
1
谢谢,问题在于我需要使用一个未被我的应用程序安装的客户端证书。在这种情况下,使用 NSBundle 中的证书是行不通的,而且我不确定苹果是否允许第三方应用程序访问共享钥匙串(像 Safari 和其他“本地”应用程序可以做到的那样)。有什么建议吗? - danisoft_2
有人用过这个吗?我试了一下,但对我来说没有用,我正在iOS 5和iOS 6上进行测试。谢谢。 - Omer
你不需要共享钥匙串。只需在Xcode的构建规则中将证书文件添加到应用程序包中即可。如果您没有添加证书,[[NSBundle mainBundle] pathForResource:@"certificate" ofType:@"cer"] 将返回 nil,并且所有后续使用 certPart/certData 的调用都将失败。 - kervich
现在可以使用SFSafariViewController(Safari Services框架)与iOS身份验证。@danisoft_2 - smat88dd
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接