Использование SSL-сертификата клиента из .mobileconfig в приложении Enterprise iOS

Мы пытаемся использовать SSL-сертификаты клиента для аутентификации пользователей в приложении iOS для предприятий.

  • Мы можем сгенерировать клиентский сертификат ssl на сервере
  • Пользователь может установить это через .mobileconfig
  • Аутентификация веб-сервера в Safari работает с установленным сертификатом.
  • Выполнение запроса HTTP из приложения iOS не выполняется (сертификат не используется).

Как нам заставить это работать? Спасибо!

Ответ 1

Обзор:

Вы установили SSL-сертификат клиента на брелок устройства.

Safari.app и Mail.app имеют доступ к этой цепочке ключей, пока приложение iOS этого не делает.

Причина заключается в том, что приложения, которые мы разрабатываем, изолированы и не имеют каких-либо прав доступа за пределами него в устройстве, не содержащем вредоносных программ.

Поскольку у сафари есть доступ к нему, у него не возникло проблем с подключением и аутентификацией против сервера.

Решение:

Включите экспортированный файл P12 с пакетом приложений и обратитесь к нему, чтобы найти правильный сертификат клиента, который ищет сервер. Это на самом деле обходной путь. Hardcoding - надежный способ захвата файла P12.

Реализация:

Используемый метод willSendRequestForAuthenticationChallenge в NSURLConenction delegate. Вам необходимо учитывать NSURLAuthenticationMethodClientCertificate тип запроса inorder для обработки запроса сервера. Здесь мы реализовали магию, чтобы извлечь правильный идентификатор сертификата из встроенного файла 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 easy to load the certificate using the code in -installCertificate
   It 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];
    }
}

Справка: Ref1, Ref2, Ref3

Надеюсь, что это поможет