从.mobileconfig中使用客户端SSL证书在企业iOS应用程序中

12
我们正在尝试在企业iOS应用中使用客户端SSL证书进行用户认证。
  • 我们可以在服务器上生成客户端SSL证书。
  • 用户可以通过.mobileconfig安装它。
  • 在Safari中向Web服务器进行身份验证时,安装的证书可以正常工作。
  • 但在iOS应用程序内部发出HTTP请求时失败(未使用证书)。
如何使其正常工作?谢谢!

你是否正在使用自签名证书来保护你的网站和服务的https连接?或者你是想将证书作为身份验证的手段,而不是使用用户名和密码? - Durai Amuthan.H
1
我们希望使用证书来对用户进行身份验证(而不是使用用户名/密码)。 - Christopher Stott
你们的Web应用程序中使用的现有解决方案是NTLM还是Kerberos身份验证? - Durai Amuthan.H
2个回答

8

概述:

您已在设备钥匙串上安装了客户端SSL证书。

Safari.app和Mail.app可以访问此钥匙串,而iOS应用程序则不能。

原因是我们开发的应用程序被沙盒化,并且在非越狱设备中没有任何外部访问权限。

由于Safari可以访问它,因此连接和对服务器挑战进行身份验证时没有任何问题。

解决方案:

将导出的P12文件包含在应用程序捆绑包中,并引用它以找到服务器要求的正确客户端证书。这实际上是一个解决方法。硬编码是抓取P12文件的可靠方法。

实施:

涉及的方法是NSURLConenction委托中的willSendRequestForAuthenticationChallenge。您需要考虑NSURLAuthenticationMethodClientCertificate挑战类型以处理服务器挑战。这就是我们实现从嵌入式P12文件中提取正确证书标识的魔法的地方。以下是代码:

- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
    if ([challenge previousFailureCount] > 0) {
       //this will cause an authentication failure
       [[challenge sender] cancelAuthenticationChallenge:challenge];
       NSLog(@"Bad Username Or Password");        
       return;
    }



     //this is checking the server certificate
        if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]) {
            SecTrustResultType result;
            //This takes the serverTrust object and checkes it against your keychain
            SecTrustEvaluate(challenge.protectionSpace.serverTrust, &result);

            //if we want to ignore invalid server for certificates, we just accept the server
            if (kSPAllowInvalidServerCertificates) {
                [challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge];
                return;
            } else if(result == kSecTrustResultProceed || result == kSecTrustResultConfirm ||  result == kSecTrustResultUnspecified) {
                //When testing this against a trusted server I got kSecTrustResultUnspecified every time. But the other two match the description of a trusted server
                [challenge.sender useCredential:[NSURLCredential credentialForTrust: challenge.protectionSpace.serverTrust] forAuthenticationChallenge: challenge];
                return;
            }
        } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodClientCertificate) {
        //this handles authenticating the client certificate

       /* 
 What we need to do here is get the certificate and an an identity so we can do this:
   NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identity certificates:myCerts persistence:NSURLCredentialPersistencePermanent];
   [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];

   It's easy to load the certificate using the code in -installCertificate
   It's more difficult to get the identity.
   We can get it from a .p12 file, but you need a passphrase:
   */

   NSString *p12Path = [[BundleManager bundleForCurrentSkin] pathForResource:kP12FileName ofType:@"p12"];
   NSData *p12Data = [[NSData alloc] initWithContentsOfFile:p12Path];

   CFStringRef password = CFSTR("PASSWORD");
   const void *keys[] = { kSecImportExportPassphrase };
   const void *values[] = { password };
   CFDictionaryRef optionsDictionary = CFDictionaryCreate(NULL, keys, values, 1, NULL, NULL);
   CFArrayRef p12Items;

   OSStatus result = SecPKCS12Import((CFDataRef)p12Data, optionsDictionary, &p12Items);

   if(result == noErr) {
             CFDictionaryRef identityDict = CFArrayGetValueAtIndex(p12Items, 0);
             SecIdentityRef identityApp =(SecIdentityRef)CFDictionaryGetValue(identityDict,kSecImportItemIdentity);

             SecCertificateRef certRef;
             SecIdentityCopyCertificate(identityApp, &certRef);

             SecCertificateRef certArray[1] = { certRef };
             CFArrayRef myCerts = CFArrayCreate(NULL, (void *)certArray, 1, NULL);
             CFRelease(certRef);

             NSURLCredential *credential = [NSURLCredential credentialWithIdentity:identityApp certificates:(NSArray *)myCerts persistence:NSURLCredentialPersistencePermanent];
             CFRelease(myCerts);

             [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
         }
    } else if ([[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodDefault || [[challenge protectionSpace] authenticationMethod] == NSURLAuthenticationMethodNTLM) {
   // For normal authentication based on username and password. This could be NTLM or Default.

        DAVCredentials *cred = _parentSession.credentials;
        NSURLCredential *credential = [NSURLCredential credentialWithUser:cred.username password:cred.password persistence:NSURLCredentialPersistenceForSession];
    [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
    } else {
        //If everything fails, we cancel the challenge.
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}

参考资料: 参考1, 参考2, 参考3

希望对您有所帮助。


@Christopher Stott - 试一下这个,然后告诉我。如果你仍然遇到问题,请随时评论。 - Durai Amuthan.H
我在其他地方读到过有私有方法可以访问设备证书。由于我正在构建一个通过企业商店分发的应用程序,所以我不关心App Store的拒绝。 - Luca Torella

0

我创建了一个处理TLS sockets/iOS/Obj-C的包。我将其连接到一个节点服务器,但它也可能适用于其他服务器。更重要的是,我提供了一些重要资源的链接,这些资源可以帮助正确创建证书,以应对最近iOS 13 TLS限制。我希望这些可以对你有所帮助:

https://github.com/eamonwhiter73/IOSObjCWebSockets/tree/master


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