web-dev-qa-db-fra.com

Faire une demande HTTPS dans iOS 9 avec un certificat auto-signé

Je souhaite envoyer une demande HTTPS au serveur personnalisé avec un certificat auto-signé. J'utilise la classe NSURLConnection et je traite les défis d'authentification, mais je reçois toujours un message d'erreur dans une console:

NSURLSession/NSURLConnection HTTP load failed (kCFStreamErrorDomainSSL, -9802)

then méthode "connection: didFailWithError:" est appelée avec l'erreur suivante:

Error Domain=NSURLErrorDomain Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSURLErrorFailingURLPeerTrustErrorKey=<SecTrustRef: 0x150094100>, NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802, NSErrorPeerCertificateChainKey=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
    0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)>
)}, NSUnderlyingError=0x1504ae170 {Error Domain=kCFErrorDomainCFNetwork Code=-1200 "An SSL error has occurred and a secure connection to the server cannot be made." UserInfo={NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSLocalizedRecoverySuggestion=Would you like to connect to the server anyway?, _kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
    0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)>
)}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
    0 : <SecIdentityRef: 0x15012cd40>
    1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)>
)}, _kCFStreamErrorDomainKey=3, NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), _kCFStreamErrorCodeKey=-9802}}, NSErrorClientCertificateChainKey=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
    0 : <SecIdentityRef: 0x15012cd40>
    1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)>
)}, NSLocalizedDescription=An SSL error has occurred and a secure connection to the server cannot be made., NSErrorFailingURLKey=https://217.92.80.156:9090/(method name and parameters), NSErrorFailingURLStringKey=https://217.92.80.156:9090/(method name and parameters), NSErrorClientCertificateStateKey=2}

L'application reçoit deux demandes d'authentification (NSURLAuthenticationMethodClientCertificate et NSURLAuthenticationMethodServerTrust) et les traite de la manière suivante:

