iOS NSURLAuthenticationMethodClientCertificate не запрашивается по сравнению с сервером ActiveSync

Я пытаюсь реализовать аутентификацию сертификата в клиенте ActiveSync, который я разрабатываю. Код для использования аутентификации сертификата может работать, но на данный момент сервер, или, точнее, интерпретация ответа сервера библиотекой iOS, кажется мне неправильной. Вот мой код:

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
    NSString *authenticationMethod = [protectionSpace authenticationMethod];

    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate])
    {
        NSURLCredential* credential = [ self buildCredentialClientCert];

        if ( credential == nil )
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
        else
        {
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        }
    }
    else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        .... // do other stuff

Проблема в том, что хотя я знаю, что сервер поддерживает аутентификацию сертификата клиента, когда я устанавливаю точку останова, authenticationMethod всегда устанавливается на NSURLAuthenticationMethodServerTrust.

Необработанный ответ HTTPS-сервера содержит следующее:

Код ошибки: 403 Запрещено. Страница требует сертификат клиента как часть процесса аутентификации. Если вы используете смарт-карту, вам нужно будет вставить ее, чтобы выбрать соответствующий сертификат. В противном случае обратитесь к администратору сервера. (12213)

Мой вопрос: что определяет, является ли вызов аутентификации NSURLAuthenticationMethodServerTrust или NSURLAuthenticationMethodClientCertificate?


person David S.    schedule 03.02.2014    source источник
comment
Я использую https-сервер golang и вижу вызов NSURLAuthenticationMethodClientCertificate с точно таким же кодом, как и вы.   -  person Rhythmic Fistman    schedule 04.02.2014


Ответы (1)


Поскольку никто не ответил на это, и я в конце концов пришел к рабочему решению, вот оно.

Доверие сервера — это не вызов от сервера клиенту, это возможность для клиента проверить доверие, предлагаемое сервером. Имея это в виду, приведенный ниже код не проверяет это доверие, но может.

Обычно вы получаете NSURLAuthenticationMethodServerTrust, а затем вы получаете NSURLAuthenticationMethodClientCertificate. Это не или-или. Вот рабочий код.

- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
    NSString *authenticationMethod = [protectionSpace authenticationMethod];

    if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate] && self.accountCertKeychainRef != nil)
    {
        SecIdentityRef identity = [KeychainUtilities retrieveIdentityWithPersistentRef:self.accountCertKeychainRef];

        NSURLCredential* credential = [CertificateUtilities getCredentialFromCert:identity];

        if ( credential == nil )
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
        else
        {
            [[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
        }
    }
    else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {
        NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
        [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
    }
    else if ([authenticationMethod isEqualToString:NSURLAuthenticationMethodNTLM] || [authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic])
    {
        self.lastProtSpace = [challenge protectionSpace];
        if ([challenge previousFailureCount] > 2)
        {
            [[challenge sender] cancelAuthenticationChallenge:challenge];
        }
        else
        {
            [[challenge sender]  useCredential:[self buildCredential] forAuthenticationChallenge:challenge];
        }

    }
    else
    {
        [[challenge sender] cancelAuthenticationChallenge:challenge];
    }
}

Для вопроса ниже, вот как вы можете получить личность:

+ (SecIdentityRef)copyIdentityAndTrustWithCertData:(CFDataRef)inPKCS12Data password:(CFStringRef)keyPassword
{
    SecIdentityRef extractedIdentity = nil;
    OSStatus securityError = errSecSuccess;

    const void *keys[] = {kSecImportExportPassphrase};
    const void *values[] = {keyPassword};
    CFDictionaryRef optionsDictionary = NULL;

    optionsDictionary = CFDictionaryCreate(NULL, keys, values, (keyPassword ? 1 : 0), NULL, NULL);

    CFArrayRef items = NULL;
    securityError = SecPKCS12Import(inPKCS12Data, optionsDictionary, &items);

    if (securityError == errSecSuccess) {
        CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);

        // get identity from dictionary
        extractedIdentity = (SecIdentityRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemIdentity);
        CFRetain(extractedIdentity);
    }

    if (optionsDictionary) {
        CFRelease(optionsDictionary);
    }

    if (items) {
        CFRelease(items);
    }

    return extractedIdentity;
}

Кому интересно, вот getCredentialForCert:

+ (NSURLCredential *)getCredentialFromCert:(SecIdentityRef)identity
{
    SecCertificateRef certificateRef = NULL;
    SecIdentityCopyCertificate(identity, &certificateRef);

    NSArray *certificateArray = [[NSArray alloc] initWithObjects:(__bridge_transfer id)(certificateRef), nil];
    NSURLCredentialPersistence persistence = NSURLCredentialPersistenceForSession;

    NSURLCredential *credential = [[NSURLCredential alloc] initWithIdentity:identity
                                                               certificates:certificateArray
                                                                persistence:persistence];

    return credential;
}
person David S.    schedule 02.04.2014
comment
Что такое KeychainUtilities и CertificateUtilities? - person Victor Engel; 21.07.2014
comment
как вы на самом деле получаете удостоверение сертификата? - person nhenrique; 06.05.2015
comment
@nhenrique, когда вы сначала сохраняете сертификат в цепочке для ключей, вы можете отделить личность. См. отредактированный ответ выше. - person David S.; 06.05.2015
comment
Я все еще озадачен, как вы получаете CredentialFromCert - person tofutim; 23.06.2015
comment
@tofutim см. измененный ответ с помощью getCredentialForCert - person David S.; 25.07.2015
comment
Я знаю, что это старый пост, но я также хотел бы увидеть код для KeychainUtilities retrieveIdentityWithPersistentRef -- и является ли self.accountCertKeychainRef именем файла сертификата/ключа для извлечения из цепочки для ключей iOS? - person Kevin OMara; 24.10.2016