- (void) connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
    if(challenge.proposedCredential && !challenge.error)
    {
        [challenge.sender useCredential:challenge.proposedCredential forAuthenticationChallenge:challenge];

        return;
    }

    NSString *strAuthenticationMethod = challenge.protectionSpace.authenticationMethod;
    NSLog(@"authentication method: %@", strAuthenticationMethod);

    NSURLCredential *credential = nil;
    if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate])
    {
        // get identity and certificate from p.12
        NSData *PKCS12Data = [NSData dataWithContentsOfFile:[[NSBundle mainBundle] pathForResource:@"client" ofType:@"p12"]];

        NSDictionary *optionsDictionary = [NSDictionary dictionaryWithObject:@"password" forKey:(__bridge id)kSecImportExportPassphrase];
        CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
        OSStatus securityError = SecPKCS12Import((__bridge CFDataRef)PKCS12Data,(__bridge CFDictionaryRef)optionsDictionary, &items);

        SecIdentityRef identity = NULL;
        SecCertificateRef certificate = NULL;
        if(securityError == errSecSuccess)
        { 
            CFDictionaryRef myIdentityAndTrust = CFArrayGetValueAtIndex(items, 0);
            identity = (SecIdentityRef)CFDictionaryGetValue (myIdentityAndTrust, kSecImportItemIdentity);

            CFArrayRef array = (CFArrayRef)CFDictionaryGetValue(myIdentityAndTrust, kSecImportItemCertChain);
            certificate = (SecCertificateRef)CFArrayGetValueAtIndex(array, 0);
        }

        credential = [NSURLCredential credentialWithIdentity:identity certificates:[NSArray arrayWithObject:(__bridge id)(certificate)] persistence:NSURLCredentialPersistenceNone];

        CFRelease(items);
    }
    else if([strAuthenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
    {       
        int trustCertificateCount = (int)SecTrustGetCertificateCount(challenge.protectionSpace.serverTrust);
        NSMutableArray *trustCertificates = [[NSMutableArray alloc] initWithCapacity:trustCertificateCount];
        for(int i = 0; i < trustCertificateCount; i ++)
        {
            SecCertificateRef trustCertificate =  SecTrustGetCertificateAtIndex(challenge.protectionSpace.serverTrust, i);
            [trustCertificates addObject:(__bridge id) trustCertificate];
        }            

        SecPolicyRef policyRef = NULL;
        policyRef = SecPolicyCreateSSL(YES, (__bridge CFStringRef) challenge.protectionSpace.Host);

        SecTrustRef trustRef = NULL;
        if(policyRef)
        {
            SecTrustCreateWithCertificates((__bridge CFArrayRef) trustCertificates, policyRef, &trustRef);
            CFRelease(policyRef);
        }

        if(trustRef)
        {
//          SecTrustSetAnchorCertificates(trustRef, (__bridge CFArrayRef) [NSArray array]);
//          SecTrustSetAnchorCertificatesOnly(trustRef, NO);

            SecTrustResultType result;
            OSStatus trustEvalStatus = SecTrustEvaluate(trustRef, &result);
            if(trustEvalStatus == errSecSuccess)
            {
                // just temporary attempt to make it working.
                // i hope, there is no such problem, when we have final working version of certificates.
                if(result == kSecTrustResultRecoverableTrustFailure)
                {
                    CFDataRef errDataRef = SecTrustCopyExceptions(trustRef);
                    SecTrustSetExceptions(trustRef, errDataRef);

                    SecTrustEvaluate(trustRef, &result);
                }

                if(result == kSecTrustResultProceed || result == kSecTrustResultUnspecified)
                    credential = [NSURLCredential credentialForTrust:trustRef];
            }

            CFRelease(trustRef);
        }
    }
    else
    {
        DDLogWarn(@"Unexpected authentication method. Cancelling authentication ...");
        [challenge.sender cancelAuthenticationChallenge:challenge];
    }

    if(credential)
        [challenge.sender useCredential:credential forAuthenticationChallenge:challenge];
    else
        [challenge.sender cancelAuthenticationChallenge:challenge];
}

Dans le journal de diagnostic de CFNetwork, je peux voir que la procédure de négociation est sur le point d'être lancée. Au moins l'application envoie le message "ClientHello", puis le serveur envoie son message "ServerHello" et requiert une authentification. Et ici, l'application tente d'envoyer une réponse d'authentification, mais reçoit immédiatement une erreur. (En même temps, dans les journaux du serveur, je ne vois aucun message concernant la prise de contact). Voici une partie du journal de diagnostic:

Sep 15 10:51:49  AppName[331] <Notice>: CFNetwork Diagnostics [3:49] 10:51:49.185 {
    Authentication Challenge
       Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0}
    Challenge: challenge space https://217.92.80.156:9090/, ServerTrustEvaluationRequested (Hash f9810ad8165b3620)
    } [3:49]
Sep 15 10:51:49  AppName[331] <Notice>: CFNetwork Diagnostics [3:50] 10:51:49.189 {
    Use Credential
        Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0}
    Credential: Name: server, Persistence: session
    } [3:50]
Sep 15 10:51:49  AppName[331] <Notice>: CFNetwork Diagnostics [3:51] 10:51:49.190 {
     touchConnection
              Loader: <CFURLRequest 0x1501931c0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0}
    Timeout Interval: 60.000 seconds
    } [3:51]
Sep 15 10:51:49  AppName[331] <Notice>: CFNetwork Diagnostics [3:52] 10:51:49.192 {
    Response Error
    Request: <CFURLRequest 0x14e5d02a0 [0x19f6dab68]> {url = https://217.92.80.156:9090/(method name and parameters), cs = 0x0}
      Error: Error Domain=kCFErrorDomainCFNetwork Code=-1200 "(null)" UserInfo={_kCFNetworkCFStreamSSLErrorOriginalValue=-9802, kCFStreamPropertySSLPeerCertificates=<CFArray 0x1500ddd90 [0x19f6dab68]>{type = immutable, count = 1, values = (
                0 : <cert(0x14e6fb370) s: (server certificate name) i: (custom CA name)>
             )}, _kCFStreamPropertySSLClientCertificateState=2, kCFStreamPropertySSLPeerTrust=<SecTrustRef: 0x150094100>, _kCFStreamPropertySSLClientCertificates=<CFArray 0x14e5ee8e0 [0x19f6dab68]>{type = mutable-small, count = 2, values = (
                0 : <SecIdentityRef: 0x15012cd40>
                1 : <cert(0x15014aa70) s: (client certificate name) i: (custom CA name)>
             )}, _kCFStreamErrorDomainKey=3, _kCFStreamErrorCodeKey=-9802}
    } [3:52]

Notre instance principale peut être installée côté client. Je ne peux donc pas définir d’exception de domaine dans le fichier Info.plist. Aussi, l'application peut demander au serveur par adresse IP sous forme IPv4, mais pas par nom de domaine (comme dans mon exemple).

Qu'ai-je essayé:

  • utilisé NSURLSession au lieu de NSURLConnection, mais sans succès;
  • vérifié les exigences ATS d’Apple pour l’implémentation du serveur here (le développeur back-end est sûr que son implémentation répond à toutes);
  • joué avec la définition de certificats d'ancrage pour la validation de la confiance, conformément aux divers problèmes résolus de stackoverflow et des forums de développeurs Apple;
  • a porté une attention particulière à similar post et à ses solution sur les forums de développeurs;

Je teste la requête https sur iPad Air 2 avec iOS 9 GM Seed (Build 13A340) et xCode 7 GM Seed (Build 7A218). Remarque importante: cette fonctionnalité fonctionne correctement avec iOS 8. Compte tenu de ce qui précède, j’imagine peut-être que le problème concerne notre serveur, mais notre développeur back-end m'a assuré que tout allait bien.

Maintenant, je suis à court d'idées. J'apprécierais que quelqu'un me donne un indice, ou au moins suggère un autre diagnostic, qui révélerait une erreur particulière, plus spécifique que "l'alerte fatale".

Merci.

EDIT 1: SecTrustEvaluate renvoie toujours kSecTrustResultRecoverableTrustFailure, c’est pourquoi j’ai dû trouver une solution de contournement.

10
alfared

Ce problème a été résolu il y a quelque temps. Il s’est avéré qu’il s’agissait d’un certificat auto-signé non valide. Il ne répondait pas à toutes les exigences d'Apple. Malheureusement, je ne sais pas ce que c'était exactement.

0
alfared

Selon cela: https://forums.developer.Apple.com/message/36842#36842

La meilleure approche pour résoudre le problème de chargement HTTP a échoué (kCFStreamErrorDomainSSL, -9802) consiste à définir une exception dans le fichier info.plist comme suit:

<key>NSAppTransportSecurity</key>
<dict>
  <key>NSExceptionDomains</key>
  <dict>
    <key>test.testdomain.com</key>
    <dict>
      <key>NSIncludesSubdomains</key>
      <true/>
      <key>NSExceptionAllowsInsecureHTTPLoads</key>
      <true/>
    </dict>
  </dict>
</dict>

Le point important est que cela n’est pas moins sécurisé que iOS8, mais pas aussi sûr que le système ATS complet pris en charge par iOS9.

3
spirographer

Avez-vous utilisé nscurl pour diagnostiquer le problème de connexion? Si vous avez un Mac sous OS X v10.11, vous pouvez exécuter quelque chose comme ceci:

/usr/bin/nscurl --ats-diagnostics https://www.yourdomain.com

Sinon, si vous n'avez pas la version 10.11, vous pouvez télécharger l'exemple de code ici: https://developer.Apple.com/library/mac/samplecode/SC1236/ et le construire avec XCode et l'exécuter comme ceci (en modifiant le chemin en fonction de votre machine):

/Users/somebody/Library/Developer/Xcode/DerivedData/TLSTool-hjuytnjaqebcfradighsrffxxyzq/Build/Products/Debug/TLSTool s_client -connect www.yourdomain.com:443

(Pour trouver le chemin complet de ce qui précède, une fois la construction effectuée, ouvrez le groupe Produits dans votre navigateur de projet, cliquez avec le bouton droit de la souris sur TLSTool et sélectionnez "Afficher dans le Finder".)

Vous avez déjà créé un lien vers la note technique d'Apple à ce sujet, https://developer.Apple.com/library/prerelease/ios/technotes/App-Transport-Security-Technote/ mais vous n'avez pas précisé si vous utilisiez nscurl ou pas.

3
DFedor

Je viens de rencontrer le même problème avec ur's. Maintenant, je le répare. C'est à cause de la version de tls et du signe du certificat. Comme le dit le document d'Apple ci-dessous Document d'Apple

Donc, je fais cette chose Réglage info.plist

et il fonctionne

0
zms